@forgeax/engine-pack 0.1.2

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 (228) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +370 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/__tests__/ai-recovery.unit.test.d.ts +2 -0
  5. package/dist/__tests__/ai-recovery.unit.test.d.ts.map +1 -0
  6. package/dist/__tests__/artifact-path.test.d.ts +2 -0
  7. package/dist/__tests__/artifact-path.test.d.ts.map +1 -0
  8. package/dist/__tests__/asset-evidence-error-hints.test.d.ts +2 -0
  9. package/dist/__tests__/asset-evidence-error-hints.test.d.ts.map +1 -0
  10. package/dist/__tests__/asset-evidence-lifecycle.test.d.ts +2 -0
  11. package/dist/__tests__/asset-evidence-lifecycle.test.d.ts.map +1 -0
  12. package/dist/__tests__/asset-evidence-schema.test.d.ts +2 -0
  13. package/dist/__tests__/asset-evidence-schema.test.d.ts.map +1 -0
  14. package/dist/__tests__/atlas-cli-region-mismatch.test.d.ts +2 -0
  15. package/dist/__tests__/atlas-cli-region-mismatch.test.d.ts.map +1 -0
  16. package/dist/__tests__/atlas-error-code-owner.test-d.d.ts +2 -0
  17. package/dist/__tests__/atlas-error-code-owner.test-d.d.ts.map +1 -0
  18. package/dist/__tests__/cli-asset-evidence.integration.test.d.ts +2 -0
  19. package/dist/__tests__/cli-asset-evidence.integration.test.d.ts.map +1 -0
  20. package/dist/__tests__/cli.unit.test.d.ts +2 -0
  21. package/dist/__tests__/cli.unit.test.d.ts.map +1 -0
  22. package/dist/__tests__/evidence-source-inventory.unit.test.d.ts +2 -0
  23. package/dist/__tests__/evidence-source-inventory.unit.test.d.ts.map +1 -0
  24. package/dist/__tests__/guid-collision.unit.test.d.ts +2 -0
  25. package/dist/__tests__/guid-collision.unit.test.d.ts.map +1 -0
  26. package/dist/__tests__/guid-validator-surface.unit.test.d.ts +2 -0
  27. package/dist/__tests__/guid-validator-surface.unit.test.d.ts.map +1 -0
  28. package/dist/__tests__/load-asset-config.test.d.ts +2 -0
  29. package/dist/__tests__/load-asset-config.test.d.ts.map +1 -0
  30. package/dist/__tests__/material-cook-dependencies.unit.test.d.ts +2 -0
  31. package/dist/__tests__/material-cook-dependencies.unit.test.d.ts.map +1 -0
  32. package/dist/__tests__/material-cook-receipt.unit.test.d.ts +2 -0
  33. package/dist/__tests__/material-cook-receipt.unit.test.d.ts.map +1 -0
  34. package/dist/__tests__/material-cook-schema.unit.test.d.ts +2 -0
  35. package/dist/__tests__/material-cook-schema.unit.test.d.ts.map +1 -0
  36. package/dist/__tests__/mesh-bin-consumer-surface.unit.test.d.ts +2 -0
  37. package/dist/__tests__/mesh-bin-consumer-surface.unit.test.d.ts.map +1 -0
  38. package/dist/__tests__/mesh-bin-contract.unit.test.d.ts +2 -0
  39. package/dist/__tests__/mesh-bin-contract.unit.test.d.ts.map +1 -0
  40. package/dist/__tests__/meta-schema-optional.test.d.ts +2 -0
  41. package/dist/__tests__/meta-schema-optional.test.d.ts.map +1 -0
  42. package/dist/__tests__/meta-source-overrides.unit.test.d.ts +2 -0
  43. package/dist/__tests__/meta-source-overrides.unit.test.d.ts.map +1 -0
  44. package/dist/__tests__/native-cooker-registry.test.d.ts +2 -0
  45. package/dist/__tests__/native-cooker-registry.test.d.ts.map +1 -0
  46. package/dist/__tests__/offline-evidence-artifact-verification-owner.test-d.d.ts +2 -0
  47. package/dist/__tests__/offline-evidence-artifact-verification-owner.test-d.d.ts.map +1 -0
  48. package/dist/__tests__/offline-evidence.integration.test.d.ts +2 -0
  49. package/dist/__tests__/offline-evidence.integration.test.d.ts.map +1 -0
  50. package/dist/__tests__/pack-v2-fixture-migration.test.d.ts +2 -0
  51. package/dist/__tests__/pack-v2-fixture-migration.test.d.ts.map +1 -0
  52. package/dist/__tests__/pack-v2-schema.test.d.ts +2 -0
  53. package/dist/__tests__/pack-v2-schema.test.d.ts.map +1 -0
  54. package/dist/__tests__/pack.unit.test.d.ts +2 -0
  55. package/dist/__tests__/pack.unit.test.d.ts.map +1 -0
  56. package/dist/__tests__/producer-contract-schema.unit.test.d.ts +2 -0
  57. package/dist/__tests__/producer-contract-schema.unit.test.d.ts.map +1 -0
  58. package/dist/__tests__/resolve-asset-source.test.d.ts +2 -0
  59. package/dist/__tests__/resolve-asset-source.test.d.ts.map +1 -0
  60. package/dist/__tests__/runtime-projection.unit.test.d.ts +2 -0
  61. package/dist/__tests__/runtime-projection.unit.test.d.ts.map +1 -0
  62. package/dist/__tests__/scanner-inventory.test.d.ts +2 -0
  63. package/dist/__tests__/scanner-inventory.test.d.ts.map +1 -0
  64. package/dist/__tests__/scanner-mount-cycle.test.d.ts +2 -0
  65. package/dist/__tests__/scanner-mount-cycle.test.d.ts.map +1 -0
  66. package/dist/__tests__/scriptable-pack-cli.integration.test.d.ts +2 -0
  67. package/dist/__tests__/scriptable-pack-cli.integration.test.d.ts.map +1 -0
  68. package/dist/__tests__/scriptable-pack-ddc-key.unit.test.d.ts +2 -0
  69. package/dist/__tests__/scriptable-pack-ddc-key.unit.test.d.ts.map +1 -0
  70. package/dist/__tests__/scriptable-pack.test-d.d.ts +2 -0
  71. package/dist/__tests__/scriptable-pack.test-d.d.ts.map +1 -0
  72. package/dist/__tests__/scriptable-pack.unit.test.d.ts +2 -0
  73. package/dist/__tests__/scriptable-pack.unit.test.d.ts.map +1 -0
  74. package/dist/__tests__/topology-actions.unit.test.d.ts +2 -0
  75. package/dist/__tests__/topology-actions.unit.test.d.ts.map +1 -0
  76. package/dist/__tests__/topology.unit.test.d.ts +2 -0
  77. package/dist/__tests__/topology.unit.test.d.ts.map +1 -0
  78. package/dist/artifact-path.d.ts +9 -0
  79. package/dist/artifact-path.d.ts.map +1 -0
  80. package/dist/atlas/run-atlas.d.ts +8 -0
  81. package/dist/atlas/run-atlas.d.ts.map +1 -0
  82. package/dist/atlas/shelf-pack.d.ts +55 -0
  83. package/dist/atlas/shelf-pack.d.ts.map +1 -0
  84. package/dist/bridge.d.ts +2 -0
  85. package/dist/bridge.d.ts.map +1 -0
  86. package/dist/bridge.mjs +3 -0
  87. package/dist/bridge.mjs.map +1 -0
  88. package/dist/builtin.d.ts +24 -0
  89. package/dist/builtin.d.ts.map +1 -0
  90. package/dist/cli-asset.d.ts +22 -0
  91. package/dist/cli-asset.d.ts.map +1 -0
  92. package/dist/cli-asset.mjs +9950 -0
  93. package/dist/cli-asset.mjs.map +1 -0
  94. package/dist/config.d.ts +6 -0
  95. package/dist/config.d.ts.map +1 -0
  96. package/dist/config.mjs +29 -0
  97. package/dist/config.mjs.map +1 -0
  98. package/dist/deriveAssetName.d.ts +15 -0
  99. package/dist/deriveAssetName.d.ts.map +1 -0
  100. package/dist/errors.d.ts +30 -0
  101. package/dist/errors.d.ts.map +1 -0
  102. package/dist/errors.mjs +19 -0
  103. package/dist/errors.mjs.map +1 -0
  104. package/dist/evidence/material-cook.d.ts +70 -0
  105. package/dist/evidence/material-cook.d.ts.map +1 -0
  106. package/dist/evidence/offline-evidence.d.ts +25 -0
  107. package/dist/evidence/offline-evidence.d.ts.map +1 -0
  108. package/dist/evidence/source-inventory.d.ts +29 -0
  109. package/dist/evidence/source-inventory.d.ts.map +1 -0
  110. package/dist/guid.d.ts +45 -0
  111. package/dist/guid.d.ts.map +1 -0
  112. package/dist/guid.mjs +94 -0
  113. package/dist/guid.mjs.map +1 -0
  114. package/dist/index.d.ts +15 -0
  115. package/dist/index.d.ts.map +1 -0
  116. package/dist/index.mjs +8475 -0
  117. package/dist/index.mjs.map +1 -0
  118. package/dist/material/artifact-writer.d.ts +14 -0
  119. package/dist/material/artifact-writer.d.ts.map +1 -0
  120. package/dist/mesh-bin-contract.d.ts +36 -0
  121. package/dist/mesh-bin-contract.d.ts.map +1 -0
  122. package/dist/name.mjs +22 -0
  123. package/dist/name.mjs.map +1 -0
  124. package/dist/native-cooker-registry.d.ts +28 -0
  125. package/dist/native-cooker-registry.d.ts.map +1 -0
  126. package/dist/native-cooker.d.ts +3 -0
  127. package/dist/native-cooker.d.ts.map +1 -0
  128. package/dist/native-cooker.mjs +105 -0
  129. package/dist/native-cooker.mjs.map +1 -0
  130. package/dist/producer-contract.d.ts +19 -0
  131. package/dist/producer-contract.d.ts.map +1 -0
  132. package/dist/resolve-asset-source.d.ts +4 -0
  133. package/dist/resolve-asset-source.d.ts.map +1 -0
  134. package/dist/resolve-asset-source.mjs +99 -0
  135. package/dist/resolve-asset-source.mjs.map +1 -0
  136. package/dist/runtime-projection.d.ts +14 -0
  137. package/dist/runtime-projection.d.ts.map +1 -0
  138. package/dist/scanner.d.ts +80 -0
  139. package/dist/scanner.d.ts.map +1 -0
  140. package/dist/scanner.mjs +9015 -0
  141. package/dist/scanner.mjs.map +1 -0
  142. package/dist/schema-compiled.d.ts +12 -0
  143. package/dist/schema-compiled.d.ts.map +1 -0
  144. package/dist/schema.mjs +7369 -0
  145. package/dist/schema.mjs.map +1 -0
  146. package/dist/scriptable-pack-node.d.ts +67 -0
  147. package/dist/scriptable-pack-node.d.ts.map +1 -0
  148. package/dist/scriptable-pack-node.mjs +1590 -0
  149. package/dist/scriptable-pack-node.mjs.map +1 -0
  150. package/dist/scriptable-pack-worker.d.ts +2 -0
  151. package/dist/scriptable-pack-worker.d.ts.map +1 -0
  152. package/dist/scriptable-pack-worker.mjs +223 -0
  153. package/dist/scriptable-pack-worker.mjs.map +1 -0
  154. package/dist/scriptable-pack.d.ts +525 -0
  155. package/dist/scriptable-pack.d.ts.map +1 -0
  156. package/dist/scriptable-pack.mjs +583 -0
  157. package/dist/scriptable-pack.mjs.map +1 -0
  158. package/dist/topology.d.ts +10 -0
  159. package/dist/topology.d.ts.map +1 -0
  160. package/package.json +129 -0
  161. package/schema/meta.schema.json +173 -0
  162. package/schema/pack.schema.json +232 -0
  163. package/src/__tests__/ai-recovery.unit.test.ts +114 -0
  164. package/src/__tests__/artifact-path.test.ts +41 -0
  165. package/src/__tests__/asset-evidence-error-hints.test.ts +46 -0
  166. package/src/__tests__/asset-evidence-lifecycle.test.ts +104 -0
  167. package/src/__tests__/asset-evidence-schema.test.ts +86 -0
  168. package/src/__tests__/atlas-cli-region-mismatch.test.ts +134 -0
  169. package/src/__tests__/atlas-error-code-owner.test-d.ts +23 -0
  170. package/src/__tests__/cli-asset-evidence.integration.test.ts +172 -0
  171. package/src/__tests__/cli.unit.test.ts +448 -0
  172. package/src/__tests__/evidence-source-inventory.unit.test.ts +37 -0
  173. package/src/__tests__/guid-collision.unit.test.ts +112 -0
  174. package/src/__tests__/guid-validator-surface.unit.test.ts +20 -0
  175. package/src/__tests__/load-asset-config.test.ts +121 -0
  176. package/src/__tests__/material-cook-dependencies.unit.test.ts +45 -0
  177. package/src/__tests__/material-cook-receipt.unit.test.ts +91 -0
  178. package/src/__tests__/material-cook-schema.unit.test.ts +98 -0
  179. package/src/__tests__/mesh-bin-consumer-surface.unit.test.ts +21 -0
  180. package/src/__tests__/mesh-bin-contract.unit.test.ts +32 -0
  181. package/src/__tests__/meta-schema-optional.test.ts +116 -0
  182. package/src/__tests__/meta-source-overrides.unit.test.ts +71 -0
  183. package/src/__tests__/native-cooker-registry.test.ts +64 -0
  184. package/src/__tests__/offline-evidence-artifact-verification-owner.test-d.ts +64 -0
  185. package/src/__tests__/offline-evidence.integration.test.ts +106 -0
  186. package/src/__tests__/pack-v2-fixture-migration.test.ts +64 -0
  187. package/src/__tests__/pack-v2-schema.test.ts +142 -0
  188. package/src/__tests__/pack.unit.test.ts +2218 -0
  189. package/src/__tests__/producer-contract-schema.unit.test.ts +92 -0
  190. package/src/__tests__/resolve-asset-source.test.ts +121 -0
  191. package/src/__tests__/runtime-projection.unit.test.ts +87 -0
  192. package/src/__tests__/scanner-inventory.test.ts +72 -0
  193. package/src/__tests__/scanner-mount-cycle.test.ts +232 -0
  194. package/src/__tests__/scriptable-pack-cli.integration.test.ts +47 -0
  195. package/src/__tests__/scriptable-pack-ddc-key.unit.test.ts +63 -0
  196. package/src/__tests__/scriptable-pack.test-d.ts +131 -0
  197. package/src/__tests__/scriptable-pack.unit.test.ts +747 -0
  198. package/src/__tests__/topology-actions.unit.test.ts +94 -0
  199. package/src/__tests__/topology.unit.test.ts +85 -0
  200. package/src/artifact-path.ts +69 -0
  201. package/src/atlas/run-atlas.ts +358 -0
  202. package/src/atlas/shelf-pack.ts +176 -0
  203. package/src/atlas/upng-ambient.d.ts +13 -0
  204. package/src/bridge.ts +2 -0
  205. package/src/builtin.ts +79 -0
  206. package/src/cli-asset.ts +590 -0
  207. package/src/config.ts +36 -0
  208. package/src/deriveAssetName.ts +42 -0
  209. package/src/errors.ts +47 -0
  210. package/src/evidence/material-cook.ts +332 -0
  211. package/src/evidence/offline-evidence.ts +90 -0
  212. package/src/evidence/source-inventory.ts +193 -0
  213. package/src/guid.ts +99 -0
  214. package/src/index.ts +104 -0
  215. package/src/material/artifact-writer.ts +26 -0
  216. package/src/mesh-bin-contract.ts +150 -0
  217. package/src/native-cooker-registry.ts +163 -0
  218. package/src/native-cooker.ts +2 -0
  219. package/src/producer-contract.ts +277 -0
  220. package/src/resolve-asset-source.ts +93 -0
  221. package/src/runtime-projection.ts +100 -0
  222. package/src/scanner.ts +716 -0
  223. package/src/schema/material-cook.schema.json +79 -0
  224. package/src/schema-compiled.ts +238 -0
  225. package/src/scriptable-pack-node.ts +1390 -0
  226. package/src/scriptable-pack-worker.ts +306 -0
  227. package/src/scriptable-pack.ts +884 -0
  228. package/src/topology.ts +156 -0
