@spine-event-engine/proto-tools 2.0.0-snapshot.10

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 (89) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +103 -0
  3. package/REFERENCE.md +90 -0
  4. package/bin/spine-proto.mjs +2 -0
  5. package/dist/src/cli/spine-proto-bootstrap.d.ts +4 -0
  6. package/dist/src/cli/spine-proto-bootstrap.d.ts.map +1 -0
  7. package/dist/src/cli/spine-proto-bootstrap.js +52 -0
  8. package/dist/src/cli/spine-proto-bootstrap.js.map +1 -0
  9. package/dist/src/cli/spine-proto.d.ts +3 -0
  10. package/dist/src/cli/spine-proto.d.ts.map +1 -0
  11. package/dist/src/cli/spine-proto.js +32 -0
  12. package/dist/src/cli/spine-proto.js.map +1 -0
  13. package/dist/src/generation/authored-interface-provider.d.ts +28 -0
  14. package/dist/src/generation/authored-interface-provider.d.ts.map +1 -0
  15. package/dist/src/generation/authored-interface-provider.js +244 -0
  16. package/dist/src/generation/authored-interface-provider.js.map +1 -0
  17. package/dist/src/generation/build-time-handler-analyzer.d.ts +195 -0
  18. package/dist/src/generation/build-time-handler-analyzer.d.ts.map +1 -0
  19. package/dist/src/generation/build-time-handler-analyzer.js +1308 -0
  20. package/dist/src/generation/build-time-handler-analyzer.js.map +1 -0
  21. package/dist/src/generation/generated-registry-writer.d.ts +58 -0
  22. package/dist/src/generation/generated-registry-writer.d.ts.map +1 -0
  23. package/dist/src/generation/generated-registry-writer.js +465 -0
  24. package/dist/src/generation/generated-registry-writer.js.map +1 -0
  25. package/dist/src/generation/generated-source-policy.d.ts +21 -0
  26. package/dist/src/generation/generated-source-policy.d.ts.map +1 -0
  27. package/dist/src/generation/generated-source-policy.js +126 -0
  28. package/dist/src/generation/generated-source-policy.js.map +1 -0
  29. package/dist/src/generation/generation-reuse.mjs +106 -0
  30. package/dist/src/generation/generator.d.ts +195 -0
  31. package/dist/src/generation/generator.d.ts.map +1 -0
  32. package/dist/src/generation/generator.js +566 -0
  33. package/dist/src/generation/generator.js.map +1 -0
  34. package/dist/src/generation/handler-codegen.d.ts +48 -0
  35. package/dist/src/generation/handler-codegen.d.ts.map +1 -0
  36. package/dist/src/generation/handler-codegen.js +214 -0
  37. package/dist/src/generation/handler-codegen.js.map +1 -0
  38. package/dist/src/generation/handler-generator.d.ts +7 -0
  39. package/dist/src/generation/handler-generator.d.ts.map +1 -0
  40. package/dist/src/generation/handler-generator.js +55 -0
  41. package/dist/src/generation/handler-generator.js.map +1 -0
  42. package/dist/src/generation/interface-generator.d.ts +21 -0
  43. package/dist/src/generation/interface-generator.d.ts.map +1 -0
  44. package/dist/src/generation/interface-generator.js +277 -0
  45. package/dist/src/generation/interface-generator.js.map +1 -0
  46. package/dist/src/generation/interface-provider.d.ts +30 -0
  47. package/dist/src/generation/interface-provider.d.ts.map +1 -0
  48. package/dist/src/generation/interface-provider.js +15 -0
  49. package/dist/src/generation/interface-provider.js.map +1 -0
  50. package/dist/src/generation/rejection-generator.d.ts +9 -0
  51. package/dist/src/generation/rejection-generator.d.ts.map +1 -0
  52. package/dist/src/generation/rejection-generator.js +62 -0
  53. package/dist/src/generation/rejection-generator.js.map +1 -0
  54. package/dist/src/generation/source-view.d.ts +103 -0
  55. package/dist/src/generation/source-view.d.ts.map +1 -0
  56. package/dist/src/generation/source-view.js +298 -0
  57. package/dist/src/generation/source-view.js.map +1 -0
  58. package/dist/src/index.d.ts +139 -0
  59. package/dist/src/index.d.ts.map +1 -0
  60. package/dist/src/index.js +546 -0
  61. package/dist/src/index.js.map +1 -0
  62. package/dist/src/io/atomic-manifest.d.ts +32 -0
  63. package/dist/src/io/atomic-manifest.d.ts.map +1 -0
  64. package/dist/src/io/atomic-manifest.js +56 -0
  65. package/dist/src/io/atomic-manifest.js.map +1 -0
  66. package/dist/src/io/manifest-reader.d.ts +8 -0
  67. package/dist/src/io/manifest-reader.d.ts.map +1 -0
  68. package/dist/src/io/manifest-reader.js +19 -0
  69. package/dist/src/io/manifest-reader.js.map +1 -0
  70. package/dist/src/model/model-graph.d.ts +68 -0
  71. package/dist/src/model/model-graph.d.ts.map +1 -0
  72. package/dist/src/model/model-graph.js +220 -0
  73. package/dist/src/model/model-graph.js.map +1 -0
  74. package/dist/src/model/npm-package-name.d.ts +7 -0
  75. package/dist/src/model/npm-package-name.d.ts.map +1 -0
  76. package/dist/src/model/npm-package-name.js +33 -0
  77. package/dist/src/model/npm-package-name.js.map +1 -0
  78. package/dist/src/model/registry-dependency.d.ts +10 -0
  79. package/dist/src/model/registry-dependency.d.ts.map +1 -0
  80. package/dist/src/model/registry-dependency.js +66 -0
  81. package/dist/src/model/registry-dependency.js.map +1 -0
  82. package/dist/src/testing/index.d.ts +8 -0
  83. package/dist/src/testing/index.d.ts.map +1 -0
  84. package/dist/src/testing/index.js +20 -0
  85. package/dist/src/testing/index.js.map +1 -0
  86. package/dist/tsconfig.tsbuildinfo +1 -0
  87. package/package.json +54 -0
  88. package/scripts/clean-dist.mjs +7 -0
  89. package/scripts/copy-runtime-modules.mjs +23 -0
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Published manifest JSON revision.
3
+ */
4
+ export declare const manifestFormatVersion = 2;
5
+ /**
6
+ * Configures an independently published Proto model package.
7
+ */
8
+ export interface ModelConfig {
9
+ /**
10
+ * Declares the version of this configuration contract.
11
+ */
12
+ readonly formatVersion: 1;
13
+ /**
14
+ * Selects an independently published model package.
15
+ */
16
+ readonly mode: "model";
17
+ /**
18
+ * Names the owning npm package.
19
+ */
20
+ readonly packageName: string;
21
+ /**
22
+ * Locates the package-contained canonical Proto sources.
23
+ */
24
+ readonly protoRoot: string;
25
+ /**
26
+ * Locates the package-contained generated TypeScript files.
27
+ */
28
+ readonly generatedRoot: string;
29
+ /**
30
+ * Names the generated import subpath root.
31
+ */
32
+ readonly exportRoot: string;
33
+ /**
34
+ * Lists direct model packages from the npm registry.
35
+ */
36
+ readonly dependencies: readonly string[];
37
+ /**
38
+ * Names the generated model-module export.
39
+ */
40
+ readonly moduleExport: string;
41
+ }
42
+ /**
43
+ * Configures an application that composes published Proto model packages.
44
+ */
45
+ export interface ApplicationConfig {
46
+ /**
47
+ * Declares the version of this configuration contract.
48
+ */
49
+ readonly formatVersion: 1;
50
+ /**
51
+ * Selects application model composition.
52
+ */
53
+ readonly mode: "application";
54
+ /**
55
+ * Lists direct model packages from the npm registry.
56
+ */
57
+ readonly modelPackages: readonly string[];
58
+ /**
59
+ * Locates the generated package registry source file.
60
+ */
61
+ readonly registryOutput: string;
62
+ }
63
+ /**
64
+ * Version-one model or application configuration selected by mode.
65
+ */
66
+ export type SpineProtoConfig = ModelConfig | ApplicationConfig;
67
+ /**
68
+ * Describes the Proto sources and generated exports owned by a model package.
69
+ */
70
+ export interface ProtoManifest {
71
+ /**
72
+ * Declares the version of this manifest contract.
73
+ */
74
+ readonly formatVersion: 2;
75
+ /**
76
+ * Stores the opaque identifier shared with the generated-root marker.
77
+ */
78
+ readonly generationId: string;
79
+ /**
80
+ * Names the package that owns every listed Proto source.
81
+ */
82
+ readonly packageName: string;
83
+ /**
84
+ * States the declared version from the owning package.json file.
85
+ */
86
+ readonly packageVersion: string;
87
+ /**
88
+ * Lists canonical package-relative Proto paths owned by the package.
89
+ */
90
+ readonly protoFiles: readonly string[];
91
+ /**
92
+ * Maps each Proto path to its generated package-relative import subpath.
93
+ */
94
+ readonly generatedExports: Readonly<Record<string, string>>;
95
+ /**
96
+ * Lists direct model-package dependencies.
97
+ */
98
+ readonly dependencies: readonly string[];
99
+ /**
100
+ * Names the generated ProtoModule export.
101
+ */
102
+ readonly moduleExport: string;
103
+ }
104
+ /**
105
+ * Provides access to the versioned Proto package configuration.
106
+ */
107
+ export declare const ProtoConfig: Readonly<{
108
+ /**
109
+ * Reads and validates `spine-proto.json` at a package root.
110
+ *
111
+ * @param packageRoot The package root that contains the configuration.
112
+ * @returns The validated model or application configuration.
113
+ */
114
+ read(packageRoot: string): SpineProtoConfig;
115
+ }>;
116
+ /**
117
+ * Provides access to deterministic Proto package manifests.
118
+ */
119
+ export declare const ProtoManifest: Readonly<{
120
+ /**
121
+ * Reads and validates a package manifest.
122
+ *
123
+ * @param packageRoot The package root that owns the manifest.
124
+ * @param manifestPath The optional manifest path within the package root.
125
+ * @returns The validated manifest.
126
+ */
127
+ read(packageRoot: string, manifestPath?: string): ProtoManifest;
128
+ /**
129
+ * Creates a manifest from package-owned Proto paths. Without a supplied generation ID,
130
+ * a fresh random ID is assigned for a new generated-root marker lifecycle.
131
+ *
132
+ * @param packageRoot The model package root.
133
+ * @param ownedProtoFiles Optional explicit package-relative Proto paths.
134
+ * @param generationId Optional generation ID; a random ID is used when omitted.
135
+ * @returns The created manifest.
136
+ */
137
+ create(packageRoot: string, ownedProtoFiles?: readonly string[], generationId?: string): ProtoManifest;
138
+ }>;
139
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AA8BA;;GAEG;AACH,eAAO,MAAM,qBAAqB,IAAI,CAAC;AAiEvC;;GAEG;AACH,MAAM,WAAW,WAAW;IAG1B;;OAEG;IACH,QAAQ,CAAC,aAAa,EAAE,CAAC,CAAC;IAE1B;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAEvB;;OAEG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAE7B;;OAEG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B;;OAEG;IACH,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAE/B;;OAEG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAE5B;;OAEG;IACH,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAEzC;;OAEG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAGhC;;OAEG;IACH,QAAQ,CAAC,aAAa,EAAE,CAAC,CAAC;IAE1B;;OAEG;IACH,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAE7B;;OAEG;IACH,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IAE1C;;OAEG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG,iBAAiB,CAAC;AAE/D;;GAEG;AACH,MAAM,WAAW,aAAa;IAG5B;;OAEG;IACH,QAAQ,CAAC,aAAa,EAAE,CAAC,CAAC;IAE1B;;OAEG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAE9B;;OAEG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAE7B;;OAEG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAEhC;;OAEG;IACH,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAEvC;;OAEG;IACH,QAAQ,CAAC,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAE5D;;OAEG;IACH,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAEzC;;OAEG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAumBD;;GAEG;AACH,eAAO,MAAM,WAAW,EAAE,QAAQ,CAAC;IAGjC;;;;;OAKG;IACH,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,gBAAgB,CAAC;CAC7C,CAYC,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,QAAQ,CAAC;IAGnC;;;;;;OAMG;IACH,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,aAAa,CAAC;IAEhE;;;;;;;;OAQG;IACH,MAAM,CACJ,WAAW,EAAE,MAAM,EACnB,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,EACnC,YAAY,CAAC,EAAE,MAAM,GACpB,aAAa,CAAC;CAClB,CA2CC,CAAC"}
@@ -0,0 +1,546 @@
1
+ /*
2
+ * Copyright 2026, CodeMatters. All rights reserved.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5
+ * in compliance with the License. You may obtain a copy of the License at
6
+ *
7
+ * https://www.apache.org/licenses/LICENSE-2.0
8
+ *
9
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
10
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11
+ * or implied. See the License for the specific language governing permissions and limitations under
12
+ * the License.
13
+ */
14
+ import { closeSync, constants, fstatSync, lstatSync, openSync, opendirSync, readFileSync, } from "node:fs";
15
+ import { randomUUID } from "node:crypto";
16
+ import { isAbsolute, join, normalize, sep } from "node:path";
17
+ import { generationMarkerFile } from "./generation/generation-reuse.mjs";
18
+ import { RegistryDependency } from "./model/registry-dependency.js";
19
+ import { NpmPackageName } from "./model/npm-package-name.js";
20
+ /**
21
+ * Published manifest JSON revision.
22
+ */
23
+ export const manifestFormatVersion = 2;
24
+ const configFormatVersion = 1;
25
+ const manifestReadAttempts = 3;
26
+ const manifestCommitDelayMs = 25;
27
+ const generationClaimScanLimit = 1_000;
28
+ function waitForManifestCommit() {
29
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT)), 0, 0, manifestCommitDelayMs);
30
+ }
31
+ function hasLiveGenerationClaim(packageRoot) {
32
+ try {
33
+ const directory = opendirSync(packageRoot, { encoding: "utf8" });
34
+ try {
35
+ let entry;
36
+ const claimPaths = [];
37
+ while ((entry = directory.readSync()) !== null) {
38
+ if (!entry.name.startsWith(".spine-proto-generate.lock."))
39
+ continue;
40
+ claimPaths.push(join(packageRoot, entry.name));
41
+ if (claimPaths.length > generationClaimScanLimit)
42
+ throw new Error("generation claim count exceeds 1000");
43
+ }
44
+ for (const path of claimPaths) {
45
+ let descriptor;
46
+ let owner;
47
+ try {
48
+ descriptor = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
49
+ if (!fstatSync(descriptor).isFile())
50
+ throw new Error("generation claim is unsafe");
51
+ owner = JSON.parse(readFileSync(descriptor, "utf8"));
52
+ }
53
+ catch (error) {
54
+ if (error instanceof Error && error.message === "generation claim is unsafe")
55
+ throw error;
56
+ throw new Error("generation claim is unsafe", { cause: error });
57
+ }
58
+ finally {
59
+ if (descriptor !== undefined)
60
+ closeSync(descriptor);
61
+ }
62
+ if (typeof owner.pid !== "number" || owner.pid <= 0)
63
+ continue;
64
+ try {
65
+ process.kill(owner.pid, 0);
66
+ return true;
67
+ }
68
+ catch (error) {
69
+ if (error.code !== "ESRCH")
70
+ return true;
71
+ }
72
+ }
73
+ }
74
+ finally {
75
+ directory.closeSync();
76
+ }
77
+ }
78
+ catch (error) {
79
+ if (error instanceof Error &&
80
+ ["generation claim count exceeds 1000", "generation claim is unsafe"].includes(error.message))
81
+ throw error;
82
+ return false;
83
+ }
84
+ return false;
85
+ }
86
+ const reservedBindings = new Set([
87
+ "await",
88
+ "break",
89
+ "case",
90
+ "catch",
91
+ "class",
92
+ "const",
93
+ "continue",
94
+ "default",
95
+ "delete",
96
+ "do",
97
+ "else",
98
+ "enum",
99
+ "export",
100
+ "extends",
101
+ "false",
102
+ "finally",
103
+ "for",
104
+ "function",
105
+ "if",
106
+ "import",
107
+ "in",
108
+ "instanceof",
109
+ "let",
110
+ "new",
111
+ "null",
112
+ "return",
113
+ "super",
114
+ "switch",
115
+ "this",
116
+ "throw",
117
+ "true",
118
+ "try",
119
+ "typeof",
120
+ "var",
121
+ "void",
122
+ "while",
123
+ "with",
124
+ "yield",
125
+ ]);
126
+ /**
127
+ * Reports invalid contained Proto package contracts.
128
+ */
129
+ const ProtoPackageErrors = Object.freeze({
130
+ fail(name, message) {
131
+ throw new Error(`spine-proto: ${name}: ${message}`);
132
+ },
133
+ });
134
+ /**
135
+ * Reads, validates, and creates contained Proto package contracts.
136
+ */
137
+ const ProtoPackage = Object.freeze({
138
+ // prettier-ignore
139
+ /**
140
+ * Reads and validates versioned `spine-proto.json` package configuration.
141
+ */
142
+ configFromPackage(packageRoot) {
143
+ const packageJson = ProtoPackage.readPackage(packageRoot);
144
+ const config = ProtoPackage.objectValue(packageJson.name, ProtoPackage.readJson(join(packageRoot, "spine-proto.json"), packageJson.name), "configuration");
145
+ if (config.formatVersion !== configFormatVersion)
146
+ ProtoPackageErrors.fail(packageJson.name, "formatVersion must be 1");
147
+ if (config.mode !== "model" && config.mode !== "application")
148
+ ProtoPackageErrors.fail(packageJson.name, "mode must be model or application");
149
+ if (config.mode === "application") {
150
+ ProtoPackage.rejectKeys(packageJson.name, config, ["formatVersion", "mode", "modelPackages", "registryOutput"], "application mode");
151
+ const modelPackages = ProtoPackage.packageNameList(packageJson.name, config.modelPackages, "modelPackages");
152
+ ProtoPackage.validateRegistryDependencies(packageJson, modelPackages, "model package");
153
+ const registryOutput = ProtoPackage.packagePath(packageRoot, packageJson.name, config.registryOutput, "registryOutput");
154
+ if (ProtoPackage.isReservedOutput(registryOutput))
155
+ ProtoPackageErrors.fail(packageJson.name, "registryOutput must name a safe source file");
156
+ return {
157
+ formatVersion: 1,
158
+ mode: "application",
159
+ modelPackages,
160
+ registryOutput,
161
+ };
162
+ }
163
+ ProtoPackage.rejectKeys(packageJson.name, config, [
164
+ "formatVersion",
165
+ "mode",
166
+ "packageName",
167
+ "protoRoot",
168
+ "generatedRoot",
169
+ "exportRoot",
170
+ "dependencies",
171
+ "moduleExport",
172
+ ], "model mode");
173
+ const packageName = ProtoPackage.packageNameValue(packageJson.name, config.packageName, "packageName");
174
+ if (packageName !== packageJson.name)
175
+ ProtoPackageErrors.fail(packageJson.name, "packageName must match package.json name");
176
+ const dependencies = ProtoPackage.packageNameList(packageJson.name, config.dependencies, "dependencies");
177
+ ProtoPackage.validateRegistryDependencies(packageJson, dependencies, "dependency");
178
+ const protoRoot = ProtoPackage.packagePath(packageRoot, packageJson.name, config.protoRoot, "protoRoot");
179
+ const generatedRoot = ProtoPackage.packagePath(packageRoot, packageJson.name, config.generatedRoot, "generatedRoot");
180
+ if (generatedRoot === "." ||
181
+ ["package.json", "spine-proto.json", "spine-proto-manifest.json"].includes(generatedRoot) ||
182
+ ProtoPackage.pathsOverlap(protoRoot, generatedRoot))
183
+ ProtoPackageErrors.fail(packageJson.name, "generatedRoot must not overlap protoRoot or package root");
184
+ const exportRoot = ProtoPackage.packagePath(packageRoot, packageJson.name, config.exportRoot, "exportRoot");
185
+ ProtoPackage.validateGeneratedExports(packageJson, exportRoot);
186
+ return {
187
+ formatVersion: 1,
188
+ mode: "model",
189
+ packageName,
190
+ protoRoot,
191
+ generatedRoot,
192
+ exportRoot,
193
+ dependencies,
194
+ moduleExport: ProtoPackage.bindingIdentifier(packageJson.name, config.moduleExport, "moduleExport"),
195
+ };
196
+ },
197
+ /**
198
+ * Validates a deterministic manifest shipped by a model package.
199
+ */
200
+ manifestFromPackage(packageRoot, manifestPath = join(packageRoot, "spine-proto-manifest.json")) {
201
+ const packageJson = ProtoPackage.readPackage(packageRoot);
202
+ const manifest = ProtoPackage.manifestFromValue(ProtoPackage.readJson(manifestPath, packageJson.name), packageJson.name);
203
+ if (manifest.packageName !== packageJson.name)
204
+ ProtoPackageErrors.fail(packageJson.name, "manifest packageName must match package.json name");
205
+ if (manifest.packageVersion !== packageJson.version)
206
+ ProtoPackageErrors.fail(packageJson.name, "manifest packageVersion must match package.json version");
207
+ const config = ProtoPackage.configFromPackage(packageRoot);
208
+ if (config.mode !== "model")
209
+ ProtoPackageErrors.fail(packageJson.name, "manifest requires model mode");
210
+ const marker = ProtoPackage.objectValue(packageJson.name, ProtoPackage.readJson(join(packageRoot, config.generatedRoot, generationMarkerFile), packageJson.name), "generated-root marker");
211
+ ProtoPackage.rejectKeys(packageJson.name, marker, ["generationId"], "generated-root marker");
212
+ if (ProtoPackage.stringValue(packageJson.name, marker.generationId, "generated-root marker generationId") !== manifest.generationId)
213
+ ProtoPackageErrors.fail(packageJson.name, "manifest generationId must match generated-root marker");
214
+ for (const protoFile of manifest.protoFiles) {
215
+ ProtoPackage.assertNoSymlinkAncestor(packageRoot, packageJson.name, protoFile, "manifest protoFiles");
216
+ ProtoPackage.assertNoSymlinkAncestor(packageRoot, packageJson.name, manifest.generatedExports[protoFile] ?? "", `manifest generated export for ${protoFile}`);
217
+ }
218
+ return manifest;
219
+ },
220
+ /**
221
+ * Builds a deterministic manifest from a model package's owned Proto paths.
222
+ */
223
+ manifestForPackage(packageRoot, ownedProtoFiles, generationId = randomUUID()) {
224
+ const config = ProtoPackage.configFromPackage(packageRoot);
225
+ if (config.mode !== "model")
226
+ ProtoPackageErrors.fail(ProtoPackage.readPackage(packageRoot).name, "manifest requires model mode");
227
+ const packageJson = ProtoPackage.readPackage(packageRoot);
228
+ if (ownedProtoFiles !== undefined && ownedProtoFiles.length > 10000)
229
+ ProtoPackageErrors.fail(packageJson.name, "owned Proto paths exceeds 10000 entries");
230
+ const protoFiles = (ownedProtoFiles ??
231
+ ProtoPackage.discoverProtoFiles(join(packageRoot, config.protoRoot), packageJson.name))
232
+ .map((file) => ProtoPackage.protoPath(packageRoot, packageJson.name, config.protoRoot, file))
233
+ .sort();
234
+ if (new Set(protoFiles).size !== protoFiles.length)
235
+ ProtoPackageErrors.fail(packageJson.name, "duplicate proto path");
236
+ return {
237
+ formatVersion: manifestFormatVersion,
238
+ generationId: ProtoPackage.stringValue(packageJson.name, generationId, "manifest generationId"),
239
+ packageName: packageJson.name,
240
+ packageVersion: packageJson.version,
241
+ protoFiles,
242
+ generatedExports: Object.fromEntries(protoFiles.map((file) => [
243
+ file,
244
+ ProtoPackage.exportPath(packageJson.name, config.exportRoot, file),
245
+ ])),
246
+ dependencies: [...config.dependencies].sort(),
247
+ moduleExport: config.moduleExport,
248
+ };
249
+ },
250
+ manifestFromValue(value, requester) {
251
+ const manifest = ProtoPackage.objectValue(requester, value, "manifest");
252
+ ProtoPackage.rejectKeys(requester, manifest, [
253
+ "formatVersion",
254
+ "generationId",
255
+ "packageName",
256
+ "packageVersion",
257
+ "protoFiles",
258
+ "generatedExports",
259
+ "dependencies",
260
+ "moduleExport",
261
+ ], "manifest");
262
+ if (manifest.formatVersion !== manifestFormatVersion)
263
+ ProtoPackageErrors.fail(requester, "manifest formatVersion must be 2");
264
+ const generationId = ProtoPackage.stringValue(requester, manifest.generationId, "manifest generationId");
265
+ const protoFiles = ProtoPackage.stringList(requester, manifest.protoFiles, "manifest protoFiles")
266
+ .map((file) => ProtoPackage.manifestPath(requester, file, "manifest protoFiles"))
267
+ .sort();
268
+ const generatedExports = ProtoPackage.objectValue(requester, manifest.generatedExports, "manifest generatedExports");
269
+ ProtoPackage.assertRecordBound(requester, generatedExports, "manifest generatedExports");
270
+ const keys = Object.keys(generatedExports).sort();
271
+ if (keys.length !== protoFiles.length || keys.some((key, index) => key !== protoFiles[index])) {
272
+ ProtoPackageErrors.fail(requester, "manifest generatedExports must map every proto file exactly once");
273
+ }
274
+ return {
275
+ formatVersion: manifestFormatVersion,
276
+ generationId,
277
+ packageName: ProtoPackage.packageNameValue(requester, manifest.packageName, "manifest packageName"),
278
+ packageVersion: ProtoPackage.stringValue(requester, manifest.packageVersion, "manifest packageVersion"),
279
+ protoFiles,
280
+ generatedExports: Object.fromEntries(protoFiles.map((file) => [
281
+ file,
282
+ ProtoPackage.manifestPath(requester, generatedExports[file], `manifest generated export for ${file}`),
283
+ ])),
284
+ dependencies: [
285
+ ...ProtoPackage.packageNameList(requester, manifest.dependencies, "manifest dependencies"),
286
+ ].sort(),
287
+ moduleExport: ProtoPackage.bindingIdentifier(requester, manifest.moduleExport, "manifest moduleExport"),
288
+ };
289
+ },
290
+ discoverProtoFiles(root, name) {
291
+ const files = [];
292
+ const pending = [{ path: root, relativePath: "", depth: 0 }];
293
+ let encountered = 0;
294
+ while (pending.length > 0) {
295
+ const directory = pending.pop();
296
+ if (directory === undefined)
297
+ break;
298
+ let directoryHandle;
299
+ try {
300
+ directoryHandle = opendirSync(directory.path, { encoding: "utf8" });
301
+ }
302
+ catch {
303
+ ProtoPackageErrors.fail(name, "proto root is missing or inaccessible");
304
+ }
305
+ try {
306
+ for (let entry = directoryHandle.readSync(); entry !== null; entry = directoryHandle.readSync()) {
307
+ encountered += 1;
308
+ if (encountered > 10000)
309
+ ProtoPackageErrors.fail(name, "proto source exceeds 10000 entries");
310
+ const relativePath = directory.relativePath === "" ? entry.name : `${directory.relativePath}/${entry.name}`;
311
+ if (entry.isDirectory()) {
312
+ if (directory.depth >= 100)
313
+ ProtoPackageErrors.fail(name, "proto source exceeds 100 directory levels");
314
+ pending.push({
315
+ path: join(directory.path, entry.name),
316
+ relativePath,
317
+ depth: directory.depth + 1,
318
+ });
319
+ }
320
+ else if (entry.isFile() && entry.name.endsWith(".proto")) {
321
+ files.push(relativePath);
322
+ }
323
+ }
324
+ }
325
+ finally {
326
+ directoryHandle.closeSync();
327
+ }
328
+ }
329
+ return files;
330
+ },
331
+ protoPath(packageRoot, name, protoRoot, value) {
332
+ const path = ProtoPackage.manifestPath(name, value, "proto path");
333
+ ProtoPackage.assertNoSymlinkAncestor(packageRoot, name, join(protoRoot, path), "proto path");
334
+ return path;
335
+ },
336
+ exportPath(name, exportRoot, protoPath) {
337
+ return ProtoPackage.manifestPath(name, `${exportRoot}/${protoPath.replace(/\.proto$/, "_pb.js")}`, "generated export");
338
+ },
339
+ packagePath(packageRoot, name, value, label) {
340
+ const path = ProtoPackage.manifestPath(name, value, label);
341
+ ProtoPackage.assertNoSymlinkAncestor(packageRoot, name, path, label);
342
+ return path;
343
+ },
344
+ pathsOverlap(left, right) {
345
+ return left === right || left.startsWith(`${right}/`) || right.startsWith(`${left}/`);
346
+ },
347
+ isReservedOutput(path) {
348
+ return (path === "." ||
349
+ ["package.json", "spine-proto.json", "spine-proto-manifest.json"].includes(path) ||
350
+ !path.includes("."));
351
+ },
352
+ bindingIdentifier(name, value, label) {
353
+ const identifier = ProtoPackage.stringValue(name, value, label);
354
+ if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(identifier) || reservedBindings.has(identifier))
355
+ ProtoPackageErrors.fail(name, `${label} must be a legal ESM binding identifier`);
356
+ return identifier;
357
+ },
358
+ manifestPath(name, value, label) {
359
+ const path = ProtoPackage.stringValue(name, value, label).replaceAll("\\", "/");
360
+ if (isAbsolute(path))
361
+ ProtoPackageErrors.fail(name, `${label} must be relative`);
362
+ if (path.split("/").includes(".."))
363
+ ProtoPackageErrors.fail(name, `${label} must not contain traversal`);
364
+ if (path.endsWith("/") || normalize(path).replaceAll("\\", "/") !== path) {
365
+ ProtoPackageErrors.fail(name, `${label} must be a normalized contained relative path`);
366
+ }
367
+ return path;
368
+ },
369
+ assertNoSymlinkAncestor(packageRoot, name, path, label) {
370
+ let current = packageRoot;
371
+ for (const segment of path.split("/")) {
372
+ current = join(current, segment);
373
+ try {
374
+ if (lstatSync(current).isSymbolicLink())
375
+ ProtoPackageErrors.fail(name, `${label} must not pass through a symlink`);
376
+ }
377
+ catch (error) {
378
+ if (error.code !== "ENOENT")
379
+ throw error;
380
+ }
381
+ }
382
+ },
383
+ validateRegistryDependencies(packageJson, names, label) {
384
+ for (const name of names) {
385
+ const specifier = packageJson.dependencies[name];
386
+ if (specifier === undefined) {
387
+ ProtoPackageErrors.fail(packageJson.name, `${label} ${name} must be declared in package.json dependencies`);
388
+ }
389
+ if (!RegistryDependency.is(specifier)) {
390
+ ProtoPackageErrors.fail(packageJson.name, `${label} ${name} must use a registry version`);
391
+ }
392
+ }
393
+ },
394
+ validateGeneratedExports(packageJson, exportRoot) {
395
+ const key = `./${exportRoot}/*.js`;
396
+ const entry = packageJson.exports[key];
397
+ const expectedRuntime = `./dist/${exportRoot}/*.js`;
398
+ const expectedTypes = `./dist/${exportRoot}/*.d.ts`;
399
+ if (typeof entry !== "object" ||
400
+ entry === null ||
401
+ Array.isArray(entry) ||
402
+ entry.default !== expectedRuntime ||
403
+ entry.types !== expectedTypes) {
404
+ ProtoPackageErrors.fail(packageJson.name, `package.json exports must expose ${key} with default ${expectedRuntime} and types ${expectedTypes}`);
405
+ }
406
+ },
407
+ readPackage(packageRoot) {
408
+ const value = ProtoPackage.objectValue(packageRoot, ProtoPackage.readJson(join(packageRoot, "package.json"), packageRoot), "package.json");
409
+ const name = ProtoPackage.packageNameValue(packageRoot, value.name, "package.json name");
410
+ return {
411
+ name,
412
+ version: ProtoPackage.stringValue(name, value.version, "package.json version"),
413
+ dependencies: ProtoPackage.stringRecord(value.dependencies, name, "package.json dependencies"),
414
+ exports: value.exports === undefined
415
+ ? {}
416
+ : ProtoPackage.objectValue(name, value.exports, "package.json exports"),
417
+ };
418
+ },
419
+ readJson(path, name) {
420
+ let descriptor;
421
+ try {
422
+ descriptor = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
423
+ if (!fstatSync(descriptor).isFile())
424
+ throw new Error("unsafe JSON input");
425
+ return JSON.parse(readFileSync(descriptor, "utf8"));
426
+ }
427
+ catch {
428
+ return ProtoPackageErrors.fail(name, `cannot read ${path.split(sep).at(-1) ?? path}`);
429
+ }
430
+ finally {
431
+ if (descriptor !== undefined)
432
+ closeSync(descriptor);
433
+ }
434
+ },
435
+ objectValue(name, value, label) {
436
+ if (typeof value !== "object" || value === null || Array.isArray(value))
437
+ return ProtoPackageErrors.fail(name, `${label} must be an object`);
438
+ return value;
439
+ },
440
+ stringValue(name, value, label) {
441
+ if (typeof value !== "string" || value.length === 0)
442
+ return ProtoPackageErrors.fail(name, `${label} must be a non-empty string`);
443
+ return value;
444
+ },
445
+ stringList(name, value, label) {
446
+ if (!Array.isArray(value))
447
+ ProtoPackageErrors.fail(name, `${label} must be an array of non-empty strings`);
448
+ if (value.length > 10000)
449
+ ProtoPackageErrors.fail(name, `${label} exceeds 10000 entries`);
450
+ const strings = value.filter((item) => typeof item === "string" && item.length > 0);
451
+ if (strings.length !== value.length)
452
+ ProtoPackageErrors.fail(name, `${label} must be an array of non-empty strings`);
453
+ if (new Set(strings).size !== strings.length)
454
+ ProtoPackageErrors.fail(name, `${label} must not contain duplicates`);
455
+ return strings;
456
+ },
457
+ packageNameValue(name, value, label) {
458
+ const packageName = ProtoPackage.stringValue(name, value, label);
459
+ if (!NpmPackageName.is(packageName))
460
+ ProtoPackageErrors.fail(name, `${label} must be a valid npm package name`);
461
+ return packageName;
462
+ },
463
+ packageNameList(name, value, label) {
464
+ return ProtoPackage.stringList(name, value, label).map((packageName) => ProtoPackage.packageNameValue(name, packageName, label));
465
+ },
466
+ assertRecordBound(name, record, label) {
467
+ let count = 0;
468
+ for (const key in record) {
469
+ if (!Object.hasOwn(record, key))
470
+ continue;
471
+ count += 1;
472
+ if (count > 10000)
473
+ ProtoPackageErrors.fail(name, `${label} exceeds 10000 entries`);
474
+ }
475
+ },
476
+ stringRecord(value, name, label) {
477
+ if (value === undefined)
478
+ return {};
479
+ const record = ProtoPackage.objectValue(name, value, label);
480
+ if (!Object.values(record).every((item) => typeof item === "string"))
481
+ ProtoPackageErrors.fail(name, `${label} must contain string versions`);
482
+ return record;
483
+ },
484
+ rejectKeys(name, value, allowed, label) {
485
+ const unexpected = Object.keys(value).find((key) => !allowed.includes(key));
486
+ if (unexpected !== undefined)
487
+ ProtoPackageErrors.fail(name, `${label} must not declare ${unexpected}`);
488
+ },
489
+ });
490
+ /**
491
+ * Provides access to the versioned Proto package configuration.
492
+ */
493
+ export const ProtoConfig = Object.freeze({
494
+ // prettier-ignore
495
+ /**
496
+ * Reads and validates `spine-proto.json` at a package root.
497
+ *
498
+ * @param packageRoot The package root that contains the configuration.
499
+ * @returns The validated model or application configuration.
500
+ */
501
+ read(packageRoot) {
502
+ return ProtoPackage.configFromPackage(packageRoot);
503
+ },
504
+ });
505
+ /**
506
+ * Provides access to deterministic Proto package manifests.
507
+ */
508
+ export const ProtoManifest = Object.freeze({
509
+ // prettier-ignore
510
+ /**
511
+ * Reads and validates a package manifest.
512
+ *
513
+ * @param packageRoot The package root that owns the manifest.
514
+ * @param manifestPath The optional manifest path within the package root.
515
+ * @returns The validated manifest.
516
+ */
517
+ read(packageRoot, manifestPath = join(packageRoot, "spine-proto-manifest.json")) {
518
+ let failure;
519
+ for (let attempt = 0; attempt < manifestReadAttempts; attempt += 1) {
520
+ try {
521
+ return ProtoPackage.manifestFromPackage(packageRoot, manifestPath);
522
+ }
523
+ catch (error) {
524
+ failure = error;
525
+ if (!hasLiveGenerationClaim(packageRoot))
526
+ throw error;
527
+ if (attempt + 1 < manifestReadAttempts)
528
+ waitForManifestCommit();
529
+ }
530
+ }
531
+ throw failure;
532
+ },
533
+ /**
534
+ * Creates a manifest from package-owned Proto paths. Without a supplied generation ID,
535
+ * a fresh random ID is assigned for a new generated-root marker lifecycle.
536
+ *
537
+ * @param packageRoot The model package root.
538
+ * @param ownedProtoFiles Optional explicit package-relative Proto paths.
539
+ * @param generationId Optional generation ID; a random ID is used when omitted.
540
+ * @returns The created manifest.
541
+ */
542
+ create(packageRoot, ownedProtoFiles, generationId) {
543
+ return ProtoPackage.manifestForPackage(packageRoot, ownedProtoFiles, generationId);
544
+ },
545
+ });
546
+ //# sourceMappingURL=index.js.map