kind 0.4.0 copy "kind: ^0.4.0" to clipboard
kind: ^0.4.0 copied to clipboard

outdated

An unified data layer framework that enables serialization, persistence, and state observability for any Dart class. Code generation is not necessary.

Pub Package Github Actions CI

Overview #

An unified data layer framework that enables serialization, persistence, and state management for any Dart class. This an early version and the APIs are not frozen. The package requires a null-safe Flutter SDK 2.0.0 or later.

Features #

  • Encode/decode JSON.
    • The package can handle most JSON serialization requirements.
  • Encode/decode Protocol Buffers.
    • The package can handle most Protocol Buffers (and GRPC) serialization requirements.
  • Use databases (upcoming).
    • Our sibling package database will use this framework in future.
  • Observe views / mutations in graphs
    • The package has ReactiveSystem for observing views and mutations of reactive states in the isolate.
    • When you are deserializing JSON or Protocol Buffers, you get reactive objects by default.

Overview of APIs #

Built-in kinds #

Other APIs #

Some alternatives #

Getting started #

1.Adding dependency #

In pubspec.yaml, you should have something like:

environment:
  sdk: '>=2.12.0 <3.0.0'

dependencies:
  kind: ^0.3.2

2.Write data models #

In the following example, we use Field and ListField. Wrapping values inside Field simplifies state observation. If you want to use normal Dart getters / setters, see "Alternative approaches" section below.

import 'package:kind/kind.dart';

class Person extends Entity {
  static final EntityKind<Person> kind = EntityKind<Person>(
    name: 'Person',
    build: (b) {
      b.optionalString(
        id: 1,
        name: 'fullName',
        minLength: 1,
        field: (e) => e.fullName,
      );
      b.requiredList<Person>(
        id: 2,
        name: 'friends',
        itemsKind: Person.kind,
        field: (e) => e.friends,
      );
      b.constructor = () => Person();
    },
  );

  /// Full name.
  late final Field<String?> fullName = Field<String?>(this);

  /// Friends.
  late final ListField<Person> friends = ListField<Person>(this);

  @override
  EntityKind getKind() => kind;
}

void main() {
    final alice = Person();
    alice.name.value = 'Alice';

    final bob = Person();
    bob.name.value = 'Bob';

    alice.friends.add(bob);
    bob.friends.add(alice);

    // Your objects have:
    //   * `==` (that supports cyclic graphs)
    //   * `hashCode`
    //   * `toString()``
    //   * And more!
}

JSON serialization #

Use jsonTreeEncode(...) and jsonTreeDecode(...):

final alice = Person();
final bob = Person();
alice.name.value = 'Alice';
bob.name.value = 'Bob';
alice.friends.add(bob);

// Person --> JSON tree
final aliceJson = person.getKind().jsonTreeEncode(alice);
// JSON tree:
//   {
//     "fullName": "Alice",
//     "friends": [
//       {
//         "fullName": "Bob",
//       }
//     ]
//   }

// JSON tree --> Person
final decodedAlice = Person.kind.jsonTreeDecode(aliceJson);

Mapping identifiers #

If you want to use underscore naming convention, simply pass UnderscoreNamer in the context object:

final namer = UnderscoreNamer();

// Person --> JSON tree
final aliceJson = person.getKind().jsonTreeEncode(
  alice,
  context: JsonEncodingContext(namer: namer),
);

// JSON tree --> Person
final decodedAlice = Person.kind.jsonTreeDecode(
  aliceJson,
  context: JsonDecodingContext(namer: namer),
);

You can also declare special rules:

final namer = UnderscoreNamer(
  rules: {
    'fullName': 'real_name',
  },
);

Protocol Buffers serialization #

For encoding/decoding Protocol Buffers bytes, use protobufBytesEncode(...) and protobufBytesDecode(...):

// Person --> bytes
final bytes = Person.kind.protobufBytesEncode(person);

// bytes --> Person
final person = Person.kind.protobufBytesDecode(bytes);

For encoding/decoding GeneratedMessage (used by package:protobuf and package:grpc), use protobufTreeEncode(...) and protobufTreeDecode(...):

