@githolon/dsl 0.102.4 → 0.103.0
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/compile_package.mjs +43 -2
- package/compile_proof.d.mts +47 -0
- package/compile_proof.mjs +343 -0
- package/dart/CHANGELOG.md +7 -0
- package/dart/lib/src/dev_cloud.dart +8 -8
- package/dart/lib/src/nms_generated_runtime.dart +2 -0
- package/dart/lib/src/runner/node-entry.mjs +5504 -1236
- package/dart/lib/src/runner/offline-install.mjs +4578 -528
- package/dart/lib/src/test_harness.dart +206 -152
- package/dart/lib/src/webview_bridge.dart +196 -89
- package/dart/pubspec.lock +1 -1
- package/dart/pubspec.yaml +2 -2
- package/dart/test/acquire_session_credentials_test.dart +4 -3
- package/dart/test/adv_realm_interference_test.dart +7 -6
- package/dart/test/cloud_bound_topology_test.dart +3 -3
- package/dart/test/cluster_births_test.dart +4 -3
- package/dart/test/cluster_device_keyed_acceptance_test.dart +10 -16
- package/dart/test/cluster_local_autosync_test.dart +3 -2
- package/dart/test/cluster_scale_test.dart +3 -2
- package/dart/test/dlq_session_surface_test.dart +7 -6
- package/dart/test/first_app_gauntlet_test.dart +14 -21
- package/dart/test/fixtures/wedge/fake-install.mjs +1 -1
- package/dart/test/four_layer_nested_topology_test.dart +4 -4
- package/dart/test/harness_phase_timeout_test.dart +2 -2
- package/dart/test/local_sync_inert_test.dart +1 -1
- package/dart/test/parent_signer_recovery_test.dart +2 -2
- package/dart/test/sealing_client_test.dart +4 -3
- package/dart/test/session_bound_test.dart +8 -7
- package/dart/test/test_harness_test.dart +13 -12
- package/dart/test/vo_read_typing_test.dart +4 -3
- package/dart/test/watch_after_offer_stall_test.dart +4 -3
- package/dart/test/webview_bridge_test.dart +102 -5
- package/dart_types/CHANGELOG.md +5 -0
- package/dart_types/lib/nomos_types.dart +9 -0
- package/dart_types/lib/src/nomos_ref.dart +20 -0
- package/dart_types/lib/src/optional_ref.dart +85 -0
- package/dart_types/lib/src/sha256_digest.dart +41 -0
- package/dart_types/lib/src/versioned_nomos_ref.dart +81 -0
- package/dart_types/pubspec.yaml +1 -1
- package/dart_types/test/optional_ref_test.dart +44 -0
- package/dart_types/test/sha256_digest_test.dart +18 -0
- package/package.json +9 -5
- package/runtime/generated-application-host.js +5365 -1105
- package/src/aggregate.ts +18 -0
- package/src/application_composition.ts +6 -32
- package/src/attested_core.ts +6 -3
- package/src/birth_meter.ts +1 -0
- package/src/birth_meter_ensure.ts +28 -2
- package/src/births.ts +1 -1
- package/src/build_package.ts +162 -538
- package/src/business_birth.ts +19 -3
- package/src/canonical_json.ts +21 -0
- package/src/check_package_main.ts +76 -35
- package/src/clear_directive.ts +19 -8
- package/src/codegen_dart.ts +305 -22
- package/src/codegen_dart_application.ts +844 -292
- package/src/codegen_dart_client.ts +1084 -407
- package/src/codegen_dart_scenario.ts +12 -12
- package/src/codegen_proof.ts +169 -39
- package/src/codegen_provider_dart.ts +17 -1
- package/src/codegen_ts.ts +298 -106
- package/src/compat_manifest.ts +2 -2
- package/src/compat_package_main.ts +8 -12
- package/src/compile_package_main.ts +1866 -597
- package/src/compose.ts +1 -1
- package/src/directive.ts +208 -15
- package/src/engine_entry.ts +165 -111
- package/src/evolve_preview.ts +43 -81
- package/src/extremum.ts +1 -1
- package/src/fields.ts +183 -4
- package/src/flow_gate.ts +75 -2
- package/src/framework/application_home.ts +2 -0
- package/src/framework/auth_provider_law.ts +2 -2
- package/src/framework/birth_meter.ts +7 -19
- package/src/framework/bootstrap.ts +0 -2
- package/src/framework/business_corrections.ts +9 -5
- package/src/framework/domain_lifecycle.ts +15 -29
- package/src/framework/encryption_scope.ts +2 -1
- package/src/framework/framework_aggregates.ts +2 -0
- package/src/framework/genesis.ts +16 -14
- package/src/framework/home.ts +20 -14
- package/src/framework/owned_custody.ts +9 -3
- package/src/framework/platform_admin.ts +3 -3
- package/src/framework/relations.ts +21 -13
- package/src/framework/signing_identity.ts +45 -40
- package/src/framework/workspace_invariant.ts +245 -0
- package/src/framework/workspaces.ts +23 -29
- package/src/index.ts +36 -16
- package/src/intent.ts +178 -6
- package/src/law_check.ts +32 -11
- package/src/law_graph.ts +2902 -0
- package/src/law_graph_usda.ts +1799 -0
- package/src/law_identity.ts +435 -0
- package/src/law_value_type.ts +230 -0
- package/src/lsp_server.ts +61 -31
- package/src/ops.ts +7 -0
- package/src/package_config.ts +141 -0
- package/src/package_dependencies.ts +108 -0
- package/src/package_dependency_usage.ts +150 -0
- package/src/package_source_imports.ts +71 -0
- package/src/plan_source_slice.ts +335 -0
- package/src/point_guard.ts +192 -0
- package/src/precondition.ts +44 -9
- package/src/read_projection_namespace.ts +1 -1
- package/src/runtime_zod.ts +44 -0
- package/src/secondary_ensure.ts +67 -14
- package/src/shape_compat.ts +1 -1
- package/src/stable_id_identity_bridge.ts +149 -0
- package/src/stable_id_lockfile.ts +278 -0
- package/src/stable_ids.ts +7 -2
- package/src/stable_ids_for_module.ts +348 -0
- package/src/stable_ids_types.ts +1 -1
- package/src/sum.ts +1 -1
- package/src/usd_state.ts +1 -1
- package/src/usda_relationship_integrity.ts +66 -0
- package/src/usda_text.ts +18 -0
- package/src/usdz.ts +78 -0
- package/src/value_object.ts +95 -0
- package/src/wire.ts +19 -0
- package/src/wire_encode.ts +22 -0
- package/src/workspace_sharding.ts +52 -36
- package/src/workspace_sharing.ts +28 -4
- package/src/zod_introspection.ts +1 -1
- package/dart/test/cluster_attested_test.dart +0 -201
- package/src/compile_engine.ts +0 -595
- package/src/manifest.ts +0 -1476
- package/src/read_projection_composition.ts +0 -117
- package/src/read_projection_manifest.ts +0 -115
- package/src/relation_composition.ts +0 -286
- package/src/usd.ts +0 -1967
- package/src/usd_layers.ts +0 -1776
|
@@ -7,13 +7,14 @@ import type { z } from "zod";
|
|
|
7
7
|
|
|
8
8
|
import type { AggregateHandle } from "./aggregate.js";
|
|
9
9
|
import type { Directive } from "./directive.js";
|
|
10
|
-
import type
|
|
10
|
+
import { isSha256DigestSchema, type Field } from "./fields.js";
|
|
11
11
|
import type { QueryDecl } from "./query.js";
|
|
12
12
|
import type { DomainModule } from "./codegen_dart.js";
|
|
13
13
|
import {
|
|
14
14
|
cap,
|
|
15
15
|
concreteClassName,
|
|
16
16
|
dartBusinessRecordInputs,
|
|
17
|
+
dartForeignReferenceInputs,
|
|
17
18
|
dartActionInputClassName,
|
|
18
19
|
introspectPayload,
|
|
19
20
|
readModelClassName,
|
|
@@ -22,7 +23,10 @@ import {
|
|
|
22
23
|
import { dartClientClassName } from "./codegen_dart_client.js";
|
|
23
24
|
import { finishCount } from "./count.js";
|
|
24
25
|
import { finishSum } from "./sum.js";
|
|
25
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
isNamespacedAggregate,
|
|
28
|
+
qualifyReadProjectionType,
|
|
29
|
+
} from "./read_projection_namespace.js";
|
|
26
30
|
import type { SurfacedResolverBinding } from "./surfaced_lint.js";
|
|
27
31
|
import {
|
|
28
32
|
isFrameworkAggregate,
|
|
@@ -39,14 +43,46 @@ import type {
|
|
|
39
43
|
|
|
40
44
|
type AnyDirective = Directive<z.ZodTypeAny>;
|
|
41
45
|
|
|
42
|
-
const camel = (s: string) =>
|
|
43
|
-
|
|
46
|
+
const camel = (s: string) =>
|
|
47
|
+
s.replace(/[_\s-]+(\w)/g, (_m, c: string) => c.toUpperCase());
|
|
48
|
+
const lcFirst = (s: string) =>
|
|
49
|
+
s.length ? s[0]!.toLowerCase() + s.slice(1) : s;
|
|
44
50
|
const reserved = new Set([
|
|
45
|
-
"assert",
|
|
46
|
-
"
|
|
47
|
-
"
|
|
51
|
+
"assert",
|
|
52
|
+
"break",
|
|
53
|
+
"case",
|
|
54
|
+
"catch",
|
|
55
|
+
"class",
|
|
56
|
+
"const",
|
|
57
|
+
"continue",
|
|
58
|
+
"default",
|
|
59
|
+
"do",
|
|
60
|
+
"else",
|
|
61
|
+
"enum",
|
|
62
|
+
"extends",
|
|
63
|
+
"false",
|
|
64
|
+
"final",
|
|
65
|
+
"for",
|
|
66
|
+
"if",
|
|
67
|
+
"in",
|
|
68
|
+
"is",
|
|
69
|
+
"new",
|
|
70
|
+
"null",
|
|
71
|
+
"rethrow",
|
|
72
|
+
"return",
|
|
73
|
+
"super",
|
|
74
|
+
"switch",
|
|
75
|
+
"this",
|
|
76
|
+
"throw",
|
|
77
|
+
"true",
|
|
78
|
+
"try",
|
|
79
|
+
"var",
|
|
80
|
+
"void",
|
|
81
|
+
"while",
|
|
82
|
+
"with",
|
|
83
|
+
"dynamic",
|
|
48
84
|
]);
|
|
49
|
-
const safe = (s: string) => reserved.has(s) ? `${s}_` : s;
|
|
85
|
+
const safe = (s: string) => (reserved.has(s) ? `${s}_` : s);
|
|
50
86
|
const member = (s: string) => safe(lcFirst(camel(s)));
|
|
51
87
|
const TENANT_ROOT_MEMBERS = new Map<string, string>([
|
|
52
88
|
["nomosWidget", "the generated Nomos Flutter bootstrap"],
|
|
@@ -66,7 +102,8 @@ function plural(name: string): string {
|
|
|
66
102
|
|
|
67
103
|
function keyParam(key: string): string {
|
|
68
104
|
const parts = key.split(".").filter(Boolean);
|
|
69
|
-
const raw =
|
|
105
|
+
const raw =
|
|
106
|
+
parts.length === 0 ? "key" : parts[0]! + parts.slice(1).map(cap).join("");
|
|
70
107
|
const cleaned = raw.replace(/[^A-Za-z0-9_]/g, "_");
|
|
71
108
|
return safe(/^[0-9]/.test(cleaned) ? `v${cleaned}` : cleaned);
|
|
72
109
|
}
|
|
@@ -77,12 +114,20 @@ function keyType(aggregate: AggregateHandle | undefined, key: string): string {
|
|
|
77
114
|
if (field?.kind === "int" || field?.kind === "counter") return "int";
|
|
78
115
|
if (field?.kind === "ref") {
|
|
79
116
|
if (field.foreignRef !== undefined) {
|
|
80
|
-
const target =
|
|
81
|
-
|
|
82
|
-
|
|
117
|
+
const target =
|
|
118
|
+
field.foreignRef.targetIsContract === true
|
|
119
|
+
? contractTypeName(field.foreignRef.targetAggregateId)
|
|
120
|
+
: concreteClassName(field.foreignRef.targetAggregateId);
|
|
83
121
|
return `ForeignRef<${target}>`;
|
|
84
122
|
}
|
|
85
|
-
if (field.refAggregateId !== undefined)
|
|
123
|
+
if (field.refAggregateId !== undefined)
|
|
124
|
+
return `NomosRef<${concreteClassName(field.refAggregateId)}>`;
|
|
125
|
+
}
|
|
126
|
+
if (
|
|
127
|
+
field?.kind === "string" &&
|
|
128
|
+
isSha256DigestSchema(field.zod as z.ZodTypeAny)
|
|
129
|
+
) {
|
|
130
|
+
return "Sha256Digest";
|
|
86
131
|
}
|
|
87
132
|
return "String";
|
|
88
133
|
}
|
|
@@ -90,13 +135,16 @@ function keyType(aggregate: AggregateHandle | undefined, key: string): string {
|
|
|
90
135
|
function sharingOrderValue(
|
|
91
136
|
policy: WorkspaceSharingPolicy,
|
|
92
137
|
fieldName: string,
|
|
93
|
-
target: "wire" | "value" = "value"
|
|
138
|
+
target: "wire" | "value" = "value"
|
|
94
139
|
): string {
|
|
95
140
|
const field = policy.orders.fields[fieldName];
|
|
96
141
|
if (field === undefined) {
|
|
97
|
-
throw new Error(
|
|
142
|
+
throw new Error(
|
|
143
|
+
`workspaceSharing(${policy.orders.id}): unknown order field '${fieldName}'`
|
|
144
|
+
);
|
|
98
145
|
}
|
|
99
|
-
const value =
|
|
146
|
+
const value =
|
|
147
|
+
`(order.${member(fieldName)} ?? (throw StateError(` +
|
|
100
148
|
`'Incomplete ${policy.orders.id} ${member(fieldName)}')))`;
|
|
101
149
|
if (target === "wire" && field.kind === "enum") return `${value}.wire`;
|
|
102
150
|
if (field.kind === "ref") return `NmsInternalNomosRefs.id(${value})`;
|
|
@@ -105,40 +153,51 @@ function sharingOrderValue(
|
|
|
105
153
|
|
|
106
154
|
function sharingReceiptCondition(
|
|
107
155
|
policy: WorkspaceSharingPolicy,
|
|
108
|
-
receipt: WorkspaceSharingReceipt
|
|
156
|
+
receipt: WorkspaceSharingReceipt
|
|
109
157
|
): string {
|
|
110
158
|
const condition = receipt.when;
|
|
111
159
|
if (condition === undefined) return "true";
|
|
112
160
|
const field = policy.orders.fields[condition.field];
|
|
113
161
|
if (field === undefined) {
|
|
114
|
-
throw new Error(
|
|
162
|
+
throw new Error(
|
|
163
|
+
`workspaceSharing(${policy.orders.id}): unknown receipt field '${condition.field}'`
|
|
164
|
+
);
|
|
115
165
|
}
|
|
116
166
|
if (!field.isOptional) {
|
|
117
167
|
if (condition.is === "absent") {
|
|
118
168
|
throw new Error(
|
|
119
|
-
`workspaceSharing(${policy.orders.id}): '${condition.field}' cannot be absent because it is required
|
|
169
|
+
`workspaceSharing(${policy.orders.id}): '${condition.field}' cannot be absent because it is required`
|
|
120
170
|
);
|
|
121
171
|
}
|
|
122
172
|
return "true";
|
|
123
173
|
}
|
|
124
|
-
return `order.${member(condition.field)} ${
|
|
174
|
+
return `order.${member(condition.field)} ${
|
|
175
|
+
condition.is === "present" ? "!=" : "=="
|
|
176
|
+
} null`;
|
|
125
177
|
}
|
|
126
178
|
|
|
127
179
|
function sharingReceiptPayload(
|
|
128
180
|
policy: WorkspaceSharingPolicy,
|
|
129
181
|
receipt: WorkspaceSharingReceipt,
|
|
130
|
-
refused: boolean
|
|
182
|
+
refused: boolean
|
|
131
183
|
): string[] {
|
|
132
|
-
const fields = introspectPayload(
|
|
184
|
+
const fields = introspectPayload(
|
|
185
|
+
receipt.records.payloadSchema as z.ZodTypeAny,
|
|
186
|
+
new Map()
|
|
187
|
+
);
|
|
133
188
|
return fields.flatMap((target) => {
|
|
134
189
|
if (target.name === "failureReason") {
|
|
135
190
|
return refused
|
|
136
|
-
? [
|
|
191
|
+
? [
|
|
192
|
+
` 'failureReason': share.message ?? 'shareDeliveryRefused',`,
|
|
193
|
+
]
|
|
137
194
|
: target.optional
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
195
|
+
? []
|
|
196
|
+
: (() => {
|
|
197
|
+
throw new Error(
|
|
198
|
+
`workspaceSharing(${policy.orders.id}): delivered receipt '${receipt.records.id}' requires failureReason`
|
|
199
|
+
);
|
|
200
|
+
})();
|
|
142
201
|
}
|
|
143
202
|
if (target.name === "deliveryHead" && target.optional) return [];
|
|
144
203
|
const sourceName = receipt.payload?.[target.name] ?? target.name;
|
|
@@ -147,7 +206,7 @@ function sharingReceiptPayload(
|
|
|
147
206
|
if (target.optional) return [];
|
|
148
207
|
throw new Error(
|
|
149
208
|
`workspaceSharing(${policy.orders.id}): receipt '${receipt.records.id}' requires '${target.name}', ` +
|
|
150
|
-
`but the durable order does not record '${sourceName}'
|
|
209
|
+
`but the durable order does not record '${sourceName}'`
|
|
151
210
|
);
|
|
152
211
|
}
|
|
153
212
|
const raw = sharingOrderValue(policy, sourceName);
|
|
@@ -156,34 +215,65 @@ function sharingReceiptPayload(
|
|
|
156
215
|
});
|
|
157
216
|
}
|
|
158
217
|
|
|
159
|
-
export const dartApplicationClassName = (domain: string): string =>
|
|
160
|
-
|
|
161
|
-
export const
|
|
162
|
-
`${
|
|
218
|
+
export const dartApplicationClassName = (domain: string): string =>
|
|
219
|
+
`${cap(camel(domain))}Application`;
|
|
220
|
+
export const dartApplicationActivityClassName = (domain: string): string =>
|
|
221
|
+
`${cap(camel(domain))}Activity`;
|
|
222
|
+
export const dartAggregateCollectionClassName = (
|
|
223
|
+
aggregate: AggregateHandle
|
|
224
|
+
): string => `${concreteClassName(aggregate.id)}Collection`;
|
|
163
225
|
|
|
164
|
-
const aggregateConflictsClassName = (
|
|
226
|
+
const aggregateConflictsClassName = (
|
|
227
|
+
domain: string,
|
|
228
|
+
aggregateId: string
|
|
229
|
+
): string => {
|
|
165
230
|
const aggregateName = cap(camel(concreteClassName(aggregateId)));
|
|
166
231
|
return aggregateName === cap(camel(domain))
|
|
167
232
|
? `${aggregateName}AggregateConflicts`
|
|
168
233
|
: `${aggregateName}Conflicts`;
|
|
169
234
|
};
|
|
170
235
|
|
|
171
|
-
const dartActionResultClassName = (directiveId: string): string =>
|
|
236
|
+
const dartActionResultClassName = (directiveId: string): string =>
|
|
237
|
+
`${cap(camel(directiveId))}Result`;
|
|
172
238
|
|
|
173
239
|
function dartActionOutputs(
|
|
174
240
|
directive: AnyDirective,
|
|
175
|
-
aggregates: readonly AggregateHandle[]
|
|
176
|
-
): {
|
|
177
|
-
|
|
241
|
+
aggregates: readonly AggregateHandle[]
|
|
242
|
+
): {
|
|
243
|
+
aggregateId: string;
|
|
244
|
+
field: string;
|
|
245
|
+
dartType: string;
|
|
246
|
+
many: boolean;
|
|
247
|
+
optional: boolean;
|
|
248
|
+
}[] {
|
|
249
|
+
const known = new Set(
|
|
250
|
+
aggregates
|
|
251
|
+
.filter(
|
|
252
|
+
(aggregate) =>
|
|
253
|
+
!isFrameworkAggregate(aggregate) &&
|
|
254
|
+
(aggregate as { businessBirthInternal?: unknown })
|
|
255
|
+
.businessBirthInternal !== true
|
|
256
|
+
)
|
|
257
|
+
.map((aggregate) => aggregate.id)
|
|
258
|
+
);
|
|
178
259
|
const raw = [
|
|
179
|
-
{
|
|
180
|
-
|
|
260
|
+
{
|
|
261
|
+
aggregateId: directive.aggregateId,
|
|
262
|
+
many: directive.outputCardinality === "many",
|
|
263
|
+
optional: directive.outputCardinality === "optional",
|
|
264
|
+
},
|
|
265
|
+
...(directive.secondaryOutputs ?? []).map((output) => ({
|
|
181
266
|
aggregateId: output.aggregateId,
|
|
182
267
|
many: output.many === true,
|
|
183
|
-
|
|
268
|
+
optional: output.optional === true,
|
|
269
|
+
})),
|
|
184
270
|
].filter((output) => known.has(output.aggregateId));
|
|
185
|
-
const outputs = raw.filter(
|
|
186
|
-
|
|
271
|
+
const outputs = raw.filter(
|
|
272
|
+
(output, index) =>
|
|
273
|
+
raw.findIndex(
|
|
274
|
+
(candidate) => candidate.aggregateId === output.aggregateId
|
|
275
|
+
) === index
|
|
276
|
+
);
|
|
187
277
|
const fields = new Set<string>();
|
|
188
278
|
return outputs.map((output) => {
|
|
189
279
|
const dartType = concreteClassName(output.aggregateId);
|
|
@@ -191,7 +281,7 @@ function dartActionOutputs(
|
|
|
191
281
|
if (fields.has(field)) {
|
|
192
282
|
throw new Error(
|
|
193
283
|
`Dart application result for '${directive.id}' has two aggregate consequences named '${field}'; ` +
|
|
194
|
-
`rename one aggregate so the generated business result is unambiguous
|
|
284
|
+
`rename one aggregate so the generated business result is unambiguous`
|
|
195
285
|
);
|
|
196
286
|
}
|
|
197
287
|
fields.add(field);
|
|
@@ -211,9 +301,16 @@ function dartConflictPublicClassNames(mod: DomainModule): string[] {
|
|
|
211
301
|
const aggregateName = cap(camel(concreteClassName(binding.aggregateId)));
|
|
212
302
|
names.add(aggregateConflictsClassName(domain, binding.aggregateId));
|
|
213
303
|
names.add(`${aggregateName}PendingConflict`);
|
|
214
|
-
if (
|
|
304
|
+
if (
|
|
305
|
+
binding.valuePayloadField !== undefined &&
|
|
306
|
+
binding.idPayloadField !== undefined
|
|
307
|
+
) {
|
|
215
308
|
names.add(`${aggregateName}${cap(camel(binding.field))}Conflict`);
|
|
216
|
-
names.add(
|
|
309
|
+
names.add(
|
|
310
|
+
`${aggregateName}${cap(
|
|
311
|
+
camel(binding.field)
|
|
312
|
+
)}ConflictModelUpdateRequired`
|
|
313
|
+
);
|
|
217
314
|
}
|
|
218
315
|
}
|
|
219
316
|
return [...names];
|
|
@@ -221,35 +318,50 @@ function dartConflictPublicClassNames(mod: DomainModule): string[] {
|
|
|
221
318
|
|
|
222
319
|
export function dartApplicationPublicClassNames(
|
|
223
320
|
mod: DomainModule,
|
|
224
|
-
opts: { readonly tenantRoot?: boolean } = {}
|
|
321
|
+
opts: { readonly tenantRoot?: boolean } = {}
|
|
225
322
|
): string[] {
|
|
226
323
|
registerDartNames(mod.aggregates);
|
|
227
|
-
const actions = (
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
(directive
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
324
|
+
const actions = (
|
|
325
|
+
(mod.applicationIntents ?? mod.directives) as AnyDirective[]
|
|
326
|
+
).filter(
|
|
327
|
+
(directive) =>
|
|
328
|
+
directive &&
|
|
329
|
+
typeof directive.id === "string" &&
|
|
330
|
+
(directive as { businessBirthInternal?: unknown })
|
|
331
|
+
.businessBirthInternal !== true &&
|
|
332
|
+
(directive as { businessBirth?: unknown }).businessBirth === undefined &&
|
|
333
|
+
(directive as { birthsChild?: unknown }).birthsChild !== true &&
|
|
334
|
+
!isFrameworkDirective(directive) &&
|
|
335
|
+
dartActionOutputs(directive, mod.aggregates).length > 0
|
|
336
|
+
);
|
|
235
337
|
return [
|
|
236
338
|
...(opts.tenantRoot === true
|
|
237
|
-
? [
|
|
339
|
+
? [
|
|
340
|
+
"Identity",
|
|
341
|
+
"User",
|
|
342
|
+
...(mod.applicationWorkspaceSharing?.length
|
|
343
|
+
? ["Recipient", "Invitation"]
|
|
344
|
+
: []),
|
|
345
|
+
]
|
|
238
346
|
: []),
|
|
239
347
|
dartApplicationClassName(mod.domain ?? mod.name),
|
|
240
348
|
dartApplicationActivityClassName(mod.domain ?? mod.name),
|
|
241
349
|
...actions.map((directive) => dartActionResultClassName(directive.id)),
|
|
242
350
|
...mod.aggregates
|
|
243
|
-
.filter(
|
|
244
|
-
(aggregate
|
|
245
|
-
|
|
351
|
+
.filter(
|
|
352
|
+
(aggregate) =>
|
|
353
|
+
(aggregate as { businessBirthInternal?: unknown })
|
|
354
|
+
.businessBirthInternal !== true && !isFrameworkAggregate(aggregate)
|
|
355
|
+
)
|
|
246
356
|
.map(dartAggregateCollectionClassName),
|
|
247
357
|
...dartConflictPublicClassNames(mod),
|
|
248
358
|
];
|
|
249
359
|
}
|
|
250
360
|
|
|
251
361
|
export interface DartApplicationOptions {
|
|
252
|
-
readonly packageAggregateDomains?: Readonly<
|
|
362
|
+
readonly packageAggregateDomains?: Readonly<
|
|
363
|
+
Record<string, { domain: string; name: string }>
|
|
364
|
+
>;
|
|
253
365
|
readonly typesImportUri?: string;
|
|
254
366
|
readonly explicitTypeImports?: readonly string[];
|
|
255
367
|
readonly supportImport?: "vendored" | "package";
|
|
@@ -267,13 +379,18 @@ export interface DartApplicationOptions {
|
|
|
267
379
|
* hosting and generated sibling facades import them directly from `src/generated`; tenant autocomplete sees
|
|
268
380
|
* only `<Domain>Application`.
|
|
269
381
|
*/
|
|
270
|
-
export function generateDartApplication(
|
|
382
|
+
export function generateDartApplication(
|
|
383
|
+
mod: DomainModule,
|
|
384
|
+
opts: DartApplicationOptions = {}
|
|
385
|
+
): string {
|
|
271
386
|
registerDartNames(mod.aggregates);
|
|
272
387
|
const domain = mod.domain ?? mod.name;
|
|
273
388
|
const application = dartApplicationClassName(domain);
|
|
274
389
|
const activity = dartApplicationActivityClassName(domain);
|
|
275
390
|
const client = dartClientClassName(domain);
|
|
276
|
-
const aggregates = new Map(
|
|
391
|
+
const aggregates = new Map(
|
|
392
|
+
mod.aggregates.map((aggregate) => [aggregate.id, aggregate])
|
|
393
|
+
);
|
|
277
394
|
const packageDomains = opts.packageAggregateDomains ?? {};
|
|
278
395
|
const appLines: string[] = [];
|
|
279
396
|
const applicationMembers = new Map<string, string>();
|
|
@@ -282,7 +399,7 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
282
399
|
if (prior !== undefined) {
|
|
283
400
|
throw new Error(
|
|
284
401
|
`Sorry mate — generated Dart application member '${name}' is already owned by ${prior}; ` +
|
|
285
|
-
`${owner} cannot be silently renamed or rerouted. Rename the tenant concept and compile again
|
|
402
|
+
`${owner} cannot be silently renamed or rerouted. Rename the tenant concept and compile again.`
|
|
286
403
|
);
|
|
287
404
|
}
|
|
288
405
|
applicationMembers.set(name, owner);
|
|
@@ -295,13 +412,114 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
295
412
|
let usesWorkspaceName = false;
|
|
296
413
|
let usesAffectedReference = false;
|
|
297
414
|
let usesAffectedReferences = false;
|
|
415
|
+
let usesAffectedReferenceOrNull = false;
|
|
298
416
|
const actionResultDeclarations: string[] = [];
|
|
299
417
|
const conflictDeclarations: string[] = [];
|
|
300
|
-
const resolverBindings: readonly SurfacedResolverBinding[] =
|
|
301
|
-
|
|
418
|
+
const resolverBindings: readonly SurfacedResolverBinding[] =
|
|
419
|
+
mod.surfacedResolvers ?? [];
|
|
420
|
+
const sharingPolicies: readonly WorkspaceSharingPolicy[] =
|
|
421
|
+
mod.applicationWorkspaceSharing ?? [];
|
|
422
|
+
const hasVersionedRefs = mod.aggregates.some((aggregate) =>
|
|
423
|
+
Object.values(aggregate.fields as Record<string, Field>).some(
|
|
424
|
+
(field) => field.versionedForeignRef !== undefined
|
|
425
|
+
)
|
|
426
|
+
);
|
|
427
|
+
if (hasVersionedRefs) {
|
|
428
|
+
claimApplicationMember(
|
|
429
|
+
"freshness",
|
|
430
|
+
"the generated versioned-reference boundary"
|
|
431
|
+
);
|
|
432
|
+
appLines.push(
|
|
433
|
+
` /// Check whether [reference]'s immutable publication is still current. This reads the`,
|
|
434
|
+
` /// stable series' one compiled pointer; it never reconstructs publication history.`,
|
|
435
|
+
` Future<ReferenceFreshness<TVersion>> freshness<TVersion, TSeries>(`,
|
|
436
|
+
` VersionedNomosRef<TVersion, TSeries> reference) =>`,
|
|
437
|
+
` _client.referenceFreshness(reference);`,
|
|
438
|
+
``
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
const versionedPayloadFactories = new Map<
|
|
442
|
+
string,
|
|
443
|
+
{
|
|
444
|
+
target: string;
|
|
445
|
+
series: string;
|
|
446
|
+
targetInput: string;
|
|
447
|
+
seriesInput: string;
|
|
448
|
+
targetWireType: string;
|
|
449
|
+
seriesWireType: string;
|
|
450
|
+
currentField: string;
|
|
451
|
+
}
|
|
452
|
+
>();
|
|
453
|
+
for (const directive of mod.directives as AnyDirective[]) {
|
|
454
|
+
if (!isBusinessIntent(directive)) continue;
|
|
455
|
+
for (const spec of introspectPayload(
|
|
456
|
+
directive.payloadSchema as z.ZodTypeAny,
|
|
457
|
+
new Map()
|
|
458
|
+
)) {
|
|
459
|
+
if (
|
|
460
|
+
spec.toJson !== "versionedRef" ||
|
|
461
|
+
spec.versionedRefTarget === undefined ||
|
|
462
|
+
spec.versionedRefSeries === undefined ||
|
|
463
|
+
spec.versionedRefTargetInput === undefined ||
|
|
464
|
+
spec.versionedRefSeriesInput === undefined ||
|
|
465
|
+
spec.versionedRefTargetWireType === undefined ||
|
|
466
|
+
spec.versionedRefSeriesWireType === undefined ||
|
|
467
|
+
spec.versionedRefCurrentField === undefined
|
|
468
|
+
)
|
|
469
|
+
continue;
|
|
470
|
+
const method = `pin${cap(camel(spec.name))}`;
|
|
471
|
+
const next = {
|
|
472
|
+
target: spec.versionedRefTarget,
|
|
473
|
+
series: spec.versionedRefSeries,
|
|
474
|
+
targetInput: spec.versionedRefTargetInput,
|
|
475
|
+
seriesInput: spec.versionedRefSeriesInput,
|
|
476
|
+
targetWireType: spec.versionedRefTargetWireType,
|
|
477
|
+
seriesWireType: spec.versionedRefSeriesWireType,
|
|
478
|
+
currentField: spec.versionedRefCurrentField,
|
|
479
|
+
};
|
|
480
|
+
const prior = versionedPayloadFactories.get(method);
|
|
481
|
+
if (
|
|
482
|
+
prior !== undefined &&
|
|
483
|
+
JSON.stringify(prior) !== JSON.stringify(next)
|
|
484
|
+
) {
|
|
485
|
+
throw new Error(
|
|
486
|
+
`generated version-pin factory '${method}' names incompatible reference contracts; rename one payload field`
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
versionedPayloadFactories.set(method, next);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
for (const [method, spec] of [...versionedPayloadFactories].sort(([a], [b]) =>
|
|
493
|
+
a.localeCompare(b)
|
|
494
|
+
)) {
|
|
495
|
+
claimApplicationMember(method, "the generated version-pin factory");
|
|
496
|
+
appLines.push(
|
|
497
|
+
` /// Pin one immutable publication to its stable series. The current-pointer field is`,
|
|
498
|
+
` /// compiled from domain law; callers provide typed records, never workspace/id strings.`,
|
|
499
|
+
` VersionedNomosRef<${spec.target}, ${spec.series}> ${method}({`,
|
|
500
|
+
` required NomosRef<${spec.targetInput}> pinned,`,
|
|
501
|
+
` required NomosRef<${spec.seriesInput}> series,`,
|
|
502
|
+
` }) {`,
|
|
503
|
+
` if (pinned.workspace != series.workspace) {`,
|
|
504
|
+
` throw ArgumentError('A version and its stable series must share one workspace');`,
|
|
505
|
+
` }`,
|
|
506
|
+
` return NmsInternalVersionedNomosRefs.create<${spec.target}, ${spec.series}>(`,
|
|
507
|
+
` pinned: NmsInternalNomosRefs.retype<${
|
|
508
|
+
spec.target
|
|
509
|
+
}>(pinned, expectedType: ${JSON.stringify(spec.targetWireType)}),`,
|
|
510
|
+
` series: NmsInternalNomosRefs.retype<${
|
|
511
|
+
spec.series
|
|
512
|
+
}>(series, expectedType: ${JSON.stringify(spec.seriesWireType)}),`,
|
|
513
|
+
` currentField: ${JSON.stringify(spec.currentField)},`,
|
|
514
|
+
` );`,
|
|
515
|
+
` }`,
|
|
516
|
+
``
|
|
517
|
+
);
|
|
518
|
+
}
|
|
302
519
|
const tenantRoot = opts.tenantRoot ?? opts.flutterHost === true;
|
|
303
520
|
if (tenantRoot) {
|
|
304
|
-
for (const [name, owner] of TENANT_ROOT_MEMBERS)
|
|
521
|
+
for (const [name, owner] of TENANT_ROOT_MEMBERS)
|
|
522
|
+
claimApplicationMember(name, owner);
|
|
305
523
|
}
|
|
306
524
|
if (!tenantRoot && sharingPolicies.length > 0) {
|
|
307
525
|
for (const name of ["recipient", "createInvitation", "redeemInvitation"]) {
|
|
@@ -315,45 +533,76 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
315
533
|
};
|
|
316
534
|
const typeImportUriFor = (name: string): string => {
|
|
317
535
|
for (const uri of explicitTypeImportUris) {
|
|
318
|
-
if (uri.endsWith(`/${name}.dart`) || uri.endsWith(`${name}.dart`))
|
|
536
|
+
if (uri.endsWith(`/${name}.dart`) || uri.endsWith(`${name}.dart`))
|
|
537
|
+
return uri;
|
|
319
538
|
}
|
|
320
539
|
return `${name}.dart`;
|
|
321
540
|
};
|
|
322
541
|
|
|
323
|
-
const actionDeclarations = (mod.applicationIntents ??
|
|
542
|
+
const actionDeclarations = (mod.applicationIntents ??
|
|
543
|
+
mod.directives) as AnyDirective[];
|
|
324
544
|
const directives = actionDeclarations.filter(
|
|
325
|
-
(directive) =>
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
545
|
+
(directive) =>
|
|
546
|
+
directive &&
|
|
547
|
+
typeof directive.id === "string" &&
|
|
548
|
+
typeof (directive as { payloadSchema?: unknown }).payloadSchema ===
|
|
549
|
+
"object" &&
|
|
550
|
+
(directive as { businessBirthInternal?: unknown })
|
|
551
|
+
.businessBirthInternal !== true &&
|
|
552
|
+
!isFrameworkDirective(directive)
|
|
329
553
|
);
|
|
330
554
|
const actionReferenceChecks = (directive: AnyDirective): string[] => {
|
|
331
555
|
let specs: ReturnType<typeof introspectPayload>;
|
|
332
556
|
try {
|
|
333
|
-
specs = introspectPayload(
|
|
557
|
+
specs = introspectPayload(
|
|
558
|
+
directive.payloadSchema as z.ZodTypeAny,
|
|
559
|
+
new Map()
|
|
560
|
+
);
|
|
334
561
|
} catch {
|
|
335
562
|
return [];
|
|
336
563
|
}
|
|
337
564
|
const recordInputIds = new Set(
|
|
338
|
-
dartBusinessRecordInputs(directive, mod.aggregates).map(
|
|
565
|
+
dartBusinessRecordInputs(directive, mod.aggregates).map(
|
|
566
|
+
(input) => input.idField
|
|
567
|
+
)
|
|
568
|
+
);
|
|
569
|
+
const foreignReferenceIds = new Set(
|
|
570
|
+
dartForeignReferenceInputs(directive, mod.aggregates).map(
|
|
571
|
+
(input) => input.idField
|
|
572
|
+
)
|
|
339
573
|
);
|
|
340
574
|
return specs
|
|
341
575
|
.filter((spec) => spec.toJson === "ref" && !recordInputIds.has(spec.name))
|
|
342
576
|
.map((spec) => {
|
|
343
|
-
const targetClass =
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
577
|
+
const targetClass =
|
|
578
|
+
spec.dartType.match(/^NomosRef<(.*)>$/)?.[1] ?? "Object?";
|
|
579
|
+
const targetWire =
|
|
580
|
+
spec.referenceType ??
|
|
581
|
+
[...aggregates.values()].find(
|
|
582
|
+
(aggregate) => concreteClassName(aggregate.id) === targetClass
|
|
583
|
+
)?.id ??
|
|
584
|
+
targetClass;
|
|
585
|
+
const expectedWorkspace = foreignReferenceIds.has(spec.name)
|
|
586
|
+
? "''"
|
|
587
|
+
: "_client.session?.workspace ?? ''";
|
|
588
|
+
const value = `facts.${member(spec.name)}`;
|
|
589
|
+
const check = `_nmsRequireReference(${value}, ${JSON.stringify(
|
|
590
|
+
targetWire
|
|
591
|
+
)}, ${expectedWorkspace});`;
|
|
349
592
|
return spec.optional
|
|
350
|
-
? `
|
|
593
|
+
? ` ${value}.when(absent: () {}, present: (reference) => _nmsRequireReference(reference, ${JSON.stringify(
|
|
594
|
+
targetWire
|
|
595
|
+
)}, ${expectedWorkspace}));`
|
|
351
596
|
: ` ${check}`;
|
|
352
597
|
});
|
|
353
598
|
};
|
|
354
599
|
|
|
355
600
|
for (const aggregate of mod.aggregates) {
|
|
356
|
-
if (
|
|
601
|
+
if (
|
|
602
|
+
(aggregate as { businessBirthInternal?: unknown })
|
|
603
|
+
.businessBirthInternal === true
|
|
604
|
+
)
|
|
605
|
+
continue;
|
|
357
606
|
if (isFrameworkAggregate(aggregate)) continue;
|
|
358
607
|
collectionLines.set(aggregate.id, []);
|
|
359
608
|
collectionMembers.set(aggregate.id, new Set());
|
|
@@ -367,7 +616,10 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
367
616
|
const rowClass = `_${cap(camel(domain))}PendingConflictRow`;
|
|
368
617
|
const byAggregate = new Map<string, SurfacedResolverBinding[]>();
|
|
369
618
|
for (const binding of resolverBindings) {
|
|
370
|
-
byAggregate.set(binding.aggregateId, [
|
|
619
|
+
byAggregate.set(binding.aggregateId, [
|
|
620
|
+
...(byAggregate.get(binding.aggregateId) ?? []),
|
|
621
|
+
binding,
|
|
622
|
+
]);
|
|
371
623
|
}
|
|
372
624
|
claimApplicationMember("conflicts", "the generated conflict policy");
|
|
373
625
|
appLines.push(` final ${conflictsClass} conflicts;`);
|
|
@@ -398,17 +650,22 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
398
650
|
` byAggregate: Map.unmodifiable(byAggregate),`,
|
|
399
651
|
` );`,
|
|
400
652
|
` }`,
|
|
401
|
-
...[...byAggregate.keys()].map(
|
|
402
|
-
|
|
653
|
+
...[...byAggregate.keys()].map(
|
|
654
|
+
(aggregateId) =>
|
|
655
|
+
` ${aggregateConflictsClassName(domain, aggregateId)} get ${member(
|
|
656
|
+
concreteClassName(aggregateId)
|
|
657
|
+
)} => ${aggregateConflictsClassName(domain, aggregateId)}._(_client);`
|
|
403
658
|
),
|
|
404
659
|
`}`,
|
|
405
|
-
|
|
660
|
+
``
|
|
406
661
|
);
|
|
407
662
|
|
|
408
663
|
for (const [aggregateId, bindings] of byAggregate) {
|
|
409
664
|
const aggregate = aggregates.get(aggregateId);
|
|
410
665
|
if (aggregate === undefined) {
|
|
411
|
-
throw new Error(
|
|
666
|
+
throw new Error(
|
|
667
|
+
`generated conflict policy refers to unknown aggregate '${aggregateId}' in domain '${domain}'`
|
|
668
|
+
);
|
|
412
669
|
}
|
|
413
670
|
const runtimeAggregateType = isNamespacedAggregate(aggregate)
|
|
414
671
|
? qualifyReadProjectionType(domain, aggregateId)
|
|
@@ -422,18 +679,23 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
422
679
|
` String get aggregateId;`,
|
|
423
680
|
` String get field;`,
|
|
424
681
|
`}`,
|
|
425
|
-
|
|
682
|
+
``
|
|
426
683
|
);
|
|
427
684
|
for (const binding of bindings) {
|
|
428
|
-
if (
|
|
685
|
+
if (
|
|
686
|
+
binding.valuePayloadField === undefined ||
|
|
687
|
+
binding.idPayloadField === undefined
|
|
688
|
+
)
|
|
689
|
+
continue;
|
|
429
690
|
const fieldKind = aggregate.fields[binding.field]?.kind;
|
|
430
|
-
const mergedType =
|
|
431
|
-
|
|
432
|
-
|
|
691
|
+
const mergedType =
|
|
692
|
+
fieldKind === "string" || fieldKind === "ref" || fieldKind === "enum"
|
|
693
|
+
? "String"
|
|
694
|
+
: fieldKind === "int"
|
|
433
695
|
? "int"
|
|
434
696
|
: fieldKind === "bool"
|
|
435
|
-
|
|
436
|
-
|
|
697
|
+
? "bool"
|
|
698
|
+
: "Object?";
|
|
437
699
|
const typeName = `${aggregateName}${cap(camel(binding.field))}Conflict`;
|
|
438
700
|
const evolutionTypeName = `${typeName}ModelUpdateRequired`;
|
|
439
701
|
const helper = `_merge${aggregateName}${cap(camel(binding.field))}`;
|
|
@@ -465,7 +727,7 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
465
727
|
` String get reason => _details.evolution?['reason']?.toString() ??`,
|
|
466
728
|
` 'The business model cannot yet compare every retained version. Update its evolution policy; unrelated work can continue.';`,
|
|
467
729
|
`}`,
|
|
468
|
-
|
|
730
|
+
``
|
|
469
731
|
);
|
|
470
732
|
}
|
|
471
733
|
conflictDeclarations.push(
|
|
@@ -473,13 +735,21 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
473
735
|
` final ${client} _client;`,
|
|
474
736
|
` const ${aggregateClass}._(this._client);`,
|
|
475
737
|
` Future<List<${pendingClass}>> pending() async {`,
|
|
476
|
-
` final rows = await _client._pendingConflictRows(${JSON.stringify(
|
|
738
|
+
` final rows = await _client._pendingConflictRows(${JSON.stringify(
|
|
739
|
+
runtimeAggregateType
|
|
740
|
+
)});`,
|
|
477
741
|
` final decisions = <${pendingClass}>[];`,
|
|
478
742
|
` for (final row in rows) {`,
|
|
479
743
|
` switch (row.field) {`,
|
|
480
744
|
...bindings.flatMap((binding) => {
|
|
481
|
-
if (
|
|
482
|
-
|
|
745
|
+
if (
|
|
746
|
+
binding.valuePayloadField === undefined ||
|
|
747
|
+
binding.idPayloadField === undefined
|
|
748
|
+
)
|
|
749
|
+
return [];
|
|
750
|
+
const typeName = `${aggregateName}${cap(
|
|
751
|
+
camel(binding.field)
|
|
752
|
+
)}Conflict`;
|
|
483
753
|
return [
|
|
484
754
|
` case ${JSON.stringify(binding.field)}:`,
|
|
485
755
|
` if (row.status == 'ready') {`,
|
|
@@ -496,94 +766,110 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
496
766
|
` return List.unmodifiable(decisions);`,
|
|
497
767
|
` }`,
|
|
498
768
|
`}`,
|
|
499
|
-
|
|
769
|
+
``
|
|
500
770
|
);
|
|
501
771
|
}
|
|
502
772
|
}
|
|
503
773
|
|
|
504
|
-
const addCollectionMember = (
|
|
774
|
+
const addCollectionMember = (
|
|
775
|
+
aggregateId: string,
|
|
776
|
+
name: string,
|
|
777
|
+
memberLines: string[]
|
|
778
|
+
) => {
|
|
505
779
|
const target = collectionLines.get(aggregateId);
|
|
506
780
|
if (target === undefined) return false;
|
|
507
781
|
const names = collectionMembers.get(aggregateId)!;
|
|
508
782
|
if (names.has(name)) {
|
|
509
|
-
throw new Error(
|
|
783
|
+
throw new Error(
|
|
784
|
+
`Dart application surface collision: '${aggregateId}.${name}' is declared more than once`
|
|
785
|
+
);
|
|
510
786
|
}
|
|
511
787
|
names.add(name);
|
|
512
788
|
target.push(...memberLines);
|
|
513
789
|
return true;
|
|
514
790
|
};
|
|
515
791
|
|
|
516
|
-
const sourceTrustAction = (
|
|
792
|
+
const sourceTrustAction = (
|
|
793
|
+
directive: AnyDirective
|
|
794
|
+
): {
|
|
517
795
|
prelude: string[];
|
|
518
796
|
call: string;
|
|
519
797
|
} => {
|
|
798
|
+
const factPath = (path: string): string =>
|
|
799
|
+
path
|
|
800
|
+
.split(".")
|
|
801
|
+
.reduce((expr, part) => `${expr}.${member(part)}`, "facts");
|
|
520
802
|
const trust = directive.businessSourceTrust;
|
|
521
803
|
const method = member(directive.id);
|
|
522
804
|
if (trust === undefined) {
|
|
523
805
|
return { prelude: [], call: `_client.${method}(facts)` };
|
|
524
806
|
}
|
|
525
|
-
const queryArgs = Object.entries(trust.queryArgs)
|
|
526
|
-
.sort(([a], [b]) => a.localeCompare(b))
|
|
527
|
-
.map(([argument, field]) => `${JSON.stringify(argument)}: facts.${member(field)}`);
|
|
528
807
|
const copied = Object.entries(trust.copy)
|
|
529
808
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
530
|
-
.map(
|
|
809
|
+
.map(
|
|
810
|
+
([target, field]) =>
|
|
811
|
+
` ${JSON.stringify(target)}: ${factPath(field)},`
|
|
812
|
+
);
|
|
531
813
|
return {
|
|
532
814
|
prelude: [
|
|
533
|
-
` final
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
` args: {${queryArgs.join(", ")}},`,
|
|
537
|
-
` );`,
|
|
538
|
-
` final sourceEntry = NmsInternalSourceTrust.entry(servedSource);`,
|
|
539
|
-
` final sourceIdentity = NmsInternalSourceTrust.attestation(sourceEntry);`,
|
|
815
|
+
` final sourceGenesis = await _client.bridge.nomosWorkspaceGenesis(${factPath(
|
|
816
|
+
trust.fromPayload
|
|
817
|
+
)});`,
|
|
540
818
|
` await _client._offer(`,
|
|
541
819
|
` ${JSON.stringify(trust.viaDirectiveId)},`,
|
|
542
820
|
` {`,
|
|
543
821
|
...copied,
|
|
544
|
-
` ${JSON.stringify(trust.genesisField)}:
|
|
545
|
-
` ${JSON.stringify(trust.keyHashesField)}: [
|
|
822
|
+
` ${JSON.stringify(trust.genesisField)}: sourceGenesis,`,
|
|
823
|
+
` ${JSON.stringify(trust.keyHashesField)}: [${JSON.stringify(
|
|
824
|
+
"0".repeat(64)
|
|
825
|
+
)}],`,
|
|
546
826
|
` },`,
|
|
547
827
|
` );`,
|
|
548
828
|
],
|
|
549
|
-
call: `_client.${method}(facts
|
|
829
|
+
call: `_client.${method}(facts)`,
|
|
550
830
|
};
|
|
551
831
|
};
|
|
552
832
|
|
|
553
833
|
if (sharingPolicies.length > 0) {
|
|
554
834
|
appLines.push(
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
835
|
+
` /// Turn an identity-provider uid into a typed business recipient.`,
|
|
836
|
+
` ///`,
|
|
837
|
+
` /// A uid crosses the string boundary once. Generated invitation actions then require`,
|
|
838
|
+
` /// [Recipient<User>], so an email address, principal or record id cannot be substituted.`,
|
|
839
|
+
` Recipient<User> recipient(String uid) =>`,
|
|
840
|
+
` NmsInternalApplicationHost.recipient<User>(uid);`,
|
|
841
|
+
``,
|
|
842
|
+
` /// Create a single-use invitation. Transport [Invitation.link] however your product chooses.`,
|
|
843
|
+
` Future<Invitation<User>> createInvitation() =>`,
|
|
844
|
+
` NmsInternalApplicationHost.createInvitation<User>(_client.bridge);`,
|
|
845
|
+
``,
|
|
846
|
+
` /// Redeem a transported invitation link as the verified [currentUser].`,
|
|
847
|
+
` Future<void> redeemInvitation(String link) =>`,
|
|
848
|
+
` NmsInternalApplicationHost.redeemInvitation<User>(`,
|
|
849
|
+
` _client.bridge,`,
|
|
850
|
+
` link: link,`,
|
|
851
|
+
` identity: _client.actor,`,
|
|
852
|
+
` policies: [`,
|
|
853
|
+
...sharingPolicies
|
|
854
|
+
.map((policy) => [
|
|
574
855
|
` NmsInternalInvitationPolicy(`,
|
|
575
856
|
` domain: ${JSON.stringify(domain)},`,
|
|
576
857
|
` domainHash: _client.domainHash,`,
|
|
577
|
-
` query: ${JSON.stringify(
|
|
578
|
-
|
|
858
|
+
` query: ${JSON.stringify(
|
|
859
|
+
policy.invitationLaw.pendingByRecipient.id
|
|
860
|
+
)},`,
|
|
861
|
+
` redeem: ${JSON.stringify(
|
|
862
|
+
policy.invitationLaw.redeem.id
|
|
863
|
+
)},`,
|
|
579
864
|
` personField: ${JSON.stringify(policy.person)},`,
|
|
580
865
|
` roleField: ${JSON.stringify(policy.role)},`,
|
|
581
866
|
` scopeField: ${JSON.stringify(policy.scope)},`,
|
|
582
867
|
` ),`,
|
|
583
|
-
])
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
868
|
+
])
|
|
869
|
+
.flat(),
|
|
870
|
+
` ],`,
|
|
871
|
+
` );`,
|
|
872
|
+
``
|
|
587
873
|
);
|
|
588
874
|
for (const [index, policy] of sharingPolicies.entries()) {
|
|
589
875
|
const pendingKey = policy.pending.key[0]!;
|
|
@@ -591,7 +877,10 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
591
877
|
const person = sharingOrderValue(policy, policy.person, "wire");
|
|
592
878
|
const role = sharingOrderValue(policy, policy.role, "wire");
|
|
593
879
|
const scope = sharingOrderValue(policy, policy.scope, "wire");
|
|
594
|
-
const receiptLines = (
|
|
880
|
+
const receiptLines = (
|
|
881
|
+
receipt: WorkspaceSharingReceipt,
|
|
882
|
+
refused: boolean
|
|
883
|
+
): string[] => {
|
|
595
884
|
const condition = sharingReceiptCondition(policy, receipt);
|
|
596
885
|
const body = [
|
|
597
886
|
` await _client._offer(`,
|
|
@@ -609,10 +898,18 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
609
898
|
? body
|
|
610
899
|
: [` if (${condition}) {`, ...body, ` }`];
|
|
611
900
|
};
|
|
612
|
-
const delivered = policy.delivered.flatMap((receipt) =>
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
const
|
|
901
|
+
const delivered = policy.delivered.flatMap((receipt) =>
|
|
902
|
+
receiptLines(receipt, false)
|
|
903
|
+
);
|
|
904
|
+
const refused = policy.refused.flatMap((receipt) =>
|
|
905
|
+
receiptLines(receipt, true)
|
|
906
|
+
);
|
|
907
|
+
const deliveredExhaustive = policy.delivered.some(
|
|
908
|
+
(receipt) => sharingReceiptCondition(policy, receipt) === "true"
|
|
909
|
+
);
|
|
910
|
+
const refusedExhaustive = policy.refused.some(
|
|
911
|
+
(receipt) => sharingReceiptCondition(policy, receipt) === "true"
|
|
912
|
+
);
|
|
616
913
|
appLines.push(
|
|
617
914
|
` Future<void>? _nmsWorkspaceSharingDrain${index};`,
|
|
618
915
|
` StreamSubscription<Object?>? _nmsWorkspaceSharingSync${index};`,
|
|
@@ -662,34 +959,46 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
662
959
|
...delivered,
|
|
663
960
|
...(deliveredExhaustive
|
|
664
961
|
? []
|
|
665
|
-
: [
|
|
962
|
+
: [
|
|
963
|
+
` throw StateError('No delivered receipt matches ${policy.orders.id} \${NmsInternalNomosRefs.id(order.ref)}');`,
|
|
964
|
+
]),
|
|
666
965
|
` }`,
|
|
667
966
|
...refused,
|
|
668
967
|
...(refusedExhaustive
|
|
669
968
|
? []
|
|
670
|
-
: [
|
|
969
|
+
: [
|
|
970
|
+
` throw StateError('No refused receipt matches ${policy.orders.id} \${NmsInternalNomosRefs.id(order.ref)}');`,
|
|
971
|
+
]),
|
|
671
972
|
` }`,
|
|
672
973
|
` }`,
|
|
673
|
-
|
|
974
|
+
``
|
|
674
975
|
);
|
|
675
976
|
}
|
|
676
977
|
appLines.push(
|
|
677
978
|
` Future<void> _nmsStartWorkspaceSharing() async {`,
|
|
678
|
-
...sharingPolicies.map(
|
|
979
|
+
...sharingPolicies.map(
|
|
980
|
+
(_policy, index) => ` await _nmsStartWorkspaceSharing${index}();`
|
|
981
|
+
),
|
|
679
982
|
` }`,
|
|
680
983
|
``,
|
|
681
984
|
` Future<void> _nmsDrainWorkspaceSharing() async {`,
|
|
682
|
-
...sharingPolicies.map(
|
|
985
|
+
...sharingPolicies.map(
|
|
986
|
+
(_policy, index) => ` await _nmsDrainWorkspaceSharing${index}();`
|
|
987
|
+
),
|
|
683
988
|
` }`,
|
|
684
|
-
|
|
989
|
+
``
|
|
685
990
|
);
|
|
686
991
|
}
|
|
687
992
|
|
|
688
993
|
const sharingInitiatorIds = new Set(
|
|
689
|
-
sharingPolicies.flatMap((policy) =>
|
|
994
|
+
sharingPolicies.flatMap((policy) =>
|
|
995
|
+
policy.initiatedBy.map((intent) => intent.id)
|
|
996
|
+
)
|
|
690
997
|
);
|
|
691
998
|
const afterBusinessWrite = (directive: AnyDirective): string[] =>
|
|
692
|
-
sharingInitiatorIds.has(directive.id)
|
|
999
|
+
sharingInitiatorIds.has(directive.id)
|
|
1000
|
+
? [` await _nmsDrainWorkspaceSharing();`]
|
|
1001
|
+
: [];
|
|
693
1002
|
const businessActionDoc = (directive: AnyDirective): string => {
|
|
694
1003
|
return sharingInitiatorIds.has(directive.id)
|
|
695
1004
|
? "One business action. Nomos durably delivers its access change and retries after restart or reconnect; do not mirror delivery in application state."
|
|
@@ -698,15 +1007,17 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
698
1007
|
|
|
699
1008
|
const birthCollectionsWithMine = new Set<string>();
|
|
700
1009
|
for (const directive of directives) {
|
|
701
|
-
const birth = (
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
1010
|
+
const birth = (
|
|
1011
|
+
directive as unknown as {
|
|
1012
|
+
businessBirth?: {
|
|
1013
|
+
aggregateId: string;
|
|
1014
|
+
namespace: string;
|
|
1015
|
+
rootId: string;
|
|
1016
|
+
keyField: string;
|
|
1017
|
+
};
|
|
1018
|
+
birthsChild?: boolean;
|
|
1019
|
+
}
|
|
1020
|
+
).businessBirth;
|
|
710
1021
|
if (birth !== undefined) {
|
|
711
1022
|
// A business birth is declared on the parent domain but its aggregate
|
|
712
1023
|
// belongs to the child domain. Keep a generated collection for that
|
|
@@ -723,9 +1034,13 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
723
1034
|
const opener = `nomosInternalOpen${targetApplication}`;
|
|
724
1035
|
const rootAggregate = aggregates.get(birth.aggregateId);
|
|
725
1036
|
const rootType = concreteClassName(birth.aggregateId);
|
|
726
|
-
const rootCollection = member(
|
|
727
|
-
|
|
728
|
-
|
|
1037
|
+
const rootCollection = member(
|
|
1038
|
+
plural(
|
|
1039
|
+
rootAggregate === undefined
|
|
1040
|
+
? birth.aggregateId
|
|
1041
|
+
: concreteClassName(rootAggregate.id)
|
|
1042
|
+
)
|
|
1043
|
+
);
|
|
729
1044
|
if (targetFile !== mod.name) {
|
|
730
1045
|
addSibling(`${targetFile}.application.dart`, targetApplication);
|
|
731
1046
|
addSibling(`${targetFile}.application.dart`, opener);
|
|
@@ -735,21 +1050,27 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
735
1050
|
const method = member(directive.id);
|
|
736
1051
|
claimApplicationMember(method, `business intent '${directive.id}'`);
|
|
737
1052
|
appLines.push(
|
|
738
|
-
` /// ${cap(camel(directive.id))}. One business action; the new ${
|
|
1053
|
+
` /// ${cap(camel(directive.id))}. One business action; the new ${
|
|
1054
|
+
birth.aggregateId
|
|
1055
|
+
} and every parent consequence commit atomically.`,
|
|
739
1056
|
` /// Canonical write path: pass business facts here; never mirror this action in application state`,
|
|
740
1057
|
` /// or call a lower-level client.`,
|
|
741
|
-
` Future<${targetApplication}> ${method}(${dartActionInputClassName(
|
|
1058
|
+
` Future<${targetApplication}> ${method}(${dartActionInputClassName(
|
|
1059
|
+
directive
|
|
1060
|
+
)} facts) async {`,
|
|
742
1061
|
` final reference = await _client.${method}(facts);`,
|
|
743
1062
|
opts.supportImport === "package"
|
|
744
1063
|
? ` return ${rootCollection}._nmsOpenReference(reference);`
|
|
745
1064
|
: ` return ${opener}(_client.bridge, reference, _client.actor);`,
|
|
746
1065
|
` }`,
|
|
747
|
-
|
|
1066
|
+
``
|
|
748
1067
|
);
|
|
749
1068
|
} else {
|
|
750
1069
|
addCollectionMember(birth.aggregateId, "birth", [
|
|
751
1070
|
` /// Give one ${birth.aggregateId} its own durable life.`,
|
|
752
|
-
` Future<${targetApplication}> birth(Create${cap(
|
|
1071
|
+
` Future<${targetApplication}> birth(Create${cap(
|
|
1072
|
+
camel(birth.aggregateId)
|
|
1073
|
+
)}Input facts) async {`,
|
|
753
1074
|
` final reference = await _client.birth(facts);`,
|
|
754
1075
|
opts.supportImport === "package"
|
|
755
1076
|
? ` return _nmsOpenReference(reference);`
|
|
@@ -760,9 +1081,10 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
760
1081
|
}
|
|
761
1082
|
if (opts.supportImport === "package") {
|
|
762
1083
|
usesWorkspaceName = true;
|
|
763
|
-
const rootModel =
|
|
764
|
-
|
|
765
|
-
|
|
1084
|
+
const rootModel =
|
|
1085
|
+
rootAggregate === undefined
|
|
1086
|
+
? `${concreteClassName(birth.aggregateId)}ReadModel`
|
|
1087
|
+
: readModelClassName(rootAggregate.id);
|
|
766
1088
|
addCollectionMember(birth.aggregateId, "open", [
|
|
767
1089
|
` final Map<String, Future<${targetApplication}>> _nmsApplications = {};`,
|
|
768
1090
|
``,
|
|
@@ -788,7 +1110,9 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
788
1110
|
` /// Open the ${birth.aggregateId} whose ${birth.keyField} is [key].`,
|
|
789
1111
|
` Future<${targetApplication}> open(String key) => _nmsOpenReference(`,
|
|
790
1112
|
` NmsInternalNomosRefs.create<${rootType}>(`,
|
|
791
|
-
` workspace: deriveWorkspaceName(${JSON.stringify(
|
|
1113
|
+
` workspace: deriveWorkspaceName(${JSON.stringify(
|
|
1114
|
+
birth.namespace
|
|
1115
|
+
)}, key),`,
|
|
792
1116
|
` type: ${JSON.stringify(birth.aggregateId)},`,
|
|
793
1117
|
` id: ${JSON.stringify(birth.rootId)},`,
|
|
794
1118
|
` ),`,
|
|
@@ -812,8 +1136,12 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
812
1136
|
` final raw = row['data'];`,
|
|
813
1137
|
` if (raw is! Map) continue;`,
|
|
814
1138
|
` final child = (raw['child'] ?? '').toString();`,
|
|
815
|
-
` if (!child.startsWith(${JSON.stringify(
|
|
816
|
-
|
|
1139
|
+
` if (!child.startsWith(${JSON.stringify(
|
|
1140
|
+
`${birth.namespace}-`
|
|
1141
|
+
)})) continue;`,
|
|
1142
|
+
` final reference = NmsInternalNomosRefs.create<${rootType}>(workspace: child, type: ${JSON.stringify(
|
|
1143
|
+
birth.aggregateId
|
|
1144
|
+
)}, id: ${JSON.stringify(birth.rootId)});`,
|
|
817
1145
|
` final application = await _nmsOpenReference(reference);`,
|
|
818
1146
|
` final record = await application.${rootCollection}.find(reference);`,
|
|
819
1147
|
` if (record != null) records.add(record);`,
|
|
@@ -840,18 +1168,22 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
840
1168
|
` for (final record in records) {`,
|
|
841
1169
|
` seen.add(NmsInternalRecordOrigins.require(record).workspace);`,
|
|
842
1170
|
` }`,
|
|
843
|
-
` final shares = await _client.bridge.query('
|
|
1171
|
+
` final shares = await _client.bridge.query('sharedByObject', {'object': 'workspace:self'}, null, _client.actor);`,
|
|
844
1172
|
` for (final row in shares) {`,
|
|
845
1173
|
` final raw = row['data'];`,
|
|
846
1174
|
` if (raw is! Map) continue;`,
|
|
847
1175
|
` final workspace = (raw['asserterWorkspace'] ?? '').toString();`,
|
|
848
1176
|
` final object = (raw['object'] ?? '').toString();`,
|
|
849
1177
|
` final prunedAt = (raw['prunedAt'] ?? '').toString();`,
|
|
850
|
-
` if (!workspace.startsWith(${JSON.stringify(
|
|
1178
|
+
` if (!workspace.startsWith(${JSON.stringify(
|
|
1179
|
+
`${birth.namespace}-`
|
|
1180
|
+
)}) ||`,
|
|
851
1181
|
` object != 'workspace:self' ||`,
|
|
852
1182
|
` prunedAt.isNotEmpty ||`,
|
|
853
1183
|
` !seen.add(workspace)) continue;`,
|
|
854
|
-
` final reference = NmsInternalNomosRefs.create<${rootType}>(workspace: workspace, type: ${JSON.stringify(
|
|
1184
|
+
` final reference = NmsInternalNomosRefs.create<${rootType}>(workspace: workspace, type: ${JSON.stringify(
|
|
1185
|
+
birth.aggregateId
|
|
1186
|
+
)}, id: ${JSON.stringify(birth.rootId)});`,
|
|
855
1187
|
` final application = await _nmsOpenReference(reference);`,
|
|
856
1188
|
` final record = await application.${rootCollection}.find(reference);`,
|
|
857
1189
|
` if (record != null) records.add(record);`,
|
|
@@ -861,15 +1193,48 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
861
1193
|
``,
|
|
862
1194
|
` /// React when this person's available ${birth.aggregateId} lives change.`,
|
|
863
1195
|
` Stream<List<${rootModel}>> watchAvailable() =>`,
|
|
864
|
-
`
|
|
865
|
-
`
|
|
866
|
-
`
|
|
1196
|
+
` Stream<List<${rootModel}>>.multi((events) {`,
|
|
1197
|
+
` var reading = false;`,
|
|
1198
|
+
` var reread = false;`,
|
|
1199
|
+
` Future<void> refresh() async {`,
|
|
1200
|
+
` if (reading) {`,
|
|
1201
|
+
` reread = true;`,
|
|
1202
|
+
` return;`,
|
|
1203
|
+
` }`,
|
|
1204
|
+
` reading = true;`,
|
|
1205
|
+
` do {`,
|
|
1206
|
+
` reread = false;`,
|
|
1207
|
+
` try {`,
|
|
1208
|
+
` final records = await available();`,
|
|
1209
|
+
` if (!events.isClosed) events.add(records);`,
|
|
1210
|
+
` } catch (error, stackTrace) {`,
|
|
1211
|
+
` if (!events.isClosed) events.addError(error, stackTrace);`,
|
|
1212
|
+
` }`,
|
|
1213
|
+
` } while (reread && !events.isClosed);`,
|
|
1214
|
+
` reading = false;`,
|
|
1215
|
+
` }`,
|
|
1216
|
+
` final birthChanges = _client.bridge`,
|
|
1217
|
+
` .watch('birthRecordsByPrincipal', {'principal': _client.actor}, null, null, 60000)`,
|
|
1218
|
+
` .skip(1)`,
|
|
1219
|
+
` .listen((_) { refresh(); }, onError: events.addError);`,
|
|
1220
|
+
` final shareChanges = _client.bridge`,
|
|
1221
|
+
` .watch('sharedByObject', {'object': 'workspace:self'}, null, _client.actor, 60000)`,
|
|
1222
|
+
` .skip(1)`,
|
|
1223
|
+
` .listen((_) { refresh(); }, onError: events.addError);`,
|
|
1224
|
+
` events.onCancel = () async {`,
|
|
1225
|
+
` await birthChanges.cancel();`,
|
|
1226
|
+
` await shareChanges.cancel();`,
|
|
1227
|
+
` };`,
|
|
1228
|
+
` refresh();`,
|
|
1229
|
+
` });`,
|
|
867
1230
|
``,
|
|
868
1231
|
` /// Continue working in the business application that produced [record].`,
|
|
869
1232
|
` Future<${targetApplication}> applicationOf(${rootModel} record) {`,
|
|
870
1233
|
` final origin = NmsInternalRecordOrigins.require(record);`,
|
|
871
1234
|
` return _nmsOpenReference(`,
|
|
872
|
-
` NmsInternalNomosRefs.create<${rootType}>(workspace: origin.workspace, type: ${JSON.stringify(
|
|
1235
|
+
` NmsInternalNomosRefs.create<${rootType}>(workspace: origin.workspace, type: ${JSON.stringify(
|
|
1236
|
+
birth.aggregateId
|
|
1237
|
+
)}, id: origin.id),`,
|
|
873
1238
|
` );`,
|
|
874
1239
|
` }`,
|
|
875
1240
|
``,
|
|
@@ -889,24 +1254,42 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
889
1254
|
if (outputs.length > 0) {
|
|
890
1255
|
usesAffectedReference = true;
|
|
891
1256
|
usesAffectedReferences ||= outputs.some((output) => output.many);
|
|
1257
|
+
usesAffectedReferenceOrNull ||= outputs.some((output) => output.optional);
|
|
892
1258
|
actionResultDeclarations.push(
|
|
893
1259
|
`/// Every business record affected by ${cap(camel(directive.id))}.`,
|
|
894
1260
|
`final class ${resultClass} {`,
|
|
895
1261
|
...outputs.map((output) =>
|
|
896
1262
|
output.many
|
|
897
1263
|
? ` final List<NomosRef<${output.dartType}>> ${output.field};`
|
|
898
|
-
:
|
|
899
|
-
|
|
1264
|
+
: output.optional
|
|
1265
|
+
? ` final NomosRef<${output.dartType}>? ${output.field};`
|
|
1266
|
+
: ` final NomosRef<${output.dartType}> ${output.field};`
|
|
1267
|
+
),
|
|
1268
|
+
` const ${resultClass}({${outputs
|
|
1269
|
+
.map((output) => `required this.${output.field}`)
|
|
1270
|
+
.join(", ")}});`,
|
|
900
1271
|
`}`,
|
|
901
|
-
|
|
1272
|
+
``
|
|
902
1273
|
);
|
|
903
1274
|
}
|
|
904
|
-
const resultExpression =
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
1275
|
+
const resultExpression =
|
|
1276
|
+
outputs.length === 0
|
|
1277
|
+
? undefined
|
|
1278
|
+
: `${resultClass}(${outputs
|
|
1279
|
+
.map((output) =>
|
|
1280
|
+
output.many
|
|
1281
|
+
? `${output.field}: _nmsAffectedReferences<${
|
|
1282
|
+
output.dartType
|
|
1283
|
+
}>(change, ${JSON.stringify(output.aggregateId)})`
|
|
1284
|
+
: output.optional
|
|
1285
|
+
? `${output.field}: _nmsAffectedReferenceOrNull<${
|
|
1286
|
+
output.dartType
|
|
1287
|
+
}>(change, ${JSON.stringify(output.aggregateId)})`
|
|
1288
|
+
: `${output.field}: _nmsAffectedReference<${
|
|
1289
|
+
output.dartType
|
|
1290
|
+
}>(change, ${JSON.stringify(output.aggregateId)})`
|
|
1291
|
+
)
|
|
1292
|
+
.join(", ")})`;
|
|
910
1293
|
if (isBusinessIntent(directive)) {
|
|
911
1294
|
claimApplicationMember(method, `business intent '${directive.id}'`);
|
|
912
1295
|
const sourceTrust = sourceTrustAction(directive);
|
|
@@ -922,7 +1305,7 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
922
1305
|
...afterBusinessWrite(directive),
|
|
923
1306
|
` return ${resultExpression};`,
|
|
924
1307
|
` }`,
|
|
925
|
-
|
|
1308
|
+
``
|
|
926
1309
|
);
|
|
927
1310
|
} else {
|
|
928
1311
|
appLines.push(
|
|
@@ -935,27 +1318,25 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
935
1318
|
` await ${sourceTrust.call};`,
|
|
936
1319
|
...afterBusinessWrite(directive),
|
|
937
1320
|
` }`,
|
|
938
|
-
|
|
1321
|
+
``
|
|
939
1322
|
);
|
|
940
1323
|
}
|
|
941
1324
|
continue;
|
|
942
1325
|
}
|
|
943
1326
|
const sourceTrust = sourceTrustAction(directive);
|
|
944
1327
|
if (resultExpression !== undefined) {
|
|
945
|
-
addCollectionMember(
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
],
|
|
958
|
-
);
|
|
1328
|
+
addCollectionMember(directive.aggregateId, method, [
|
|
1329
|
+
` /// ${cap(
|
|
1330
|
+
camel(directive.id)
|
|
1331
|
+
)}. Returns every affected typed business record reference.`,
|
|
1332
|
+
` Future<${resultClass}> ${method}(${payload} facts) async {`,
|
|
1333
|
+
...referenceChecks,
|
|
1334
|
+
...sourceTrust.prelude,
|
|
1335
|
+
` final change = await ${sourceTrust.call};`,
|
|
1336
|
+
` return ${resultExpression};`,
|
|
1337
|
+
` }`,
|
|
1338
|
+
``,
|
|
1339
|
+
]);
|
|
959
1340
|
} else {
|
|
960
1341
|
addCollectionMember(directive.aggregateId, method, [
|
|
961
1342
|
` /// ${cap(camel(directive.id))}.`,
|
|
@@ -972,7 +1353,8 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
972
1353
|
const publicQueries = ((mod.queries ?? []) as QueryDecl[]).filter(
|
|
973
1354
|
(candidate) =>
|
|
974
1355
|
!isFrameworkQuery(candidate) &&
|
|
975
|
-
(candidate as { businessBirthInternal?: unknown })
|
|
1356
|
+
(candidate as { businessBirthInternal?: unknown })
|
|
1357
|
+
.businessBirthInternal !== true
|
|
976
1358
|
);
|
|
977
1359
|
const signedInSubjectKey = (query: QueryDecl): string | undefined => {
|
|
978
1360
|
if (query.key.length !== 1) return undefined;
|
|
@@ -985,31 +1367,46 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
985
1367
|
if (signedInSubjectKey(query) !== undefined) {
|
|
986
1368
|
mineQueriesByAggregate.set(
|
|
987
1369
|
query.returns,
|
|
988
|
-
(mineQueriesByAggregate.get(query.returns) ?? 0) + 1
|
|
1370
|
+
(mineQueriesByAggregate.get(query.returns) ?? 0) + 1
|
|
989
1371
|
);
|
|
990
1372
|
}
|
|
991
1373
|
}
|
|
992
1374
|
for (const query of publicQueries) {
|
|
993
1375
|
const model = readModelClassName(query.returns);
|
|
994
1376
|
const aggregate = aggregates.get(query.returns);
|
|
995
|
-
const params = query.key
|
|
996
|
-
|
|
1377
|
+
const params = query.key
|
|
1378
|
+
.map((key) => `required ${keyType(aggregate, key)} ${keyParam(key)}`)
|
|
1379
|
+
.join(", ");
|
|
1380
|
+
const args = query.key
|
|
1381
|
+
.map((key) => `${keyParam(key)}: ${keyParam(key)}`)
|
|
1382
|
+
.join(", ");
|
|
997
1383
|
const method = member(query.id);
|
|
998
1384
|
addCollectionMember(query.returns, method, [
|
|
999
1385
|
` Future<List<${model}>> ${method}({${params}}) => _client.${method}(${args});`,
|
|
1000
|
-
` Stream<List<${model}>> watch${cap(
|
|
1386
|
+
` Stream<List<${model}>> watch${cap(
|
|
1387
|
+
camel(query.id)
|
|
1388
|
+
)}({${params}}) => _client.watch${cap(camel(query.id))}(${args});`,
|
|
1001
1389
|
``,
|
|
1002
1390
|
]);
|
|
1003
1391
|
const subjectKey = signedInSubjectKey(query);
|
|
1004
|
-
if (
|
|
1392
|
+
if (
|
|
1393
|
+
subjectKey !== undefined &&
|
|
1394
|
+
!birthCollectionsWithMine.has(query.returns)
|
|
1395
|
+
) {
|
|
1005
1396
|
const subjectParam = keyParam(subjectKey);
|
|
1006
1397
|
const subjectQueryCount = mineQueriesByAggregate.get(query.returns) ?? 0;
|
|
1007
1398
|
const role = cap(camel(subjectKey.replace(/Subject$/, "") || "subject"));
|
|
1008
1399
|
const mineMethod = subjectQueryCount === 1 ? "mine" : `mineAs${role}`;
|
|
1009
|
-
const watchMineMethod =
|
|
1010
|
-
|
|
1400
|
+
const watchMineMethod =
|
|
1401
|
+
subjectQueryCount === 1 ? "watchMine" : `watchMineAs${role}`;
|
|
1402
|
+
const relationship =
|
|
1403
|
+
subjectQueryCount === 1
|
|
1404
|
+
? "belonging to"
|
|
1405
|
+
: `where they are the ${role.toLowerCase()}`;
|
|
1011
1406
|
addCollectionMember(query.returns, mineMethod, [
|
|
1012
|
-
` /// ${cap(
|
|
1407
|
+
` /// ${cap(
|
|
1408
|
+
camel(query.returns)
|
|
1409
|
+
)} records ${relationship} the signed-in person.`,
|
|
1013
1410
|
` Future<List<${model}>> ${mineMethod}() {`,
|
|
1014
1411
|
` final subject = _client.actor;`,
|
|
1015
1412
|
` if (subject == null || subject.trim().isEmpty) {`,
|
|
@@ -1023,7 +1420,9 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1023
1420
|
` if (subject == null || subject.trim().isEmpty) {`,
|
|
1024
1421
|
` throw StateError('The signed-in person is not available.');`,
|
|
1025
1422
|
` }`,
|
|
1026
|
-
` return _client.watch${cap(
|
|
1423
|
+
` return _client.watch${cap(
|
|
1424
|
+
camel(query.id)
|
|
1425
|
+
)}(${subjectParam}: subject);`,
|
|
1027
1426
|
` }`,
|
|
1028
1427
|
``,
|
|
1029
1428
|
]);
|
|
@@ -1036,7 +1435,8 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1036
1435
|
for (const aggregate of mod.aggregates.filter(
|
|
1037
1436
|
(candidate) =>
|
|
1038
1437
|
!isFrameworkAggregate(candidate) &&
|
|
1039
|
-
(candidate as { businessBirthInternal?: unknown })
|
|
1438
|
+
(candidate as { businessBirthInternal?: unknown })
|
|
1439
|
+
.businessBirthInternal !== true
|
|
1040
1440
|
)) {
|
|
1041
1441
|
const sourceModel = readModelClassName(aggregate.id);
|
|
1042
1442
|
for (const contractId of aggregate.implementsContracts ?? []) {
|
|
@@ -1049,7 +1449,9 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1049
1449
|
` /// the contract that this aggregate explicitly implements. This does not grant access.`,
|
|
1050
1450
|
` NomosRef<${contract}> ${method}(${sourceModel} record) {`,
|
|
1051
1451
|
` final reference = record.ref;`,
|
|
1052
|
-
` _nmsRequireReference(reference, ${JSON.stringify(
|
|
1452
|
+
` _nmsRequireReference(reference, ${JSON.stringify(
|
|
1453
|
+
aggregate.id
|
|
1454
|
+
)}, '');`,
|
|
1053
1455
|
` return NmsInternalNomosRefs.create<${contract}>(`,
|
|
1054
1456
|
` workspace: reference.workspace,`,
|
|
1055
1457
|
` type: ${JSON.stringify(contractId)},`,
|
|
@@ -1065,8 +1467,12 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1065
1467
|
` /// Use this after following a generated cross-application reference and opening`,
|
|
1066
1468
|
` /// its business application. Nomos checks the declared contract and access on read.`,
|
|
1067
1469
|
` Future<${sourceModel}?> ${resolveMethod}(ForeignRef<${contract}> reference) {`,
|
|
1068
|
-
` _nmsRequireReference(reference.ref, ${JSON.stringify(
|
|
1069
|
-
|
|
1470
|
+
` _nmsRequireReference(reference.ref, ${JSON.stringify(
|
|
1471
|
+
contractId
|
|
1472
|
+
)}, '');`,
|
|
1473
|
+
` return find(NmsInternalNomosRefs.create<${concreteClassName(
|
|
1474
|
+
aggregate.id
|
|
1475
|
+
)}>(`,
|
|
1070
1476
|
` workspace: reference.workspace,`,
|
|
1071
1477
|
` type: ${JSON.stringify(aggregate.id)},`,
|
|
1072
1478
|
` id: reference.id,`,
|
|
@@ -1078,8 +1484,12 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1078
1484
|
addCollectionMember(aggregate.id, watchMethod, [
|
|
1079
1485
|
` /// React when the concrete record behind [reference] changes.`,
|
|
1080
1486
|
` Stream<${sourceModel}?> ${watchMethod}(ForeignRef<${contract}> reference) {`,
|
|
1081
|
-
` _nmsRequireReference(reference.ref, ${JSON.stringify(
|
|
1082
|
-
|
|
1487
|
+
` _nmsRequireReference(reference.ref, ${JSON.stringify(
|
|
1488
|
+
contractId
|
|
1489
|
+
)}, '');`,
|
|
1490
|
+
` return watchOne(NmsInternalNomosRefs.create<${concreteClassName(
|
|
1491
|
+
aggregate.id
|
|
1492
|
+
)}>(`,
|
|
1083
1493
|
` workspace: reference.workspace,`,
|
|
1084
1494
|
` type: ${JSON.stringify(aggregate.id)},`,
|
|
1085
1495
|
` id: reference.id,`,
|
|
@@ -1088,20 +1498,31 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1088
1498
|
``,
|
|
1089
1499
|
]);
|
|
1090
1500
|
}
|
|
1091
|
-
const sameDomainRefs = Object.entries(aggregate.fields).filter(
|
|
1092
|
-
field
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1501
|
+
const sameDomainRefs = Object.entries(aggregate.fields).filter(
|
|
1502
|
+
([, field]) =>
|
|
1503
|
+
field.kind === "ref" &&
|
|
1504
|
+
field.foreignRef === undefined &&
|
|
1505
|
+
field.refAggregateId !== undefined &&
|
|
1506
|
+
field.refAggregateId !== aggregate.id &&
|
|
1507
|
+
aggregates.has(field.refAggregateId)
|
|
1508
|
+
);
|
|
1509
|
+
const crossDomainRefs = Object.entries(aggregate.fields).filter(
|
|
1510
|
+
([, field]) => {
|
|
1511
|
+
if (
|
|
1512
|
+
field.kind !== "ref" ||
|
|
1513
|
+
field.foreignRef !== undefined ||
|
|
1514
|
+
field.refAggregateId === undefined
|
|
1515
|
+
)
|
|
1516
|
+
return false;
|
|
1517
|
+
if (aggregates.has(field.refAggregateId)) return false;
|
|
1518
|
+
const owner = packageDomains[field.refAggregateId];
|
|
1519
|
+
return owner !== undefined && owner.domain !== domain;
|
|
1520
|
+
}
|
|
1097
1521
|
);
|
|
1098
|
-
const
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
return owner !== undefined && owner.domain !== domain;
|
|
1103
|
-
});
|
|
1104
|
-
for (const [fieldName, field] of Object.entries(aggregate.fields) as [string, Field][]) {
|
|
1522
|
+
for (const [fieldName, field] of Object.entries(aggregate.fields) as [
|
|
1523
|
+
string,
|
|
1524
|
+
Field
|
|
1525
|
+
][]) {
|
|
1105
1526
|
const foreign = field.foreignRef;
|
|
1106
1527
|
if (field.kind !== "ref") continue;
|
|
1107
1528
|
if (foreign === undefined) {
|
|
@@ -1116,13 +1537,19 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1116
1537
|
const sourceFieldValue = admitsNull(field)
|
|
1117
1538
|
? `record.${sourceFieldMember}.valueOrNull`
|
|
1118
1539
|
: `record.${sourceFieldMember}`;
|
|
1119
|
-
const isSameDomain = sameDomainRefs.some(
|
|
1120
|
-
|
|
1540
|
+
const isSameDomain = sameDomainRefs.some(
|
|
1541
|
+
([name]) => name === fieldName
|
|
1542
|
+
);
|
|
1543
|
+
const isCrossDomain = crossDomainRefs.some(
|
|
1544
|
+
([name]) => name === fieldName
|
|
1545
|
+
);
|
|
1121
1546
|
if (!isSameDomain && !isCrossDomain) continue;
|
|
1122
1547
|
|
|
1123
1548
|
if (isSameDomain) {
|
|
1124
1549
|
const targetModel = readModelClassName(targetAggregateId);
|
|
1125
|
-
const targetById = safe(
|
|
1550
|
+
const targetById = safe(
|
|
1551
|
+
`${lcFirst(camel(concreteClassName(targetAggregateId)))}ById`
|
|
1552
|
+
);
|
|
1126
1553
|
addCollectionMember(aggregate.id, method, [
|
|
1127
1554
|
` /// Resolve [record]'s ${fieldName} inside this business application.`,
|
|
1128
1555
|
` Future<${targetModel}?> ${method}(${sourceModel} record) async {`,
|
|
@@ -1132,7 +1559,9 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1132
1559
|
` if (id == null) return null;`,
|
|
1133
1560
|
` final rows = await _client.${targetById}(NmsInternalNomosRefs.id(id));`,
|
|
1134
1561
|
]
|
|
1135
|
-
: [
|
|
1562
|
+
: [
|
|
1563
|
+
` final rows = await _client.${targetById}(NmsInternalNomosRefs.id(record.${sourceFieldMember}));`,
|
|
1564
|
+
]),
|
|
1136
1565
|
` return rows.isEmpty ? null : rows.first;`,
|
|
1137
1566
|
` }`,
|
|
1138
1567
|
``,
|
|
@@ -1143,13 +1572,18 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1143
1572
|
const owner = packageDomains[targetAggregateId];
|
|
1144
1573
|
if (owner === undefined) continue;
|
|
1145
1574
|
const targetModel = readModelClassName(targetAggregateId);
|
|
1146
|
-
const targetCollection = member(
|
|
1575
|
+
const targetCollection = member(
|
|
1576
|
+
plural(concreteClassName(targetAggregateId))
|
|
1577
|
+
);
|
|
1147
1578
|
const targetApplication = dartApplicationClassName(owner.domain);
|
|
1148
1579
|
const opener = `nomosInternalOpen${targetApplication}`;
|
|
1149
1580
|
// Flat output needs the sibling type file. Layered output's ordinary
|
|
1150
1581
|
// types barrel already exports every domain; the explicit-barrel mode
|
|
1151
1582
|
// supplies direct package imports in explicitTypeImportUris.
|
|
1152
|
-
if (
|
|
1583
|
+
if (
|
|
1584
|
+
opts.typesImportUri === undefined ||
|
|
1585
|
+
explicitTypeImportUris.size > 0
|
|
1586
|
+
) {
|
|
1153
1587
|
explicitTypeImportUris.add(typeImportUriFor(owner.name));
|
|
1154
1588
|
}
|
|
1155
1589
|
addSibling(`${owner.name}.application.dart`, targetApplication);
|
|
@@ -1211,14 +1645,17 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1211
1645
|
]);
|
|
1212
1646
|
continue;
|
|
1213
1647
|
}
|
|
1214
|
-
const target =
|
|
1215
|
-
|
|
1216
|
-
|
|
1648
|
+
const target =
|
|
1649
|
+
foreign.targetIsContract === true
|
|
1650
|
+
? contractTypeName(foreign.targetAggregateId)
|
|
1651
|
+
: concreteClassName(foreign.targetAggregateId);
|
|
1217
1652
|
const fieldMember = member(fieldName);
|
|
1218
1653
|
const fieldValue = admitsNull(field)
|
|
1219
1654
|
? `record.${fieldMember}.valueOrNull`
|
|
1220
1655
|
: `record.${fieldMember}`;
|
|
1221
|
-
const stem = fieldMember.endsWith("Id")
|
|
1656
|
+
const stem = fieldMember.endsWith("Id")
|
|
1657
|
+
? fieldMember.slice(0, -2)
|
|
1658
|
+
: fieldMember;
|
|
1222
1659
|
const method = member(`${stem}Of`);
|
|
1223
1660
|
addCollectionMember(aggregate.id, method, [
|
|
1224
1661
|
` /// Follow [record]'s ${fieldName} to its typed business record.`,
|
|
@@ -1227,15 +1664,18 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1227
1664
|
` if (reference == null) return null;`,
|
|
1228
1665
|
` final target = await NmsInternalForeignRefs.resolve(reference, _client.bridge.foreignReader());`,
|
|
1229
1666
|
` if (target == null) return null;`,
|
|
1230
|
-
` return NmsInternalRecordOrigins.remember(target, workspace: NmsInternalNomosRefs.workspace(reference.ref), type: ${JSON.stringify(
|
|
1667
|
+
` return NmsInternalRecordOrigins.remember(target, workspace: NmsInternalNomosRefs.workspace(reference.ref), type: ${JSON.stringify(
|
|
1668
|
+
foreign.targetAggregateId
|
|
1669
|
+
)}, id: NmsInternalNomosRefs.id(reference.ref));`,
|
|
1231
1670
|
` }`,
|
|
1232
1671
|
``,
|
|
1233
1672
|
]);
|
|
1234
|
-
const targetOwner =
|
|
1673
|
+
const targetOwner =
|
|
1674
|
+
packageDomains[foreign.targetAggregateId] ??
|
|
1235
1675
|
(foreign.targetDomain === undefined
|
|
1236
1676
|
? undefined
|
|
1237
1677
|
: Object.values(packageDomains).find(
|
|
1238
|
-
(candidate) => candidate.domain === foreign.targetDomain
|
|
1678
|
+
(candidate) => candidate.domain === foreign.targetDomain
|
|
1239
1679
|
));
|
|
1240
1680
|
if (targetOwner !== undefined && targetOwner.domain !== domain) {
|
|
1241
1681
|
const targetApplication = dartApplicationClassName(targetOwner.domain);
|
|
@@ -1262,21 +1702,35 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1262
1702
|
|
|
1263
1703
|
for (const count of (mod.counts ?? []).map((raw) => finishCount(raw))) {
|
|
1264
1704
|
const method = member(count.id);
|
|
1265
|
-
const groupType =
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1705
|
+
const groupType =
|
|
1706
|
+
count.by == null ? "" : keyType(aggregates.get(count.of), count.by);
|
|
1707
|
+
addCollectionMember(count.of, method, [
|
|
1708
|
+
count.by == null
|
|
1709
|
+
? ` Future<int> ${method}() => _client.${method}();`
|
|
1710
|
+
: ` Future<int> ${method}(${groupType} ${keyParam(
|
|
1711
|
+
count.by
|
|
1712
|
+
)}) => _client.${method}(${keyParam(count.by)});`,
|
|
1713
|
+
]);
|
|
1269
1714
|
}
|
|
1270
1715
|
for (const sum of (mod.sums ?? []).map((raw) => finishSum(raw))) {
|
|
1271
1716
|
const method = member(sum.id);
|
|
1272
|
-
const groupType =
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1717
|
+
const groupType =
|
|
1718
|
+
sum.by == null ? "" : keyType(aggregates.get(sum.of), sum.by);
|
|
1719
|
+
addCollectionMember(sum.of, method, [
|
|
1720
|
+
sum.by == null
|
|
1721
|
+
? ` Future<int> ${method}() => _client.${method}();`
|
|
1722
|
+
: ` Future<int> ${method}(${groupType} ${keyParam(
|
|
1723
|
+
sum.by
|
|
1724
|
+
)}) => _client.${method}(${keyParam(sum.by)});`,
|
|
1725
|
+
]);
|
|
1276
1726
|
}
|
|
1277
1727
|
|
|
1278
1728
|
for (const aggregate of mod.aggregates) {
|
|
1279
|
-
if (
|
|
1729
|
+
if (
|
|
1730
|
+
(aggregate as { businessBirthInternal?: unknown })
|
|
1731
|
+
.businessBirthInternal === true
|
|
1732
|
+
)
|
|
1733
|
+
continue;
|
|
1280
1734
|
if (isFrameworkAggregate(aggregate)) continue;
|
|
1281
1735
|
const concrete = concreteClassName(aggregate.id);
|
|
1282
1736
|
const model = readModelClassName(aggregate.id);
|
|
@@ -1284,6 +1738,27 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1284
1738
|
const Cap = cap(camel(concrete));
|
|
1285
1739
|
const aggregateRefType = `NomosRef<${concrete}>`;
|
|
1286
1740
|
const clientList = safe(`list${Cap}s`);
|
|
1741
|
+
addCollectionMember(aggregate.id, "ref", [
|
|
1742
|
+
` /// Qualify [id] as a typed ${concrete} reference in this application workspace.`,
|
|
1743
|
+
` ///`,
|
|
1744
|
+
` /// Use this at UI boundaries such as route parameters that intentionally retain only`,
|
|
1745
|
+
` /// the local aggregate id. Use [fromAddress] when the workspace is part of the value.`,
|
|
1746
|
+
` ${aggregateRefType} ref(String id) {`,
|
|
1747
|
+
` final workspace = _client.session?.workspace ?? '';`,
|
|
1748
|
+
` if (workspace.isEmpty) {`,
|
|
1749
|
+
` throw StateError('Cannot qualify a ${concrete} id before its application workspace is open.');`,
|
|
1750
|
+
` }`,
|
|
1751
|
+
` if (id.trim().isEmpty) {`,
|
|
1752
|
+
` throw ArgumentError.value(id, 'id', 'A business id cannot be empty.');`,
|
|
1753
|
+
` }`,
|
|
1754
|
+
` return NmsInternalNomosRefs.create<${concrete}>(`,
|
|
1755
|
+
` workspace: workspace,`,
|
|
1756
|
+
` type: ${JSON.stringify(aggregate.id)},`,
|
|
1757
|
+
` id: id,`,
|
|
1758
|
+
` );`,
|
|
1759
|
+
` }`,
|
|
1760
|
+
``,
|
|
1761
|
+
]);
|
|
1287
1762
|
addCollectionMember(aggregate.id, "fromAddress", [
|
|
1288
1763
|
` /// Recover a typed ${concrete} reference from its stable public address.`,
|
|
1289
1764
|
` ///`,
|
|
@@ -1291,14 +1766,18 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1291
1766
|
` /// still verify the signed-in person's access when the reference is used.`,
|
|
1292
1767
|
` ${aggregateRefType} fromAddress(String address) {`,
|
|
1293
1768
|
` final reference = NmsInternalNomosRefs.fromUri<${concrete}>(address);`,
|
|
1294
|
-
` _nmsRequireReference(reference, ${JSON.stringify(
|
|
1769
|
+
` _nmsRequireReference(reference, ${JSON.stringify(
|
|
1770
|
+
aggregate.id
|
|
1771
|
+
)}, '');`,
|
|
1295
1772
|
` return reference;`,
|
|
1296
1773
|
` }`,
|
|
1297
1774
|
``,
|
|
1298
1775
|
]);
|
|
1299
1776
|
addCollectionMember(aggregate.id, "find", [
|
|
1300
1777
|
` Future<${model}?> find(${aggregateRefType} reference) async {`,
|
|
1301
|
-
` _nmsRequireReference(reference, ${JSON.stringify(
|
|
1778
|
+
` _nmsRequireReference(reference, ${JSON.stringify(
|
|
1779
|
+
aggregate.id
|
|
1780
|
+
)}, '');`,
|
|
1302
1781
|
` final targetWorkspace = NmsInternalNomosRefs.workspace(reference);`,
|
|
1303
1782
|
` final currentWorkspace = _client.session?.workspace ?? '';`,
|
|
1304
1783
|
` if (currentWorkspace.isNotEmpty && targetWorkspace != currentWorkspace) {`,
|
|
@@ -1307,21 +1786,27 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1307
1786
|
` );`,
|
|
1308
1787
|
` return application.${member(plural(concrete))}.find(reference);`,
|
|
1309
1788
|
` }`,
|
|
1310
|
-
` final matches = await _client.${safe(
|
|
1789
|
+
` final matches = await _client.${safe(
|
|
1790
|
+
`${stem}ById`
|
|
1791
|
+
)}(NmsInternalNomosRefs.id(reference));`,
|
|
1311
1792
|
` return matches.isEmpty ? null : matches.single;`,
|
|
1312
1793
|
` }`,
|
|
1313
1794
|
``,
|
|
1314
1795
|
]);
|
|
1315
1796
|
addCollectionMember(aggregate.id, "watchOne", [
|
|
1316
1797
|
` Stream<${model}?> watchOne(${aggregateRefType} reference) {`,
|
|
1317
|
-
` _nmsRequireReference(reference, ${JSON.stringify(
|
|
1798
|
+
` _nmsRequireReference(reference, ${JSON.stringify(
|
|
1799
|
+
aggregate.id
|
|
1800
|
+
)}, '');`,
|
|
1318
1801
|
` final targetWorkspace = NmsInternalNomosRefs.workspace(reference);`,
|
|
1319
1802
|
` final currentWorkspace = _client.session?.workspace ?? '';`,
|
|
1320
1803
|
` if (currentWorkspace.isNotEmpty && targetWorkspace != currentWorkspace) {`,
|
|
1321
1804
|
` return Stream.fromFuture(nomosInternalOpen${application}(`,
|
|
1322
1805
|
` _client.bridge, reference, _client.actor,`,
|
|
1323
1806
|
` )).asyncExpand(`,
|
|
1324
|
-
` (application) => application.${member(
|
|
1807
|
+
` (application) => application.${member(
|
|
1808
|
+
plural(concrete)
|
|
1809
|
+
)}.watchOne(reference),`,
|
|
1325
1810
|
` );`,
|
|
1326
1811
|
` }`,
|
|
1327
1812
|
` return _client.watch${Cap}ById(NmsInternalNomosRefs.id(reference))`,
|
|
@@ -1343,12 +1828,14 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1343
1828
|
const initializers: string[] = [];
|
|
1344
1829
|
for (const [aggregateId, members] of collectionLines) {
|
|
1345
1830
|
const aggregate = aggregates.get(aggregateId);
|
|
1346
|
-
const concrete =
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1831
|
+
const concrete =
|
|
1832
|
+
aggregate === undefined
|
|
1833
|
+
? concreteClassName(aggregateId)
|
|
1834
|
+
: concreteClassName(aggregate.id);
|
|
1835
|
+
const collection =
|
|
1836
|
+
aggregate === undefined
|
|
1837
|
+
? `${concrete}Collection`
|
|
1838
|
+
: dartAggregateCollectionClassName(aggregate);
|
|
1352
1839
|
const property = member(plural(concrete));
|
|
1353
1840
|
claimApplicationMember(property, `aggregate collection '${aggregateId}'`);
|
|
1354
1841
|
appLines.push(` final ${collection} ${property};`);
|
|
@@ -1365,39 +1852,65 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1365
1852
|
``,
|
|
1366
1853
|
...members,
|
|
1367
1854
|
`}`,
|
|
1368
|
-
|
|
1855
|
+
``
|
|
1369
1856
|
);
|
|
1370
1857
|
}
|
|
1371
1858
|
|
|
1372
1859
|
const imports = [...siblingImports.entries()]
|
|
1373
1860
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
1374
|
-
.map(
|
|
1861
|
+
.map(
|
|
1862
|
+
([file, names]) =>
|
|
1863
|
+
`import '${file}' show ${[...names].sort().join(", ")};`
|
|
1864
|
+
);
|
|
1375
1865
|
const typesImport = opts.typesImportUri ?? `${mod.name}.dart`;
|
|
1376
|
-
const runtimeImport =
|
|
1377
|
-
|
|
1378
|
-
|
|
1866
|
+
const runtimeImport =
|
|
1867
|
+
opts.supportImport === "package"
|
|
1868
|
+
? "package:nomos_client/src/nms_generated_runtime.dart"
|
|
1869
|
+
: "nomos_client.dart";
|
|
1379
1870
|
const applicationInitializers = [
|
|
1380
1871
|
"_client = client",
|
|
1381
1872
|
...(tenantRoot ? ["currentUser = currentUser"] : []),
|
|
1382
1873
|
`activity = ${activity}._(client)`,
|
|
1383
|
-
...(resolverBindings.length > 0
|
|
1874
|
+
...(resolverBindings.length > 0
|
|
1875
|
+
? [`conflicts = ${cap(camel(domain))}Conflicts._(client)`]
|
|
1876
|
+
: []),
|
|
1384
1877
|
...initializers,
|
|
1385
1878
|
];
|
|
1386
1879
|
return [
|
|
1387
1880
|
`// AUTO-GENERATED by nomos-compile — the tenant application surface for "${domain}".`,
|
|
1388
1881
|
`// Business facts in, business records out. Runtime lifecycle is deliberately absent.`,
|
|
1389
|
-
...(sharingPolicies.length > 0
|
|
1390
|
-
|
|
1882
|
+
...(sharingPolicies.length > 0
|
|
1883
|
+
? [`import 'dart:async' show StreamSubscription, unawaited;`]
|
|
1884
|
+
: []),
|
|
1885
|
+
`import '${runtimeImport}' show NmsInternalForeignRefs, NmsInternalNomosRefs, NmsInternalRecordOrigins, NomosBridge, NomosOfferOutcome, NomosRef, NomosSession${
|
|
1886
|
+
usesWorkspaceName ? ", deriveWorkspaceName" : ""
|
|
1887
|
+
}${
|
|
1888
|
+
sharingPolicies.length > 0
|
|
1889
|
+
? ", Invitation, Recipient, SyncPhase, User"
|
|
1890
|
+
: ""
|
|
1891
|
+
}${
|
|
1892
|
+
tenantRoot ? ", NmsInternalUsers, nmsInternalReceivingGroupNameOf" : ""
|
|
1893
|
+
};`,
|
|
1391
1894
|
...(opts.flutterHost === true || sharingPolicies.length > 0
|
|
1392
1895
|
? [
|
|
1393
1896
|
...(tenantRoot ? [`import 'package:flutter/widgets.dart';`] : []),
|
|
1394
1897
|
...(sharingPolicies.length > 0
|
|
1395
|
-
? [
|
|
1898
|
+
? [
|
|
1899
|
+
`import 'package:nomos_flutter/src/application_share.dart' show ApplicationShareResult;`,
|
|
1900
|
+
]
|
|
1396
1901
|
: []),
|
|
1397
1902
|
...(opts.flutterHost === true && tenantRoot
|
|
1398
|
-
? [
|
|
1903
|
+
? [
|
|
1904
|
+
`import 'package:nomos_flutter/src/application_status.dart' show ApplicationSignIn, ApplicationStatus;`,
|
|
1905
|
+
]
|
|
1399
1906
|
: []),
|
|
1400
|
-
`import 'package:nomos_flutter/src/generated_application_host.dart' show NmsInternalApplicationUserBinding${
|
|
1907
|
+
`import 'package:nomos_flutter/src/generated_application_host.dart' show NmsInternalApplicationUserBinding${
|
|
1908
|
+
tenantRoot || sharingPolicies.length > 0
|
|
1909
|
+
? ", NmsInternalApplicationHost"
|
|
1910
|
+
: ""
|
|
1911
|
+
}${
|
|
1912
|
+
sharingPolicies.length > 0 ? ", NmsInternalInvitationPolicy" : ""
|
|
1913
|
+
};`,
|
|
1401
1914
|
]
|
|
1402
1915
|
: []),
|
|
1403
1916
|
`import '${typesImport}';`,
|
|
@@ -1406,9 +1919,7 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1406
1919
|
...imports,
|
|
1407
1920
|
``,
|
|
1408
1921
|
...(tenantRoot
|
|
1409
|
-
? [
|
|
1410
|
-
`/// The canonical stateful entry point for this business domain.`,
|
|
1411
|
-
]
|
|
1922
|
+
? [`/// The canonical stateful entry point for this business domain.`]
|
|
1412
1923
|
: [
|
|
1413
1924
|
`/// A business domain handle owned by the generated package application.`,
|
|
1414
1925
|
]),
|
|
@@ -1440,7 +1951,9 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1440
1951
|
...(tenantRoot ? [` final User currentUser;`] : []),
|
|
1441
1952
|
` final ${activity} activity;`,
|
|
1442
1953
|
...appLines,
|
|
1443
|
-
` ${application}._(${client} client${
|
|
1954
|
+
` ${application}._(${client} client${
|
|
1955
|
+
tenantRoot ? ", User currentUser" : ""
|
|
1956
|
+
}) : ${applicationInitializers.join(", ")};`,
|
|
1444
1957
|
...(opts.flutterHost === true && tenantRoot
|
|
1445
1958
|
? [
|
|
1446
1959
|
``,
|
|
@@ -1473,14 +1986,23 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1473
1986
|
: []),
|
|
1474
1987
|
`}`,
|
|
1475
1988
|
``,
|
|
1476
|
-
`/// The verified business
|
|
1989
|
+
`/// The verified recent business activity of this application, newest first.`,
|
|
1477
1990
|
`final class ${activity} {`,
|
|
1478
1991
|
` final ${client} _client;`,
|
|
1479
1992
|
` const ${activity}._(this._client);`,
|
|
1480
1993
|
``,
|
|
1481
|
-
` Future<List<ActivityEvent>>
|
|
1994
|
+
` Future<List<ActivityEvent>> recent({required int limit}) =>`,
|
|
1995
|
+
` _client.recentActivity(limit: limit);`,
|
|
1482
1996
|
``,
|
|
1483
|
-
` Stream<List<ActivityEvent>>
|
|
1997
|
+
` Stream<List<ActivityEvent>> watchRecent({required int limit}) =>`,
|
|
1998
|
+
` _client.watchRecentActivity(limit: limit);`,
|
|
1999
|
+
``,
|
|
2000
|
+
` @Deprecated('Reads ledger history. Use recent with an explicit limit, or a DSL managed count for quantities.')`,
|
|
2001
|
+
` Future<List<ActivityEvent>> all({int? limit}) =>`,
|
|
2002
|
+
` recent(limit: limit ?? 50);`,
|
|
2003
|
+
``,
|
|
2004
|
+
` @Deprecated('Reads ledger history. Use watchRecent with an explicit limit.')`,
|
|
2005
|
+
` Stream<List<ActivityEvent>> watchAll() => watchRecent(limit: 50);`,
|
|
1484
2006
|
`}`,
|
|
1485
2007
|
``,
|
|
1486
2008
|
...actionResultDeclarations,
|
|
@@ -1518,6 +2040,26 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1518
2040
|
` return NmsInternalNomosRefs.create<T>(workspace: workspace, type: affected.type, id: affected.id);`,
|
|
1519
2041
|
`}`,
|
|
1520
2042
|
``,
|
|
2043
|
+
...(usesAffectedReferenceOrNull
|
|
2044
|
+
? [
|
|
2045
|
+
`NomosRef<T>? _nmsAffectedReferenceOrNull<T>(NomosOfferOutcome change, String expectedType) {`,
|
|
2046
|
+
` final matches = change.affected`,
|
|
2047
|
+
` .where((affected) => _nmsReferenceTypeMatches(affected.type, expectedType))`,
|
|
2048
|
+
` .toList(growable: false);`,
|
|
2049
|
+
` if (matches.length > 1) {`,
|
|
2050
|
+
` throw StateError('The business action expected at most one affected $expectedType record, received \${matches.length}');`,
|
|
2051
|
+
` }`,
|
|
2052
|
+
` if (matches.isEmpty) return null;`,
|
|
2053
|
+
` final affected = matches.single;`,
|
|
2054
|
+
` final workspace = affected.workspace.isNotEmpty ? affected.workspace : change.workspace;`,
|
|
2055
|
+
` if (workspace.isEmpty) {`,
|
|
2056
|
+
` throw StateError('The affected record did not carry its business workspace');`,
|
|
2057
|
+
` }`,
|
|
2058
|
+
` return NmsInternalNomosRefs.create<T>(workspace: workspace, type: affected.type, id: affected.id);`,
|
|
2059
|
+
`}`,
|
|
2060
|
+
``,
|
|
2061
|
+
]
|
|
2062
|
+
: []),
|
|
1521
2063
|
...(usesAffectedReferences
|
|
1522
2064
|
? [
|
|
1523
2065
|
`List<NomosRef<T>> _nmsAffectedReferences<T>(NomosOfferOutcome change, String expectedType) {`,
|
|
@@ -1542,9 +2084,13 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1542
2084
|
`Future<${application}> nomosInternalBind${application}(`,
|
|
1543
2085
|
` NomosSession session, NmsInternalApplicationUserBinding user) async {`,
|
|
1544
2086
|
` final application = ${application}._(`,
|
|
1545
|
-
` await ${client}.bind(session, actor: user.nmsInternalActor)${
|
|
2087
|
+
` await ${client}.bind(session, actor: user.nmsInternalActor)${
|
|
2088
|
+
tenantRoot ? "," : ""
|
|
2089
|
+
}`,
|
|
1546
2090
|
...(tenantRoot
|
|
1547
|
-
? [
|
|
2091
|
+
? [
|
|
2092
|
+
` NmsInternalUsers.create(identityId: user.nmsInternalIdentityId, identityAddress: user.nmsInternalIdentityAddress),`,
|
|
2093
|
+
]
|
|
1548
2094
|
: []),
|
|
1549
2095
|
` );`,
|
|
1550
2096
|
]
|
|
@@ -1552,7 +2098,9 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1552
2098
|
`Future<${application}> nomosInternalBind${application}(NomosSession session, String? actor) async {`,
|
|
1553
2099
|
` final application = ${application}._(await ${client}.bind(session, actor: actor));`,
|
|
1554
2100
|
]),
|
|
1555
|
-
...(sharingPolicies.length > 0
|
|
2101
|
+
...(sharingPolicies.length > 0
|
|
2102
|
+
? [` await application._nmsStartWorkspaceSharing();`]
|
|
2103
|
+
: []),
|
|
1556
2104
|
` return application;`,
|
|
1557
2105
|
`}`,
|
|
1558
2106
|
``,
|
|
@@ -1570,8 +2118,12 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1570
2118
|
` ),`,
|
|
1571
2119
|
` );`,
|
|
1572
2120
|
]
|
|
1573
|
-
: [
|
|
1574
|
-
|
|
2121
|
+
: [
|
|
2122
|
+
` final application = ${application}._(await ${client}.open(bridge, ref: reference, actor: actor));`,
|
|
2123
|
+
]),
|
|
2124
|
+
...(sharingPolicies.length > 0
|
|
2125
|
+
? [` await application._nmsStartWorkspaceSharing();`]
|
|
2126
|
+
: []),
|
|
1575
2127
|
` return application;`,
|
|
1576
2128
|
`}`,
|
|
1577
2129
|
``,
|