@githolon/dsl 0.103.0 → 0.103.1
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/dart/CHANGELOG.md +7 -0
- package/dart/lib/src/runner/node-entry.mjs +46 -3
- package/dart/lib/src/webview_bridge.dart +51 -21
- package/dart/pubspec.yaml +1 -1
- package/dart/test/acquire_session_test.dart +9 -0
- package/package.json +1 -1
- package/src/codegen_dart_application.ts +18 -0
- package/src/compile_package_main.ts +2 -2
package/dart/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## 0.64.3
|
|
2
|
+
|
|
3
|
+
- Single-flight generated child-workspace acquisition and continuous sync so concurrent references share one
|
|
4
|
+
open, one lifecycle result and one error rather than cloning or registering the same workspace twice.
|
|
5
|
+
- Generate separate shared-workspace reads so applications can render denormalized owned-workspace cards
|
|
6
|
+
without eagerly opening every founded child merely to discover genuinely shared lives.
|
|
7
|
+
|
|
1
8
|
## 0.64.2
|
|
2
9
|
|
|
3
10
|
- Execute generated applications through the typed USDA-native kernel boundary, including native read
|
|
@@ -16,6 +16,43 @@ var RUNNER_BUNDLE_VERSION = typeof __NOMOS_BUNDLE_VERSION__ !== "undefined" ? __
|
|
|
16
16
|
var plainObject = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
17
17
|
var errorMessage = (e, fallback = "nomos runtime error") => String(e?.message ?? e ?? fallback);
|
|
18
18
|
var compact = (o) => Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0 && v !== null && v !== ""));
|
|
19
|
+
var PERF_COMPONENT_LIMIT = 12;
|
|
20
|
+
var PERF_PHASE_LIMIT = 16;
|
|
21
|
+
var PERF_NOTE_LIMIT = 12;
|
|
22
|
+
var numericEntries = (value, limit) => {
|
|
23
|
+
const entries = Object.entries(plainObject(value) || {}).filter(([, v]) => typeof v === "number" && Number.isFinite(v) && v !== 0).map(([name, n]) => [name, n]).sort((a, b) => Math.abs(b[1]) - Math.abs(a[1]));
|
|
24
|
+
return {
|
|
25
|
+
values: Object.fromEntries(entries.slice(0, limit)),
|
|
26
|
+
omitted: Math.max(0, entries.length - limit)
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
function boundedKernelSpans(value) {
|
|
30
|
+
const root = plainObject(value);
|
|
31
|
+
if (!root) return value;
|
|
32
|
+
const ranked = Object.entries(root).map(([name, raw]) => {
|
|
33
|
+
const row = plainObject(raw) || {};
|
|
34
|
+
const phases = numericEntries(row.phases, PERF_PHASE_LIMIT);
|
|
35
|
+
const notes = numericEntries(row.notes, PERF_NOTE_LIMIT);
|
|
36
|
+
const flat = numericEntries(row, PERF_PHASE_LIMIT);
|
|
37
|
+
const totalUs = typeof row.totalUs === "number" && Number.isFinite(row.totalUs) ? row.totalUs : Object.values(flat.values).reduce((sum, n) => sum + Number(n), 0);
|
|
38
|
+
const shaped = plainObject(row.phases) || plainObject(row.notes) || row.totalUs !== void 0 ? compact({
|
|
39
|
+
totalUs,
|
|
40
|
+
phases: Object.keys(phases.values).length ? phases.values : void 0,
|
|
41
|
+
notes: Object.keys(notes.values).length ? notes.values : void 0,
|
|
42
|
+
omittedPhases: phases.omitted || void 0,
|
|
43
|
+
omittedNotes: notes.omitted || void 0
|
|
44
|
+
}) : compact({
|
|
45
|
+
...flat.values,
|
|
46
|
+
omittedMetrics: flat.omitted || void 0
|
|
47
|
+
});
|
|
48
|
+
return { name, totalUs, shaped };
|
|
49
|
+
}).sort((a, b) => b.totalUs - a.totalUs);
|
|
50
|
+
const kept = ranked.slice(0, PERF_COMPONENT_LIMIT);
|
|
51
|
+
return {
|
|
52
|
+
...Object.fromEntries(kept.map(({ name, shaped }) => [name, shaped])),
|
|
53
|
+
...ranked.length > PERF_COMPONENT_LIMIT ? { _summary: { omittedComponents: ranked.length - PERF_COMPONENT_LIMIT } } : {}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
19
56
|
var errorInfo = (e, req = {}, component = "nomos.webview.runner") => {
|
|
20
57
|
const err = e;
|
|
21
58
|
const existing = plainObject(err?.errorInfo) || plainObject(err?.info);
|
|
@@ -99,6 +136,7 @@ function createWebviewRunner({ post: post2, openRealm: openRealm2 }) {
|
|
|
99
136
|
const spanStarts = /* @__PURE__ */ new Map();
|
|
100
137
|
const forwardSpan = (session, raw) => {
|
|
101
138
|
const s = plainObject(raw) ?? {};
|
|
139
|
+
const presented = s.kernelSpans === void 0 ? s : { ...s, kernelSpans: boundedKernelSpans(s.kernelSpans) };
|
|
102
140
|
const id = String(s.id ?? "");
|
|
103
141
|
if (s.phase === "begin" && id) {
|
|
104
142
|
spanStarts.set(id, s);
|
|
@@ -115,13 +153,13 @@ function createWebviewRunner({ post: post2, openRealm: openRealm2 }) {
|
|
|
115
153
|
elapsedMs,
|
|
116
154
|
domain: s.domain,
|
|
117
155
|
directiveId: s.directiveId,
|
|
118
|
-
kernelSpans:
|
|
156
|
+
kernelSpans: presented.kernelSpans,
|
|
119
157
|
born: s.born,
|
|
120
158
|
birthOutcomes: s.birthOutcomes
|
|
121
159
|
}))}`);
|
|
122
160
|
}
|
|
123
161
|
}
|
|
124
|
-
post2({ v: WEBVIEW_BRIDGE_VERSION, event: "span", session, span:
|
|
162
|
+
post2({ v: WEBVIEW_BRIDGE_VERSION, event: "span", session, span: presented });
|
|
125
163
|
};
|
|
126
164
|
const pendingCustodyAsks = /* @__PURE__ */ new Map();
|
|
127
165
|
let connectGate = Promise.resolve();
|
|
@@ -566,7 +604,12 @@ function createWebviewRunner({ post: post2, openRealm: openRealm2 }) {
|
|
|
566
604
|
return await (await need(a)).__nomosPrepareOwnedRecordAction({ action: a.action, scope: a.scope, record: a.record, peer: a.peer });
|
|
567
605
|
},
|
|
568
606
|
async cryptoOwnedBirthScopeKey(a) {
|
|
569
|
-
return await (await need(a)).__nomosOwnedBirthScopeKey({
|
|
607
|
+
return await (await need(a)).__nomosOwnedBirthScopeKey({
|
|
608
|
+
scope: a.scope,
|
|
609
|
+
namespace: a.namespace,
|
|
610
|
+
key: a.key,
|
|
611
|
+
...a.record !== void 0 ? { record: a.record } : {}
|
|
612
|
+
});
|
|
570
613
|
},
|
|
571
614
|
async cryptoOwnedDeviceRequest(a) {
|
|
572
615
|
return { request: await (await need(a)).__nomosOwnedDeviceRequest({ scope: a.scope }) };
|
|
@@ -1039,6 +1039,11 @@ class NomosBridge {
|
|
|
1039
1039
|
// [acquireSession]'s per-workspace single-flight: workspace → the in-flight open every concurrent
|
|
1040
1040
|
// acquirer joins (removed when it completes; a live session then hits the fast path instead).
|
|
1041
1041
|
final Map<String, Future<NomosSession>> _acquiring = {};
|
|
1042
|
+
// Every workspace opened as part of one stateful application participates in continuous custody exchange.
|
|
1043
|
+
// Starting that lifecycle is itself single-flight: generated navigation may discover the same child through
|
|
1044
|
+
// several references, but the runner gets one autoSync registration for that session.
|
|
1045
|
+
final Set<String> _autoSyncSessions = {};
|
|
1046
|
+
final Map<String, Future<void>> _autoSyncStarting = {};
|
|
1042
1047
|
int _seq = 0;
|
|
1043
1048
|
final String _traceId = _newTraceId();
|
|
1044
1049
|
late final StreamSubscription<Map<String, Object?>> _sub;
|
|
@@ -1875,14 +1880,34 @@ class NomosBridge {
|
|
|
1875
1880
|
plane._sessionPrincipals[workspace] = sourcePrincipal;
|
|
1876
1881
|
}
|
|
1877
1882
|
|
|
1883
|
+
Future<NomosSession> ready(NomosSession opened) async {
|
|
1884
|
+
inheritPrincipal();
|
|
1885
|
+
if (!plane._autoSyncSessions.contains(workspace)) {
|
|
1886
|
+
final start = plane._autoSyncStarting.putIfAbsent(workspace, () async {
|
|
1887
|
+
await opened.autoSync();
|
|
1888
|
+
plane._autoSyncSessions.add(workspace);
|
|
1889
|
+
});
|
|
1890
|
+
try {
|
|
1891
|
+
await start;
|
|
1892
|
+
} finally {
|
|
1893
|
+
if (identical(plane._autoSyncStarting[workspace], start)) {
|
|
1894
|
+
plane._autoSyncStarting.remove(workspace);
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
return opened;
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1878
1901
|
if (plane._sessionNames.contains(workspace)) {
|
|
1879
1902
|
inheritPrincipal();
|
|
1880
|
-
return
|
|
1903
|
+
return ready(session(workspace));
|
|
1881
1904
|
}
|
|
1882
1905
|
// SINGLE-FLIGHT: one in-flight open per workspace per plane; concurrent callers join it.
|
|
1883
1906
|
return plane._acquiring.putIfAbsent(workspace, () async {
|
|
1884
1907
|
try {
|
|
1885
|
-
if (plane._sessionNames.contains(workspace))
|
|
1908
|
+
if (plane._sessionNames.contains(workspace)) {
|
|
1909
|
+
return ready(session(workspace));
|
|
1910
|
+
}
|
|
1886
1911
|
// Consult the plane's TRUTHFUL registry (mounted ∪ parked ∪ resident custody — the runner's
|
|
1887
1912
|
// realm.list()): anything the device already holds opens locally, no cloud, no credentials.
|
|
1888
1913
|
var resident = false;
|
|
@@ -1898,8 +1923,7 @@ class NomosBridge {
|
|
|
1898
1923
|
session: workspace,
|
|
1899
1924
|
allowUnborn: true,
|
|
1900
1925
|
authorSecret: authorSecret);
|
|
1901
|
-
|
|
1902
|
-
return session(workspace);
|
|
1926
|
+
return ready(session(workspace));
|
|
1903
1927
|
}
|
|
1904
1928
|
if (ifAbsentCredentials == null) {
|
|
1905
1929
|
throw StateError(
|
|
@@ -1916,8 +1940,7 @@ class NomosBridge {
|
|
|
1916
1940
|
authorSecret: c.authorSecret ?? authorSecret,
|
|
1917
1941
|
allowUnborn: c.allowUnborn,
|
|
1918
1942
|
);
|
|
1919
|
-
|
|
1920
|
-
return session(workspace);
|
|
1943
|
+
return ready(session(workspace));
|
|
1921
1944
|
} finally {
|
|
1922
1945
|
// completed (either way) — the next acquire re-evaluates fresh (a live session hits the fast path).
|
|
1923
1946
|
plane._acquiring.remove(workspace);
|
|
@@ -1935,11 +1958,12 @@ class NomosBridge {
|
|
|
1935
1958
|
/// @internal The immutable genesis of a sibling custody chain. This is observational metadata used to
|
|
1936
1959
|
/// compose an ordinary source-enrolment offer; the kernel still reopens and proves every foreign read.
|
|
1937
1960
|
Future<String> nomosWorkspaceGenesis(String workspace,
|
|
1938
|
-
|
|
1961
|
+
{String? session}) async =>
|
|
1939
1962
|
(_asMap(await _call('nomosWorkspaceGenesis', {
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1963
|
+
'workspace': workspace,
|
|
1964
|
+
if (session != null) 'session': session,
|
|
1965
|
+
}))['genesis'] ??
|
|
1966
|
+
'')
|
|
1943
1967
|
.toString();
|
|
1944
1968
|
|
|
1945
1969
|
/// UNDO the most-recent action via the kernel STRIKE primitive (the fold drops the struck intent by parity).
|
|
@@ -2452,7 +2476,8 @@ class NomosBridge {
|
|
|
2452
2476
|
/// Compare an immutable publication pin with the stable series' one compiled
|
|
2453
2477
|
/// current pointer. This is an O(current-state) check: it neither resolves the
|
|
2454
2478
|
/// pinned publication nor reconstructs aggregate history.
|
|
2455
|
-
Future<ReferenceFreshness<TVersion>>
|
|
2479
|
+
Future<ReferenceFreshness<TVersion>>
|
|
2480
|
+
checkReferenceFreshness<TVersion, TSeries>(
|
|
2456
2481
|
VersionedNomosRef<TVersion, TSeries> reference,
|
|
2457
2482
|
) async {
|
|
2458
2483
|
final result = _asMap(await _call('checkRefFreshness', {
|
|
@@ -2460,8 +2485,7 @@ class NomosBridge {
|
|
|
2460
2485
|
{
|
|
2461
2486
|
'pinned': NmsInternalNomosRefs.toJson(reference.pinned),
|
|
2462
2487
|
'series': NmsInternalNomosRefs.toJson(reference.series),
|
|
2463
|
-
'currentField':
|
|
2464
|
-
NmsInternalVersionedNomosRefs.currentField(reference),
|
|
2488
|
+
'currentField': NmsInternalVersionedNomosRefs.currentField(reference),
|
|
2465
2489
|
},
|
|
2466
2490
|
],
|
|
2467
2491
|
}));
|
|
@@ -2666,8 +2690,8 @@ class NomosBridge {
|
|
|
2666
2690
|
}));
|
|
2667
2691
|
|
|
2668
2692
|
/// FRAMEWORK-INTERNAL business birth: derive the unborn child's owned key from its deterministic address.
|
|
2669
|
-
Future<NomosScopeKey> nomosOwnedBirthScopeKey(
|
|
2670
|
-
|
|
2693
|
+
Future<NomosScopeKey> nomosOwnedBirthScopeKey(String scope, String namespace,
|
|
2694
|
+
Object? key, String recordType, String recordId) async {
|
|
2671
2695
|
final out = _asMap(await _call('cryptoOwnedBirthScopeKey', {
|
|
2672
2696
|
'scope': scope,
|
|
2673
2697
|
'namespace': namespace,
|
|
@@ -2799,14 +2823,20 @@ class NomosBridge {
|
|
|
2799
2823
|
/// Each round arrives on [syncEvents] as `{ event:'sync', reason, result }`; socket state as `{event:'link'}`.
|
|
2800
2824
|
/// Call once after [connect] — then the app never polls.
|
|
2801
2825
|
Future<void> autoSync(
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2826
|
+
{int? debounceMs, int? fallbackMs, String? signalId}) async {
|
|
2827
|
+
await _call('autoSync', {
|
|
2828
|
+
if (debounceMs != null) 'debounceMs': debounceMs,
|
|
2829
|
+
if (fallbackMs != null) 'fallbackMs': fallbackMs,
|
|
2830
|
+
if (signalId != null) 'signalId': signalId
|
|
2831
|
+
});
|
|
2832
|
+
final workspace = _sessionName;
|
|
2833
|
+
if (workspace != null) _eventPlane._autoSyncSessions.add(workspace);
|
|
2834
|
+
}
|
|
2835
|
+
|
|
2808
2836
|
Future<void> stopAutoSync() async {
|
|
2809
2837
|
await _call('stopAutoSync');
|
|
2838
|
+
final workspace = _sessionName;
|
|
2839
|
+
if (workspace != null) _eventPlane._autoSyncSessions.remove(workspace);
|
|
2810
2840
|
}
|
|
2811
2841
|
|
|
2812
2842
|
/// Background-sync telemetry: each autoSync round (`event:'sync'`) and doorbell socket state (`event:'link'`).
|
package/dart/pubspec.yaml
CHANGED
|
@@ -8,7 +8,7 @@ description: >-
|
|
|
8
8
|
Nomos 2 Dart frontend types — the hand-written kernel wire types plus the
|
|
9
9
|
code-generated per-domain aggregate + directive-payload types. (De)serializes
|
|
10
10
|
the EXACT serde JSON the Rust kernel + projection produce/consume.
|
|
11
|
-
version: 0.64.
|
|
11
|
+
version: 0.64.3
|
|
12
12
|
|
|
13
13
|
environment:
|
|
14
14
|
sdk: ^3.5.0
|
|
@@ -25,6 +25,11 @@ class FakeRunner implements BridgeTransport {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
int get connectCount => sent.where((m) => m['op'] == 'connect').length;
|
|
28
|
+
int autoSyncCount(String workspace) => sent
|
|
29
|
+
.where((m) =>
|
|
30
|
+
m['op'] == 'autoSync' &&
|
|
31
|
+
((m['args'] as Map)['session']).toString() == workspace)
|
|
32
|
+
.length;
|
|
28
33
|
Map<String, Object?> connectArgs(int i) =>
|
|
29
34
|
(sent.where((m) => m['op'] == 'connect').elementAt(i)['args'] as Map)
|
|
30
35
|
.cast<String, Object?>();
|
|
@@ -89,6 +94,8 @@ void main() {
|
|
|
89
94
|
fail('credentials must not be requested for a mounted session'));
|
|
90
95
|
expect(identical(s, bridge.session('home')), isTrue);
|
|
91
96
|
expect(runner.connectCount, 1, reason: 'no re-connect for a live session');
|
|
97
|
+
expect(runner.autoSyncCount('home'), 1,
|
|
98
|
+
reason: 'an acquired application workspace continuously syncs');
|
|
92
99
|
await bridge.dispose();
|
|
93
100
|
});
|
|
94
101
|
|
|
@@ -140,6 +147,8 @@ void main() {
|
|
|
140
147
|
reason: 'ONE connect for three concurrent acquires');
|
|
141
148
|
expect(identical(results[0], results[1]), isTrue);
|
|
142
149
|
expect(identical(results[1], results[2]), isTrue);
|
|
150
|
+
expect(runner.autoSyncCount('cloudy'), 1,
|
|
151
|
+
reason: 'the workspace lifecycle is single-flight too');
|
|
143
152
|
final args = runner.connectArgs(0);
|
|
144
153
|
expect(args['cloud'], 'https://cloud.example');
|
|
145
154
|
expect(args['authToken'], 'tok');
|
package/package.json
CHANGED
|
@@ -1168,6 +1168,18 @@ export function generateDartApplication(
|
|
|
1168
1168
|
` for (final record in records) {`,
|
|
1169
1169
|
` seen.add(NmsInternalRecordOrigins.require(record).workspace);`,
|
|
1170
1170
|
` }`,
|
|
1171
|
+
` for (final record in await shared()) {`,
|
|
1172
|
+
` final workspace = NmsInternalRecordOrigins.require(record).workspace;`,
|
|
1173
|
+
` if (seen.add(workspace)) records.add(record);`,
|
|
1174
|
+
` }`,
|
|
1175
|
+
` return records;`,
|
|
1176
|
+
` }`,
|
|
1177
|
+
``,
|
|
1178
|
+
` /// The ${birth.aggregateId} lives shared with the signed-in person.`,
|
|
1179
|
+
` /// Unlike [available], this does not open lives founded by that person.`,
|
|
1180
|
+
` Future<List<${rootModel}>> shared() async {`,
|
|
1181
|
+
` final records = <${rootModel}>[];`,
|
|
1182
|
+
` final seen = <String>{};`,
|
|
1171
1183
|
` final shares = await _client.bridge.query('sharedByObject', {'object': 'workspace:self'}, null, _client.actor);`,
|
|
1172
1184
|
` for (final row in shares) {`,
|
|
1173
1185
|
` final raw = row['data'];`,
|
|
@@ -1191,6 +1203,12 @@ export function generateDartApplication(
|
|
|
1191
1203
|
` return records;`,
|
|
1192
1204
|
` }`,
|
|
1193
1205
|
``,
|
|
1206
|
+
` /// React when this person's shared ${birth.aggregateId} lives change.`,
|
|
1207
|
+
` Stream<List<${rootModel}>> watchShared() =>`,
|
|
1208
|
+
` _client.bridge`,
|
|
1209
|
+
` .watch('sharedByObject', {'object': 'workspace:self'}, null, _client.actor, 60000)`,
|
|
1210
|
+
` .asyncMap((_) => shared());`,
|
|
1211
|
+
``,
|
|
1194
1212
|
` /// React when this person's available ${birth.aggregateId} lives change.`,
|
|
1195
1213
|
` Stream<List<${rootModel}>> watchAvailable() =>`,
|
|
1196
1214
|
` Stream<List<${rootModel}>>.multi((events) {`,
|
|
@@ -1603,8 +1603,8 @@ const NOMOS_TYPES_DEP = "^0.8.0";
|
|
|
1603
1603
|
// 0.48.x: session-bound clients (XClient.bind/NomosClientInterface/DomainClientSessionMismatch/
|
|
1604
1604
|
// DomainNotInstalledForSession, engine.follow, pendingConflicts lanes) — the generated client
|
|
1605
1605
|
// REQUIRES this surface. 0.48.1 fixes NomosSession.watchPendingConflicts delegation.
|
|
1606
|
-
const NOMOS_CLIENT_DEP = "^0.64.
|
|
1607
|
-
const NOMOS_FLUTTER_DEP = "^0.63.
|
|
1606
|
+
const NOMOS_CLIENT_DEP = "^0.64.3";
|
|
1607
|
+
const NOMOS_FLUTTER_DEP = "^0.63.4";
|
|
1608
1608
|
// DRIFT TRIPWIRE: pubspec_pins.test.ts asserts these equal the CURRENT versions in dsl/dart/pubspec.yaml
|
|
1609
1609
|
// and dsl/dart_types/pubspec.yaml — a Dart release without this bump fails the suite, so tenants never
|
|
1610
1610
|
// land back in dependency_overrides (the exact co2 report this closes, third occurrence).
|