@zmdb/app 1.0.0-beta.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.
Files changed (108) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +27 -0
  3. package/dist/application.d.ts +45 -0
  4. package/dist/application.d.ts.map +1 -0
  5. package/dist/application.js +186 -0
  6. package/dist/application.js.map +1 -0
  7. package/dist/commands/index.d.ts +31 -0
  8. package/dist/commands/index.d.ts.map +1 -0
  9. package/dist/commands/index.js +338 -0
  10. package/dist/commands/index.js.map +1 -0
  11. package/dist/cqrs/index.d.ts +47 -0
  12. package/dist/cqrs/index.d.ts.map +1 -0
  13. package/dist/cqrs/index.js +76 -0
  14. package/dist/cqrs/index.js.map +1 -0
  15. package/dist/data/index.d.ts +12 -0
  16. package/dist/data/index.d.ts.map +1 -0
  17. package/dist/data/index.js +25 -0
  18. package/dist/data/index.js.map +1 -0
  19. package/dist/di/index.d.ts +44 -0
  20. package/dist/di/index.d.ts.map +1 -0
  21. package/dist/di/index.js +136 -0
  22. package/dist/di/index.js.map +1 -0
  23. package/dist/events/index.d.ts +46 -0
  24. package/dist/events/index.d.ts.map +1 -0
  25. package/dist/events/index.js +181 -0
  26. package/dist/events/index.js.map +1 -0
  27. package/dist/health/index.d.ts +35 -0
  28. package/dist/health/index.d.ts.map +1 -0
  29. package/dist/health/index.js +14 -0
  30. package/dist/health/index.js.map +1 -0
  31. package/dist/index.d.ts +11 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +22 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/lifecycle.d.ts +21 -0
  36. package/dist/lifecycle.d.ts.map +1 -0
  37. package/dist/lifecycle.js +52 -0
  38. package/dist/lifecycle.js.map +1 -0
  39. package/dist/messaging/index.d.ts +176 -0
  40. package/dist/messaging/index.d.ts.map +1 -0
  41. package/dist/messaging/index.js +442 -0
  42. package/dist/messaging/index.js.map +1 -0
  43. package/dist/messaging/transport-kit.d.ts +22 -0
  44. package/dist/messaging/transport-kit.d.ts.map +1 -0
  45. package/dist/messaging/transport-kit.js +174 -0
  46. package/dist/messaging/transport-kit.js.map +1 -0
  47. package/dist/modules/index.d.ts +52 -0
  48. package/dist/modules/index.d.ts.map +1 -0
  49. package/dist/modules/index.js +351 -0
  50. package/dist/modules/index.js.map +1 -0
  51. package/dist/modules/lifecycle-instances.d.ts +4 -0
  52. package/dist/modules/lifecycle-instances.d.ts.map +1 -0
  53. package/dist/modules/lifecycle-instances.js +19 -0
  54. package/dist/modules/lifecycle-instances.js.map +1 -0
  55. package/dist/modules/runtime.d.ts +18 -0
  56. package/dist/modules/runtime.d.ts.map +1 -0
  57. package/dist/modules/runtime.js +8 -0
  58. package/dist/modules/runtime.js.map +1 -0
  59. package/dist/observability/index.d.ts +27 -0
  60. package/dist/observability/index.d.ts.map +1 -0
  61. package/dist/observability/index.js +145 -0
  62. package/dist/observability/index.js.map +1 -0
  63. package/dist/observability/propagation.d.ts +10 -0
  64. package/dist/observability/propagation.d.ts.map +1 -0
  65. package/dist/observability/propagation.js +96 -0
  66. package/dist/observability/propagation.js.map +1 -0
  67. package/dist/observability/types.d.ts +66 -0
  68. package/dist/observability/types.d.ts.map +1 -0
  69. package/dist/observability/types.js +10 -0
  70. package/dist/observability/types.js.map +1 -0
  71. package/dist/polyfill.d.ts +2 -0
  72. package/dist/polyfill.d.ts.map +1 -0
  73. package/dist/polyfill.js +21 -0
  74. package/dist/polyfill.js.map +1 -0
  75. package/dist/state/fixtures.d.ts +10 -0
  76. package/dist/state/fixtures.d.ts.map +1 -0
  77. package/dist/state/fixtures.js +14 -0
  78. package/dist/state/fixtures.js.map +1 -0
  79. package/dist/state/index.d.ts +26 -0
  80. package/dist/state/index.d.ts.map +1 -0
  81. package/dist/state/index.js +37 -0
  82. package/dist/state/index.js.map +1 -0
  83. package/dist/state-transitions.d.ts +57 -0
  84. package/dist/state-transitions.d.ts.map +1 -0
  85. package/dist/state-transitions.js +31 -0
  86. package/dist/state-transitions.js.map +1 -0
  87. package/package.json +99 -0
  88. package/src/application.ts +243 -0
  89. package/src/commands/index.ts +447 -0
  90. package/src/cqrs/index.ts +124 -0
  91. package/src/data/index.ts +33 -0
  92. package/src/di/index.ts +179 -0
  93. package/src/events/index.ts +248 -0
  94. package/src/health/index.ts +54 -0
  95. package/src/index.ts +68 -0
  96. package/src/lifecycle.ts +62 -0
  97. package/src/messaging/index.ts +668 -0
  98. package/src/messaging/transport-kit.ts +234 -0
  99. package/src/modules/index.ts +476 -0
  100. package/src/modules/lifecycle-instances.ts +23 -0
  101. package/src/modules/runtime.ts +26 -0
  102. package/src/observability/index.ts +204 -0
  103. package/src/observability/propagation.ts +107 -0
  104. package/src/observability/types.ts +68 -0
  105. package/src/polyfill.ts +24 -0
  106. package/src/state/fixtures.ts +20 -0
  107. package/src/state/index.ts +57 -0
  108. package/src/state-transitions.ts +150 -0
