@githolon/dsl 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +1 -1
- package/compile_package.mjs +1 -1
- package/dart/.dart_tool/package_config.json +328 -0
- package/dart/.dart_tool/package_graph.json +485 -0
- package/dart/.dart_tool/pub/bin/test/test.dart-3.11.5.snapshot +0 -0
- package/dart/.dart_tool/test/incremental_kernel.Ly9AZGFydD0zLjU= +0 -0
- package/dart/.dart_tool/version +1 -0
- package/dart/FINDINGS.md +147 -0
- package/dart/build/native_assets/macos/native_assets.json +1 -0
- package/dart/build/test_cache/build/89a6598c8854ed031dfc25d83c80860e.cache.dill.track.dill +0 -0
- package/dart/build/unit_test_assets/AssetManifest.bin +0 -0
- package/dart/build/unit_test_assets/FontManifest.json +1 -0
- package/dart/build/unit_test_assets/NOTICES.Z +0 -0
- package/dart/build/unit_test_assets/NativeAssetsManifest.json +1 -0
- package/dart/build/unit_test_assets/shaders/ink_sparkle.frag +0 -0
- package/dart/build/unit_test_assets/shaders/stretch_effect.frag +0 -0
- package/dart/lib/nomos_dsl.dart +43 -0
- package/dart/lib/src/dispatch.dart +87 -0
- package/dart/lib/src/driver.dart +122 -0
- package/dart/lib/src/provider.dart +139 -0
- package/dart/lib/src/schema_validation.dart +44 -0
- package/dart/lib/src/subscriptions.dart +549 -0
- package/dart/lib/src/wire.dart +439 -0
- package/dart/pubspec.lock +421 -0
- package/dart/pubspec.yaml +23 -0
- package/dart/test/schema_validation_test.dart +66 -0
- package/dart/test/wire_test.dart +162 -0
- package/package.json +2 -1
- package/src/codegen_ts.ts +5 -5
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
// NOMOS — Nomos Sovereign: participants act · verify · remember LOCALLY; hosted
|
|
2
|
+
// remotes are replaceable custody/transport, not truth. ⇒ ONE Nomos GitHolon
|
|
3
|
+
// wasm32-wasip1 artifact {kernel · projection · embedded
|
|
4
|
+
// QuickJS engine} on V8 + WASI-shim, byte-identical everywhere. V8 = portability; the one
|
|
5
|
+
// wasm = determinism. No native, no wasmtime, no 2nd artifact, no domain-JS on bare V8.
|
|
6
|
+
// If a file isn't this / hosting this / authoring for this / proving this — it's gone.
|
|
7
|
+
|
|
8
|
+
/// The kernel's wire shapes — byte-compatible with serde_json's
|
|
9
|
+
/// externally-tagged enum encoding. These types mirror
|
|
10
|
+
/// `nomos2/kernel/src/lib.rs` EXACTLY; the shape was discovered empirically
|
|
11
|
+
/// (see ../../FINDINGS.md) by serializing real kernel values.
|
|
12
|
+
///
|
|
13
|
+
/// serde externally-tags enums:
|
|
14
|
+
/// - unit variant -> bare string: Driver::Lww => "Lww"
|
|
15
|
+
/// - newtype variant -> {Tag: inner}: Value::Int(10) => {"Int":10}
|
|
16
|
+
/// - struct variant -> {Tag: {..}}: Op::SetEntry{key,value} => {"SetEntry":{"key":..,"value":..}}
|
|
17
|
+
/// - newtype struct -> transparent: ReplicaId(7) => 7
|
|
18
|
+
///
|
|
19
|
+
/// This file is HAND-WRITTEN (the fixed wire types). The per-domain aggregate
|
|
20
|
+
/// and directive-payload types under `generated/` are CODE-GENERATED from the
|
|
21
|
+
/// TS domain definitions by `nomos2/dsl/src/codegen_dart.ts`.
|
|
22
|
+
library;
|
|
23
|
+
|
|
24
|
+
/// `ReplicaId(pub u64)` serializes as a bare number. Modelled as `int` (Dart
|
|
25
|
+
/// has no unsigned type; replica ids in practice fit a signed 64-bit int).
|
|
26
|
+
typedef WireReplicaId = int;
|
|
27
|
+
|
|
28
|
+
/// `Hlc { physical: u64, logical: u32, replica: ReplicaId }`.
|
|
29
|
+
///
|
|
30
|
+
/// Note: `replica` is a BARE number on the wire (newtype-struct transparency),
|
|
31
|
+
/// not `{"ReplicaId": 7}`.
|
|
32
|
+
class Hlc {
|
|
33
|
+
final int physical;
|
|
34
|
+
final int logical;
|
|
35
|
+
final WireReplicaId replica;
|
|
36
|
+
|
|
37
|
+
const Hlc({
|
|
38
|
+
required this.physical,
|
|
39
|
+
required this.logical,
|
|
40
|
+
required this.replica,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
factory Hlc.fromJson(Map<String, dynamic> json) => Hlc(
|
|
44
|
+
physical: (json['physical'] as num).toInt(),
|
|
45
|
+
logical: (json['logical'] as num).toInt(),
|
|
46
|
+
replica: (json['replica'] as num).toInt(),
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
Map<String, dynamic> toJson() => {
|
|
50
|
+
'physical': physical,
|
|
51
|
+
'logical': logical,
|
|
52
|
+
'replica': replica,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
@override
|
|
56
|
+
bool operator ==(Object other) =>
|
|
57
|
+
other is Hlc &&
|
|
58
|
+
other.physical == physical &&
|
|
59
|
+
other.logical == logical &&
|
|
60
|
+
other.replica == replica;
|
|
61
|
+
|
|
62
|
+
@override
|
|
63
|
+
int get hashCode => Object.hash(physical, logical, replica);
|
|
64
|
+
|
|
65
|
+
@override
|
|
66
|
+
String toString() => 'Hlc(physical: $physical, logical: $logical, replica: $replica)';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// `Value = Str | Int | Set | Map`.
|
|
70
|
+
///
|
|
71
|
+
/// Externally tagged: `{"Str":"x"}` / `{"Int":10}` / `{"Set":["a","b"]}` /
|
|
72
|
+
/// `{"Map":{"k":<Field>}}`. `Set` is a `BTreeSet<String>` in the kernel, so it
|
|
73
|
+
/// serializes as a JSON array and is sorted/deduped on the kernel side.
|
|
74
|
+
sealed class Value {
|
|
75
|
+
const Value();
|
|
76
|
+
|
|
77
|
+
factory Value.fromJson(Map<String, dynamic> json) {
|
|
78
|
+
if (json.containsKey('Str')) {
|
|
79
|
+
return ValueStr(json['Str'] as String);
|
|
80
|
+
}
|
|
81
|
+
if (json.containsKey('Int')) {
|
|
82
|
+
return ValueInt((json['Int'] as num).toInt());
|
|
83
|
+
}
|
|
84
|
+
if (json.containsKey('Set')) {
|
|
85
|
+
return ValueSet(
|
|
86
|
+
(json['Set'] as List<dynamic>).map((e) => e as String).toList(),
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
if (json.containsKey('Map')) {
|
|
90
|
+
final raw = json['Map'] as Map<String, dynamic>;
|
|
91
|
+
return ValueMap(raw.map(
|
|
92
|
+
(k, v) => MapEntry(k, WireField.fromJson(v as Map<String, dynamic>)),
|
|
93
|
+
));
|
|
94
|
+
}
|
|
95
|
+
throw FormatException('Unknown Value variant: ${json.keys}');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
Map<String, dynamic> toJson();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// `Value::Str(String)` -> `{"Str":"x"}`.
|
|
102
|
+
class ValueStr extends Value {
|
|
103
|
+
final String value;
|
|
104
|
+
const ValueStr(this.value);
|
|
105
|
+
|
|
106
|
+
@override
|
|
107
|
+
Map<String, dynamic> toJson() => {'Str': value};
|
|
108
|
+
|
|
109
|
+
@override
|
|
110
|
+
bool operator ==(Object other) => other is ValueStr && other.value == value;
|
|
111
|
+
@override
|
|
112
|
+
int get hashCode => Object.hash('Str', value);
|
|
113
|
+
@override
|
|
114
|
+
String toString() => 'ValueStr($value)';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/// `Value::Int(i64)` -> `{"Int":10}`.
|
|
118
|
+
class ValueInt extends Value {
|
|
119
|
+
final int value;
|
|
120
|
+
const ValueInt(this.value);
|
|
121
|
+
|
|
122
|
+
@override
|
|
123
|
+
Map<String, dynamic> toJson() => {'Int': value};
|
|
124
|
+
|
|
125
|
+
@override
|
|
126
|
+
bool operator ==(Object other) => other is ValueInt && other.value == value;
|
|
127
|
+
@override
|
|
128
|
+
int get hashCode => Object.hash('Int', value);
|
|
129
|
+
@override
|
|
130
|
+
String toString() => 'ValueInt($value)';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/// `Value::Set(BTreeSet<String>)` -> `{"Set":["a","b"]}`.
|
|
134
|
+
class ValueSet extends Value {
|
|
135
|
+
final List<String> value;
|
|
136
|
+
const ValueSet(this.value);
|
|
137
|
+
|
|
138
|
+
@override
|
|
139
|
+
Map<String, dynamic> toJson() => {'Set': value};
|
|
140
|
+
|
|
141
|
+
@override
|
|
142
|
+
bool operator ==(Object other) =>
|
|
143
|
+
other is ValueSet && _listEq(other.value, value);
|
|
144
|
+
@override
|
|
145
|
+
int get hashCode => Object.hashAll(['Set', ...value]);
|
|
146
|
+
@override
|
|
147
|
+
String toString() => 'ValueSet($value)';
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/// `Value::Map(BTreeMap<String, Field>)` -> `{"Map":{"k":<Field>}}`.
|
|
151
|
+
class ValueMap extends Value {
|
|
152
|
+
final Map<String, WireField> value;
|
|
153
|
+
const ValueMap(this.value);
|
|
154
|
+
|
|
155
|
+
@override
|
|
156
|
+
Map<String, dynamic> toJson() =>
|
|
157
|
+
{'Map': value.map((k, v) => MapEntry(k, v.toJson()))};
|
|
158
|
+
|
|
159
|
+
@override
|
|
160
|
+
bool operator ==(Object other) =>
|
|
161
|
+
other is ValueMap && _mapEq(other.value, value);
|
|
162
|
+
@override
|
|
163
|
+
int get hashCode => Object.hashAll(['Map', ...value.entries.map((e) => Object.hash(e.key, e.value))]);
|
|
164
|
+
@override
|
|
165
|
+
String toString() => 'ValueMap($value)';
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/// `Field { value: Value, stamp: Hlc }` — only used when reading state back.
|
|
169
|
+
///
|
|
170
|
+
/// Named `WireField` to avoid clashing with the DSL field-builder concept and
|
|
171
|
+
/// with Flutter/material `Field` widgets the frontend may import.
|
|
172
|
+
class WireField {
|
|
173
|
+
final Value value;
|
|
174
|
+
final Hlc stamp;
|
|
175
|
+
|
|
176
|
+
const WireField({required this.value, required this.stamp});
|
|
177
|
+
|
|
178
|
+
factory WireField.fromJson(Map<String, dynamic> json) => WireField(
|
|
179
|
+
value: Value.fromJson(json['value'] as Map<String, dynamic>),
|
|
180
|
+
stamp: Hlc.fromJson(json['stamp'] as Map<String, dynamic>),
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
Map<String, dynamic> toJson() => {
|
|
184
|
+
'value': value.toJson(),
|
|
185
|
+
'stamp': stamp.toJson(),
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
@override
|
|
189
|
+
bool operator ==(Object other) =>
|
|
190
|
+
other is WireField && other.value == value && other.stamp == stamp;
|
|
191
|
+
@override
|
|
192
|
+
int get hashCode => Object.hash(value, stamp);
|
|
193
|
+
@override
|
|
194
|
+
String toString() => 'WireField(value: $value, stamp: $stamp)';
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/// `Op = Set(Value) | AddToSet(BTreeSet<String>) | SetEntry { key, value }`.
|
|
198
|
+
sealed class Op {
|
|
199
|
+
const Op();
|
|
200
|
+
|
|
201
|
+
factory Op.fromJson(Map<String, dynamic> json) {
|
|
202
|
+
if (json.containsKey('Set')) {
|
|
203
|
+
return OpSet(Value.fromJson(json['Set'] as Map<String, dynamic>));
|
|
204
|
+
}
|
|
205
|
+
if (json.containsKey('AddToSet')) {
|
|
206
|
+
return OpAddToSet(
|
|
207
|
+
(json['AddToSet'] as List<dynamic>).map((e) => e as String).toList(),
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
if (json.containsKey('SetEntry')) {
|
|
211
|
+
final entry = json['SetEntry'] as Map<String, dynamic>;
|
|
212
|
+
return OpSetEntry(
|
|
213
|
+
key: entry['key'] as String,
|
|
214
|
+
value: Value.fromJson(entry['value'] as Map<String, dynamic>),
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
throw FormatException('Unknown Op variant: ${json.keys}');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
Map<String, dynamic> toJson();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/// `Op::Set(Value)` -> `{"Set":{"Int":10}}`.
|
|
224
|
+
class OpSet extends Op {
|
|
225
|
+
final Value value;
|
|
226
|
+
const OpSet(this.value);
|
|
227
|
+
|
|
228
|
+
@override
|
|
229
|
+
Map<String, dynamic> toJson() => {'Set': value.toJson()};
|
|
230
|
+
|
|
231
|
+
@override
|
|
232
|
+
bool operator ==(Object other) => other is OpSet && other.value == value;
|
|
233
|
+
@override
|
|
234
|
+
int get hashCode => Object.hash('Set', value);
|
|
235
|
+
@override
|
|
236
|
+
String toString() => 'OpSet($value)';
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/// `Op::AddToSet(BTreeSet<String>)` -> `{"AddToSet":["x","y"]}`.
|
|
240
|
+
class OpAddToSet extends Op {
|
|
241
|
+
final List<String> items;
|
|
242
|
+
const OpAddToSet(this.items);
|
|
243
|
+
|
|
244
|
+
@override
|
|
245
|
+
Map<String, dynamic> toJson() => {'AddToSet': items};
|
|
246
|
+
|
|
247
|
+
@override
|
|
248
|
+
bool operator ==(Object other) =>
|
|
249
|
+
other is OpAddToSet && _listEq(other.items, items);
|
|
250
|
+
@override
|
|
251
|
+
int get hashCode => Object.hashAll(['AddToSet', ...items]);
|
|
252
|
+
@override
|
|
253
|
+
String toString() => 'OpAddToSet($items)';
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/// `Op::SetEntry { key, value }` -> `{"SetEntry":{"key":"color","value":{"Str":"red"}}}`.
|
|
257
|
+
class OpSetEntry extends Op {
|
|
258
|
+
final String key;
|
|
259
|
+
final Value value;
|
|
260
|
+
const OpSetEntry({required this.key, required this.value});
|
|
261
|
+
|
|
262
|
+
@override
|
|
263
|
+
Map<String, dynamic> toJson() => {
|
|
264
|
+
'SetEntry': {'key': key, 'value': value.toJson()},
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
@override
|
|
268
|
+
bool operator ==(Object other) =>
|
|
269
|
+
other is OpSetEntry && other.key == key && other.value == value;
|
|
270
|
+
@override
|
|
271
|
+
int get hashCode => Object.hash('SetEntry', key, value);
|
|
272
|
+
@override
|
|
273
|
+
String toString() => 'OpSetEntry(key: $key, value: $value)';
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/// `FieldOp { field: FieldId, op: Op }`.
|
|
277
|
+
class FieldOp {
|
|
278
|
+
final String field;
|
|
279
|
+
final Op op;
|
|
280
|
+
|
|
281
|
+
const FieldOp({required this.field, required this.op});
|
|
282
|
+
|
|
283
|
+
factory FieldOp.fromJson(Map<String, dynamic> json) => FieldOp(
|
|
284
|
+
field: json['field'] as String,
|
|
285
|
+
op: Op.fromJson(json['op'] as Map<String, dynamic>),
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
Map<String, dynamic> toJson() => {'field': field, 'op': op.toJson()};
|
|
289
|
+
|
|
290
|
+
@override
|
|
291
|
+
bool operator ==(Object other) =>
|
|
292
|
+
other is FieldOp && other.field == field && other.op == op;
|
|
293
|
+
@override
|
|
294
|
+
int get hashCode => Object.hash(field, op);
|
|
295
|
+
@override
|
|
296
|
+
String toString() => 'FieldOp(field: $field, op: $op)';
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/// `RefMode = Create | Mutate | Ensure | Archive` — the referential intent of an
|
|
300
|
+
/// event's touch on its aggregate. serde unit-variant: a BARE string on the wire
|
|
301
|
+
/// (`"Create"` / `"Mutate"` / `"Ensure"` / `"Archive"`). The DSL's
|
|
302
|
+
/// `.creates`/`.mutates`/`.ensures`/`.archives` markers lower to these; the
|
|
303
|
+
/// kernel's referential guard enforces them at write time. `serde(default)` =
|
|
304
|
+
/// `Ensure`, so a missing `marker` on older wire decodes as [RefMode.ensure].
|
|
305
|
+
enum RefMode {
|
|
306
|
+
create('Create'),
|
|
307
|
+
mutate('Mutate'),
|
|
308
|
+
ensure('Ensure'),
|
|
309
|
+
archive('Archive');
|
|
310
|
+
|
|
311
|
+
final String wire;
|
|
312
|
+
const RefMode(this.wire);
|
|
313
|
+
|
|
314
|
+
static RefMode fromWire(String wire) => RefMode.values.firstWhere(
|
|
315
|
+
(e) => e.wire == wire,
|
|
316
|
+
orElse: () => throw FormatException('Unknown RefMode: $wire'),
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/// `Event { aggregate: AggregateId, marker: RefMode, ops: Vec<FieldOp> }` — one
|
|
321
|
+
/// aggregate's mutations (the *where* + the field-ops) plus its referential
|
|
322
|
+
/// [marker], #56. A directive fanning out across N aggregates emits N events,
|
|
323
|
+
/// all carried by one [Intent]. `marker` is `serde(default)` = [RefMode.ensure].
|
|
324
|
+
class Event {
|
|
325
|
+
final String aggregate;
|
|
326
|
+
final RefMode marker;
|
|
327
|
+
final List<FieldOp> ops;
|
|
328
|
+
|
|
329
|
+
const Event({
|
|
330
|
+
required this.aggregate,
|
|
331
|
+
this.marker = RefMode.ensure,
|
|
332
|
+
required this.ops,
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
factory Event.fromJson(Map<String, dynamic> json) => Event(
|
|
336
|
+
aggregate: json['aggregate'] as String,
|
|
337
|
+
marker: json.containsKey('marker')
|
|
338
|
+
? RefMode.fromWire(json['marker'] as String)
|
|
339
|
+
: RefMode.ensure,
|
|
340
|
+
ops: (json['ops'] as List<dynamic>)
|
|
341
|
+
.map((e) => FieldOp.fromJson(e as Map<String, dynamic>))
|
|
342
|
+
.toList(),
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
Map<String, dynamic> toJson() => {
|
|
346
|
+
'aggregate': aggregate,
|
|
347
|
+
'marker': marker.wire,
|
|
348
|
+
'ops': ops.map((o) => o.toJson()).toList(),
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
@override
|
|
352
|
+
bool operator ==(Object other) =>
|
|
353
|
+
other is Event &&
|
|
354
|
+
other.aggregate == aggregate &&
|
|
355
|
+
other.marker == marker &&
|
|
356
|
+
_listEq(other.ops, ops);
|
|
357
|
+
@override
|
|
358
|
+
int get hashCode => Object.hash(aggregate, marker, Object.hashAll(ops));
|
|
359
|
+
@override
|
|
360
|
+
String toString() =>
|
|
361
|
+
'Event(aggregate: $aggregate, marker: $marker, ops: $ops)';
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/// `Intent { id: IntentId, hlc: Hlc, strikes: Vec<IntentId>, events: Vec<Event> }`
|
|
365
|
+
/// — one intent = one commit = the atomic unit (#56). Carries the whole event set:
|
|
366
|
+
/// many per-aggregate events, all sharing the intent's HLC, all-or-nothing.
|
|
367
|
+
/// Written as `intent.json` in the kernel's commit tree.
|
|
368
|
+
///
|
|
369
|
+
/// [id] identifies the commit; [strikes] lists intent ids this intent retracts
|
|
370
|
+
/// (the strike-out / revert hatch). Both are `serde(default)` on the kernel, so a
|
|
371
|
+
/// missing `id`/`strikes` on older wire decodes as `''` / `[]`.
|
|
372
|
+
class Intent {
|
|
373
|
+
final String id;
|
|
374
|
+
final Hlc hlc;
|
|
375
|
+
final List<String> strikes;
|
|
376
|
+
final List<Event> events;
|
|
377
|
+
|
|
378
|
+
const Intent({
|
|
379
|
+
this.id = '',
|
|
380
|
+
required this.hlc,
|
|
381
|
+
this.strikes = const [],
|
|
382
|
+
required this.events,
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
/// Derive the deterministic intent id from its HLC (physical.logical.replica)
|
|
386
|
+
/// — the same rule the TS `intentIdFromHlc` uses, so both sides emit the
|
|
387
|
+
/// byte-identical `id`.
|
|
388
|
+
static String idFromHlc(Hlc hlc) =>
|
|
389
|
+
'${hlc.physical}.${hlc.logical}.${hlc.replica}';
|
|
390
|
+
|
|
391
|
+
factory Intent.fromJson(Map<String, dynamic> json) => Intent(
|
|
392
|
+
id: (json['id'] as String?) ?? '',
|
|
393
|
+
hlc: Hlc.fromJson(json['hlc'] as Map<String, dynamic>),
|
|
394
|
+
strikes: (json['strikes'] as List<dynamic>?)
|
|
395
|
+
?.map((e) => e as String)
|
|
396
|
+
.toList() ??
|
|
397
|
+
const [],
|
|
398
|
+
events: (json['events'] as List<dynamic>)
|
|
399
|
+
.map((e) => Event.fromJson(e as Map<String, dynamic>))
|
|
400
|
+
.toList(),
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
Map<String, dynamic> toJson() => {
|
|
404
|
+
'id': id,
|
|
405
|
+
'hlc': hlc.toJson(),
|
|
406
|
+
'strikes': strikes,
|
|
407
|
+
'events': events.map((e) => e.toJson()).toList(),
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
@override
|
|
411
|
+
bool operator ==(Object other) =>
|
|
412
|
+
other is Intent &&
|
|
413
|
+
other.id == id &&
|
|
414
|
+
other.hlc == hlc &&
|
|
415
|
+
_listEq(other.strikes, strikes) &&
|
|
416
|
+
_listEq(other.events, events);
|
|
417
|
+
@override
|
|
418
|
+
int get hashCode =>
|
|
419
|
+
Object.hash(id, hlc, Object.hashAll(strikes), Object.hashAll(events));
|
|
420
|
+
@override
|
|
421
|
+
String toString() =>
|
|
422
|
+
'Intent(id: $id, hlc: $hlc, strikes: $strikes, events: $events)';
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
bool _listEq<T>(List<T> a, List<T> b) {
|
|
426
|
+
if (a.length != b.length) return false;
|
|
427
|
+
for (var i = 0; i < a.length; i++) {
|
|
428
|
+
if (a[i] != b[i]) return false;
|
|
429
|
+
}
|
|
430
|
+
return true;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
bool _mapEq<K, V>(Map<K, V> a, Map<K, V> b) {
|
|
434
|
+
if (a.length != b.length) return false;
|
|
435
|
+
for (final entry in a.entries) {
|
|
436
|
+
if (!b.containsKey(entry.key) || b[entry.key] != entry.value) return false;
|
|
437
|
+
}
|
|
438
|
+
return true;
|
|
439
|
+
}
|