@@ -0,0 +1,884 @@
1
+ import type { Asset, AssetGuid, AssetPublicationEnvelope, Result } from '@forgeax/engine-types';
2
+ import { err, ok } from '@forgeax/engine-types';
3
+ import { AssetGuid as AssetGuidCodec } from './guid.js';
4
+
5
+ /** Closed ordinary kind vocabulary shared by authoring, producers, and loaders. */
6
+ export type ScriptablePackAssetKind = Asset['kind'];
7
+
8
+ /** Complete ordinary Asset kind vocabulary exposed by ScriptablePack discovery. */
9
+ export const SCRIPTABLE_PACK_ASSET_KINDS = [
10
+ 'mesh',
11
+ 'material',
12
+ 'scene',
13
+ 'texture',
14
+ 'equirect',
15
+ 'sampler',
16
+ 'font',
17
+ 'render-pipeline',
18
+ 'tileset',
19
+ 'video',
20
+ 'skeleton',
21
+ 'skin',
22
+ 'animation-clip',
23
+ 'animation-graph',
24
+ 'audio',
25
+ 'particle-effect',
26
+ ] as const satisfies readonly ScriptablePackAssetKind[];
27
+
28
+ export function isScriptablePackAssetKind(value: string): value is ScriptablePackAssetKind {
29
+ return (SCRIPTABLE_PACK_ASSET_KINDS as readonly string[]).includes(value);
30
+ }
31
+
32
+ export interface ScriptablePackAssetDeclaration<
33
+ TKind extends ScriptablePackAssetKind = ScriptablePackAssetKind,
34
+ > {
35
+ readonly guid: AssetGuid;
36
+ readonly kind: TKind;
37
+ readonly name?: string;
38
+ }
39
+
40
+ export type ScriptablePackAssetDeclarations = Readonly<
41
+ Record<string, ScriptablePackAssetDeclaration>
42
+ >;
43
+
44
+ export type ScriptablePackExternalAssets = Readonly<Record<string, AssetGuid>>;
45
+
46
+ /** Pack-facing name for the shared Engine publication envelope. */
47
+ export type ScriptablePackPublicationEnvelope = AssetPublicationEnvelope;
48
+
49
+ /** Structured domain failure permitted to cross the isolated build-time AssetReader seam. */
50
+ export interface ScriptablePackErrorProvenance {
51
+ /** Published source generation observed by the reader, when available. */
52
+ readonly generation?: number;
53
+ /** Stable producer identity for the failed output, when available. */
54
+ readonly sourceKey?: string;
55
+ /** GUID associated with the failed read or output, when available. */
56
+ readonly guid?: string;
57
+ }
58
+
59
+ export interface ScriptablePackReadError {
60
+ readonly code: string;
61
+ readonly expected: string;
62
+ readonly hint: string;
63
+ readonly detail?: unknown;
64
+ readonly provenance?: ScriptablePackErrorProvenance;
65
+ }
66
+
67
+ export type ScriptablePackAssetFor<TDescriptor extends ScriptablePackAssetDeclaration> =
68
+ TDescriptor extends { readonly kind: infer TKind extends ScriptablePackAssetKind }
69
+ ? Extract<Asset, { readonly kind: TKind }>
70
+ : never;
71
+
72
+ export type ScriptablePackOutputs<TAssets extends ScriptablePackAssetDeclarations> = {
73
+ readonly [TKey in keyof TAssets]: TAssets[TKey] extends ScriptablePackAssetDeclaration
74
+ ? ScriptablePackAssetFor<TAssets[TKey]>
75
+ : never;
76
+ };
77
+
78
+ export interface AssetReader {
79
+ /** Load a declared GUID as a durable payload; this never mints a World handle. */
80
+ readByGuid<TAsset extends Asset = Asset>(
81
+ guid: AssetGuid,
82
+ ): Promise<Result<TAsset, ScriptablePackReadError>>;
83
+ }
84
+
85
+ export type ScriptablePackBuildResult<
86
+ TAssets extends ScriptablePackAssetDeclarations,
87
+ TError,
88
+ > = Result<ScriptablePackOutputs<TAssets>, TError>;
89
+
90
+ export interface ScriptablePackDefinition<
91
+ TAssets extends ScriptablePackAssetDeclarations = ScriptablePackAssetDeclarations,
92
+ TExternal extends ScriptablePackExternalAssets = ScriptablePackExternalAssets,
93
+ TError = ScriptablePackReadError,
94
+ > {
95
+ readonly schemaVersion: '1.0.0';
96
+ readonly packageId: AssetGuid;
97
+ readonly name?: string;
98
+ readonly assets: TAssets;
99
+ readonly externalAssets: TExternal;
100
+ readonly build: (
101
+ assetReader: AssetReader,
102
+ ) =>
103
+ | ScriptablePackBuildResult<TAssets, TError>
104
+ | Promise<ScriptablePackBuildResult<TAssets, TError>>;
105
+ }
106
+
107
+ export interface ScriptablePackMetaJson {
108
+ readonly schemaVersion: '1.0.0';
109
+ readonly kind: 'external-asset-package';
110
+ readonly packageId: string;
111
+ readonly name?: string;
112
+ readonly importer: 'pack-ts';
113
+ readonly source: string;
114
+ readonly importSettings: {
115
+ readonly contractVersion: '1.0.0';
116
+ readonly externalAssets: readonly {
117
+ readonly alias: string;
118
+ readonly guid: string;
119
+ }[];
120
+ };
121
+ readonly subAssets: readonly {
122
+ readonly guid: string;
123
+ readonly sourceIndex: number;
124
+ readonly sourceKey: string;
125
+ readonly kind: ScriptablePackAssetKind;
126
+ readonly name?: string;
127
+ }[];
128
+ }
129
+
130
+ export interface ScriptablePackOperationEnvelope {
131
+ readonly requestId: string;
132
+ readonly expectedRevision?: string;
133
+ }
134
+
135
+ export type ScriptablePackGatewayOperation =
136
+ | (ScriptablePackOperationEnvelope & {
137
+ readonly kind: 'preflight';
138
+ readonly sourcePath: string;
139
+ })
140
+ | (ScriptablePackOperationEnvelope & {
141
+ readonly kind: 'create-scriptable-pack';
142
+ readonly sourcePath: string;
143
+ readonly name?: string;
144
+ readonly initialOutput: {
145
+ readonly sourceKey: string;
146
+ readonly kind: ScriptablePackAssetKind;
147
+ readonly name?: string;
148
+ };
149
+ })
150
+ | (ScriptablePackOperationEnvelope & {
151
+ readonly kind: 'add-output';
152
+ readonly sourcePath: string;
153
+ readonly sourceKey: string;
154
+ readonly assetKind: ScriptablePackAssetKind;
155
+ readonly name?: string;
156
+ })
157
+ | (ScriptablePackOperationEnvelope & {
158
+ readonly kind: 'add-external-asset';
159
+ readonly sourcePath: string;
160
+ readonly alias: string;
161
+ readonly guid: AssetGuid;
162
+ })
163
+ | (ScriptablePackOperationEnvelope & {
164
+ readonly kind: 'rename-display';
165
+ readonly sourcePath: string;
166
+ readonly target:
167
+ | { readonly kind: 'package' }
168
+ | { readonly kind: 'output'; readonly sourceKey: string };
169
+ readonly name: string;
170
+ })
171
+ | (ScriptablePackOperationEnvelope & {
172
+ readonly kind: 'remove-output';
173
+ readonly sourcePath: string;
174
+ readonly sourceKey: string;
175
+ /** Exact incoming reference identities acknowledged by the caller. */
176
+ readonly confirmIncomingRefs?: readonly string[];
177
+ })
178
+ | (ScriptablePackOperationEnvelope & {
179
+ readonly kind: 'clone-scriptable-pack';
180
+ readonly sourcePath: string;
181
+ readonly targetPath: string;
182
+ })
183
+ | (ScriptablePackOperationEnvelope & {
184
+ readonly kind: 'inspect-meta';
185
+ readonly sourcePath: string;
186
+ })
187
+ | (ScriptablePackOperationEnvelope & {
188
+ readonly kind: 'rebuild' | 'cold-cook';
189
+ readonly sourcePath: string;
190
+ });
191
+
192
+ export interface ScriptablePackMutationCapabilities {
193
+ readonly inspect: true;
194
+ readonly rebuild: true;
195
+ readonly coldCook: true;
196
+ readonly create?: true;
197
+ readonly addOutput?: true;
198
+ readonly addExternalAsset?: true;
199
+ readonly renameDisplay?: true;
200
+ readonly removeOutput?: true;
201
+ readonly clone?: true;
202
+ readonly reason?: string;
203
+ }
204
+
205
+ export interface ScriptablePackPreflight {
206
+ readonly sourcePath: string;
207
+ readonly revision: string;
208
+ readonly meta: ScriptablePackMetaJson;
209
+ readonly capabilities: ScriptablePackMutationCapabilities;
210
+ readonly incomingRefs: readonly string[];
211
+ }
212
+
213
+ export interface ScriptablePackGatewayMutation {
214
+ readonly sourcePath: string;
215
+ readonly revision: string;
216
+ readonly meta: ScriptablePackMetaJson;
217
+ }
218
+
219
+ export interface ScriptablePackAuthoringGateway {
220
+ execute(
221
+ operation: ScriptablePackGatewayOperation,
222
+ ): Promise<Result<ScriptablePackGatewayMutation | ScriptablePackPreflight, ScriptablePackError>>;
223
+ }
224
+
225
+ export type ScriptablePackAuthoringMutation =
226
+ | (ScriptablePackOperationEnvelope & {
227
+ readonly kind: 'create-scriptable-pack';
228
+ readonly sourcePath: string;
229
+ readonly packageId: AssetGuid;
230
+ readonly name?: string;
231
+ readonly initialOutput: {
232
+ readonly sourceKey: string;
233
+ readonly kind: ScriptablePackAssetKind;
234
+ readonly guid: AssetGuid;
235
+ readonly name?: string;
236
+ };
237
+ })
238
+ | (ScriptablePackOperationEnvelope & {
239
+ readonly kind: 'add-output';
240
+ readonly sourcePath: string;
241
+ readonly sourceKey: string;
242
+ readonly assetKind: ScriptablePackAssetKind;
243
+ readonly guid: AssetGuid;
244
+ readonly name?: string;
245
+ })
246
+ | Extract<
247
+ ScriptablePackGatewayOperation,
248
+ { readonly kind: 'add-external-asset' | 'rename-display' | 'remove-output' }
249
+ >
250
+ | (ScriptablePackOperationEnvelope & {
251
+ readonly kind: 'clone-scriptable-pack';
252
+ readonly sourcePath: string;
253
+ readonly targetPath: string;
254
+ readonly packageId: AssetGuid;
255
+ readonly outputGuids: Readonly<Record<string, AssetGuid>>;
256
+ });
257
+
258
+ export interface ScriptablePackAuthoringPort {
259
+ preflight(
260
+ sourcePath: string,
261
+ ): Promise<Result<Omit<ScriptablePackPreflight, 'sourcePath'>, ScriptablePackError>>;
262
+ mutate(
263
+ operation: ScriptablePackAuthoringMutation,
264
+ ): Promise<
265
+ Result<{ readonly sourcePath: string; readonly revision: string }, ScriptablePackError>
266
+ >;
267
+ inspect(
268
+ sourcePath: string,
269
+ ): Promise<
270
+ Result<
271
+ { readonly revision: string; readonly meta: ScriptablePackMetaJson },
272
+ ScriptablePackError
273
+ >
274
+ >;
275
+ rebuild(
276
+ sourcePath: string,
277
+ mode: 'rebuild' | 'cold-cook',
278
+ ): Promise<
279
+ Result<
280
+ { readonly revision: string; readonly meta: ScriptablePackMetaJson },
281
+ ScriptablePackError
282
+ >
283
+ >;
284
+ }
285
+
286
+ /** Keep GUID allocation and multi-identity operations in one auditable gateway transaction. */
287
+ export function createScriptablePackAuthoringGateway(
288
+ port: ScriptablePackAuthoringPort,
289
+ allocateGuid: () => AssetGuid = AssetGuidCodec.random,
290
+ ): ScriptablePackAuthoringGateway {
291
+ const requests = new Map<
292
+ string,
293
+ {
294
+ readonly fingerprint: string;
295
+ readonly result: Promise<
296
+ Result<ScriptablePackGatewayMutation | ScriptablePackPreflight, ScriptablePackError>
297
+ >;
298
+ }
299
+ >();
300
+
301
+ async function mutationResult(
302
+ result: Awaited<ReturnType<ScriptablePackAuthoringPort['mutate']>>,
303
+ ): Promise<Result<ScriptablePackGatewayMutation, ScriptablePackError>> {
304
+ if (!result.ok) return result;
305
+ const inspected = await port.inspect(result.value.sourcePath);
306
+ if (!inspected.ok) return inspected;
307
+ return ok({
308
+ sourcePath: result.value.sourcePath,
309
+ revision: inspected.value.revision,
310
+ meta: inspected.value.meta,
311
+ });
312
+ }
313
+
314
+ async function dispatch(
315
+ operation: ScriptablePackGatewayOperation,
316
+ ): Promise<Result<ScriptablePackGatewayMutation | ScriptablePackPreflight, ScriptablePackError>> {
317
+ switch (operation.kind) {
318
+ case 'preflight': {
319
+ const preflight = await port.preflight(operation.sourcePath);
320
+ return preflight.ok
321
+ ? ok({ sourcePath: operation.sourcePath, ...preflight.value })
322
+ : preflight;
323
+ }
324
+ case 'inspect-meta': {
325
+ const inspected = await port.inspect(operation.sourcePath);
326
+ return inspected.ok
327
+ ? ok({ sourcePath: operation.sourcePath, ...inspected.value })
328
+ : inspected;
329
+ }
330
+ case 'rebuild':
331
+ case 'cold-cook': {
332
+ const preflight = await port.preflight(operation.sourcePath);
333
+ if (!preflight.ok) return preflight;
334
+ if (
335
+ operation.expectedRevision !== undefined &&
336
+ operation.expectedRevision !== preflight.value.revision
337
+ ) {
338
+ return err({
339
+ code: 'pack-source-revision-conflict',
340
+ expected: operation.expectedRevision,
341
+ actual: preflight.value.revision,
342
+ hint: 'inspect the current source revision, then retry the build with a new requestId',
343
+ retryable: true,
344
+ recoveryActions: ['asset.preflight', 'mint-request-id'],
345
+ detail: { requestId: operation.requestId, sourcePath: operation.sourcePath },
346
+ });
347
+ }
348
+ const rebuilt = await port.rebuild(operation.sourcePath, operation.kind);
349
+ return rebuilt.ok ? ok({ sourcePath: operation.sourcePath, ...rebuilt.value }) : rebuilt;
350
+ }
351
+ case 'create-scriptable-pack':
352
+ return mutationResult(
353
+ await port.mutate({
354
+ ...operation,
355
+ packageId: allocateGuid(),
356
+ initialOutput: { ...operation.initialOutput, guid: allocateGuid() },
357
+ }),
358
+ );
359
+ case 'add-output':
360
+ return mutationResult(await port.mutate({ ...operation, guid: allocateGuid() }));
361
+ case 'clone-scriptable-pack': {
362
+ const inspected = await port.inspect(operation.sourcePath);
363
+ if (!inspected.ok) return inspected;
364
+ const outputGuids = Object.fromEntries(
365
+ inspected.value.meta.subAssets.map((asset) => [asset.sourceKey, allocateGuid()]),
366
+ );
367
+ return mutationResult(
368
+ await port.mutate({
369
+ ...operation,
370
+ packageId: allocateGuid(),
371
+ outputGuids,
372
+ }),
373
+ );
374
+ }
375
+ case 'add-external-asset':
376
+ case 'rename-display':
377
+ case 'remove-output':
378
+ return mutationResult(await port.mutate(operation));
379
+ }
380
+ }
381
+
382
+ return {
383
+ execute(operation) {
384
+ if (operation.requestId.trim().length === 0) {
385
+ return Promise.resolve(
386
+ err({
387
+ code: 'pack-source-operation-committed',
388
+ expected: 'a non-empty caller-minted requestId',
389
+ actual: operation.requestId,
390
+ hint: 'mint a new requestId and retry the operation once',
391
+ retryable: true,
392
+ recoveryActions: ['mint-request-id'],
393
+ detail: { requestId: operation.requestId, sourcePath: operation.sourcePath },
394
+ }),
395
+ );
396
+ }
397
+ const fingerprint = JSON.stringify(operation);
398
+ const prior = requests.get(operation.requestId);
399
+ if (prior !== undefined) {
400
+ if (prior.fingerprint === fingerprint) return prior.result;
401
+ return Promise.resolve(
402
+ err({
403
+ code: 'pack-source-operation-committed',
404
+ expected: 'one immutable operation per requestId',
405
+ actual: operation.kind,
406
+ hint: 'read the original operation result or mint a new requestId',
407
+ retryable: false,
408
+ recoveryActions: ['read-operation-run', 'mint-request-id'],
409
+ detail: { requestId: operation.requestId, sourcePath: operation.sourcePath },
410
+ }),
411
+ );
412
+ }
413
+ const result = dispatch(operation);
414
+ requests.set(operation.requestId, { fingerprint, result });
415
+ return result;
416
+ },
417
+ };
418
+ }
419
+
420
+ const SCRIPTABLE_PACK_REQUEST_ID_SCHEMA = {
421
+ type: 'string',
422
+ minLength: 1,
423
+ maxLength: 128,
424
+ pattern: '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$',
425
+ description: 'Caller-minted identity for one idempotent ScriptablePack operation.',
426
+ } as const;
427
+ const SCRIPTABLE_PACK_SOURCE_PATH_SCHEMA = {
428
+ type: 'string',
429
+ minLength: 8,
430
+ pattern: '^[^/].*\\.pack\\.ts$',
431
+ description: 'Game-root-relative ScriptablePack source path.',
432
+ } as const;
433
+ const SCRIPTABLE_PACK_OWNER_GUID_SCHEMA = {
434
+ type: 'string',
435
+ format: 'uuid',
436
+ description: 'Catalog GUID used to resolve the producer-owned source subject.',
437
+ } as const;
438
+ const SCRIPTABLE_PACK_REVISION_SCHEMA = {
439
+ type: 'string',
440
+ minLength: 64,
441
+ maxLength: 64,
442
+ pattern: '^[a-f0-9]{64}$',
443
+ description: 'SHA-256 source revision returned by preflight.',
444
+ } as const;
445
+ const SCRIPTABLE_PACK_SOURCE_KEY_SCHEMA = { type: 'string', minLength: 1 } as const;
446
+ const SCRIPTABLE_PACK_ASSET_KIND_SCHEMA = {
447
+ enum: SCRIPTABLE_PACK_ASSET_KINDS,
448
+ description: 'Ordinary Asset kind supported by the ScriptablePack producer/loader matrix.',
449
+ } as const;
450
+ const SCRIPTABLE_PACK_NAME_SCHEMA = { type: 'string', minLength: 1 } as const;
451
+
452
+ function scriptablePackArgsSchema(
453
+ properties: Readonly<Record<string, unknown>>,
454
+ required: readonly string[],
455
+ options: { readonly subject?: boolean; readonly guidSubject?: boolean } = {},
456
+ ) {
457
+ const subjectRequired = ['requestId', ...required];
458
+ return {
459
+ type: 'object',
460
+ properties: {
461
+ sourcePath: SCRIPTABLE_PACK_SOURCE_PATH_SCHEMA,
462
+ ownerGuid: SCRIPTABLE_PACK_OWNER_GUID_SCHEMA,
463
+ requestId: SCRIPTABLE_PACK_REQUEST_ID_SCHEMA,
464
+ ...properties,
465
+ },
466
+ required: subjectRequired,
467
+ ...(options.subject === false
468
+ ? {}
469
+ : {
470
+ anyOf: [
471
+ { required: [...subjectRequired, 'sourcePath'] },
472
+ { required: [...subjectRequired, 'ownerGuid'] },
473
+ ...(options.guidSubject === false ? [] : [{ required: [...subjectRequired, 'guid'] }]),
474
+ ],
475
+ }),
476
+ additionalProperties: false,
477
+ } as const;
478
+ }
479
+
480
+ export const SOURCE_AUTHORING_OPERATION_DESCRIPTORS = [
481
+ {
482
+ id: 'asset-source.create',
483
+ kind: 'create',
484
+ domain: 'session',
485
+ title: 'Create Asset Source',
486
+ argsSchema: scriptablePackArgsSchema(
487
+ {
488
+ name: SCRIPTABLE_PACK_NAME_SCHEMA,
489
+ initialOutput: {
490
+ type: 'object',
491
+ properties: {
492
+ sourceKey: SCRIPTABLE_PACK_SOURCE_KEY_SCHEMA,
493
+ kind: SCRIPTABLE_PACK_ASSET_KIND_SCHEMA,
494
+ name: SCRIPTABLE_PACK_NAME_SCHEMA,
495
+ },
496
+ required: ['sourceKey', 'kind'],
497
+ additionalProperties: false,
498
+ },
499
+ },
500
+ ['sourcePath', 'initialOutput'],
501
+ { subject: false },
502
+ ),
503
+ },
504
+ {
505
+ id: 'asset-source.add-output',
506
+ kind: 'add-output',
507
+ domain: 'session',
508
+ title: 'Add Asset Source Output',
509
+ argsSchema: scriptablePackArgsSchema(
510
+ {
511
+ sourceKey: SCRIPTABLE_PACK_SOURCE_KEY_SCHEMA,
512
+ assetKind: SCRIPTABLE_PACK_ASSET_KIND_SCHEMA,
513
+ name: SCRIPTABLE_PACK_NAME_SCHEMA,
514
+ expectedRevision: SCRIPTABLE_PACK_REVISION_SCHEMA,
515
+ },
516
+ ['sourceKey', 'assetKind', 'expectedRevision'],
517
+ ),
518
+ },
519
+ {
520
+ id: 'asset-source.add-external-asset',
521
+ kind: 'add-external-asset',
522
+ domain: 'session',
523
+ title: 'Add Asset Source External Asset',
524
+ argsSchema: scriptablePackArgsSchema(
525
+ {
526
+ alias: SCRIPTABLE_PACK_SOURCE_KEY_SCHEMA,
527
+ guid: { type: 'string', format: 'uuid' },
528
+ expectedRevision: SCRIPTABLE_PACK_REVISION_SCHEMA,
529
+ },
530
+ ['alias', 'guid', 'expectedRevision'],
531
+ { guidSubject: false },
532
+ ),
533
+ },
534
+ {
535
+ id: 'asset-source.rename',
536
+ kind: 'rename',
537
+ domain: 'session',
538
+ title: 'Rename Asset Source Display Name',
539
+ argsSchema: scriptablePackArgsSchema(
540
+ {
541
+ target: {
542
+ oneOf: [
543
+ {
544
+ type: 'object',
545
+ properties: { kind: { const: 'package' } },
546
+ required: ['kind'],
547
+ additionalProperties: false,
548
+ },
549
+ {
550
+ type: 'object',
551
+ properties: {
552
+ kind: { const: 'output' },
553
+ sourceKey: SCRIPTABLE_PACK_SOURCE_KEY_SCHEMA,
554
+ },
555
+ required: ['kind', 'sourceKey'],
556
+ additionalProperties: false,
557
+ },
558
+ ],
559
+ },
560
+ name: SCRIPTABLE_PACK_NAME_SCHEMA,
561
+ expectedRevision: SCRIPTABLE_PACK_REVISION_SCHEMA,
562
+ },
563
+ ['target', 'name', 'expectedRevision'],
564
+ ),
565
+ },
566
+ {
567
+ id: 'asset-source.remove-output',
568
+ kind: 'remove-output',
569
+ domain: 'session',
570
+ title: 'Remove Asset Source Output',
571
+ destructive: true,
572
+ argsSchema: scriptablePackArgsSchema(
573
+ {
574
+ sourceKey: SCRIPTABLE_PACK_SOURCE_KEY_SCHEMA,
575
+ confirmIncomingRefs: { type: 'array', items: { type: 'string' } },
576
+ expectedRevision: SCRIPTABLE_PACK_REVISION_SCHEMA,
577
+ },
578
+ ['sourceKey', 'expectedRevision'],
579
+ ),
580
+ },
581
+ {
582
+ id: 'asset-source.clone',
583
+ kind: 'clone',
584
+ domain: 'session',
585
+ title: 'Clone Asset Source',
586
+ argsSchema: scriptablePackArgsSchema(
587
+ {
588
+ targetPath: SCRIPTABLE_PACK_SOURCE_PATH_SCHEMA,
589
+ expectedRevision: SCRIPTABLE_PACK_REVISION_SCHEMA,
590
+ },
591
+ ['targetPath', 'expectedRevision'],
592
+ ),
593
+ },
594
+ {
595
+ id: 'asset-source.rebuild',
596
+ kind: 'rebuild',
597
+ domain: 'session',
598
+ title: 'Rebuild Asset Source',
599
+ argsSchema: scriptablePackArgsSchema({ expectedRevision: SCRIPTABLE_PACK_REVISION_SCHEMA }, [
600
+ 'expectedRevision',
601
+ ]),
602
+ },
603
+ {
604
+ id: 'asset-source.cold-cook',
605
+ kind: 'cold-cook',
606
+ domain: 'session',
607
+ title: 'Cold Cook Asset Source',
608
+ argsSchema: scriptablePackArgsSchema({ expectedRevision: SCRIPTABLE_PACK_REVISION_SCHEMA }, [
609
+ 'expectedRevision',
610
+ ]),
611
+ },
612
+ ] as const;
613
+
614
+ export type ScriptablePackError =
615
+ | {
616
+ readonly code: 'pack-source-load-failed';
617
+ readonly expected: string;
618
+ readonly hint: string;
619
+ readonly detail: {
620
+ readonly sourcePath: string;
621
+ readonly reason: 'module-load' | 'timeout' | 'source-changed';
622
+ readonly phase: 'module-load' | 'build';
623
+ readonly diagnostic: string;
624
+ readonly timeoutMs?: number;
625
+ };
626
+ }
627
+ | {
628
+ readonly code: 'pack-source-definition-invalid';
629
+ readonly expected: string;
630
+ readonly hint: string;
631
+ readonly detail: {
632
+ readonly sourcePath?: string;
633
+ readonly propertyPath: string;
634
+ readonly actual: string;
635
+ };
636
+ }
637
+ | {
638
+ readonly code: 'pack-source-output-invalid';
639
+ readonly expected: string;
640
+ readonly hint: string;
641
+ readonly detail: {
642
+ readonly missingGuids: readonly string[];
643
+ readonly unexpectedSourceKeys: readonly string[];
644
+ readonly kindMismatches: readonly {
645
+ readonly sourceKey: string;
646
+ readonly expected: string;
647
+ readonly actual: string;
648
+ }[];
649
+ };
650
+ }
651
+ | {
652
+ readonly code: 'pack-source-external-closure-mismatch';
653
+ readonly expected: string;
654
+ readonly hint: string;
655
+ readonly detail: {
656
+ readonly undeclaredReferencedGuids: readonly string[];
657
+ readonly undeclaredReadGuids: readonly string[];
658
+ readonly unusedDeclaredGuids: readonly string[];
659
+ };
660
+ }
661
+ | {
662
+ readonly code:
663
+ | 'pack-source-path-invalid'
664
+ | 'pack-source-revision-conflict'
665
+ | 'pack-source-mutation-unsupported'
666
+ | 'pack-source-reference-conflict'
667
+ | 'pack-source-write-failed'
668
+ | 'pack-source-build-cycle'
669
+ | 'pack-source-publication-timeout'
670
+ | 'pack-source-operation-committed';
671
+ readonly expected: string;
672
+ readonly actual?: string;
673
+ readonly hint: string;
674
+ readonly retryable: boolean;
675
+ readonly recoveryActions: readonly string[];
676
+ readonly detail: {
677
+ readonly requestId?: string;
678
+ readonly sourcePath?: string;
679
+ readonly packageId?: string;
680
+ readonly sourceKey?: string;
681
+ readonly incomingRefs?: readonly string[];
682
+ };
683
+ };
684
+
685
+ function actual(value: unknown): string {
686
+ if (value === null) return 'null';
687
+ if (Array.isArray(value)) return 'array';
688
+ if (ArrayBuffer.isView(value)) return value.constructor.name;
689
+ return typeof value;
690
+ }
691
+
692
+ function invalid(
693
+ propertyPath: string,
694
+ expected: string,
695
+ value: unknown,
696
+ sourcePath?: string,
697
+ ): Result<never, ScriptablePackError> {
698
+ return err({
699
+ code: 'pack-source-definition-invalid',
700
+ expected,
701
+ hint: 'repair the default exported ScriptablePack definition, then inspect Meta again',
702
+ detail: {
703
+ ...(sourcePath === undefined ? {} : { sourcePath }),
704
+ propertyPath,
705
+ actual: actual(value),
706
+ },
707
+ });
708
+ }
709
+
710
+ function isRecord(value: unknown): value is Record<string, unknown> {
711
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
712
+ }
713
+
714
+ function isAssetGuid(value: unknown): value is AssetGuid {
715
+ return value instanceof Uint8Array && value.byteLength === 16;
716
+ }
717
+
718
+ function guidKey(guid: AssetGuid): string {
719
+ return AssetGuidCodec.format(guid);
720
+ }
721
+
722
+ function freezeDefinition(
723
+ definition: ScriptablePackDefinition,
724
+ ): Readonly<ScriptablePackDefinition> {
725
+ const assets = Object.fromEntries(
726
+ Object.entries(definition.assets).map(([sourceKey, descriptor]) => [
727
+ sourceKey,
728
+ Object.freeze({ ...descriptor, guid: descriptor.guid.slice() as AssetGuid }),
729
+ ]),
730
+ );
731
+ const externalAssets = Object.fromEntries(
732
+ Object.entries(definition.externalAssets).map(([alias, guid]) => [
733
+ alias,
734
+ guid.slice() as AssetGuid,
735
+ ]),
736
+ );
737
+ return Object.freeze({
738
+ ...definition,
739
+ packageId: definition.packageId.slice() as AssetGuid,
740
+ assets: Object.freeze(assets),
741
+ externalAssets: Object.freeze(externalAssets),
742
+ });
743
+ }
744
+
745
+ export function validateScriptablePackDefinition(
746
+ value: unknown,
747
+ sourcePath?: string,
748
+ ): Result<Readonly<ScriptablePackDefinition>, ScriptablePackError> {
749
+ if (!isRecord(value))
750
+ return invalid('$', 'a ScriptablePack definition object', value, sourcePath);
751
+ if (value.schemaVersion !== '1.0.0') {
752
+ return invalid('$.schemaVersion', "the literal '1.0.0'", value.schemaVersion, sourcePath);
753
+ }
754
+ if (!isAssetGuid(value.packageId)) {
755
+ return invalid('$.packageId', 'a 16-byte AssetGuid', value.packageId, sourcePath);
756
+ }
757
+ if (value.name !== undefined && typeof value.name !== 'string') {
758
+ return invalid('$.name', 'a string when present', value.name, sourcePath);
759
+ }
760
+ if (!isRecord(value.assets) || Object.keys(value.assets).length === 0) {
761
+ return invalid(
762
+ '$.assets',
763
+ 'a non-empty sourceKey to descriptor object',
764
+ value.assets,
765
+ sourcePath,
766
+ );
767
+ }
768
+ if (!isRecord(value.externalAssets)) {
769
+ return invalid(
770
+ '$.externalAssets',
771
+ 'an alias to AssetGuid object',
772
+ value.externalAssets,
773
+ sourcePath,
774
+ );
775
+ }
776
+ if (typeof value.build !== 'function') {
777
+ return invalid('$.build', 'a build(assetReader) function', value.build, sourcePath);
778
+ }
779
+
780
+ const packageGuid = guidKey(value.packageId);
781
+ const localGuids = new Set<string>();
782
+ for (const [sourceKey, descriptor] of Object.entries(value.assets)) {
783
+ if (sourceKey.trim().length === 0) {
784
+ return invalid('$.assets', 'non-empty sourceKey object keys', sourceKey, sourcePath);
785
+ }
786
+ if (!isRecord(descriptor)) {
787
+ return invalid(
788
+ `$.assets[${JSON.stringify(sourceKey)}]`,
789
+ 'an asset descriptor',
790
+ descriptor,
791
+ sourcePath,
792
+ );
793
+ }
794
+ if (!isAssetGuid(descriptor.guid)) {
795
+ return invalid(
796
+ `$.assets[${JSON.stringify(sourceKey)}].guid`,
797
+ 'a 16-byte AssetGuid',
798
+ descriptor.guid,
799
+ sourcePath,
800
+ );
801
+ }
802
+ if (typeof descriptor.kind !== 'string' || !isScriptablePackAssetKind(descriptor.kind)) {
803
+ return invalid(
804
+ `$.assets[${JSON.stringify(sourceKey)}].kind`,
805
+ 'one of SCRIPTABLE_PACK_ASSET_KINDS',
806
+ descriptor.kind,
807
+ sourcePath,
808
+ );
809
+ }
810
+ if (descriptor.name !== undefined && typeof descriptor.name !== 'string') {
811
+ return invalid(
812
+ `$.assets[${JSON.stringify(sourceKey)}].name`,
813
+ 'a string when present',
814
+ descriptor.name,
815
+ sourcePath,
816
+ );
817
+ }
818
+ const guid = guidKey(descriptor.guid);
819
+ if (guid === packageGuid || localGuids.has(guid)) {
820
+ return invalid(
821
+ `$.assets[${JSON.stringify(sourceKey)}].guid`,
822
+ 'a GUID unique within the package and distinct from packageId',
823
+ descriptor.guid,
824
+ sourcePath,
825
+ );
826
+ }
827
+ localGuids.add(guid);
828
+ }
829
+
830
+ const externalGuids = new Set<string>();
831
+ for (const [alias, guidValue] of Object.entries(value.externalAssets)) {
832
+ if (alias.trim().length === 0) {
833
+ return invalid('$.externalAssets', 'non-empty alias object keys', alias, sourcePath);
834
+ }
835
+ if (!isAssetGuid(guidValue)) {
836
+ return invalid(
837
+ `$.externalAssets[${JSON.stringify(alias)}]`,
838
+ 'a 16-byte AssetGuid',
839
+ guidValue,
840
+ sourcePath,
841
+ );
842
+ }
843
+ const guid = guidKey(guidValue);
844
+ if (guid === packageGuid || localGuids.has(guid) || externalGuids.has(guid)) {
845
+ return invalid(
846
+ `$.externalAssets[${JSON.stringify(alias)}]`,
847
+ 'a unique foreign GUID not owned by this package',
848
+ guidValue,
849
+ sourcePath,
850
+ );
851
+ }
852
+ externalGuids.add(guid);
853
+ }
854
+
855
+ return ok(freezeDefinition(value as unknown as ScriptablePackDefinition));
856
+ }
857
+
858
+ export function projectScriptablePackMeta(
859
+ definition: ScriptablePackDefinition,
860
+ source: string,
861
+ ): ScriptablePackMetaJson {
862
+ const subAssets = Object.entries(definition.assets)
863
+ .sort(([left], [right]) => left.localeCompare(right))
864
+ .map(([sourceKey, descriptor], sourceIndex) => ({
865
+ guid: guidKey(descriptor.guid),
866
+ sourceIndex,
867
+ sourceKey,
868
+ kind: descriptor.kind,
869
+ ...(descriptor.name === undefined ? {} : { name: descriptor.name }),
870
+ }));
871
+ const externalAssets = Object.entries(definition.externalAssets)
872
+ .sort(([left], [right]) => left.localeCompare(right))
873
+ .map(([alias, guid]) => ({ alias, guid: guidKey(guid) }));
874
+ return {
875
+ schemaVersion: '1.0.0',
876
+ kind: 'external-asset-package',
877
+ packageId: guidKey(definition.packageId),
878
+ ...(definition.name === undefined ? {} : { name: definition.name }),
879
+ importer: 'pack-ts',
880
+ source,
881
+ importSettings: { contractVersion: '1.0.0', externalAssets },
882
+ subAssets,
883
+ };
884
+ }