package/README.md ADDED
@@ -0,0 +1,27 @@
1
+ # @zmdb/app
2
+
3
+ The protocol-neutral application kernel for zmdb. It owns Stage-3 metadata, dependency injection, modules, lifecycle, transport-neutral messaging, command applications, events, CQRS, state machines,
4
+ health contracts, and dependency-free observability ports.
5
+
6
+ For the cohesive server product, install `zmdb` and use `zmdb/app` for application concerns. The [server journey](https://ambasta.github.io/zmdb/docs/web-overview.html) combines HTTP and selected jobs
7
+ under this lifecycle.
8
+
9
+ ## Advanced: install the kernel alone
10
+
11
+ ```bash
12
+ yarn add @zmdb/app@1.0.0-beta.1
13
+ ```
14
+
15
+ The package is ESM-only and requires Node.js 26 or later.
16
+
17
+ ## Entry points
18
+
19
+ `@zmdb/app`, `@zmdb/app/commands`, `@zmdb/app/cqrs`, `@zmdb/app/data`, `@zmdb/app/di`, `@zmdb/app/events`, `@zmdb/app/health`, `@zmdb/app/lifecycle`, `@zmdb/app/messaging`, `@zmdb/app/modules`,
20
+ `@zmdb/app/observability`, and `@zmdb/app/state`.
21
+
22
+ HTTP adapters live in `@zmdb/web`. Queues and scheduling live in `@zmdb/jobs`; concrete broker integrations remain separately installed and implement the public `@zmdb/app/messaging` strategy
23
+ contract.
24
+
25
+ ## License
26
+
27
+ GNU General Public License v3.0 or later (GPL-3.0-or-later).
@@ -0,0 +1,45 @@
1
+ import type { Container } from './di/index.js';
2
+ import { type CompiledModule, type LazyModuleHandle, type ModuleClass } from './modules/index.js';
3
+ import { type CompiledController } from './modules/runtime.js';
4
+ import type { Observability } from './observability/types.js';
5
+ /** Private cross-package bridge used by HTTP/testing adapters without widening Application. */
6
+ export interface ApplicationBridge {
7
+ readonly compiled: CompiledModule;
8
+ readonly controllers: readonly CompiledController[];
9
+ }
10
+ export interface ApplicationExtensionContext {
11
+ readonly container: Container;
12
+ readonly controllers: readonly object[];
13
+ readonly commands: readonly object[];
14
+ readonly observability: Observability;
15
+ }
16
+ export interface ApplicationExtension {
17
+ readonly name: string;
18
+ start(context: ApplicationExtensionContext): void | Promise<void>;
19
+ stop(options: {
20
+ readonly graceMs: number;
21
+ }): void | Promise<void>;
22
+ }
23
+ export interface ApplicationOptions {
24
+ readonly extensions?: readonly ApplicationExtension[];
25
+ readonly observability?: Observability;
26
+ readonly graceMs?: number;
27
+ }
28
+ export interface Application extends AsyncDisposable {
29
+ readonly container: Container;
30
+ readonly lazy: readonly LazyModuleHandle[];
31
+ init(): Promise<void>;
32
+ }
33
+ /**
34
+ * Compile one protocol-neutral application and own its complete lifecycle.
35
+ *
36
+ * The two symbol-keyed bridge properties are non-enumerable implementation
37
+ * details. They let the HTTP and testing adapters consume the same compiled
38
+ * identities without adding public Application fields or compiling twice.
39
+ */
40
+ export declare function createApplication(rootModule: ModuleClass, options?: ApplicationOptions): Application;
41
+ /** Package-private controller declarations for extensions with eager-only rules. */
42
+ export declare function applicationExtensionControllersOf(context: ApplicationExtensionContext): readonly CompiledController[];
43
+ /** Package-private access for command applications. */
44
+ export declare function applicationBridgeOf(application: Application): ApplicationBridge;
45
+ //# sourceMappingURL=application.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"application.d.ts","sourceRoot":"","sources":["../src/application.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE/C,OAAO,EAAiB,KAAK,cAAc,EAAE,KAAK,gBAAgB,EAAE,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjH,OAAO,EAAa,KAAK,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAY9D,+FAA+F;AAC/F,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,QAAQ,CAAC,WAAW,EAAE,SAAS,kBAAkB,EAAE,CAAC;CACrD;AAcD,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;CACvC;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,OAAO,EAAE,2BAA2B,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE,IAAI,CAAC,OAAO,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnE;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,oBAAoB,EAAE,CAAC;IACtD,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;IACvC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,WAAY,SAAQ,eAAe;IAClD,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC3C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,WAAW,EAAE,OAAO,GAAE,kBAAuB,GAAG,WAAW,CAsHxG;AAED,oFAAoF;AACpF,wBAAgB,iCAAiC,CAAC,OAAO,EAAE,2BAA2B,GAAG,SAAS,kBAAkB,EAAE,CAMrH;AAED,uDAAuD;AACvD,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,WAAW,GAAG,iBAAiB,CAO/E"}
@@ -0,0 +1,186 @@
1
+ import { runInit, runShutdown } from './lifecycle.js';
2
+ import { compileModule } from './modules/index.js';
3
+ import { lifecycleInstances } from './modules/lifecycle-instances.js';
4
+ import { runtimeOf } from './modules/runtime.js';
5
+ const APPLICATION_BRIDGE = Symbol.for('@zmdb/app.application-bridge');
6
+ const APPLICATION_EXTENSION_BRIDGE = Symbol('@zmdb/app.application-extension-bridge');
7
+ const COMPILED_APPLICATION = Symbol.for('@zmdb/app.compiled-application');
8
+ const DEFAULT_GRACE_MS = 5_000;
9
+ const EMPTY_OBSERVABILITY = Object.freeze({});
10
+ /**
11
+ * Compile one protocol-neutral application and own its complete lifecycle.
12
+ *
13
+ * The two symbol-keyed bridge properties are non-enumerable implementation
14
+ * details. They let the HTTP and testing adapters consume the same compiled
15
+ * identities without adding public Application fields or compiling twice.
16
+ */
17
+ export function createApplication(rootModule, options = {}) {
18
+ const graceMs = applicationGrace(options.graceMs ?? DEFAULT_GRACE_MS);
19
+ const extensions = validatedExtensions(options.extensions ?? []);
20
+ const compiled = compiledApplication(options) ?? compileModule(rootModule);
21
+ const runtime = runtimeOf(compiled);
22
+ const instances = lifecycleInstances(compiled.container);
23
+ const observability = options.observability ?? EMPTY_OBSERVABILITY;
24
+ const controllerBindings = Object.freeze([
25
+ ...(runtime?.controllers ?? compiled.controllers.map(controller => ({ kind: 'eager', controller }))),
26
+ ]);
27
+ let context;
28
+ if (extensions.length > 0) {
29
+ const extensionContext = {
30
+ container: compiled.container,
31
+ controllers: Object.freeze([...compiled.controllers]),
32
+ commands: Object.freeze([...compiled.commands]),
33
+ observability,
34
+ };
35
+ Object.defineProperty(extensionContext, APPLICATION_EXTENSION_BRIDGE, {
36
+ value: Object.freeze({ controllers: controllerBindings }),
37
+ });
38
+ context = Object.freeze(extensionContext);
39
+ }
40
+ const entered = [];
41
+ let state = 'created';
42
+ let initPromise;
43
+ let disposePromise;
44
+ const stopEntered = async () => {
45
+ const errors = [];
46
+ const deadline = Date.now() + graceMs;
47
+ for (let index = entered.length - 1; index >= 0; index -= 1) {
48
+ const extension = entered[index];
49
+ if (extension === undefined)
50
+ continue;
51
+ try {
52
+ await extension.stop({ graceMs: Math.max(0, deadline - Date.now()) });
53
+ }
54
+ catch (error) {
55
+ errors.push(error);
56
+ }
57
+ }
58
+ entered.length = 0;
59
+ return errors;
60
+ };
61
+ const shutdownInstances = async () => {
62
+ try {
63
+ await runShutdown(instances);
64
+ return [];
65
+ }
66
+ catch (error) {
67
+ return errorsOf(error);
68
+ }
69
+ };
70
+ const start = async () => {
71
+ state = 'starting';
72
+ try {
73
+ await runInit(instances);
74
+ if (context !== undefined) {
75
+ for (const extension of extensions) {
76
+ entered.push(extension);
77
+ await extension.start(context);
78
+ }
79
+ }
80
+ state = 'running';
81
+ }
82
+ catch (startupError) {
83
+ runtime?.beginShutdown();
84
+ await runtime?.waitForLoads();
85
+ const cleanupErrors = [...(await stopEntered()), ...(await shutdownInstances())];
86
+ state = 'failed-cleaned';
87
+ if (cleanupErrors.length === 0)
88
+ throw startupError;
89
+ throw new AggregateError([startupError, ...cleanupErrors], '@zmdb/app: application startup cleanup failed', {
90
+ cause: startupError,
91
+ });
92
+ }
93
+ };
94
+ const dispose = async () => {
95
+ runtime?.beginShutdown();
96
+ if (initPromise !== undefined) {
97
+ try {
98
+ await initPromise;
99
+ }
100
+ catch {
101
+ // Failed initialization performed its own complete cleanup transaction.
102
+ return;
103
+ }
104
+ }
105
+ if (state === 'failed-cleaned' || state === 'stopped')
106
+ return;
107
+ state = 'stopping';
108
+ await runtime?.waitForLoads();
109
+ const errors = [...(await stopEntered()), ...(await shutdownInstances())];
110
+ state = 'stopped';
111
+ throwObserved(errors, '@zmdb/app: application shutdown failed');
112
+ };
113
+ const application = {
114
+ container: compiled.container,
115
+ lazy: compiled.lazy,
116
+ init: () => {
117
+ if (disposePromise !== undefined) {
118
+ return Promise.reject(new Error('@zmdb/app: application is shutting down'));
119
+ }
120
+ initPromise ??= start();
121
+ return initPromise;
122
+ },
123
+ [Symbol.asyncDispose]: () => {
124
+ disposePromise ??= dispose();
125
+ return disposePromise;
126
+ },
127
+ };
128
+ Object.defineProperty(application, APPLICATION_BRIDGE, {
129
+ value: Object.freeze({
130
+ compiled,
131
+ controllers: controllerBindings,
132
+ }),
133
+ });
134
+ return application;
135
+ }
136
+ /** Package-private controller declarations for extensions with eager-only rules. */
137
+ export function applicationExtensionControllersOf(context) {
138
+ const carrier = context;
139
+ return (carrier[APPLICATION_EXTENSION_BRIDGE]?.controllers ??
140
+ Object.freeze(context.controllers.map(controller => ({ kind: 'eager', controller }))));
141
+ }
142
+ /** Package-private access for command applications. */
143
+ export function applicationBridgeOf(application) {
144
+ const carrier = application;
145
+ const bridge = carrier[APPLICATION_BRIDGE];
146
+ if (bridge === undefined) {
147
+ throw new Error('@zmdb/app: application omitted its compiled graph');
148
+ }
149
+ return bridge;
150
+ }
151
+ function compiledApplication(options) {
152
+ const carrier = options;
153
+ return carrier[COMPILED_APPLICATION];
154
+ }
155
+ function applicationGrace(value) {
156
+ if (!Number.isInteger(value) || value <= 0) {
157
+ throw new RangeError('@zmdb/app: graceMs must be a positive integer');
158
+ }
159
+ return value;
160
+ }
161
+ function validatedExtensions(values) {
162
+ const names = new Set();
163
+ const extensions = [...values];
164
+ for (const extension of extensions) {
165
+ if (extension.name.length === 0) {
166
+ throw new RangeError('@zmdb/app: an extension name cannot be empty');
167
+ }
168
+ if (names.has(extension.name)) {
169
+ throw new Error(`@zmdb/app: duplicate extension name "${extension.name}"`);
170
+ }
171
+ names.add(extension.name);
172
+ }
173
+ return Object.freeze(extensions);
174
+ }
175
+ function errorsOf(error) {
176
+ return error instanceof AggregateError ? [...error.errors] : [error];
177
+ }
178
+ function throwObserved(errors, message) {
179
+ if (errors.length === 0)
180
+ return;
181
+ const first = errors[0];
182
+ if (errors.length === 1 && first !== undefined)
183
+ throw first;
184
+ throw new AggregateError(errors, message);
185
+ }
186
+ //# sourceMappingURL=application.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"application.js","sourceRoot":"","sources":["../src/application.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import { type JsonSchemaObject } from '@zmdb/schema/ir';
2
+ import type { Container } from '../di/index.js';
3
+ import type { ModuleClass } from '../modules/index.js';
4
+ import '../polyfill.js';
5
+ /** Declaration recorded by `@Command`. */
6
+ export interface CommandDef<A> {
7
+ readonly name: string;
8
+ readonly description: string;
9
+ readonly args?: JsonSchemaObject;
10
+ readonly validate?: (raw: unknown) => A;
11
+ readonly positionals?: readonly string[];
12
+ }
13
+ /** A zero-argument command class; dependencies arrive through `@Inject` fields. */
14
+ export type CommandClass<A> = abstract new () => {
15
+ run(args: A): unknown;
16
+ };
17
+ /** A module graph driven from argv rather than an HTTP request. */
18
+ export interface CommandApp extends AsyncDisposable {
19
+ readonly container: Container;
20
+ run(argv?: readonly string[]): Promise<number>;
21
+ init(): Promise<void>;
22
+ }
23
+ /** Stage-3 class decorator: record one command declaration in `context.metadata`. */
24
+ export declare function Command<A>(def: CommandDef<A>): <T extends CommandClass<A>>(target: T, context: ClassDecoratorContext<T>) => void;
25
+ /**
26
+ * Compile a root module through `createApplication`, then dispatch its declared
27
+ * commands. The returned app never calls `process.exit`; callers own
28
+ * `process.exitCode`.
29
+ */
30
+ export declare function createCommandApp(rootModule: ModuleClass): CommandApp;
31
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/commands/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAIxD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,gBAAgB,CAAC;AAExB,0CAA0C;AAC1C,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACjC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,CAAC,CAAC;IACxC,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1C;AAED,mFAAmF;AACnF,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,QAAQ,WAAW;IAAE,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,OAAO,CAAA;CAAE,CAAC;AAE3E,mEAAmE;AACnE,MAAM,WAAW,UAAW,SAAQ,eAAe;IACjD,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,GAAG,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAyCD,qFAAqF;AACrF,wBAAgB,OAAO,CAAC,CAAC,EACvB,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,GACjB,CAAC,CAAC,SAAS,YAAY,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,qBAAqB,CAAC,CAAC,CAAC,KAAK,IAAI,CAInF;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,WAAW,GAAG,UAAU,CAUpE"}
@@ -0,0 +1,338 @@
1
+ // @zmdb/app — command applications (epic #497, spec ./SPEC.md).
2
+ // Commands use the same module compilation, container and lifecycle as
3
+ // createApplication, with argv validation at the terminal boundary and no
4
+ // runtime reflection or HTTP router.
5
+ import {} from '@zmdb/schema/ir';
6
+ import { coerce } from '@zmdb/validator/advanced';
7
+ import { applicationBridgeOf, createApplication } from '../application.js';
8
+ import '../polyfill.js';
9
+ const COMMAND = Symbol('zmdb.app.command');
10
+ function commandView(metadata) {
11
+ return metadata;
12
+ }
13
+ /** Stage-3 class decorator: record one command declaration in `context.metadata`. */
14
+ export function Command(def) {
15
+ return function (_target, context) {
16
+ commandView(context.metadata)[COMMAND] = def;
17
+ };
18
+ }
19
+ /**
20
+ * Compile a root module through `createApplication`, then dispatch its declared
21
+ * commands. The returned app never calls `process.exit`; callers own
22
+ * `process.exitCode`.
23
+ */
24
+ export function createCommandApp(rootModule) {
25
+ const application = createApplication(rootModule);
26
+ const commands = registerCommands(applicationBridgeOf(application).compiled.commands);
27
+ return {
28
+ container: application.container,
29
+ init: application.init,
30
+ run: (argv = process.argv.slice(2)) => runCommand(commands, argv),
31
+ [Symbol.asyncDispose]: application[Symbol.asyncDispose],
32
+ };
33
+ }
34
+ function registerCommands(instances) {
35
+ const commands = [];
36
+ const names = new Set();
37
+ for (const instance of instances) {
38
+ const definition = commandDefinitionOf(instance);
39
+ if (definition === undefined) {
40
+ throw new Error(`@zmdb/app: ${instance.constructor.name} is listed in commands but has no @Command declaration`);
41
+ }
42
+ if (!isRunnableCommand(instance)) {
43
+ throw new Error(`@zmdb/app: command "${definition.name}" has no run(args) method`);
44
+ }
45
+ if (definition.name.length === 0) {
46
+ throw new Error('@zmdb/app: a command name cannot be empty');
47
+ }
48
+ if (names.has(definition.name)) {
49
+ throw new Error(`@zmdb/app: duplicate command name "${definition.name}"`);
50
+ }
51
+ names.add(definition.name);
52
+ commands.push(prepareCommand(instance, definition));
53
+ }
54
+ return commands;
55
+ }
56
+ function commandDefinitionOf(instance) {
57
+ const carrier = instance.constructor;
58
+ const metadata = carrier[Symbol.metadata];
59
+ if (metadata === undefined || metadata === null) {
60
+ return undefined;
61
+ }
62
+ return commandView(metadata)[COMMAND];
63
+ }
64
+ function isRunnableCommand(instance) {
65
+ return 'run' in instance && typeof instance.run === 'function';
66
+ }
67
+ function prepareCommand(instance, definition) {
68
+ const positionals = [...(definition.positionals ?? [])];
69
+ const properties = definition.args === undefined
70
+ ? []
71
+ : Object.entries(definition.args.properties)
72
+ .map(([name, schema]) => argumentProperty(definition.name, name, schema))
73
+ .toSorted((left, right) => left.option.localeCompare(right.option));
74
+ const propertyByName = new Map(properties.map(property => [property.name, property]));
75
+ const seenPositionals = new Set();
76
+ for (const positional of positionals) {
77
+ if (seenPositionals.has(positional)) {
78
+ throw new Error(`@zmdb/app: command "${definition.name}" repeats positional "${positional}"`);
79
+ }
80
+ seenPositionals.add(positional);
81
+ if (definition.args !== undefined && !propertyByName.has(positional)) {
82
+ throw new Error(`@zmdb/app: command "${definition.name}" names positional "${positional}" but its args type has no such property`);
83
+ }
84
+ }
85
+ const optionNames = new Set();
86
+ for (const property of properties) {
87
+ if (optionNames.has(property.option)) {
88
+ throw new Error(`@zmdb/app: command "${definition.name}" maps more than one property to --${property.option}`);
89
+ }
90
+ optionNames.add(property.option);
91
+ }
92
+ return { definition, instance, properties, propertyByName, positionals };
93
+ }
94
+ function argumentProperty(command, name, schema) {
95
+ const types = schemaTypes(schema);
96
+ if (types.includes('object')) {
97
+ throw nestedArgument(command, name);
98
+ }
99
+ if (types.includes('array')) {
100
+ const items = schemaKeyword(schema, 'items');
101
+ const itemTypes = schemaTypes(items);
102
+ if (itemTypes.length === 0 || itemTypes.includes('object') || itemTypes.includes('array')) {
103
+ throw nestedArgument(command, name);
104
+ }
105
+ const itemType = scalarType(itemTypes);
106
+ return {
107
+ name,
108
+ option: kebabCase(name),
109
+ parseType: itemType === 'boolean' ? 'boolean' : 'string',
110
+ multiple: true,
111
+ numeric: itemType === 'number' || itemType === 'integer',
112
+ };
113
+ }
114
+ if (schemaKeyword(schema, 'properties') !== undefined || schemaKeyword(schema, '$ref') !== undefined) {
115
+ throw nestedArgument(command, name);
116
+ }
117
+ const type = scalarType(types);
118
+ return {
119
+ name,
120
+ option: kebabCase(name),
121
+ parseType: type === 'boolean' ? 'boolean' : 'string',
122
+ multiple: false,
123
+ numeric: type === 'number' || type === 'integer',
124
+ };
125
+ }
126
+ function nestedArgument(command, property) {
127
+ return new Error(`@zmdb/app: command "${command}" argument "${property}" has a nested shape; argv must be flat`);
128
+ }
129
+ function schemaTypes(schema) {
130
+ const type = schemaKeyword(schema, 'type');
131
+ if (typeof type === 'string') {
132
+ return [type];
133
+ }
134
+ if (Array.isArray(type)) {
135
+ return type.filter(value => typeof value === 'string' && value !== 'null');
136
+ }
137
+ return [];
138
+ }
139
+ function schemaKeyword(schema, keyword) {
140
+ return typeof schema === 'object' && schema !== null ? Reflect.get(schema, keyword) : undefined;
141
+ }
142
+ function scalarType(types) {
143
+ return types.find(type => type !== 'null');
144
+ }
145
+ function kebabCase(name) {
146
+ return name.replaceAll(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
147
+ }
148
+ async function runCommand(commands, argv) {
149
+ const selected = selectCommand(commands, argv);
150
+ if (selected.kind === 'list') {
151
+ process.stdout.write(commandList(commands));
152
+ return 0;
153
+ }
154
+ if (selected.kind === 'unknown') {
155
+ process.stderr.write(`unknown command "${selected.name}"\n\n${commandList(commands)}`);
156
+ return 2;
157
+ }
158
+ if (selected.help) {
159
+ process.stdout.write(commandHelp(selected.command));
160
+ return 0;
161
+ }
162
+ let args;
163
+ try {
164
+ args = commandArguments(selected.command, selected.argv);
165
+ }
166
+ catch (error) {
167
+ process.stderr.write(`${lowerFirst(errorMessage(error))}\n\n${commandHelp(selected.command)}`);
168
+ return 2;
169
+ }
170
+ try {
171
+ const result = await selected.command.instance.run(args);
172
+ return exitCode(result);
173
+ }
174
+ catch (error) {
175
+ process.stderr.write(`${errorMessage(error)}\n`);
176
+ return 1;
177
+ }
178
+ }
179
+ function selectCommand(commands, argv) {
180
+ if (commands.length === 1) {
181
+ const command = commands[0];
182
+ if (command === undefined) {
183
+ return { kind: 'list' };
184
+ }
185
+ const named = argv[0] === command.definition.name;
186
+ const commandArgv = named ? argv.slice(1) : argv;
187
+ return {
188
+ kind: 'command',
189
+ command,
190
+ argv: commandArgv,
191
+ help: requestsHelp(commandArgv),
192
+ };
193
+ }
194
+ const name = argv[0];
195
+ if (name === undefined || name === '--help') {
196
+ return { kind: 'list' };
197
+ }
198
+ const command = commands.find(candidate => candidate.definition.name === name);
199
+ if (command === undefined) {
200
+ return { kind: 'unknown', name };
201
+ }
202
+ const commandArgv = argv.slice(1);
203
+ return {
204
+ kind: 'command',
205
+ command,
206
+ argv: commandArgv,
207
+ help: requestsHelp(commandArgv),
208
+ };
209
+ }
210
+ function requestsHelp(argv) {
211
+ const terminator = argv.indexOf('--');
212
+ const beforeTerminator = terminator === -1 ? argv : argv.slice(0, terminator);
213
+ return beforeTerminator.includes('--help');
214
+ }
215
+ function parseCommandArgs(config) {
216
+ return process.getBuiltinModule('node:util').parseArgs(config);
217
+ }
218
+ function commandArguments(command, argv) {
219
+ if (command.definition.args === undefined) {
220
+ const parsed = parseCommandArgs({
221
+ args: argv,
222
+ allowNegative: true,
223
+ allowPositionals: true,
224
+ strict: false,
225
+ tokens: true,
226
+ });
227
+ const parts = positionalParts(parsed.tokens);
228
+ const raw = {
229
+ values: parsed.values,
230
+ positionals: parts.positionals,
231
+ passthrough: parts.passthrough,
232
+ };
233
+ return command.definition.validate === undefined ? raw : command.definition.validate(raw);
234
+ }
235
+ const positionalNames = new Set(command.positionals);
236
+ const options = {};
237
+ for (const property of command.properties) {
238
+ if (!positionalNames.has(property.name)) {
239
+ options[property.option] = {
240
+ type: property.parseType,
241
+ ...(property.multiple ? { multiple: true } : {}),
242
+ };
243
+ }
244
+ }
245
+ const parsed = parseCommandArgs({
246
+ args: argv,
247
+ allowNegative: command.properties.some(property => property.parseType === 'boolean'),
248
+ allowPositionals: true,
249
+ options,
250
+ strict: true,
251
+ tokens: true,
252
+ });
253
+ const parts = positionalParts(parsed.tokens);
254
+ if (parts.positionals.length > command.positionals.length) {
255
+ const unexpected = parts.positionals[command.positionals.length] ?? '';
256
+ throw new Error(`unexpected positional argument "${unexpected}"`);
257
+ }
258
+ const raw = {};
259
+ for (const property of command.properties) {
260
+ if (positionalNames.has(property.name)) {
261
+ continue;
262
+ }
263
+ const value = parsed.values[property.option];
264
+ if (value !== undefined) {
265
+ raw[property.name] = coerceArgument(property, value);
266
+ }
267
+ }
268
+ for (const [index, name] of command.positionals.entries()) {
269
+ const value = parts.positionals[index];
270
+ if (value === undefined) {
271
+ continue;
272
+ }
273
+ const property = command.propertyByName.get(name);
274
+ raw[name] = property === undefined ? value : coerceArgument(property, value);
275
+ }
276
+ return command.definition.validate === undefined ? raw : command.definition.validate(raw);
277
+ }
278
+ function positionalParts(tokens) {
279
+ const positionals = [];
280
+ const passthrough = [];
281
+ let afterTerminator = false;
282
+ for (const token of tokens) {
283
+ if (token.kind === 'option-terminator') {
284
+ afterTerminator = true;
285
+ }
286
+ else if (token.kind === 'positional' && token.value !== undefined) {
287
+ (afterTerminator ? passthrough : positionals).push(token.value);
288
+ }
289
+ }
290
+ return { positionals, passthrough };
291
+ }
292
+ function coerceArgument(property, value) {
293
+ if (property.multiple) {
294
+ const values = Array.isArray(value) ? value : [value];
295
+ return property.numeric ? values.map(item => coerce.number(item)) : values;
296
+ }
297
+ return property.numeric ? coerce.number(value) : value;
298
+ }
299
+ function commandList(commands) {
300
+ const rows = commands.length === 0
301
+ ? [' (no commands registered)']
302
+ : commands
303
+ .toSorted((left, right) => left.definition.name.localeCompare(right.definition.name))
304
+ .map(command => ` ${command.definition.name} ${command.definition.description}`);
305
+ return `Usage: <command> [options]\n\nCommands:\n${rows.join('\n')}\n`;
306
+ }
307
+ function commandHelp(command) {
308
+ const required = new Set(command.definition.args?.required ?? []);
309
+ const positionalNames = new Set(command.positionals);
310
+ const positionalUsage = command.positionals.map(name => (required.has(name) ? `<${name}>` : `[${name}]`)).join(' ');
311
+ const usage = `Usage: ${command.definition.name}${positionalUsage.length === 0 ? '' : ` ${positionalUsage}`}`;
312
+ const options = command.properties
313
+ .filter(property => !positionalNames.has(property.name))
314
+ .map(property => {
315
+ const value = property.parseType === 'boolean' ? '' : ` <value>${property.multiple ? '...' : ''}`;
316
+ const marker = required.has(property.name) ? ' (required)' : '';
317
+ return ` --${property.option}${value}${marker}`;
318
+ });
319
+ options.push(' --help');
320
+ return `${usage}\n\n${command.definition.description}\n\nOptions:\n${options.join('\n')}\n`;
321
+ }
322
+ function exitCode(value) {
323
+ if (typeof value === 'boolean') {
324
+ return value ? 0 : 1;
325
+ }
326
+ if (typeof value === 'number') {
327
+ const integer = Math.floor(value);
328
+ return Number.isNaN(integer) ? 1 : Math.max(0, Math.min(255, integer));
329
+ }
330
+ return 0;
331
+ }
332
+ function errorMessage(error) {
333
+ return error instanceof Error && error.message.length > 0 ? error.message : String(error);
334
+ }
335
+ function lowerFirst(value) {
336
+ return value.length === 0 ? value : `${value[0]?.toLowerCase()}${value.slice(1)}`;
337
+ }
338
+ //# sourceMappingURL=index.js.map