@githolon/dsl 0.102.5 → 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/compile_package.mjs +43 -2
- package/compile_proof.d.mts +47 -0
- package/compile_proof.mjs +343 -0
- package/dart/CHANGELOG.md +14 -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 +5539 -1228
- 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 +239 -102
- 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/acquire_session_test.dart +9 -0
- 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 +73 -33
- package/src/clear_directive.ts +19 -8
- package/src/codegen_dart.ts +305 -22
- package/src/codegen_dart_application.ts +862 -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 +1860 -592
- package/src/compose.ts +1 -1
- package/src/directive.ts +208 -15
- package/src/engine_entry.ts +165 -111
- package/src/evolve_preview.ts +29 -69
- 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 +58 -29
- 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 +140 -6
- package/src/stable_ids.ts +1 -1
- 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,34 @@ 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
|
|
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>{};`,
|
|
1183
|
+
` final shares = await _client.bridge.query('sharedByObject', {'object': 'workspace:self'}, null, _client.actor);`,
|
|
844
1184
|
` for (final row in shares) {`,
|
|
845
1185
|
` final raw = row['data'];`,
|
|
846
1186
|
` if (raw is! Map) continue;`,
|
|
847
1187
|
` final workspace = (raw['asserterWorkspace'] ?? '').toString();`,
|
|
848
1188
|
` final object = (raw['object'] ?? '').toString();`,
|
|
849
1189
|
` final prunedAt = (raw['prunedAt'] ?? '').toString();`,
|
|
850
|
-
` if (!workspace.startsWith(${JSON.stringify(
|
|
1190
|
+
` if (!workspace.startsWith(${JSON.stringify(
|
|
1191
|
+
`${birth.namespace}-`
|
|
1192
|
+
)}) ||`,
|
|
851
1193
|
` object != 'workspace:self' ||`,
|
|
852
1194
|
` prunedAt.isNotEmpty ||`,
|
|
853
1195
|
` !seen.add(workspace)) continue;`,
|
|
854
|
-
` final reference = NmsInternalNomosRefs.create<${rootType}>(workspace: workspace, type: ${JSON.stringify(
|
|
1196
|
+
` final reference = NmsInternalNomosRefs.create<${rootType}>(workspace: workspace, type: ${JSON.stringify(
|
|
1197
|
+
birth.aggregateId
|
|
1198
|
+
)}, id: ${JSON.stringify(birth.rootId)});`,
|
|
855
1199
|
` final application = await _nmsOpenReference(reference);`,
|
|
856
1200
|
` final record = await application.${rootCollection}.find(reference);`,
|
|
857
1201
|
` if (record != null) records.add(record);`,
|
|
@@ -859,17 +1203,56 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
859
1203
|
` return records;`,
|
|
860
1204
|
` }`,
|
|
861
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
|
+
``,
|
|
862
1212
|
` /// React when this person's available ${birth.aggregateId} lives change.`,
|
|
863
1213
|
` Stream<List<${rootModel}>> watchAvailable() =>`,
|
|
864
|
-
`
|
|
865
|
-
`
|
|
866
|
-
`
|
|
1214
|
+
` Stream<List<${rootModel}>>.multi((events) {`,
|
|
1215
|
+
` var reading = false;`,
|
|
1216
|
+
` var reread = false;`,
|
|
1217
|
+
` Future<void> refresh() async {`,
|
|
1218
|
+
` if (reading) {`,
|
|
1219
|
+
` reread = true;`,
|
|
1220
|
+
` return;`,
|
|
1221
|
+
` }`,
|
|
1222
|
+
` reading = true;`,
|
|
1223
|
+
` do {`,
|
|
1224
|
+
` reread = false;`,
|
|
1225
|
+
` try {`,
|
|
1226
|
+
` final records = await available();`,
|
|
1227
|
+
` if (!events.isClosed) events.add(records);`,
|
|
1228
|
+
` } catch (error, stackTrace) {`,
|
|
1229
|
+
` if (!events.isClosed) events.addError(error, stackTrace);`,
|
|
1230
|
+
` }`,
|
|
1231
|
+
` } while (reread && !events.isClosed);`,
|
|
1232
|
+
` reading = false;`,
|
|
1233
|
+
` }`,
|
|
1234
|
+
` final birthChanges = _client.bridge`,
|
|
1235
|
+
` .watch('birthRecordsByPrincipal', {'principal': _client.actor}, null, null, 60000)`,
|
|
1236
|
+
` .skip(1)`,
|
|
1237
|
+
` .listen((_) { refresh(); }, onError: events.addError);`,
|
|
1238
|
+
` final shareChanges = _client.bridge`,
|
|
1239
|
+
` .watch('sharedByObject', {'object': 'workspace:self'}, null, _client.actor, 60000)`,
|
|
1240
|
+
` .skip(1)`,
|
|
1241
|
+
` .listen((_) { refresh(); }, onError: events.addError);`,
|
|
1242
|
+
` events.onCancel = () async {`,
|
|
1243
|
+
` await birthChanges.cancel();`,
|
|
1244
|
+
` await shareChanges.cancel();`,
|
|
1245
|
+
` };`,
|
|
1246
|
+
` refresh();`,
|
|
1247
|
+
` });`,
|
|
867
1248
|
``,
|
|
868
1249
|
` /// Continue working in the business application that produced [record].`,
|
|
869
1250
|
` Future<${targetApplication}> applicationOf(${rootModel} record) {`,
|
|
870
1251
|
` final origin = NmsInternalRecordOrigins.require(record);`,
|
|
871
1252
|
` return _nmsOpenReference(`,
|
|
872
|
-
` NmsInternalNomosRefs.create<${rootType}>(workspace: origin.workspace, type: ${JSON.stringify(
|
|
1253
|
+
` NmsInternalNomosRefs.create<${rootType}>(workspace: origin.workspace, type: ${JSON.stringify(
|
|
1254
|
+
birth.aggregateId
|
|
1255
|
+
)}, id: origin.id),`,
|
|
873
1256
|
` );`,
|
|
874
1257
|
` }`,
|
|
875
1258
|
``,
|
|
@@ -889,24 +1272,42 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
889
1272
|
if (outputs.length > 0) {
|
|
890
1273
|
usesAffectedReference = true;
|
|
891
1274
|
usesAffectedReferences ||= outputs.some((output) => output.many);
|
|
1275
|
+
usesAffectedReferenceOrNull ||= outputs.some((output) => output.optional);
|
|
892
1276
|
actionResultDeclarations.push(
|
|
893
1277
|
`/// Every business record affected by ${cap(camel(directive.id))}.`,
|
|
894
1278
|
`final class ${resultClass} {`,
|
|
895
1279
|
...outputs.map((output) =>
|
|
896
1280
|
output.many
|
|
897
1281
|
? ` final List<NomosRef<${output.dartType}>> ${output.field};`
|
|
898
|
-
:
|
|
899
|
-
|
|
1282
|
+
: output.optional
|
|
1283
|
+
? ` final NomosRef<${output.dartType}>? ${output.field};`
|
|
1284
|
+
: ` final NomosRef<${output.dartType}> ${output.field};`
|
|
1285
|
+
),
|
|
1286
|
+
` const ${resultClass}({${outputs
|
|
1287
|
+
.map((output) => `required this.${output.field}`)
|
|
1288
|
+
.join(", ")}});`,
|
|
900
1289
|
`}`,
|
|
901
|
-
|
|
1290
|
+
``
|
|
902
1291
|
);
|
|
903
1292
|
}
|
|
904
|
-
const resultExpression =
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
1293
|
+
const resultExpression =
|
|
1294
|
+
outputs.length === 0
|
|
1295
|
+
? undefined
|
|
1296
|
+
: `${resultClass}(${outputs
|
|
1297
|
+
.map((output) =>
|
|
1298
|
+
output.many
|
|
1299
|
+
? `${output.field}: _nmsAffectedReferences<${
|
|
1300
|
+
output.dartType
|
|
1301
|
+
}>(change, ${JSON.stringify(output.aggregateId)})`
|
|
1302
|
+
: output.optional
|
|
1303
|
+
? `${output.field}: _nmsAffectedReferenceOrNull<${
|
|
1304
|
+
output.dartType
|
|
1305
|
+
}>(change, ${JSON.stringify(output.aggregateId)})`
|
|
1306
|
+
: `${output.field}: _nmsAffectedReference<${
|
|
1307
|
+
output.dartType
|
|
1308
|
+
}>(change, ${JSON.stringify(output.aggregateId)})`
|
|
1309
|
+
)
|
|
1310
|
+
.join(", ")})`;
|
|
910
1311
|
if (isBusinessIntent(directive)) {
|
|
911
1312
|
claimApplicationMember(method, `business intent '${directive.id}'`);
|
|
912
1313
|
const sourceTrust = sourceTrustAction(directive);
|
|
@@ -922,7 +1323,7 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
922
1323
|
...afterBusinessWrite(directive),
|
|
923
1324
|
` return ${resultExpression};`,
|
|
924
1325
|
` }`,
|
|
925
|
-
|
|
1326
|
+
``
|
|
926
1327
|
);
|
|
927
1328
|
} else {
|
|
928
1329
|
appLines.push(
|
|
@@ -935,27 +1336,25 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
935
1336
|
` await ${sourceTrust.call};`,
|
|
936
1337
|
...afterBusinessWrite(directive),
|
|
937
1338
|
` }`,
|
|
938
|
-
|
|
1339
|
+
``
|
|
939
1340
|
);
|
|
940
1341
|
}
|
|
941
1342
|
continue;
|
|
942
1343
|
}
|
|
943
1344
|
const sourceTrust = sourceTrustAction(directive);
|
|
944
1345
|
if (resultExpression !== undefined) {
|
|
945
|
-
addCollectionMember(
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
],
|
|
958
|
-
);
|
|
1346
|
+
addCollectionMember(directive.aggregateId, method, [
|
|
1347
|
+
` /// ${cap(
|
|
1348
|
+
camel(directive.id)
|
|
1349
|
+
)}. Returns every affected typed business record reference.`,
|
|
1350
|
+
` Future<${resultClass}> ${method}(${payload} facts) async {`,
|
|
1351
|
+
...referenceChecks,
|
|
1352
|
+
...sourceTrust.prelude,
|
|
1353
|
+
` final change = await ${sourceTrust.call};`,
|
|
1354
|
+
` return ${resultExpression};`,
|
|
1355
|
+
` }`,
|
|
1356
|
+
``,
|
|
1357
|
+
]);
|
|
959
1358
|
} else {
|
|
960
1359
|
addCollectionMember(directive.aggregateId, method, [
|
|
961
1360
|
` /// ${cap(camel(directive.id))}.`,
|
|
@@ -972,7 +1371,8 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
972
1371
|
const publicQueries = ((mod.queries ?? []) as QueryDecl[]).filter(
|
|
973
1372
|
(candidate) =>
|
|
974
1373
|
!isFrameworkQuery(candidate) &&
|
|
975
|
-
(candidate as { businessBirthInternal?: unknown })
|
|
1374
|
+
(candidate as { businessBirthInternal?: unknown })
|
|
1375
|
+
.businessBirthInternal !== true
|
|
976
1376
|
);
|
|
977
1377
|
const signedInSubjectKey = (query: QueryDecl): string | undefined => {
|
|
978
1378
|
if (query.key.length !== 1) return undefined;
|
|
@@ -985,31 +1385,46 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
985
1385
|
if (signedInSubjectKey(query) !== undefined) {
|
|
986
1386
|
mineQueriesByAggregate.set(
|
|
987
1387
|
query.returns,
|
|
988
|
-
(mineQueriesByAggregate.get(query.returns) ?? 0) + 1
|
|
1388
|
+
(mineQueriesByAggregate.get(query.returns) ?? 0) + 1
|
|
989
1389
|
);
|
|
990
1390
|
}
|
|
991
1391
|
}
|
|
992
1392
|
for (const query of publicQueries) {
|
|
993
1393
|
const model = readModelClassName(query.returns);
|
|
994
1394
|
const aggregate = aggregates.get(query.returns);
|
|
995
|
-
const params = query.key
|
|
996
|
-
|
|
1395
|
+
const params = query.key
|
|
1396
|
+
.map((key) => `required ${keyType(aggregate, key)} ${keyParam(key)}`)
|
|
1397
|
+
.join(", ");
|
|
1398
|
+
const args = query.key
|
|
1399
|
+
.map((key) => `${keyParam(key)}: ${keyParam(key)}`)
|
|
1400
|
+
.join(", ");
|
|
997
1401
|
const method = member(query.id);
|
|
998
1402
|
addCollectionMember(query.returns, method, [
|
|
999
1403
|
` Future<List<${model}>> ${method}({${params}}) => _client.${method}(${args});`,
|
|
1000
|
-
` Stream<List<${model}>> watch${cap(
|
|
1404
|
+
` Stream<List<${model}>> watch${cap(
|
|
1405
|
+
camel(query.id)
|
|
1406
|
+
)}({${params}}) => _client.watch${cap(camel(query.id))}(${args});`,
|
|
1001
1407
|
``,
|
|
1002
1408
|
]);
|
|
1003
1409
|
const subjectKey = signedInSubjectKey(query);
|
|
1004
|
-
if (
|
|
1410
|
+
if (
|
|
1411
|
+
subjectKey !== undefined &&
|
|
1412
|
+
!birthCollectionsWithMine.has(query.returns)
|
|
1413
|
+
) {
|
|
1005
1414
|
const subjectParam = keyParam(subjectKey);
|
|
1006
1415
|
const subjectQueryCount = mineQueriesByAggregate.get(query.returns) ?? 0;
|
|
1007
1416
|
const role = cap(camel(subjectKey.replace(/Subject$/, "") || "subject"));
|
|
1008
1417
|
const mineMethod = subjectQueryCount === 1 ? "mine" : `mineAs${role}`;
|
|
1009
|
-
const watchMineMethod =
|
|
1010
|
-
|
|
1418
|
+
const watchMineMethod =
|
|
1419
|
+
subjectQueryCount === 1 ? "watchMine" : `watchMineAs${role}`;
|
|
1420
|
+
const relationship =
|
|
1421
|
+
subjectQueryCount === 1
|
|
1422
|
+
? "belonging to"
|
|
1423
|
+
: `where they are the ${role.toLowerCase()}`;
|
|
1011
1424
|
addCollectionMember(query.returns, mineMethod, [
|
|
1012
|
-
` /// ${cap(
|
|
1425
|
+
` /// ${cap(
|
|
1426
|
+
camel(query.returns)
|
|
1427
|
+
)} records ${relationship} the signed-in person.`,
|
|
1013
1428
|
` Future<List<${model}>> ${mineMethod}() {`,
|
|
1014
1429
|
` final subject = _client.actor;`,
|
|
1015
1430
|
` if (subject == null || subject.trim().isEmpty) {`,
|
|
@@ -1023,7 +1438,9 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1023
1438
|
` if (subject == null || subject.trim().isEmpty) {`,
|
|
1024
1439
|
` throw StateError('The signed-in person is not available.');`,
|
|
1025
1440
|
` }`,
|
|
1026
|
-
` return _client.watch${cap(
|
|
1441
|
+
` return _client.watch${cap(
|
|
1442
|
+
camel(query.id)
|
|
1443
|
+
)}(${subjectParam}: subject);`,
|
|
1027
1444
|
` }`,
|
|
1028
1445
|
``,
|
|
1029
1446
|
]);
|
|
@@ -1036,7 +1453,8 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1036
1453
|
for (const aggregate of mod.aggregates.filter(
|
|
1037
1454
|
(candidate) =>
|
|
1038
1455
|
!isFrameworkAggregate(candidate) &&
|
|
1039
|
-
(candidate as { businessBirthInternal?: unknown })
|
|
1456
|
+
(candidate as { businessBirthInternal?: unknown })
|
|
1457
|
+
.businessBirthInternal !== true
|
|
1040
1458
|
)) {
|
|
1041
1459
|
const sourceModel = readModelClassName(aggregate.id);
|
|
1042
1460
|
for (const contractId of aggregate.implementsContracts ?? []) {
|
|
@@ -1049,7 +1467,9 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1049
1467
|
` /// the contract that this aggregate explicitly implements. This does not grant access.`,
|
|
1050
1468
|
` NomosRef<${contract}> ${method}(${sourceModel} record) {`,
|
|
1051
1469
|
` final reference = record.ref;`,
|
|
1052
|
-
` _nmsRequireReference(reference, ${JSON.stringify(
|
|
1470
|
+
` _nmsRequireReference(reference, ${JSON.stringify(
|
|
1471
|
+
aggregate.id
|
|
1472
|
+
)}, '');`,
|
|
1053
1473
|
` return NmsInternalNomosRefs.create<${contract}>(`,
|
|
1054
1474
|
` workspace: reference.workspace,`,
|
|
1055
1475
|
` type: ${JSON.stringify(contractId)},`,
|
|
@@ -1065,8 +1485,12 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1065
1485
|
` /// Use this after following a generated cross-application reference and opening`,
|
|
1066
1486
|
` /// its business application. Nomos checks the declared contract and access on read.`,
|
|
1067
1487
|
` Future<${sourceModel}?> ${resolveMethod}(ForeignRef<${contract}> reference) {`,
|
|
1068
|
-
` _nmsRequireReference(reference.ref, ${JSON.stringify(
|
|
1069
|
-
|
|
1488
|
+
` _nmsRequireReference(reference.ref, ${JSON.stringify(
|
|
1489
|
+
contractId
|
|
1490
|
+
)}, '');`,
|
|
1491
|
+
` return find(NmsInternalNomosRefs.create<${concreteClassName(
|
|
1492
|
+
aggregate.id
|
|
1493
|
+
)}>(`,
|
|
1070
1494
|
` workspace: reference.workspace,`,
|
|
1071
1495
|
` type: ${JSON.stringify(aggregate.id)},`,
|
|
1072
1496
|
` id: reference.id,`,
|
|
@@ -1078,8 +1502,12 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1078
1502
|
addCollectionMember(aggregate.id, watchMethod, [
|
|
1079
1503
|
` /// React when the concrete record behind [reference] changes.`,
|
|
1080
1504
|
` Stream<${sourceModel}?> ${watchMethod}(ForeignRef<${contract}> reference) {`,
|
|
1081
|
-
` _nmsRequireReference(reference.ref, ${JSON.stringify(
|
|
1082
|
-
|
|
1505
|
+
` _nmsRequireReference(reference.ref, ${JSON.stringify(
|
|
1506
|
+
contractId
|
|
1507
|
+
)}, '');`,
|
|
1508
|
+
` return watchOne(NmsInternalNomosRefs.create<${concreteClassName(
|
|
1509
|
+
aggregate.id
|
|
1510
|
+
)}>(`,
|
|
1083
1511
|
` workspace: reference.workspace,`,
|
|
1084
1512
|
` type: ${JSON.stringify(aggregate.id)},`,
|
|
1085
1513
|
` id: reference.id,`,
|
|
@@ -1088,20 +1516,31 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1088
1516
|
``,
|
|
1089
1517
|
]);
|
|
1090
1518
|
}
|
|
1091
|
-
const sameDomainRefs = Object.entries(aggregate.fields).filter(
|
|
1092
|
-
field
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1519
|
+
const sameDomainRefs = Object.entries(aggregate.fields).filter(
|
|
1520
|
+
([, field]) =>
|
|
1521
|
+
field.kind === "ref" &&
|
|
1522
|
+
field.foreignRef === undefined &&
|
|
1523
|
+
field.refAggregateId !== undefined &&
|
|
1524
|
+
field.refAggregateId !== aggregate.id &&
|
|
1525
|
+
aggregates.has(field.refAggregateId)
|
|
1526
|
+
);
|
|
1527
|
+
const crossDomainRefs = Object.entries(aggregate.fields).filter(
|
|
1528
|
+
([, field]) => {
|
|
1529
|
+
if (
|
|
1530
|
+
field.kind !== "ref" ||
|
|
1531
|
+
field.foreignRef !== undefined ||
|
|
1532
|
+
field.refAggregateId === undefined
|
|
1533
|
+
)
|
|
1534
|
+
return false;
|
|
1535
|
+
if (aggregates.has(field.refAggregateId)) return false;
|
|
1536
|
+
const owner = packageDomains[field.refAggregateId];
|
|
1537
|
+
return owner !== undefined && owner.domain !== domain;
|
|
1538
|
+
}
|
|
1097
1539
|
);
|
|
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][]) {
|
|
1540
|
+
for (const [fieldName, field] of Object.entries(aggregate.fields) as [
|
|
1541
|
+
string,
|
|
1542
|
+
Field
|
|
1543
|
+
][]) {
|
|
1105
1544
|
const foreign = field.foreignRef;
|
|
1106
1545
|
if (field.kind !== "ref") continue;
|
|
1107
1546
|
if (foreign === undefined) {
|
|
@@ -1116,13 +1555,19 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1116
1555
|
const sourceFieldValue = admitsNull(field)
|
|
1117
1556
|
? `record.${sourceFieldMember}.valueOrNull`
|
|
1118
1557
|
: `record.${sourceFieldMember}`;
|
|
1119
|
-
const isSameDomain = sameDomainRefs.some(
|
|
1120
|
-
|
|
1558
|
+
const isSameDomain = sameDomainRefs.some(
|
|
1559
|
+
([name]) => name === fieldName
|
|
1560
|
+
);
|
|
1561
|
+
const isCrossDomain = crossDomainRefs.some(
|
|
1562
|
+
([name]) => name === fieldName
|
|
1563
|
+
);
|
|
1121
1564
|
if (!isSameDomain && !isCrossDomain) continue;
|
|
1122
1565
|
|
|
1123
1566
|
if (isSameDomain) {
|
|
1124
1567
|
const targetModel = readModelClassName(targetAggregateId);
|
|
1125
|
-
const targetById = safe(
|
|
1568
|
+
const targetById = safe(
|
|
1569
|
+
`${lcFirst(camel(concreteClassName(targetAggregateId)))}ById`
|
|
1570
|
+
);
|
|
1126
1571
|
addCollectionMember(aggregate.id, method, [
|
|
1127
1572
|
` /// Resolve [record]'s ${fieldName} inside this business application.`,
|
|
1128
1573
|
` Future<${targetModel}?> ${method}(${sourceModel} record) async {`,
|
|
@@ -1132,7 +1577,9 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1132
1577
|
` if (id == null) return null;`,
|
|
1133
1578
|
` final rows = await _client.${targetById}(NmsInternalNomosRefs.id(id));`,
|
|
1134
1579
|
]
|
|
1135
|
-
: [
|
|
1580
|
+
: [
|
|
1581
|
+
` final rows = await _client.${targetById}(NmsInternalNomosRefs.id(record.${sourceFieldMember}));`,
|
|
1582
|
+
]),
|
|
1136
1583
|
` return rows.isEmpty ? null : rows.first;`,
|
|
1137
1584
|
` }`,
|
|
1138
1585
|
``,
|
|
@@ -1143,13 +1590,18 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1143
1590
|
const owner = packageDomains[targetAggregateId];
|
|
1144
1591
|
if (owner === undefined) continue;
|
|
1145
1592
|
const targetModel = readModelClassName(targetAggregateId);
|
|
1146
|
-
const targetCollection = member(
|
|
1593
|
+
const targetCollection = member(
|
|
1594
|
+
plural(concreteClassName(targetAggregateId))
|
|
1595
|
+
);
|
|
1147
1596
|
const targetApplication = dartApplicationClassName(owner.domain);
|
|
1148
1597
|
const opener = `nomosInternalOpen${targetApplication}`;
|
|
1149
1598
|
// Flat output needs the sibling type file. Layered output's ordinary
|
|
1150
1599
|
// types barrel already exports every domain; the explicit-barrel mode
|
|
1151
1600
|
// supplies direct package imports in explicitTypeImportUris.
|
|
1152
|
-
if (
|
|
1601
|
+
if (
|
|
1602
|
+
opts.typesImportUri === undefined ||
|
|
1603
|
+
explicitTypeImportUris.size > 0
|
|
1604
|
+
) {
|
|
1153
1605
|
explicitTypeImportUris.add(typeImportUriFor(owner.name));
|
|
1154
1606
|
}
|
|
1155
1607
|
addSibling(`${owner.name}.application.dart`, targetApplication);
|
|
@@ -1211,14 +1663,17 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1211
1663
|
]);
|
|
1212
1664
|
continue;
|
|
1213
1665
|
}
|
|
1214
|
-
const target =
|
|
1215
|
-
|
|
1216
|
-
|
|
1666
|
+
const target =
|
|
1667
|
+
foreign.targetIsContract === true
|
|
1668
|
+
? contractTypeName(foreign.targetAggregateId)
|
|
1669
|
+
: concreteClassName(foreign.targetAggregateId);
|
|
1217
1670
|
const fieldMember = member(fieldName);
|
|
1218
1671
|
const fieldValue = admitsNull(field)
|
|
1219
1672
|
? `record.${fieldMember}.valueOrNull`
|
|
1220
1673
|
: `record.${fieldMember}`;
|
|
1221
|
-
const stem = fieldMember.endsWith("Id")
|
|
1674
|
+
const stem = fieldMember.endsWith("Id")
|
|
1675
|
+
? fieldMember.slice(0, -2)
|
|
1676
|
+
: fieldMember;
|
|
1222
1677
|
const method = member(`${stem}Of`);
|
|
1223
1678
|
addCollectionMember(aggregate.id, method, [
|
|
1224
1679
|
` /// Follow [record]'s ${fieldName} to its typed business record.`,
|
|
@@ -1227,15 +1682,18 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1227
1682
|
` if (reference == null) return null;`,
|
|
1228
1683
|
` final target = await NmsInternalForeignRefs.resolve(reference, _client.bridge.foreignReader());`,
|
|
1229
1684
|
` if (target == null) return null;`,
|
|
1230
|
-
` return NmsInternalRecordOrigins.remember(target, workspace: NmsInternalNomosRefs.workspace(reference.ref), type: ${JSON.stringify(
|
|
1685
|
+
` return NmsInternalRecordOrigins.remember(target, workspace: NmsInternalNomosRefs.workspace(reference.ref), type: ${JSON.stringify(
|
|
1686
|
+
foreign.targetAggregateId
|
|
1687
|
+
)}, id: NmsInternalNomosRefs.id(reference.ref));`,
|
|
1231
1688
|
` }`,
|
|
1232
1689
|
``,
|
|
1233
1690
|
]);
|
|
1234
|
-
const targetOwner =
|
|
1691
|
+
const targetOwner =
|
|
1692
|
+
packageDomains[foreign.targetAggregateId] ??
|
|
1235
1693
|
(foreign.targetDomain === undefined
|
|
1236
1694
|
? undefined
|
|
1237
1695
|
: Object.values(packageDomains).find(
|
|
1238
|
-
(candidate) => candidate.domain === foreign.targetDomain
|
|
1696
|
+
(candidate) => candidate.domain === foreign.targetDomain
|
|
1239
1697
|
));
|
|
1240
1698
|
if (targetOwner !== undefined && targetOwner.domain !== domain) {
|
|
1241
1699
|
const targetApplication = dartApplicationClassName(targetOwner.domain);
|
|
@@ -1262,21 +1720,35 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1262
1720
|
|
|
1263
1721
|
for (const count of (mod.counts ?? []).map((raw) => finishCount(raw))) {
|
|
1264
1722
|
const method = member(count.id);
|
|
1265
|
-
const groupType =
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1723
|
+
const groupType =
|
|
1724
|
+
count.by == null ? "" : keyType(aggregates.get(count.of), count.by);
|
|
1725
|
+
addCollectionMember(count.of, method, [
|
|
1726
|
+
count.by == null
|
|
1727
|
+
? ` Future<int> ${method}() => _client.${method}();`
|
|
1728
|
+
: ` Future<int> ${method}(${groupType} ${keyParam(
|
|
1729
|
+
count.by
|
|
1730
|
+
)}) => _client.${method}(${keyParam(count.by)});`,
|
|
1731
|
+
]);
|
|
1269
1732
|
}
|
|
1270
1733
|
for (const sum of (mod.sums ?? []).map((raw) => finishSum(raw))) {
|
|
1271
1734
|
const method = member(sum.id);
|
|
1272
|
-
const groupType =
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1735
|
+
const groupType =
|
|
1736
|
+
sum.by == null ? "" : keyType(aggregates.get(sum.of), sum.by);
|
|
1737
|
+
addCollectionMember(sum.of, method, [
|
|
1738
|
+
sum.by == null
|
|
1739
|
+
? ` Future<int> ${method}() => _client.${method}();`
|
|
1740
|
+
: ` Future<int> ${method}(${groupType} ${keyParam(
|
|
1741
|
+
sum.by
|
|
1742
|
+
)}) => _client.${method}(${keyParam(sum.by)});`,
|
|
1743
|
+
]);
|
|
1276
1744
|
}
|
|
1277
1745
|
|
|
1278
1746
|
for (const aggregate of mod.aggregates) {
|
|
1279
|
-
if (
|
|
1747
|
+
if (
|
|
1748
|
+
(aggregate as { businessBirthInternal?: unknown })
|
|
1749
|
+
.businessBirthInternal === true
|
|
1750
|
+
)
|
|
1751
|
+
continue;
|
|
1280
1752
|
if (isFrameworkAggregate(aggregate)) continue;
|
|
1281
1753
|
const concrete = concreteClassName(aggregate.id);
|
|
1282
1754
|
const model = readModelClassName(aggregate.id);
|
|
@@ -1284,6 +1756,27 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1284
1756
|
const Cap = cap(camel(concrete));
|
|
1285
1757
|
const aggregateRefType = `NomosRef<${concrete}>`;
|
|
1286
1758
|
const clientList = safe(`list${Cap}s`);
|
|
1759
|
+
addCollectionMember(aggregate.id, "ref", [
|
|
1760
|
+
` /// Qualify [id] as a typed ${concrete} reference in this application workspace.`,
|
|
1761
|
+
` ///`,
|
|
1762
|
+
` /// Use this at UI boundaries such as route parameters that intentionally retain only`,
|
|
1763
|
+
` /// the local aggregate id. Use [fromAddress] when the workspace is part of the value.`,
|
|
1764
|
+
` ${aggregateRefType} ref(String id) {`,
|
|
1765
|
+
` final workspace = _client.session?.workspace ?? '';`,
|
|
1766
|
+
` if (workspace.isEmpty) {`,
|
|
1767
|
+
` throw StateError('Cannot qualify a ${concrete} id before its application workspace is open.');`,
|
|
1768
|
+
` }`,
|
|
1769
|
+
` if (id.trim().isEmpty) {`,
|
|
1770
|
+
` throw ArgumentError.value(id, 'id', 'A business id cannot be empty.');`,
|
|
1771
|
+
` }`,
|
|
1772
|
+
` return NmsInternalNomosRefs.create<${concrete}>(`,
|
|
1773
|
+
` workspace: workspace,`,
|
|
1774
|
+
` type: ${JSON.stringify(aggregate.id)},`,
|
|
1775
|
+
` id: id,`,
|
|
1776
|
+
` );`,
|
|
1777
|
+
` }`,
|
|
1778
|
+
``,
|
|
1779
|
+
]);
|
|
1287
1780
|
addCollectionMember(aggregate.id, "fromAddress", [
|
|
1288
1781
|
` /// Recover a typed ${concrete} reference from its stable public address.`,
|
|
1289
1782
|
` ///`,
|
|
@@ -1291,14 +1784,18 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1291
1784
|
` /// still verify the signed-in person's access when the reference is used.`,
|
|
1292
1785
|
` ${aggregateRefType} fromAddress(String address) {`,
|
|
1293
1786
|
` final reference = NmsInternalNomosRefs.fromUri<${concrete}>(address);`,
|
|
1294
|
-
` _nmsRequireReference(reference, ${JSON.stringify(
|
|
1787
|
+
` _nmsRequireReference(reference, ${JSON.stringify(
|
|
1788
|
+
aggregate.id
|
|
1789
|
+
)}, '');`,
|
|
1295
1790
|
` return reference;`,
|
|
1296
1791
|
` }`,
|
|
1297
1792
|
``,
|
|
1298
1793
|
]);
|
|
1299
1794
|
addCollectionMember(aggregate.id, "find", [
|
|
1300
1795
|
` Future<${model}?> find(${aggregateRefType} reference) async {`,
|
|
1301
|
-
` _nmsRequireReference(reference, ${JSON.stringify(
|
|
1796
|
+
` _nmsRequireReference(reference, ${JSON.stringify(
|
|
1797
|
+
aggregate.id
|
|
1798
|
+
)}, '');`,
|
|
1302
1799
|
` final targetWorkspace = NmsInternalNomosRefs.workspace(reference);`,
|
|
1303
1800
|
` final currentWorkspace = _client.session?.workspace ?? '';`,
|
|
1304
1801
|
` if (currentWorkspace.isNotEmpty && targetWorkspace != currentWorkspace) {`,
|
|
@@ -1307,21 +1804,27 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1307
1804
|
` );`,
|
|
1308
1805
|
` return application.${member(plural(concrete))}.find(reference);`,
|
|
1309
1806
|
` }`,
|
|
1310
|
-
` final matches = await _client.${safe(
|
|
1807
|
+
` final matches = await _client.${safe(
|
|
1808
|
+
`${stem}ById`
|
|
1809
|
+
)}(NmsInternalNomosRefs.id(reference));`,
|
|
1311
1810
|
` return matches.isEmpty ? null : matches.single;`,
|
|
1312
1811
|
` }`,
|
|
1313
1812
|
``,
|
|
1314
1813
|
]);
|
|
1315
1814
|
addCollectionMember(aggregate.id, "watchOne", [
|
|
1316
1815
|
` Stream<${model}?> watchOne(${aggregateRefType} reference) {`,
|
|
1317
|
-
` _nmsRequireReference(reference, ${JSON.stringify(
|
|
1816
|
+
` _nmsRequireReference(reference, ${JSON.stringify(
|
|
1817
|
+
aggregate.id
|
|
1818
|
+
)}, '');`,
|
|
1318
1819
|
` final targetWorkspace = NmsInternalNomosRefs.workspace(reference);`,
|
|
1319
1820
|
` final currentWorkspace = _client.session?.workspace ?? '';`,
|
|
1320
1821
|
` if (currentWorkspace.isNotEmpty && targetWorkspace != currentWorkspace) {`,
|
|
1321
1822
|
` return Stream.fromFuture(nomosInternalOpen${application}(`,
|
|
1322
1823
|
` _client.bridge, reference, _client.actor,`,
|
|
1323
1824
|
` )).asyncExpand(`,
|
|
1324
|
-
` (application) => application.${member(
|
|
1825
|
+
` (application) => application.${member(
|
|
1826
|
+
plural(concrete)
|
|
1827
|
+
)}.watchOne(reference),`,
|
|
1325
1828
|
` );`,
|
|
1326
1829
|
` }`,
|
|
1327
1830
|
` return _client.watch${Cap}ById(NmsInternalNomosRefs.id(reference))`,
|
|
@@ -1343,12 +1846,14 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1343
1846
|
const initializers: string[] = [];
|
|
1344
1847
|
for (const [aggregateId, members] of collectionLines) {
|
|
1345
1848
|
const aggregate = aggregates.get(aggregateId);
|
|
1346
|
-
const concrete =
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1849
|
+
const concrete =
|
|
1850
|
+
aggregate === undefined
|
|
1851
|
+
? concreteClassName(aggregateId)
|
|
1852
|
+
: concreteClassName(aggregate.id);
|
|
1853
|
+
const collection =
|
|
1854
|
+
aggregate === undefined
|
|
1855
|
+
? `${concrete}Collection`
|
|
1856
|
+
: dartAggregateCollectionClassName(aggregate);
|
|
1352
1857
|
const property = member(plural(concrete));
|
|
1353
1858
|
claimApplicationMember(property, `aggregate collection '${aggregateId}'`);
|
|
1354
1859
|
appLines.push(` final ${collection} ${property};`);
|
|
@@ -1365,39 +1870,65 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1365
1870
|
``,
|
|
1366
1871
|
...members,
|
|
1367
1872
|
`}`,
|
|
1368
|
-
|
|
1873
|
+
``
|
|
1369
1874
|
);
|
|
1370
1875
|
}
|
|
1371
1876
|
|
|
1372
1877
|
const imports = [...siblingImports.entries()]
|
|
1373
1878
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
1374
|
-
.map(
|
|
1879
|
+
.map(
|
|
1880
|
+
([file, names]) =>
|
|
1881
|
+
`import '${file}' show ${[...names].sort().join(", ")};`
|
|
1882
|
+
);
|
|
1375
1883
|
const typesImport = opts.typesImportUri ?? `${mod.name}.dart`;
|
|
1376
|
-
const runtimeImport =
|
|
1377
|
-
|
|
1378
|
-
|
|
1884
|
+
const runtimeImport =
|
|
1885
|
+
opts.supportImport === "package"
|
|
1886
|
+
? "package:nomos_client/src/nms_generated_runtime.dart"
|
|
1887
|
+
: "nomos_client.dart";
|
|
1379
1888
|
const applicationInitializers = [
|
|
1380
1889
|
"_client = client",
|
|
1381
1890
|
...(tenantRoot ? ["currentUser = currentUser"] : []),
|
|
1382
1891
|
`activity = ${activity}._(client)`,
|
|
1383
|
-
...(resolverBindings.length > 0
|
|
1892
|
+
...(resolverBindings.length > 0
|
|
1893
|
+
? [`conflicts = ${cap(camel(domain))}Conflicts._(client)`]
|
|
1894
|
+
: []),
|
|
1384
1895
|
...initializers,
|
|
1385
1896
|
];
|
|
1386
1897
|
return [
|
|
1387
1898
|
`// AUTO-GENERATED by nomos-compile — the tenant application surface for "${domain}".`,
|
|
1388
1899
|
`// Business facts in, business records out. Runtime lifecycle is deliberately absent.`,
|
|
1389
|
-
...(sharingPolicies.length > 0
|
|
1390
|
-
|
|
1900
|
+
...(sharingPolicies.length > 0
|
|
1901
|
+
? [`import 'dart:async' show StreamSubscription, unawaited;`]
|
|
1902
|
+
: []),
|
|
1903
|
+
`import '${runtimeImport}' show NmsInternalForeignRefs, NmsInternalNomosRefs, NmsInternalRecordOrigins, NomosBridge, NomosOfferOutcome, NomosRef, NomosSession${
|
|
1904
|
+
usesWorkspaceName ? ", deriveWorkspaceName" : ""
|
|
1905
|
+
}${
|
|
1906
|
+
sharingPolicies.length > 0
|
|
1907
|
+
? ", Invitation, Recipient, SyncPhase, User"
|
|
1908
|
+
: ""
|
|
1909
|
+
}${
|
|
1910
|
+
tenantRoot ? ", NmsInternalUsers, nmsInternalReceivingGroupNameOf" : ""
|
|
1911
|
+
};`,
|
|
1391
1912
|
...(opts.flutterHost === true || sharingPolicies.length > 0
|
|
1392
1913
|
? [
|
|
1393
1914
|
...(tenantRoot ? [`import 'package:flutter/widgets.dart';`] : []),
|
|
1394
1915
|
...(sharingPolicies.length > 0
|
|
1395
|
-
? [
|
|
1916
|
+
? [
|
|
1917
|
+
`import 'package:nomos_flutter/src/application_share.dart' show ApplicationShareResult;`,
|
|
1918
|
+
]
|
|
1396
1919
|
: []),
|
|
1397
1920
|
...(opts.flutterHost === true && tenantRoot
|
|
1398
|
-
? [
|
|
1921
|
+
? [
|
|
1922
|
+
`import 'package:nomos_flutter/src/application_status.dart' show ApplicationSignIn, ApplicationStatus;`,
|
|
1923
|
+
]
|
|
1399
1924
|
: []),
|
|
1400
|
-
`import 'package:nomos_flutter/src/generated_application_host.dart' show NmsInternalApplicationUserBinding${
|
|
1925
|
+
`import 'package:nomos_flutter/src/generated_application_host.dart' show NmsInternalApplicationUserBinding${
|
|
1926
|
+
tenantRoot || sharingPolicies.length > 0
|
|
1927
|
+
? ", NmsInternalApplicationHost"
|
|
1928
|
+
: ""
|
|
1929
|
+
}${
|
|
1930
|
+
sharingPolicies.length > 0 ? ", NmsInternalInvitationPolicy" : ""
|
|
1931
|
+
};`,
|
|
1401
1932
|
]
|
|
1402
1933
|
: []),
|
|
1403
1934
|
`import '${typesImport}';`,
|
|
@@ -1406,9 +1937,7 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1406
1937
|
...imports,
|
|
1407
1938
|
``,
|
|
1408
1939
|
...(tenantRoot
|
|
1409
|
-
? [
|
|
1410
|
-
`/// The canonical stateful entry point for this business domain.`,
|
|
1411
|
-
]
|
|
1940
|
+
? [`/// The canonical stateful entry point for this business domain.`]
|
|
1412
1941
|
: [
|
|
1413
1942
|
`/// A business domain handle owned by the generated package application.`,
|
|
1414
1943
|
]),
|
|
@@ -1440,7 +1969,9 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1440
1969
|
...(tenantRoot ? [` final User currentUser;`] : []),
|
|
1441
1970
|
` final ${activity} activity;`,
|
|
1442
1971
|
...appLines,
|
|
1443
|
-
` ${application}._(${client} client${
|
|
1972
|
+
` ${application}._(${client} client${
|
|
1973
|
+
tenantRoot ? ", User currentUser" : ""
|
|
1974
|
+
}) : ${applicationInitializers.join(", ")};`,
|
|
1444
1975
|
...(opts.flutterHost === true && tenantRoot
|
|
1445
1976
|
? [
|
|
1446
1977
|
``,
|
|
@@ -1473,14 +2004,23 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1473
2004
|
: []),
|
|
1474
2005
|
`}`,
|
|
1475
2006
|
``,
|
|
1476
|
-
`/// The verified business
|
|
2007
|
+
`/// The verified recent business activity of this application, newest first.`,
|
|
1477
2008
|
`final class ${activity} {`,
|
|
1478
2009
|
` final ${client} _client;`,
|
|
1479
2010
|
` const ${activity}._(this._client);`,
|
|
1480
2011
|
``,
|
|
1481
|
-
` Future<List<ActivityEvent>>
|
|
2012
|
+
` Future<List<ActivityEvent>> recent({required int limit}) =>`,
|
|
2013
|
+
` _client.recentActivity(limit: limit);`,
|
|
2014
|
+
``,
|
|
2015
|
+
` Stream<List<ActivityEvent>> watchRecent({required int limit}) =>`,
|
|
2016
|
+
` _client.watchRecentActivity(limit: limit);`,
|
|
2017
|
+
``,
|
|
2018
|
+
` @Deprecated('Reads ledger history. Use recent with an explicit limit, or a DSL managed count for quantities.')`,
|
|
2019
|
+
` Future<List<ActivityEvent>> all({int? limit}) =>`,
|
|
2020
|
+
` recent(limit: limit ?? 50);`,
|
|
1482
2021
|
``,
|
|
1483
|
-
`
|
|
2022
|
+
` @Deprecated('Reads ledger history. Use watchRecent with an explicit limit.')`,
|
|
2023
|
+
` Stream<List<ActivityEvent>> watchAll() => watchRecent(limit: 50);`,
|
|
1484
2024
|
`}`,
|
|
1485
2025
|
``,
|
|
1486
2026
|
...actionResultDeclarations,
|
|
@@ -1518,6 +2058,26 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1518
2058
|
` return NmsInternalNomosRefs.create<T>(workspace: workspace, type: affected.type, id: affected.id);`,
|
|
1519
2059
|
`}`,
|
|
1520
2060
|
``,
|
|
2061
|
+
...(usesAffectedReferenceOrNull
|
|
2062
|
+
? [
|
|
2063
|
+
`NomosRef<T>? _nmsAffectedReferenceOrNull<T>(NomosOfferOutcome change, String expectedType) {`,
|
|
2064
|
+
` final matches = change.affected`,
|
|
2065
|
+
` .where((affected) => _nmsReferenceTypeMatches(affected.type, expectedType))`,
|
|
2066
|
+
` .toList(growable: false);`,
|
|
2067
|
+
` if (matches.length > 1) {`,
|
|
2068
|
+
` throw StateError('The business action expected at most one affected $expectedType record, received \${matches.length}');`,
|
|
2069
|
+
` }`,
|
|
2070
|
+
` if (matches.isEmpty) return null;`,
|
|
2071
|
+
` final affected = matches.single;`,
|
|
2072
|
+
` final workspace = affected.workspace.isNotEmpty ? affected.workspace : change.workspace;`,
|
|
2073
|
+
` if (workspace.isEmpty) {`,
|
|
2074
|
+
` throw StateError('The affected record did not carry its business workspace');`,
|
|
2075
|
+
` }`,
|
|
2076
|
+
` return NmsInternalNomosRefs.create<T>(workspace: workspace, type: affected.type, id: affected.id);`,
|
|
2077
|
+
`}`,
|
|
2078
|
+
``,
|
|
2079
|
+
]
|
|
2080
|
+
: []),
|
|
1521
2081
|
...(usesAffectedReferences
|
|
1522
2082
|
? [
|
|
1523
2083
|
`List<NomosRef<T>> _nmsAffectedReferences<T>(NomosOfferOutcome change, String expectedType) {`,
|
|
@@ -1542,9 +2102,13 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1542
2102
|
`Future<${application}> nomosInternalBind${application}(`,
|
|
1543
2103
|
` NomosSession session, NmsInternalApplicationUserBinding user) async {`,
|
|
1544
2104
|
` final application = ${application}._(`,
|
|
1545
|
-
` await ${client}.bind(session, actor: user.nmsInternalActor)${
|
|
2105
|
+
` await ${client}.bind(session, actor: user.nmsInternalActor)${
|
|
2106
|
+
tenantRoot ? "," : ""
|
|
2107
|
+
}`,
|
|
1546
2108
|
...(tenantRoot
|
|
1547
|
-
? [
|
|
2109
|
+
? [
|
|
2110
|
+
` NmsInternalUsers.create(identityId: user.nmsInternalIdentityId, identityAddress: user.nmsInternalIdentityAddress),`,
|
|
2111
|
+
]
|
|
1548
2112
|
: []),
|
|
1549
2113
|
` );`,
|
|
1550
2114
|
]
|
|
@@ -1552,7 +2116,9 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1552
2116
|
`Future<${application}> nomosInternalBind${application}(NomosSession session, String? actor) async {`,
|
|
1553
2117
|
` final application = ${application}._(await ${client}.bind(session, actor: actor));`,
|
|
1554
2118
|
]),
|
|
1555
|
-
...(sharingPolicies.length > 0
|
|
2119
|
+
...(sharingPolicies.length > 0
|
|
2120
|
+
? [` await application._nmsStartWorkspaceSharing();`]
|
|
2121
|
+
: []),
|
|
1556
2122
|
` return application;`,
|
|
1557
2123
|
`}`,
|
|
1558
2124
|
``,
|
|
@@ -1570,8 +2136,12 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
|
|
|
1570
2136
|
` ),`,
|
|
1571
2137
|
` );`,
|
|
1572
2138
|
]
|
|
1573
|
-
: [
|
|
1574
|
-
|
|
2139
|
+
: [
|
|
2140
|
+
` final application = ${application}._(await ${client}.open(bridge, ref: reference, actor: actor));`,
|
|
2141
|
+
]),
|
|
2142
|
+
...(sharingPolicies.length > 0
|
|
2143
|
+
? [` await application._nmsStartWorkspaceSharing();`]
|
|
2144
|
+
: []),
|
|
1575
2145
|
` return application;`,
|
|
1576
2146
|
`}`,
|
|
1577
2147
|
``,
|