@githolon/dsl 0.101.6 → 0.102.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dart/CHANGELOG.md +13 -0
- package/dart/lib/nomos_client.dart +3 -52
- package/dart/lib/src/dev_cloud.dart +5 -0
- package/dart/lib/src/home.dart +10 -15
- package/dart/lib/src/nms_conformance_runtime.dart +12 -0
- package/dart/lib/src/nms_generated_runtime.dart +29 -0
- package/dart/lib/src/nms_test_environment.dart +8 -0
- package/dart/lib/src/node_runner_transport.dart +56 -18
- package/dart/lib/src/provider.dart +5 -1
- package/dart/lib/src/record_origin.dart +55 -0
- package/dart/lib/src/runner/node-entry.mjs +9105 -0
- package/dart/lib/src/runner/offline-install.mjs +4729 -0
- package/dart/lib/src/session_bound.dart +23 -10
- package/dart/lib/src/source_trust.dart +51 -0
- package/dart/lib/src/test_harness.dart +490 -58
- package/dart/lib/src/webview_bridge.dart +167 -58
- package/dart/lib/src/workspace_name.dart +7 -1
- package/dart/pubspec.lock +3 -3
- package/dart/pubspec.yaml +4 -2
- package/dart/test/acquire_session_credentials_test.dart +34 -17
- package/dart/test/acquire_session_test.dart +109 -24
- package/dart/test/adv_realm_interference_test.dart +227 -99
- package/dart/test/bundle_version_pairing_test.dart +1 -1
- package/dart/test/cloud_bound_topology_test.dart +1 -1
- package/dart/test/cluster_attested_test.dart +52 -35
- package/dart/test/cluster_births_test.dart +5 -2
- package/dart/test/cluster_device_keyed_acceptance_test.dart +5 -2
- package/dart/test/cluster_local_autosync_test.dart +5 -2
- package/dart/test/cluster_scale_test.dart +5 -2
- package/dart/test/compat_test.dart +1 -1
- package/dart/test/dlq_session_surface_test.dart +2 -1
- package/dart/test/errors_test.dart +1 -1
- package/dart/test/first_app_gauntlet_test.dart +78 -51
- package/dart/test/four_layer_nested_topology_test.dart +1 -1
- package/dart/test/harness_phase_timeout_test.dart +1 -1
- package/dart/test/home_parity_test.dart +9 -5
- package/dart/test/local_sync_inert_test.dart +22 -14
- package/dart/test/node_runner_transport_test.dart +9 -5
- package/dart/test/null_slot_test.dart +1 -1
- package/dart/test/parent_signer_recovery_test.dart +5 -2
- package/dart/test/schema_validation_test.dart +1 -1
- package/dart/test/sealing_client_test.dart +2 -1
- package/dart/test/session_bound_test.dart +92 -119
- package/dart/test/stale_bundle_test.dart +1 -1
- package/dart/test/test_harness_test.dart +77 -22
- package/dart/test/vo_read_typing_test.dart +32 -14
- package/dart/test/watch_after_offer_stall_test.dart +2 -1
- package/dart/test/webview_bridge_test.dart +17 -4
- package/dart/test/wire_test.dart +1 -1
- package/dart_types/CHANGELOG.md +8 -0
- package/dart_types/lib/nomos_types.dart +7 -6
- package/dart_types/lib/src/activity.dart +23 -13
- package/dart_types/lib/src/foreign_ref.dart +61 -112
- package/dart_types/lib/src/invitation.dart +96 -0
- package/dart_types/lib/src/nomos_ref.dart +97 -59
- package/dart_types/pubspec.yaml +1 -1
- package/dart_types/test/surfaced_test.dart +125 -19
- package/package.json +7 -3
- package/runtime/generated-application-host.js +8851 -0
- package/src/activity.ts +2 -2
- package/src/aggregate.ts +24 -1
- package/src/application_composition.ts +282 -0
- package/src/application_topology.ts +151 -0
- package/src/attested_core.ts +2 -2
- package/src/authoring_context.ts +9 -0
- package/src/birth_meter_ensure.ts +1 -1
- package/src/births.ts +53 -5
- package/src/build_package.ts +79 -71
- package/src/business_birth.ts +93 -10
- package/src/clear_directive.ts +3 -1
- package/src/codegen_dart.ts +369 -420
- package/src/codegen_dart_application.ts +1508 -0
- package/src/codegen_dart_client.ts +156 -95
- package/src/codegen_dart_scenario.ts +299 -0
- package/src/codegen_openapi.ts +8 -3
- package/src/codegen_proof.ts +1113 -1137
- package/src/codegen_provider_dart.ts +12 -1
- package/src/codegen_ts.ts +1176 -496
- package/src/compat_package_main.ts +22 -10
- package/src/compile_package_main.ts +1536 -337
- package/src/contract.ts +10 -4
- package/src/dart_barrel_guard.ts +5 -1
- package/src/describe_model.ts +13 -5
- package/src/directive.ts +244 -5
- package/src/engine_entry.ts +56 -13
- package/src/engine_javascript.ts +31 -0
- package/src/evolve_preview.ts +3 -12
- package/src/framework/application_home.ts +24 -10
- package/src/framework/capability.ts +15 -1
- package/src/framework/domain_lifecycle.ts +8 -0
- package/src/framework/framework_aggregates.ts +2 -2
- package/src/framework/genesis.ts +11 -1
- package/src/framework/home.ts +178 -75
- package/src/framework/impure_capability.ts +24 -7
- package/src/framework/relations.ts +13 -5
- package/src/framework/workspaces.ts +13 -12
- package/src/generated_client_names.ts +42 -3
- package/src/idof_lint.ts +65 -13
- package/src/index.ts +46 -12
- package/src/intent.ts +587 -0
- package/src/law_check.ts +64 -16
- package/src/precondition.ts +16 -4
- package/src/read_projection_namespace.ts +2 -7
- package/src/recipient.ts +33 -0
- package/src/relation_schema.ts +93 -0
- package/src/secondary_ensure.ts +11 -2
- package/src/shape_compat.ts +11 -1
- package/src/usd.ts +30 -8
- package/src/usd_layers.ts +447 -246
- package/src/usd_state.ts +1 -1
- package/src/value_object.ts +49 -13
- package/src/wire_encode.ts +124 -26
- package/src/workspace_sharing.ts +269 -0
- package/src/zod_introspection.ts +84 -0
- package/dart/example/nomos_secure_key_store.dart +0 -82
- package/dart/example/nomos_webview_transport.dart +0 -63
- package/dart/lib/nomos_headless.dart +0 -50
- package/dart/lib/testing.dart +0 -14
- package/dart/test/co2_product_gauntlet_test.dart +0 -244
- package/src/framework/identity.ts +0 -537
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import type { DomainModule } from "./codegen_dart.js";
|
|
2
|
+
import { cap } from "./codegen_dart.js";
|
|
3
|
+
import { dartApplicationClassName } from "./codegen_dart_application.js";
|
|
4
|
+
import { synthesizePayload } from "./codegen_proof.js";
|
|
5
|
+
import { isBusinessIntent } from "./intent.js";
|
|
6
|
+
|
|
7
|
+
const camel = (s: string) => s.replace(/[_\s-]+(\w)/g, (_m, c: string) => c.toUpperCase());
|
|
8
|
+
|
|
9
|
+
export const dartScenarioClassName = (domain: string): string => `${cap(camel(domain))}Scenario`;
|
|
10
|
+
export const dartTestPersonClassName = (domain: string): string => `${cap(camel(domain))}TestPerson`;
|
|
11
|
+
export const dartScenarioGivenClassName = (domain: string): string => `${cap(camel(domain))}ScenarioGiven`;
|
|
12
|
+
|
|
13
|
+
interface DelegatedScenarioPermission {
|
|
14
|
+
readonly method: string;
|
|
15
|
+
readonly domain: string;
|
|
16
|
+
readonly acceptanceIntent: string;
|
|
17
|
+
readonly relation: string;
|
|
18
|
+
readonly subjectField: string;
|
|
19
|
+
readonly certificateField: string;
|
|
20
|
+
readonly sampleFacts: Readonly<Record<string, unknown>>;
|
|
21
|
+
readonly actorFields: readonly string[];
|
|
22
|
+
readonly timeFields: readonly string[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function delegatedScenarioPermissions(modules: readonly DomainModule[]): DelegatedScenarioPermission[] {
|
|
26
|
+
const declarations = modules.flatMap((module) => {
|
|
27
|
+
const domain = module.domain ?? module.name;
|
|
28
|
+
return (module.directives ?? []).map((directive) => ({ domain, directive }));
|
|
29
|
+
});
|
|
30
|
+
const gated = declarations.filter(
|
|
31
|
+
({ directive }) => isBusinessIntent(directive) && directive.requiresRelationGate !== undefined,
|
|
32
|
+
);
|
|
33
|
+
const duplicateIds = new Map<string, number>();
|
|
34
|
+
for (const { directive } of gated) {
|
|
35
|
+
duplicateIds.set(directive.id, (duplicateIds.get(directive.id) ?? 0) + 1);
|
|
36
|
+
}
|
|
37
|
+
const out: DelegatedScenarioPermission[] = [];
|
|
38
|
+
for (const target of gated) {
|
|
39
|
+
const relation = target.directive.requiresRelationGate!.relation;
|
|
40
|
+
const acceptance = declarations.find(
|
|
41
|
+
({ directive }) =>
|
|
42
|
+
isBusinessIntent(directive) &&
|
|
43
|
+
directive.delegatedRoleGate?.relation === relation,
|
|
44
|
+
);
|
|
45
|
+
if (acceptance?.directive.delegatedRoleGate === undefined) continue;
|
|
46
|
+
const gate = acceptance.directive.delegatedRoleGate;
|
|
47
|
+
const synthesized = synthesizePayload(
|
|
48
|
+
acceptance.directive.payloadSchema,
|
|
49
|
+
`scenario permission ${target.domain}/${target.directive.id}`,
|
|
50
|
+
);
|
|
51
|
+
const sample = synthesized.value;
|
|
52
|
+
const sampleFacts: Record<string, unknown> = { ...sample };
|
|
53
|
+
delete sampleFacts[gate.subjectField];
|
|
54
|
+
delete sampleFacts[gate.certField];
|
|
55
|
+
const actorFields = Object.keys(sampleFacts).filter(
|
|
56
|
+
(field) => typeof sampleFacts[field] === "string" && /(?:By|Actor|Principal|Subject)$/.test(field),
|
|
57
|
+
);
|
|
58
|
+
const timeFields = [
|
|
59
|
+
...Object.keys(sampleFacts).filter(
|
|
60
|
+
(field) => typeof sampleFacts[field] === "string" && /(?:At|Time|Timestamp)$/.test(field),
|
|
61
|
+
),
|
|
62
|
+
...synthesized.autoStamped,
|
|
63
|
+
].filter((field, index, fields) => fields.indexOf(field) === index);
|
|
64
|
+
for (const field of [...actorFields, ...timeFields]) delete sampleFacts[field];
|
|
65
|
+
const methodStem = (duplicateIds.get(target.directive.id) ?? 0) > 1
|
|
66
|
+
? `${target.domain}_${target.directive.id}`
|
|
67
|
+
: target.directive.id;
|
|
68
|
+
out.push({
|
|
69
|
+
method: `may${cap(camel(methodStem))}`,
|
|
70
|
+
domain: acceptance.domain,
|
|
71
|
+
acceptanceIntent: acceptance.directive.id,
|
|
72
|
+
relation,
|
|
73
|
+
subjectField: gate.subjectField,
|
|
74
|
+
certificateField: gate.certField,
|
|
75
|
+
sampleFacts,
|
|
76
|
+
actorFields,
|
|
77
|
+
timeFields,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return out.sort((left, right) => left.method.localeCompare(right.method));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Generate the test environment for one application without exporting the underlying harness. */
|
|
84
|
+
export function generateDartScenario(
|
|
85
|
+
mod: DomainModule,
|
|
86
|
+
clientPackage: string,
|
|
87
|
+
testPackage: string,
|
|
88
|
+
deployFile: string,
|
|
89
|
+
options: {
|
|
90
|
+
readonly surfaceName?: string;
|
|
91
|
+
readonly applicationFile?: string;
|
|
92
|
+
readonly applicationModules?: readonly DomainModule[];
|
|
93
|
+
} = {},
|
|
94
|
+
): string {
|
|
95
|
+
const domain = options.surfaceName ?? mod.domain ?? mod.name;
|
|
96
|
+
const application = dartApplicationClassName(domain);
|
|
97
|
+
const scenario = dartScenarioClassName(domain);
|
|
98
|
+
const testPerson = dartTestPersonClassName(domain);
|
|
99
|
+
const scenarioGiven = dartScenarioGivenClassName(domain);
|
|
100
|
+
const permissions = delegatedScenarioPermissions(options.applicationModules ?? [mod]);
|
|
101
|
+
const usesJsonFacts = permissions.some(
|
|
102
|
+
(permission) => Object.keys(permission.sampleFacts).length > 0,
|
|
103
|
+
);
|
|
104
|
+
const bind = `nomosInternalBind${application}`;
|
|
105
|
+
const applicationFile = options.applicationFile ?? `${mod.name}.application`;
|
|
106
|
+
return [
|
|
107
|
+
`// AUTO-GENERATED by nomos-compile — business application test environment.`,
|
|
108
|
+
// This generated package is the deliberate framework-test friend boundary. Tenant production code
|
|
109
|
+
// receives only the business application barrel; scenarios may drive private harness setup underneath it.
|
|
110
|
+
`// ignore_for_file: invalid_use_of_internal_member`,
|
|
111
|
+
...(usesJsonFacts ? [`import 'dart:convert' show jsonDecode;`] : []),
|
|
112
|
+
`import 'dart:io';`,
|
|
113
|
+
``,
|
|
114
|
+
`import 'package:flutter/services.dart' show rootBundle;`,
|
|
115
|
+
`import 'package:flutter/widgets.dart' show WidgetsFlutterBinding;`,
|
|
116
|
+
`import 'package:${clientPackage}/${clientPackage}.dart' show ${application};`,
|
|
117
|
+
`import 'package:${clientPackage}/src/generated/${applicationFile}.dart' show ${bind};`,
|
|
118
|
+
`import 'package:nomos_flutter/src/generated_application_host.dart' show NmsInternalApplicationUserBinding;`,
|
|
119
|
+
`import 'package:nomos_client/src/home.dart' show nmsInternalReceivingGroupNameOf;`,
|
|
120
|
+
`import 'package:nomos_client/src/nms_test_environment.dart' show NomosTestCloud, NomosTestHarness;`,
|
|
121
|
+
``,
|
|
122
|
+
`final class ${testPerson} {`,
|
|
123
|
+
` static final RegExp _idPattern = RegExp(r'^[A-Za-z0-9][A-Za-z0-9._-]*$');`,
|
|
124
|
+
` final String id;`,
|
|
125
|
+
` final String _principal;`,
|
|
126
|
+
``,
|
|
127
|
+
` factory ${testPerson}.named(String id) {`,
|
|
128
|
+
` if (id.isEmpty || !_idPattern.hasMatch(id)) {`,
|
|
129
|
+
` throw ArgumentError.value(id, "id", "must use only letters, numbers, dots, underscores, or hyphens");`,
|
|
130
|
+
` }`,
|
|
131
|
+
` return ${testPerson}._(id: id, principal: "user:\$id");`,
|
|
132
|
+
` }`,
|
|
133
|
+
``,
|
|
134
|
+
` ${testPerson}._({required this.id, required String principal}) : _principal = principal;`,
|
|
135
|
+
`}`,
|
|
136
|
+
``,
|
|
137
|
+
`final class ${scenarioGiven} {`,
|
|
138
|
+
...(permissions.length === 0
|
|
139
|
+
? [` const ${scenarioGiven}._();`]
|
|
140
|
+
: [
|
|
141
|
+
` final ${scenario} _scenario;`,
|
|
142
|
+
` final ${testPerson} _person;`,
|
|
143
|
+
` ${scenarioGiven}._(this._scenario, this._person);`,
|
|
144
|
+
]),
|
|
145
|
+
...permissions.flatMap((permission) => {
|
|
146
|
+
const jsonFacts = JSON.stringify(permission.sampleFacts);
|
|
147
|
+
return [
|
|
148
|
+
``,
|
|
149
|
+
` /// Establish the real business authority needed to perform this action.`,
|
|
150
|
+
` Future<void> ${permission.method}() async {`,
|
|
151
|
+
` await _scenario._environment.nomosInternalAllowDelegatedScenarioAction(`,
|
|
152
|
+
` domain: ${JSON.stringify(permission.domain)},`,
|
|
153
|
+
` acceptanceIntent: ${JSON.stringify(permission.acceptanceIntent)},`,
|
|
154
|
+
` relation: ${JSON.stringify(permission.relation)},`,
|
|
155
|
+
` subject: _person._principal,`,
|
|
156
|
+
` subjectField: ${JSON.stringify(permission.subjectField)},`,
|
|
157
|
+
` certificateField: ${JSON.stringify(permission.certificateField)},`,
|
|
158
|
+
` facts: <String, Object?>{`,
|
|
159
|
+
...(Object.keys(permission.sampleFacts).length === 0
|
|
160
|
+
? []
|
|
161
|
+
: [` ...Map<String, Object?>.from(jsonDecode(${JSON.stringify(jsonFacts)}) as Map),`]),
|
|
162
|
+
...permission.actorFields.map((field) => ` ${JSON.stringify(field)}: _person._principal,`),
|
|
163
|
+
...permission.timeFields.map((field) => ` ${JSON.stringify(field)}: '2000-01-01T00:00:00.000Z',`),
|
|
164
|
+
` },`,
|
|
165
|
+
` );`,
|
|
166
|
+
` }`,
|
|
167
|
+
];
|
|
168
|
+
}),
|
|
169
|
+
`}`,
|
|
170
|
+
``,
|
|
171
|
+
`final class ${scenario} {`,
|
|
172
|
+
` final NomosTestHarness _environment;`,
|
|
173
|
+
` final String _principal;`,
|
|
174
|
+
` final File _packageFile;`,
|
|
175
|
+
` ${application} _application;`,
|
|
176
|
+
` ${scenario}._(this._environment, this._principal, this._packageFile, this._application);`,
|
|
177
|
+
``,
|
|
178
|
+
` /// The exact generated application used by production widgets.`,
|
|
179
|
+
` ${application} get application => _application;`,
|
|
180
|
+
``,
|
|
181
|
+
` /// State a typed business precondition for the person currently using this scenario.`,
|
|
182
|
+
` ${scenarioGiven} given(${testPerson} person) {`,
|
|
183
|
+
` if (person._principal != _principal) {`,
|
|
184
|
+
` throw ArgumentError.value(person.id, 'person', 'start a separate scenario to act as another person');`,
|
|
185
|
+
` }`,
|
|
186
|
+
` return ${permissions.length === 0 ? `const ${scenarioGiven}._()` : `${scenarioGiven}._(this, person)`};`,
|
|
187
|
+
` }`,
|
|
188
|
+
``,
|
|
189
|
+
` /// Start a fresh local application with networking forbidden.`,
|
|
190
|
+
` static Future<${scenario}> offline({`,
|
|
191
|
+
` ${testPerson}? signedInAs,`,
|
|
192
|
+
` void Function(Future<void> Function())? registerTearDown,`,
|
|
193
|
+
` }) async {`,
|
|
194
|
+
` final person = signedInAs ?? ${testPerson}.named('test');`,
|
|
195
|
+
` final packageFile = await _applicationPackage();`,
|
|
196
|
+
` final environment = await NomosTestHarness.ephemeral(`,
|
|
197
|
+
` deployJsonPaths: [packageFile.path],`,
|
|
198
|
+
` ownerSubject: person._principal,`,
|
|
199
|
+
` workspace: nmsInternalReceivingGroupNameOf(person._principal),`,
|
|
200
|
+
` nodeEntry: Platform.environment['NOMOS_NODE_ENTRY'],`,
|
|
201
|
+
` phaseTimeout: const Duration(minutes: 2),`,
|
|
202
|
+
` onDiagnostic: Platform.environment['NOMOS_TEST_DIAGNOSTICS'] == '1' ? stderr.writeln : null,`,
|
|
203
|
+
` );`,
|
|
204
|
+
` final application = await ${bind}(`,
|
|
205
|
+
` environment.session(environment.workspace), NmsInternalApplicationUserBinding.fromActor(person._principal),`,
|
|
206
|
+
` );`,
|
|
207
|
+
` final scenario = ${scenario}._(environment, person._principal, packageFile, application);`,
|
|
208
|
+
` registerTearDown?.call(scenario.close);`,
|
|
209
|
+
` return scenario;`,
|
|
210
|
+
` }`,
|
|
211
|
+
``,
|
|
212
|
+
` /// Start with real remote custody under an isolated local test cloud.`,
|
|
213
|
+
` static Future<${scenario}> connected({`,
|
|
214
|
+
` ${testPerson}? signedInAs,`,
|
|
215
|
+
` void Function(Future<void> Function())? registerTearDown,`,
|
|
216
|
+
` }) async {`,
|
|
217
|
+
` final person = signedInAs ?? ${testPerson}.named('test');`,
|
|
218
|
+
` final serverEntry = Platform.environment['NOMOS_TEST_CLOUD_ENTRY'];`,
|
|
219
|
+
` if (serverEntry == null || serverEntry.isEmpty) {`,
|
|
220
|
+
` throw StateError('Connected scenarios require the Nomos test cloud. Run this test through githolon test.');`,
|
|
221
|
+
` }`,
|
|
222
|
+
` final applicationName = 'test-application-\${DateTime.now().microsecondsSinceEpoch}';`,
|
|
223
|
+
` final packageFile = await _applicationPackage();`,
|
|
224
|
+
` final cloud = await NomosTestCloud.start(`,
|
|
225
|
+
` serverEntry: serverEntry, platform: applicationName, deployJsonPath: packageFile.path,`,
|
|
226
|
+
` );`,
|
|
227
|
+
` late final NomosTestHarness environment;`,
|
|
228
|
+
` try {`,
|
|
229
|
+
` environment = await NomosTestHarness.cloudBound(`,
|
|
230
|
+
` cloud: cloud, deployJsonPaths: [packageFile.path],`,
|
|
231
|
+
` platformWorkspace: applicationName, subject: person._principal,`,
|
|
232
|
+
` nodeEntry: Platform.environment['NOMOS_NODE_ENTRY'],`,
|
|
233
|
+
` phaseTimeout: const Duration(minutes: 2),`,
|
|
234
|
+
` );`,
|
|
235
|
+
` } catch (_) {`,
|
|
236
|
+
` await cloud.stop();`,
|
|
237
|
+
` rethrow;`,
|
|
238
|
+
` }`,
|
|
239
|
+
` final application = await ${bind}(environment.homeSession, NmsInternalApplicationUserBinding.fromActor(person._principal));`,
|
|
240
|
+
` final scenario = ${scenario}._(environment, person._principal, packageFile, application);`,
|
|
241
|
+
` registerTearDown?.call(scenario.close);`,
|
|
242
|
+
` return scenario;`,
|
|
243
|
+
` }`,
|
|
244
|
+
``,
|
|
245
|
+
` /// Simulate losing connectivity. Local business actions remain available.`,
|
|
246
|
+
` void disconnect() => _environment.testCloud?.setOnline(false);`,
|
|
247
|
+
``,
|
|
248
|
+
` /// Restore connectivity and wait until hidden custody exchange has settled.`,
|
|
249
|
+
` Future<void> reconnect() async {`,
|
|
250
|
+
` final cloud = _environment.testCloud;`,
|
|
251
|
+
` if (cloud == null) return;`,
|
|
252
|
+
` cloud.setOnline(true);`,
|
|
253
|
+
` await _environment.converge();`,
|
|
254
|
+
` }`,
|
|
255
|
+
``,
|
|
256
|
+
` /// Kill and reopen the application from durable local state, then return the same business API.`,
|
|
257
|
+
` Future<${application}> restart() async {`,
|
|
258
|
+
` await _environment.restart();`,
|
|
259
|
+
` _application = await ${bind}(_environment.homeSession, NmsInternalApplicationUserBinding.fromActor(_principal));`,
|
|
260
|
+
` return _application;`,
|
|
261
|
+
` }`,
|
|
262
|
+
``,
|
|
263
|
+
` /// Remove local custody and reinstall entirely from remote history.`,
|
|
264
|
+
` Future<${application}> reinstall() async {`,
|
|
265
|
+
` await _environment.reinstall();`,
|
|
266
|
+
` _application = await ${bind}(_environment.homeSession, NmsInternalApplicationUserBinding.fromActor(_principal));`,
|
|
267
|
+
` return _application;`,
|
|
268
|
+
` }`,
|
|
269
|
+
``,
|
|
270
|
+
` Future<void> close() async {`,
|
|
271
|
+
` await _environment.dispose();`,
|
|
272
|
+
` await _environment.testCloud?.stop();`,
|
|
273
|
+
` try { await _packageFile.parent.delete(recursive: true); } catch (_) {}`,
|
|
274
|
+
` }`,
|
|
275
|
+
``,
|
|
276
|
+
` static Future<File> _applicationPackage() async {`,
|
|
277
|
+
` WidgetsFlutterBinding.ensureInitialized();`,
|
|
278
|
+
` final assetKeys = [`,
|
|
279
|
+
` 'packages/${testPackage}/lib/src/generated/${deployFile}',`,
|
|
280
|
+
` 'lib/src/generated/${deployFile}',`,
|
|
281
|
+
` ];`,
|
|
282
|
+
` Object? lastError;`,
|
|
283
|
+
` for (final key in assetKeys) {`,
|
|
284
|
+
` try {`,
|
|
285
|
+
` final data = await rootBundle.load(key);`,
|
|
286
|
+
` final dir = await Directory.systemTemp.createTemp('nms-application-package-');`,
|
|
287
|
+
` final file = File('\${dir.path}\${Platform.pathSeparator}${deployFile}');`,
|
|
288
|
+
` await file.writeAsBytes(data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes), flush: true);`,
|
|
289
|
+
` return file;`,
|
|
290
|
+
` } catch (error) {`,
|
|
291
|
+
` lastError = error;`,
|
|
292
|
+
` }`,
|
|
293
|
+
` }`,
|
|
294
|
+
` throw StateError('The generated application package asset is missing: \$lastError');`,
|
|
295
|
+
` }`,
|
|
296
|
+
`}`,
|
|
297
|
+
``,
|
|
298
|
+
].join("\n");
|
|
299
|
+
}
|
package/src/codegen_openapi.ts
CHANGED
|
@@ -40,6 +40,7 @@ import { finishSum } from "./sum.js";
|
|
|
40
40
|
import { finishExtremum } from "./extremum.js";
|
|
41
41
|
import { nullDocLine } from "./null_meta.js";
|
|
42
42
|
import type { Field } from "./fields.js";
|
|
43
|
+
import { isBusinessIntent } from "./intent.js";
|
|
43
44
|
|
|
44
45
|
type AnyDirective = Directive<unknown>;
|
|
45
46
|
|
|
@@ -128,7 +129,7 @@ function generateOpenApiSurface(
|
|
|
128
129
|
const businessBirth = (dir as {
|
|
129
130
|
businessBirth?: { aggregateId: string; publicPayloadSchema: unknown };
|
|
130
131
|
}).businessBirth;
|
|
131
|
-
const publicId = businessBirth === undefined ? dir.id : "birth";
|
|
132
|
+
const publicId = businessBirth === undefined || isBusinessIntent(dir) ? dir.id : "birth";
|
|
132
133
|
const publicDir = businessBirth === undefined
|
|
133
134
|
? dir
|
|
134
135
|
: { ...dir, payloadSchema: businessBirth.publicPayloadSchema } as AnyDirective;
|
|
@@ -266,10 +267,12 @@ export function generateOpenApiFragment(
|
|
|
266
267
|
const isInternal = (value: unknown) =>
|
|
267
268
|
!!value && typeof value === "object"
|
|
268
269
|
&& (value as { businessBirthInternal?: unknown }).businessBirthInternal === true;
|
|
270
|
+
const publicDirectiveIds = new Set(module.applicationIntents?.map((intent) => intent.id) ?? []);
|
|
271
|
+
const publicDirectives = module.applicationIntents ?? module.directives;
|
|
269
272
|
const publicModule: DomainModule = {
|
|
270
273
|
...module,
|
|
271
274
|
aggregates: module.aggregates.filter((a) => !isInternal(a)),
|
|
272
|
-
directives:
|
|
275
|
+
directives: publicDirectives.filter((d) => !isInternal(d)),
|
|
273
276
|
...(module.queries === undefined ? {} : { queries: module.queries.filter((q) => !isInternal(q)) }),
|
|
274
277
|
};
|
|
275
278
|
const publicDoc = generateOpenApiSurface(publicModule, opts);
|
|
@@ -286,7 +289,9 @@ export function generateOpenApiFragment(
|
|
|
286
289
|
const frameworkModule: DomainModule = {
|
|
287
290
|
...module,
|
|
288
291
|
aggregates: module.aggregates.filter(isInternal),
|
|
289
|
-
directives: module.directives.filter(
|
|
292
|
+
directives: module.directives.filter((directive) =>
|
|
293
|
+
isInternal(directive) ||
|
|
294
|
+
(module.applicationIntents !== undefined && !publicDirectiveIds.has(directive.id))),
|
|
290
295
|
...(module.queries === undefined ? {} : { queries: module.queries.filter(isInternal) }),
|
|
291
296
|
// Framework routing is for declared internal offers/queries. Business maintained reads remain
|
|
292
297
|
// on the public module; do not duplicate them into the private table.
|