// Person --> GeneratedMessage
final generatedMessage = Person.kind.protobufTreeEncode(person);

// GeneratedMessage --> Person
final person = Person.kind.protobufTreeDecode(generatedMessage);

You can also generate GeneratedMessage classes with GRPC tooling and merge messages using mergeFromMessage.

Alternative approaches to specifying data classes #

Why / why not? #

The alternative approaches:

  • Do not force you to deviate from the way you normally write classes.
  • Perform better when you have millions of objects.
  • Do not support reactive programming with ReactiveSystem unless you write a lot error-prone boilerplate code.
    • At some point, we may release a code generator that generates boilerplate for you, but there will inevitably going to be some complexity unless Dart language designers decide to support something like decorator annotations.

Mutable and non-reactive #

You just define getter and setter in Prop for ordinary Dart fields:

import 'package:kind/kind.dart';

class Person {
  /// Full name.
  String? fullName = '';

  /// Friends.
  final Set<Person> friends = {};
}

/// EntityKind for [Person].
final EntityKind<Person> personKind = EntityKind<Person>(
  name: 'Person',
  build: (builder) {
    builder.optionalString(
      id: 1,
      name: 'fullName',
      getter: (t) => t.fullName,
      setter: (t,v) => t.fullName = v,
    );
    builder.requiredSet<Person>(
      id: 2,
      name: 'friends',
      itemsKind: personKind,
      getter: (t) => t.friends,
    );
    builder.constructor = () => Person();
  },
);

Mutable and reactive #

You can use ReactiveMixin for implementing getters and setters that send notifications to ReactiveSystem:

// ...

class Person extends Entity with ReactiveMixin {
  String? _fullName;
  final Set<Person> _friends = ReactiveSet<Person>();

  /// Full name of the person.
  String? get fullName => beforeGet(_fullName);
  set fullName(String? value) => _fullName = beforeSet(_fullName, value);

  /// Friends of the person.
  Set<Person> get friends => beforeGet(_friends);

  @override
  EntityKind<Person> getKind() => personKind;
}

// `personKind` is identical to the previous example.
// ...

Immutable and non-reactive #

import 'package:kind/kind.dart';

// Extending Entity is optional, but recommended.
class Person {
  /// Full name of the person.
  final String? name;

  /// Friends of the person.
  final Set<Person> friends;

  Person({
    this.fullName,
    this.friends = const {},
  });
}

/// EntityKind for [Person].
final EntityKind<Person> personKind = EntityKind<Person>(
  name: 'Person',
  build: (builder) {
    final fullName = builder.optionalString(
      id: 1,
      name: 'fullName',
      getter: (t) => t.fullName,
    );
    final friends = builder.requiredSet<Person>(
      id: 2,
      name: 'friends',
      itemsKind: personKind,
      getter: (t) => t.friends,
    );
    builder.constructorFromData = (data) {
      return Person(
        name: data.get(fullName),
        friends: data.get(friends),
      );
    };
  },
);

Immutable and reactive #

You can use ReactiveMixin for implementing getters and setters that send notifications to ReactiveSystem:

// ...

// Extending Entity is optional, but recommended.
class Person extends Entity with ReactiveMixin {
  final String? _fullName;
  final Set<Person> _friends;

  /// Full name of the person.
  String? get fullName => beforeGet(_fullName);

  /// Friends of the person.
  Set<Person> get friends => beforeGet(_friends);

  Person({
    required String? name,
    Set<Person> friends = const {},
  }) :
    _fullName = name,
    _friends = ReactiveSet<Person>.wrap(friends);

  @override
  EntityKind<Person> getKind() => personKind;
}

// `personKind` is identical to the previous example.
// ...
7
likes
0
pub points
18%
popularity

Publisher

verified publisherdint.dev

An unified data layer framework that enables serialization, persistence, and state observability for any Dart class. Code generation is not necessary.

Repository (GitHub)
View/report issues

License

unknown (LICENSE)

Dependencies

collection, fixnum, meta, protobuf

More

Packages that depend on kind