@neocompose/cli 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/neo.mjs CHANGED
@@ -1386,11 +1386,11 @@ var init_source_protocol = __esm({
1386
1386
  "src/schema/generated/source-protocol.ts"() {
1387
1387
  "use strict";
1388
1388
  SCHEMA_MANIFEST_FORMAT_VERSION = 3;
1389
- SCHEMA_MANIFEST_CONTRACT_VERSION = "3.0";
1389
+ SCHEMA_MANIFEST_CONTRACT_VERSION = "3.1";
1390
1390
  SCHEMA_SOURCE_MANIFEST_NAME = "neo-schema-manifest-v3";
1391
- SCHEMA_COMPILER_PROTOCOL_VERSION = 4;
1392
- SCHEMA_SDK_VERSION = "3.0.0";
1393
- SCHEMA_CLI_VERSION = "0.3.0";
1391
+ SCHEMA_COMPILER_PROTOCOL_VERSION = 5;
1392
+ SCHEMA_SDK_VERSION = "3.1.0";
1393
+ SCHEMA_CLI_VERSION = "0.4.0";
1394
1394
  SCHEMA_API_TARGET = "netstandard2.1";
1395
1395
  SCHEMA_REFERENCE_ASSEMBLY_SET = "NETStandard.Library.Ref/2.1.0";
1396
1396
  SCHEMA_REQUIRED_TOOLING_FILES = [
@@ -1455,7 +1455,7 @@ var init_source_protocol = __esm({
1455
1455
  "CompilerResponse": {
1456
1456
  "fields": {
1457
1457
  "protocolVersion": {
1458
- "literal": 4
1458
+ "literal": 5
1459
1459
  },
1460
1460
  "id": {
1461
1461
  "type": "string",
@@ -1560,7 +1560,8 @@ var init_source_protocol = __esm({
1560
1560
  "audioTemplate",
1561
1561
  "localization",
1562
1562
  "localizationStatus",
1563
- "registry"
1563
+ "registry",
1564
+ "relationConfiguration"
1564
1565
  ]
1565
1566
  },
1566
1567
  "id": {
@@ -9981,7 +9982,7 @@ var init_schema_contract_generated = __esm({
9981
9982
  "../packages/neoscript-language/src/schema-contract.generated.ts"() {
9982
9983
  "use strict";
9983
9984
  SCHEMA_MANIFEST_FORMAT_VERSION2 = 3;
9984
- SCHEMA_MANIFEST_CONTRACT_VERSION2 = "3.0";
9985
+ SCHEMA_MANIFEST_CONTRACT_VERSION2 = "3.1";
9985
9986
  }
9986
9987
  });
9987
9988
 
@@ -12106,7 +12107,8 @@ function assertSchemaManifestV3(value) {
12106
12107
  "textureTemplates",
12107
12108
  "audioTemplates",
12108
12109
  "localization",
12109
- "localizationStatuses"
12110
+ "localizationStatuses",
12111
+ "internalRecordRelations"
12110
12112
  ]);
12111
12113
  exactValue(
12112
12114
  manifest.formatVersion,
@@ -12135,6 +12137,11 @@ function assertSchemaManifestV3(value) {
12135
12137
  "$.localizationStatuses",
12136
12138
  assertLocalizationStatus
12137
12139
  );
12140
+ arrayOf(
12141
+ manifest.internalRecordRelations,
12142
+ "$.internalRecordRelations",
12143
+ assertInternalRecordRelation
12144
+ );
12138
12145
  uniqueIds(manifest.classes, "$.classes");
12139
12146
  uniqueIds(manifest.members, "$.members");
12140
12147
  uniqueIds(manifest.interfaces, "$.interfaces");
@@ -12142,8 +12149,283 @@ function assertSchemaManifestV3(value) {
12142
12149
  uniqueIds(manifest.textureTemplates, "$.textureTemplates");
12143
12150
  uniqueIds(manifest.audioTemplates, "$.audioTemplates");
12144
12151
  uniqueIds(manifest.localizationStatuses, "$.localizationStatuses");
12152
+ uniqueIds(manifest.internalRecordRelations, "$.internalRecordRelations");
12153
+ assertInternalRecordRelationInvariants(manifest);
12145
12154
  assertStaticMemberInvariants(manifest);
12146
12155
  }
12156
+ function assertInternalRecordRelation(value, path) {
12157
+ const relation = objectAt(value, path, [
12158
+ "id",
12159
+ "source",
12160
+ "relationKind",
12161
+ "sourceRecordKind",
12162
+ "sourceRecordId",
12163
+ "targetRecordKind",
12164
+ "targetRecordId",
12165
+ "order"
12166
+ ]);
12167
+ nonEmptyString(relation.id, `${path}.id`);
12168
+ assertSourceIdentity(relation.source, `${path}.source`);
12169
+ nonEmptyString(relation.relationKind, `${path}.relationKind`);
12170
+ projectRecordKind(relation.sourceRecordKind, `${path}.sourceRecordKind`);
12171
+ nonEmptyString(relation.sourceRecordId, `${path}.sourceRecordId`);
12172
+ projectRecordKind(relation.targetRecordKind, `${path}.targetRecordKind`);
12173
+ nonEmptyString(relation.targetRecordId, `${path}.targetRecordId`);
12174
+ if (relation.order !== null && (typeof relation.order !== "number" || !Number.isInteger(relation.order) || relation.order < 0)) {
12175
+ invalid(`${path}.order`, "expected null or a non-negative integer.");
12176
+ }
12177
+ }
12178
+ function assertInternalRecordRelationInvariants(manifest) {
12179
+ const relations = requireArray(
12180
+ manifest.internalRecordRelations,
12181
+ "$.internalRecordRelations"
12182
+ ).map(
12183
+ (value, index) => requireRecord(value, `$.internalRecordRelations[${index}]`)
12184
+ );
12185
+ const seenEdges = /* @__PURE__ */ new Map();
12186
+ const orderedGroups = /* @__PURE__ */ new Map();
12187
+ const classes = requireArray(manifest.classes, "$.classes").map(
12188
+ (value, index) => requireRecord(value, `$.classes[${index}]`)
12189
+ );
12190
+ const classesById = new Map(
12191
+ classes.map((schemaClass2) => [String(schemaClass2.id), schemaClass2])
12192
+ );
12193
+ const endpoints = manifestEndpointKeys(manifest, relations);
12194
+ const singletonSources = /* @__PURE__ */ new Map();
12195
+ const graph = /* @__PURE__ */ new Map();
12196
+ for (const [index, relation] of relations.entries()) {
12197
+ const path = `$.internalRecordRelations[${index}]`;
12198
+ const edgeKey = [
12199
+ relation.relationKind,
12200
+ relation.sourceRecordKind,
12201
+ relation.sourceRecordId,
12202
+ relation.targetRecordKind,
12203
+ relation.targetRecordId
12204
+ ].join("\0");
12205
+ const duplicate = seenEdges.get(edgeKey);
12206
+ if (duplicate !== void 0) {
12207
+ invalid(
12208
+ path,
12209
+ `duplicates the qualified edge declared at $.internalRecordRelations[${duplicate}].`
12210
+ );
12211
+ }
12212
+ seenEdges.set(edgeKey, index);
12213
+ const sourceKey = `${String(relation.sourceRecordKind)}:${String(relation.sourceRecordId)}`;
12214
+ const targetKey = `${String(relation.targetRecordKind)}:${String(relation.targetRecordId)}`;
12215
+ if (manifestContainsEndpointKind(String(relation.sourceRecordKind)) && !endpoints.has(sourceKey)) {
12216
+ invalid(
12217
+ `${path}.sourceRecordId`,
12218
+ `references missing ${String(relation.sourceRecordKind)} record ${JSON.stringify(relation.sourceRecordId)}.`
12219
+ );
12220
+ }
12221
+ if (manifestContainsEndpointKind(String(relation.targetRecordKind)) && !endpoints.has(targetKey)) {
12222
+ invalid(
12223
+ `${path}.targetRecordId`,
12224
+ `references missing ${String(relation.targetRecordKind)} record ${JSON.stringify(relation.targetRecordId)}.`
12225
+ );
12226
+ }
12227
+ if (sourceKey === targetKey) {
12228
+ invalid(
12229
+ path,
12230
+ "self-edges are not allowed by the schema authoring contract."
12231
+ );
12232
+ }
12233
+ const targets = graph.get(sourceKey) ?? [];
12234
+ targets.push(targetKey);
12235
+ graph.set(sourceKey, targets);
12236
+ const contract = WORLD_RELATION_CONTRACTS[String(relation.relationKind)];
12237
+ if (contract === void 0) {
12238
+ invalid(
12239
+ `${path}.relationKind`,
12240
+ `uses unknown relation kind ${JSON.stringify(relation.relationKind)}.`
12241
+ );
12242
+ } else {
12243
+ if (relation.sourceRecordKind !== contract.sourceRecordKind || relation.targetRecordKind !== contract.targetRecordKind) {
12244
+ invalid(
12245
+ path,
12246
+ `${String(relation.relationKind)} requires ${contract.sourceRecordKind}-to-${contract.targetRecordKind} endpoints.`
12247
+ );
12248
+ }
12249
+ const sourceClass = contract.sourceRecordKind === "class" ? classesById.get(String(relation.sourceRecordId)) : void 0;
12250
+ const targetClass = contract.targetRecordKind === "class" ? classesById.get(String(relation.targetRecordId)) : void 0;
12251
+ if (sourceClass !== void 0) {
12252
+ const actual = effectiveWorldKind(sourceClass, classesById);
12253
+ if (actual !== contract.sourceWorldKind) {
12254
+ invalid(
12255
+ `${path}.sourceRecordId`,
12256
+ `${String(relation.relationKind)} requires a ${contract.sourceWorldKind} source Class; received ${actual ?? "no world kind"}.`
12257
+ );
12258
+ }
12259
+ }
12260
+ if (targetClass !== void 0) {
12261
+ const actual = effectiveWorldKind(targetClass, classesById);
12262
+ if (actual !== contract.targetWorldKind) {
12263
+ invalid(
12264
+ `${path}.targetRecordId`,
12265
+ `${String(relation.relationKind)} requires a ${contract.targetWorldKind} target Class; received ${actual ?? "no world kind"}.`
12266
+ );
12267
+ }
12268
+ if (!contract.abstractTarget && targetClass.declarationModifier === "abstract") {
12269
+ invalid(
12270
+ `${path}.targetRecordId`,
12271
+ `${String(relation.relationKind)} requires a concrete target Class.`
12272
+ );
12273
+ }
12274
+ }
12275
+ if (contract.ordered !== (relation.order !== null)) {
12276
+ invalid(
12277
+ `${path}.order`,
12278
+ contract.ordered ? `${String(relation.relationKind)} requires an ordered position.` : `${String(relation.relationKind)} is unordered and must use null.`
12279
+ );
12280
+ }
12281
+ if (contract.singleton) {
12282
+ const singletonKey = `${String(relation.relationKind)}\0${sourceKey}`;
12283
+ const duplicateSingleton = singletonSources.get(singletonKey);
12284
+ if (duplicateSingleton !== void 0) {
12285
+ invalid(
12286
+ path,
12287
+ `declares a second direct singleton already present at $.internalRecordRelations[${duplicateSingleton}].`
12288
+ );
12289
+ }
12290
+ singletonSources.set(singletonKey, index);
12291
+ }
12292
+ }
12293
+ if (relation.order === null) continue;
12294
+ const groupKey = [
12295
+ relation.relationKind,
12296
+ relation.sourceRecordKind,
12297
+ relation.sourceRecordId
12298
+ ].join("\0");
12299
+ const group = orderedGroups.get(groupKey) ?? [];
12300
+ group.push({ order: Number(relation.order), index });
12301
+ orderedGroups.set(groupKey, group);
12302
+ }
12303
+ for (const group of orderedGroups.values()) {
12304
+ group.sort((left, right) => left.order - right.order);
12305
+ for (const [expected, entry] of group.entries()) {
12306
+ if (entry.order === expected) continue;
12307
+ invalid(
12308
+ `$.internalRecordRelations[${entry.index}].order`,
12309
+ `ordered direct relation positions must be contiguous from zero; expected ${expected}.`
12310
+ );
12311
+ }
12312
+ }
12313
+ assertAcyclicRelationGraph(graph);
12314
+ assertDefaultLayerCompatibility(relations, classesById);
12315
+ }
12316
+ function effectiveWorldKind(schemaClass2, classesById) {
12317
+ const visited = /* @__PURE__ */ new Set();
12318
+ let current = schemaClass2;
12319
+ while (current !== void 0) {
12320
+ const id = String(current.id);
12321
+ if (visited.has(id)) return null;
12322
+ visited.add(id);
12323
+ const system = current.system;
12324
+ if (system !== null && typeof system === "object" && !Array.isArray(system)) {
12325
+ const worldKind2 = system.worldKind;
12326
+ if (typeof worldKind2 === "string") return worldKind2;
12327
+ }
12328
+ current = typeof current.extendsClassId === "string" ? classesById.get(current.extendsClassId) : void 0;
12329
+ }
12330
+ return null;
12331
+ }
12332
+ function manifestEndpointKeys(manifest, relations) {
12333
+ const result = /* @__PURE__ */ new Set();
12334
+ const collections = [
12335
+ ["classes", "class"],
12336
+ ["members", "member"],
12337
+ ["interfaces", "interface"],
12338
+ ["enums", "enum"],
12339
+ ["textureTemplates", "unity-texture-template"],
12340
+ ["audioTemplates", "unity-audio-clip-template"],
12341
+ ["localizationStatuses", "localization-status"]
12342
+ ];
12343
+ for (const [collection, kind] of collections) {
12344
+ for (const entry of requireArray(manifest[collection], `$.${collection}`)) {
12345
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry))
12346
+ continue;
12347
+ const id = entry.id;
12348
+ if (typeof id === "string") result.add(`${kind}:${id}`);
12349
+ }
12350
+ }
12351
+ const localization = requireRecord(manifest.localization, "$.localization");
12352
+ if (typeof localization.id === "string") {
12353
+ result.add(`localization-config:${localization.id}`);
12354
+ }
12355
+ for (const relation of relations) {
12356
+ result.add(`internal-record-relation:${String(relation.id)}`);
12357
+ }
12358
+ return result;
12359
+ }
12360
+ function manifestContainsEndpointKind(recordKind) {
12361
+ return MANIFEST_ENDPOINT_KINDS.has(recordKind);
12362
+ }
12363
+ function assertAcyclicRelationGraph(graph) {
12364
+ const complete2 = /* @__PURE__ */ new Set();
12365
+ const visiting = /* @__PURE__ */ new Set();
12366
+ const visit = (node) => {
12367
+ if (complete2.has(node)) return;
12368
+ if (visiting.has(node)) {
12369
+ invalid(
12370
+ "$.internalRecordRelations",
12371
+ `declared relations contain a cycle through ${JSON.stringify(node)}.`
12372
+ );
12373
+ }
12374
+ visiting.add(node);
12375
+ for (const target of graph.get(node) ?? []) visit(target);
12376
+ visiting.delete(node);
12377
+ complete2.add(node);
12378
+ };
12379
+ for (const node of graph.keys()) visit(node);
12380
+ }
12381
+ function assertDefaultLayerCompatibility(relations, classesById) {
12382
+ const pairs = [
12383
+ ["world.tile.default-layer", "world.tile.compatible-layer"],
12384
+ ["world.object.default-layer", "world.object.compatible-layer"]
12385
+ ];
12386
+ for (const [defaultKind, compatibleKind] of pairs) {
12387
+ for (const relation of relations.filter(
12388
+ (candidate) => candidate.relationKind === defaultKind
12389
+ )) {
12390
+ const sourceAncestors = classAncestorIds(
12391
+ String(relation.sourceRecordId),
12392
+ classesById
12393
+ );
12394
+ const targetAncestors = classAncestorIds(
12395
+ String(relation.targetRecordId),
12396
+ classesById
12397
+ );
12398
+ const covered = relations.some(
12399
+ (candidate) => candidate.relationKind === compatibleKind && sourceAncestors.includes(String(candidate.sourceRecordId)) && targetAncestors.includes(String(candidate.targetRecordId))
12400
+ );
12401
+ if (!covered) {
12402
+ invalid(
12403
+ "$.internalRecordRelations",
12404
+ `${defaultKind} relation ${JSON.stringify(relation.id)} must also be covered by ${compatibleKind}.`
12405
+ );
12406
+ }
12407
+ }
12408
+ }
12409
+ }
12410
+ function classAncestorIds(classId, classesById) {
12411
+ const result = [];
12412
+ const visited = /* @__PURE__ */ new Set();
12413
+ let current = classesById.get(classId);
12414
+ while (current !== void 0) {
12415
+ const id = String(current.id);
12416
+ if (visited.has(id)) break;
12417
+ visited.add(id);
12418
+ result.push(id);
12419
+ current = typeof current.extendsClassId === "string" ? classesById.get(current.extendsClassId) : void 0;
12420
+ }
12421
+ return result;
12422
+ }
12423
+ function projectRecordKind(value, path) {
12424
+ if (typeof value === "string" && PROJECT_RECORD_KINDS.has(value)) {
12425
+ return;
12426
+ }
12427
+ invalid(path, "expected a registered project record kind.");
12428
+ }
12147
12429
  function assertStaticMemberInvariants(manifest) {
12148
12430
  const members = requireArray(manifest.members, "$.members").map(
12149
12431
  (value, index) => requireRecord(value, `$.members[${index}]`)
@@ -13551,7 +13833,7 @@ function exactValue(value, expected, path) {
13551
13833
  function invalid(path, message) {
13552
13834
  throw new SchemaManifestValidationError(path, message);
13553
13835
  }
13554
- var SchemaManifestValidationError, SOURCE_KINDS, SYSTEM_OPERATIONS, WORLD_KINDS, MEMBER_COMMON_KEYS, MEMBER_KEYS_BY_KIND, PRIMITIVE_TYPE_KINDS, TEXTURE_TYPES, ALPHA_SOURCES, NPOT_SCALES, WRAP_MODES, RESIZE_ALGORITHMS, TEXTURE_COMPRESSIONS, PLATFORM_FORMATS, SPRITE_ALIGNMENTS;
13836
+ var SchemaManifestValidationError, WORLD_RELATION_CONTRACTS, MANIFEST_ENDPOINT_KINDS, PROJECT_RECORD_KINDS, SOURCE_KINDS, SYSTEM_OPERATIONS, WORLD_KINDS, MEMBER_COMMON_KEYS, MEMBER_KEYS_BY_KIND, PRIMITIVE_TYPE_KINDS, TEXTURE_TYPES, ALPHA_SOURCES, NPOT_SCALES, WRAP_MODES, RESIZE_ALGORITHMS, TEXTURE_COMPRESSIONS, PLATFORM_FORMATS, SPRITE_ALIGNMENTS;
13555
13837
  var init_validate = __esm({
13556
13838
  "src/schema/manifest/validate.ts"() {
13557
13839
  "use strict";
@@ -13566,6 +13848,138 @@ var init_validate = __esm({
13566
13848
  path;
13567
13849
  code;
13568
13850
  };
13851
+ WORLD_RELATION_CONTRACTS = {
13852
+ "world.grid.tile-import": {
13853
+ sourceRecordKind: "class",
13854
+ targetRecordKind: "class",
13855
+ sourceWorldKind: "tileGrid",
13856
+ targetWorldKind: "tile",
13857
+ abstractTarget: true,
13858
+ ordered: false,
13859
+ singleton: false
13860
+ },
13861
+ "world.grid.object-import": {
13862
+ sourceRecordKind: "class",
13863
+ targetRecordKind: "class",
13864
+ sourceWorldKind: "tileGrid",
13865
+ targetWorldKind: "object",
13866
+ abstractTarget: true,
13867
+ ordered: false,
13868
+ singleton: false
13869
+ },
13870
+ "world.grid.tile-layer": {
13871
+ sourceRecordKind: "class",
13872
+ targetRecordKind: "class",
13873
+ sourceWorldKind: "tileGrid",
13874
+ targetWorldKind: "tileLayer",
13875
+ abstractTarget: false,
13876
+ ordered: true,
13877
+ singleton: false
13878
+ },
13879
+ "world.grid.object-layer": {
13880
+ sourceRecordKind: "class",
13881
+ targetRecordKind: "class",
13882
+ sourceWorldKind: "tileGrid",
13883
+ targetWorldKind: "objectLayer",
13884
+ abstractTarget: false,
13885
+ ordered: true,
13886
+ singleton: false
13887
+ },
13888
+ "world.tile.compatible-layer": {
13889
+ sourceRecordKind: "class",
13890
+ targetRecordKind: "class",
13891
+ sourceWorldKind: "tile",
13892
+ targetWorldKind: "tileLayer",
13893
+ abstractTarget: true,
13894
+ ordered: false,
13895
+ singleton: false
13896
+ },
13897
+ "world.tile.default-layer": {
13898
+ sourceRecordKind: "class",
13899
+ targetRecordKind: "class",
13900
+ sourceWorldKind: "tile",
13901
+ targetWorldKind: "tileLayer",
13902
+ abstractTarget: false,
13903
+ ordered: false,
13904
+ singleton: true
13905
+ },
13906
+ "world.object.compatible-layer": {
13907
+ sourceRecordKind: "class",
13908
+ targetRecordKind: "class",
13909
+ sourceWorldKind: "object",
13910
+ targetWorldKind: "objectLayer",
13911
+ abstractTarget: true,
13912
+ ordered: false,
13913
+ singleton: false
13914
+ },
13915
+ "world.object.default-layer": {
13916
+ sourceRecordKind: "class",
13917
+ targetRecordKind: "class",
13918
+ sourceWorldKind: "object",
13919
+ targetWorldKind: "objectLayer",
13920
+ abstractTarget: false,
13921
+ ordered: false,
13922
+ singleton: true
13923
+ },
13924
+ "world.tile-layer-link.target": {
13925
+ sourceRecordKind: "class",
13926
+ targetRecordKind: "class",
13927
+ sourceWorldKind: "tileLayerLink",
13928
+ targetWorldKind: "tileLayer",
13929
+ abstractTarget: false,
13930
+ ordered: false,
13931
+ singleton: true
13932
+ },
13933
+ "world.object-layer-link.target": {
13934
+ sourceRecordKind: "class",
13935
+ targetRecordKind: "class",
13936
+ sourceWorldKind: "objectLayerLink",
13937
+ targetWorldKind: "objectLayer",
13938
+ abstractTarget: false,
13939
+ ordered: false,
13940
+ singleton: true
13941
+ },
13942
+ "world.smart-tile-neighbor.tile": {
13943
+ sourceRecordKind: "value",
13944
+ targetRecordKind: "class",
13945
+ sourceWorldKind: "smartTileNeighbor",
13946
+ targetWorldKind: "tile",
13947
+ abstractTarget: true,
13948
+ ordered: false,
13949
+ singleton: true
13950
+ }
13951
+ };
13952
+ MANIFEST_ENDPOINT_KINDS = /* @__PURE__ */ new Set([
13953
+ "class",
13954
+ "member",
13955
+ "interface",
13956
+ "enum",
13957
+ "unity-texture-template",
13958
+ "unity-audio-clip-template",
13959
+ "localization-config",
13960
+ "localization-status",
13961
+ "internal-record-relation"
13962
+ ]);
13963
+ PROJECT_RECORD_KINDS = /* @__PURE__ */ new Set([
13964
+ "project",
13965
+ "member",
13966
+ "value",
13967
+ "class",
13968
+ "internal-record-relation",
13969
+ "enum",
13970
+ "interface",
13971
+ "dialogue",
13972
+ "dialogue-node",
13973
+ "dialogue-group",
13974
+ "priority-group",
13975
+ "project-file",
13976
+ "unity-texture-template",
13977
+ "unity-audio-clip-template",
13978
+ "localization-config",
13979
+ "localization-status",
13980
+ "localized-text",
13981
+ "migration"
13982
+ ]);
13569
13983
  SOURCE_KINDS = /* @__PURE__ */ new Set([
13570
13984
  "class",
13571
13985
  "member",
@@ -13576,7 +13990,8 @@ var init_validate = __esm({
13576
13990
  "textureTemplate",
13577
13991
  "audioTemplate",
13578
13992
  "localization",
13579
- "localizationStatus"
13993
+ "localizationStatus",
13994
+ "internalRecordRelation"
13580
13995
  ]);
13581
13996
  SYSTEM_OPERATIONS = /* @__PURE__ */ new Set([
13582
13997
  "editRecord",
@@ -13585,7 +14000,6 @@ var init_validate = __esm({
13585
14000
  "replaceValue"
13586
14001
  ]);
13587
14002
  WORLD_KINDS = /* @__PURE__ */ new Set([
13588
- "worldAssets",
13589
14003
  "tileGrid",
13590
14004
  "tileLayer",
13591
14005
  "objectLayer",
@@ -14055,7 +14469,6 @@ function systemOperation(value, location2, frontend) {
14055
14469
  function worldKind(value, location2, frontend) {
14056
14470
  const result = lowerFirst(value);
14057
14471
  const allowed = /* @__PURE__ */ new Set([
14058
- "worldAssets",
14059
14472
  "tileGrid",
14060
14473
  "tileLayer",
14061
14474
  "objectLayer",
@@ -14085,7 +14498,7 @@ function worldKind(value, location2, frontend) {
14085
14498
  function lowerFirst(value) {
14086
14499
  return value.length === 0 ? value : `${value[0].toLowerCase()}${value.slice(1)}`;
14087
14500
  }
14088
- var SchemaFrontendError, SchemaFrontend, map, TEXTURE_TYPES2, ALPHA_SOURCES2, NPOT_SCALES2, MIP_FILTERS, WRAP_MODES2, FILTER_MODES, RESIZE_ALGORITHMS2, COMPRESSIONS, TEXTURE_FORMATS, SPRITE_MODES, MESH_TYPES, ALIGNMENTS, NORMAL_FILTERS, COOKIE_LIGHT_TYPES, SINGLE_CHANNELS, SWIZZLES, AUDIO_LOAD_TYPES, AUDIO_COMPRESSIONS, AUDIO_SAMPLE_RATES;
14501
+ var SchemaFrontendError, SPECIALIZED_RELATION_SETTINGS, PROJECT_RECORD_KIND_BY_ENUM, SchemaFrontend, map, TEXTURE_TYPES2, ALPHA_SOURCES2, NPOT_SCALES2, MIP_FILTERS, WRAP_MODES2, FILTER_MODES, RESIZE_ALGORITHMS2, COMPRESSIONS, TEXTURE_FORMATS, SPRITE_MODES, MESH_TYPES, ALIGNMENTS, NORMAL_FILTERS, COOKIE_LIGHT_TYPES, SINGLE_CHANNELS, SWIZZLES, AUDIO_LOAD_TYPES, AUDIO_COMPRESSIONS, AUDIO_SAMPLE_RATES;
14089
14502
  var init_frontend = __esm({
14090
14503
  "src/schema/frontend.ts"() {
14091
14504
  "use strict";
@@ -14101,6 +14514,98 @@ var init_frontend = __esm({
14101
14514
  }
14102
14515
  diagnostics;
14103
14516
  };
14517
+ SPECIALIZED_RELATION_SETTINGS = {
14518
+ "NeoCompose.Schema.NeoGridRelationSettings": [
14519
+ {
14520
+ property: "TileImports",
14521
+ relationKind: "world.grid.tile-import",
14522
+ collection: true,
14523
+ ordered: false
14524
+ },
14525
+ {
14526
+ property: "ObjectImports",
14527
+ relationKind: "world.grid.object-import",
14528
+ collection: true,
14529
+ ordered: false
14530
+ },
14531
+ {
14532
+ property: "TileLayers",
14533
+ relationKind: "world.grid.tile-layer",
14534
+ collection: true,
14535
+ ordered: true
14536
+ },
14537
+ {
14538
+ property: "ObjectLayers",
14539
+ relationKind: "world.grid.object-layer",
14540
+ collection: true,
14541
+ ordered: true
14542
+ }
14543
+ ],
14544
+ "NeoCompose.Schema.NeoTileRelationSettings": [
14545
+ {
14546
+ property: "CompatibleLayers",
14547
+ relationKind: "world.tile.compatible-layer",
14548
+ collection: true,
14549
+ ordered: false
14550
+ },
14551
+ {
14552
+ property: "DefaultLayer",
14553
+ relationKind: "world.tile.default-layer",
14554
+ collection: false,
14555
+ ordered: false
14556
+ }
14557
+ ],
14558
+ "NeoCompose.Schema.NeoObjectRelationSettings": [
14559
+ {
14560
+ property: "CompatibleLayers",
14561
+ relationKind: "world.object.compatible-layer",
14562
+ collection: true,
14563
+ ordered: false
14564
+ },
14565
+ {
14566
+ property: "DefaultLayer",
14567
+ relationKind: "world.object.default-layer",
14568
+ collection: false,
14569
+ ordered: false
14570
+ }
14571
+ ],
14572
+ "NeoCompose.Schema.NeoTileLayerLinkRelationSettings": [
14573
+ {
14574
+ property: "TargetLayer",
14575
+ relationKind: "world.tile-layer-link.target",
14576
+ collection: false,
14577
+ ordered: false
14578
+ }
14579
+ ],
14580
+ "NeoCompose.Schema.NeoObjectLayerLinkRelationSettings": [
14581
+ {
14582
+ property: "TargetLayer",
14583
+ relationKind: "world.object-layer-link.target",
14584
+ collection: false,
14585
+ ordered: false
14586
+ }
14587
+ ]
14588
+ };
14589
+ PROJECT_RECORD_KIND_BY_ENUM = {
14590
+ Project: "project",
14591
+ Member: "member",
14592
+ Value: "value",
14593
+ Class: "class",
14594
+ InternalRecordRelation: "internal-record-relation",
14595
+ Enum: "enum",
14596
+ Interface: "interface",
14597
+ Dialogue: "dialogue",
14598
+ DialogueNode: "dialogue-node",
14599
+ DialogueGroup: "dialogue-group",
14600
+ PriorityGroup: "priority-group",
14601
+ ProjectFile: "project-file",
14602
+ UnityTextureTemplate: "unity-texture-template",
14603
+ UnityAudioClipTemplate: "unity-audio-clip-template",
14604
+ LocalizationConfig: "localization-config",
14605
+ LocalizationStatus: "localization-status",
14606
+ LocalizedText: "localized-text",
14607
+ Migration: "migration"
14608
+ };
14104
14609
  SchemaFrontend = class {
14105
14610
  constructor(source, options) {
14106
14611
  this.source = source;
@@ -14167,6 +14672,7 @@ var init_frontend = __esm({
14167
14672
  }
14168
14673
  const localization = this.parseLocalization(localizationDeclarations[0]);
14169
14674
  const localizationStatuses = this.declarations.filter((entry) => entry.declaration.kind === "localizationStatus").map((entry) => this.parseLocalizationStatus(entry));
14675
+ const internalRecordRelations = this.parseInternalRecordRelations();
14170
14676
  const manifest = {
14171
14677
  formatVersion: SCHEMA_MANIFEST_FORMAT_VERSION,
14172
14678
  contractVersion: SCHEMA_MANIFEST_CONTRACT_VERSION,
@@ -14178,7 +14684,8 @@ var init_frontend = __esm({
14178
14684
  textureTemplates,
14179
14685
  audioTemplates,
14180
14686
  localization,
14181
- localizationStatuses
14687
+ localizationStatuses,
14688
+ internalRecordRelations
14182
14689
  };
14183
14690
  try {
14184
14691
  assertSchemaManifestV3(manifest);
@@ -14520,6 +15027,280 @@ var init_frontend = __esm({
14520
15027
  extendsGenericBindings
14521
15028
  };
14522
15029
  }
15030
+ parseInternalRecordRelations() {
15031
+ const result = [];
15032
+ for (const info of this.declarations.filter(
15033
+ (entry) => entry.declaration.kind === "class"
15034
+ )) {
15035
+ const fields = info.declaration.members.filter(
15036
+ (member) => member.kind === "settings" && member.name === "NeoRelations"
15037
+ );
15038
+ if (fields.length === 0) continue;
15039
+ if (fields.length !== 1) {
15040
+ this.fail(
15041
+ "NEO3120",
15042
+ `Class ${info.declaration.name} must declare at most one NeoRelations settings field.`,
15043
+ fields[1]?.location ?? info.declaration.location
15044
+ );
15045
+ }
15046
+ const field = fields[0];
15047
+ if (!field.modifiers.includes("static")) {
15048
+ this.fail(
15049
+ "NEO3121",
15050
+ `${info.declaration.name}.NeoRelations must be static.`,
15051
+ field.location
15052
+ );
15053
+ }
15054
+ if (field.initializer === void 0) {
15055
+ this.fail(
15056
+ "NEO3122",
15057
+ `${info.declaration.name}.NeoRelations requires an initializer.`,
15058
+ field.location
15059
+ );
15060
+ }
15061
+ const type = parseTypeShape(field.type ?? "").name;
15062
+ const settings = this.objectValue(
15063
+ field.initializer,
15064
+ `${info.declaration.name}.NeoRelations`,
15065
+ field.location
15066
+ );
15067
+ const groups = SPECIALIZED_RELATION_SETTINGS[type];
15068
+ if (groups === void 0) {
15069
+ this.fail(
15070
+ "NEO3123",
15071
+ `${info.declaration.name}.NeoRelations has unsupported type ${JSON.stringify(field.type)}.`,
15072
+ field.location
15073
+ );
15074
+ }
15075
+ for (const group of groups) {
15076
+ const raw = settings.properties?.[group.property];
15077
+ if (group.collection) {
15078
+ const entries = raw === void 0 ? [] : this.arrayValue(
15079
+ raw,
15080
+ `${type}.${group.property}`,
15081
+ field.location
15082
+ );
15083
+ for (const [index, entry] of entries.entries()) {
15084
+ result.push(
15085
+ this.parseSpecializedClassRelation(
15086
+ info,
15087
+ field,
15088
+ entry,
15089
+ group.relationKind,
15090
+ group.ordered ? index : null,
15091
+ `${group.property}-${index}`
15092
+ )
15093
+ );
15094
+ }
15095
+ continue;
15096
+ }
15097
+ if (raw !== void 0 && raw.kind !== "null") {
15098
+ result.push(
15099
+ this.parseSpecializedClassRelation(
15100
+ info,
15101
+ field,
15102
+ raw,
15103
+ group.relationKind,
15104
+ null,
15105
+ group.property
15106
+ )
15107
+ );
15108
+ }
15109
+ }
15110
+ }
15111
+ const configurations = this.declarations.filter(
15112
+ (entry) => entry.declaration.kind === "relationConfiguration"
15113
+ );
15114
+ if (configurations.length > 1) {
15115
+ this.fail(
15116
+ "NEO3124",
15117
+ `At most one [NeoRelationConfiguration] declaration is allowed; found ${configurations.length}.`,
15118
+ configurations[1].declaration.location
15119
+ );
15120
+ }
15121
+ const configuration = configurations[0];
15122
+ if (configuration !== void 0) {
15123
+ if (!configuration.declaration.modifiers.includes("static")) {
15124
+ this.fail(
15125
+ "NEO3131",
15126
+ `[NeoRelationConfiguration] ${configuration.declaration.name} must be a static class.`,
15127
+ configuration.declaration.location
15128
+ );
15129
+ }
15130
+ const fields = configuration.declaration.members.filter(
15131
+ (member) => member.kind === "settings" && member.name === "NeoRelations"
15132
+ );
15133
+ if (fields.length !== 1 || fields[0]?.initializer === void 0) {
15134
+ this.fail(
15135
+ "NEO3125",
15136
+ `[NeoRelationConfiguration] ${configuration.declaration.name} must declare exactly one initialized static NeoRelations field.`,
15137
+ fields[0]?.location ?? configuration.declaration.location
15138
+ );
15139
+ }
15140
+ if (!fields[0].modifiers.includes("static")) {
15141
+ this.fail(
15142
+ "NEO3132",
15143
+ `[NeoRelationConfiguration] ${configuration.declaration.name}.NeoRelations must be static.`,
15144
+ fields[0].location
15145
+ );
15146
+ }
15147
+ const fieldType = parseTypeShape(fields[0].type ?? "");
15148
+ if (!fieldType.array || fieldType.arguments[0]?.name !== "NeoCompose.Schema.NeoInternalRecordRelationDefinition") {
15149
+ this.fail(
15150
+ "NEO3133",
15151
+ `[NeoRelationConfiguration] ${configuration.declaration.name}.NeoRelations must be NeoInternalRecordRelationDefinition[].`,
15152
+ fields[0].location
15153
+ );
15154
+ }
15155
+ for (const [index, entry] of this.arrayValue(
15156
+ fields[0].initializer,
15157
+ "ProjectRelations.NeoRelations",
15158
+ fields[0].location
15159
+ ).entries()) {
15160
+ result.push(
15161
+ this.parseGenericInternalRecordRelation(
15162
+ entry,
15163
+ fields[0],
15164
+ `generic-${index}`
15165
+ )
15166
+ );
15167
+ }
15168
+ }
15169
+ const ids = /* @__PURE__ */ new Map();
15170
+ for (const relation of result) {
15171
+ const duplicate = ids.get(relation.id);
15172
+ if (duplicate !== void 0) {
15173
+ this.fail(
15174
+ "NEO3126",
15175
+ `Duplicate internal relation id ${JSON.stringify(relation.id)}.`,
15176
+ void 0
15177
+ );
15178
+ }
15179
+ ids.set(relation.id, relation);
15180
+ }
15181
+ return result;
15182
+ }
15183
+ parseSpecializedClassRelation(source, field, value, relationKind, order, pendingSuffix) {
15184
+ const definition2 = this.objectValue(
15185
+ value,
15186
+ "NeoClassRelationDefinition",
15187
+ field.location
15188
+ );
15189
+ const positional = definition2.items ?? [];
15190
+ const idValue = definition2.properties?.Id ?? (positional.length === 2 ? positional[0] : void 0);
15191
+ const targetValue = definition2.properties?.Target ?? (positional.length === 2 ? positional[1] : positional[0]);
15192
+ const targetName = this.typeOfArgument(
15193
+ targetValue,
15194
+ "NeoClassRelationDefinition.Target",
15195
+ field.location
15196
+ );
15197
+ if (targetName === void 0) {
15198
+ this.fail(
15199
+ "NEO3127",
15200
+ `Relation ${relationKind} requires a typeof(...) target.`,
15201
+ field.location
15202
+ );
15203
+ }
15204
+ const target = this.resolveDeclaration(this.classesByName, targetName);
15205
+ if (target === void 0) {
15206
+ this.fail(
15207
+ "NEO3128",
15208
+ `Relation ${relationKind} targets unknown Neo Class ${JSON.stringify(targetName)}.`,
15209
+ field.location
15210
+ );
15211
+ }
15212
+ const id = this.stringArgument(
15213
+ idValue,
15214
+ "NeoClassRelationDefinition.Id",
15215
+ field.location
15216
+ ) ?? pendingId("internal-record-relation", field.location, pendingSuffix);
15217
+ return {
15218
+ id,
15219
+ source: sourceIdentity(
15220
+ "internalRecordRelation",
15221
+ id,
15222
+ field.location,
15223
+ field.selectionLocation
15224
+ ),
15225
+ relationKind,
15226
+ sourceRecordKind: "class",
15227
+ sourceRecordId: source.id,
15228
+ targetRecordKind: "class",
15229
+ targetRecordId: target.id,
15230
+ order
15231
+ };
15232
+ }
15233
+ parseGenericInternalRecordRelation(value, field, pendingSuffix) {
15234
+ const definition2 = this.objectValue(
15235
+ value,
15236
+ "NeoInternalRecordRelationDefinition",
15237
+ field.location
15238
+ );
15239
+ const positional = definition2.items ?? [];
15240
+ if (positional.length !== 4) {
15241
+ this.fail(
15242
+ "NEO3129",
15243
+ "NeoInternalRecordRelationDefinition requires id, relation kind, source, and target constructor arguments.",
15244
+ field.location
15245
+ );
15246
+ }
15247
+ const id = this.stringArgument(positional[0], "relation id", field.location) ?? pendingId("internal-record-relation", field.location, pendingSuffix);
15248
+ return {
15249
+ id,
15250
+ source: sourceIdentity(
15251
+ "internalRecordRelation",
15252
+ id,
15253
+ field.location,
15254
+ field.selectionLocation
15255
+ ),
15256
+ relationKind: this.requiredStringArgument(
15257
+ positional[1],
15258
+ "relation kind",
15259
+ field.location
15260
+ ),
15261
+ ...this.parseGenericRelationEndpoints(
15262
+ positional[2],
15263
+ positional[3],
15264
+ field.location
15265
+ ),
15266
+ order: null
15267
+ };
15268
+ }
15269
+ parseGenericRelationEndpoints(sourceValue, targetValue, location2) {
15270
+ const parse = (value, description) => {
15271
+ const reference = this.objectValue(value, description, location2);
15272
+ const positional = reference.items ?? [];
15273
+ const enumName = this.enumArgument(
15274
+ reference.properties?.Kind ?? positional[0],
15275
+ `${description}.Kind`,
15276
+ location2
15277
+ );
15278
+ const kind = enumName === void 0 ? void 0 : PROJECT_RECORD_KIND_BY_ENUM[enumName];
15279
+ if (kind === void 0) {
15280
+ this.fail(
15281
+ "NEO3130",
15282
+ `${description} has an unknown NeoProjectRecordKind.`,
15283
+ location2
15284
+ );
15285
+ }
15286
+ return {
15287
+ kind,
15288
+ id: this.requiredStringArgument(
15289
+ reference.properties?.Id ?? positional[1],
15290
+ `${description}.Id`,
15291
+ location2
15292
+ )
15293
+ };
15294
+ };
15295
+ const source = parse(sourceValue, "relation source");
15296
+ const target = parse(targetValue, "relation target");
15297
+ return {
15298
+ sourceRecordKind: source.kind,
15299
+ sourceRecordId: source.id,
15300
+ targetRecordKind: target.kind,
15301
+ targetRecordId: target.id
15302
+ };
15303
+ }
14523
15304
  compareMemberOrder(left, right) {
14524
15305
  const leftMarker = this.attribute(left.attributes, "NeoMember", false);
14525
15306
  const rightMarker = this.attribute(right.attributes, "NeoMember", false);
@@ -18209,6 +18990,26 @@ var init_document_contracts = __esm({
18209
18990
  "transitionToWhenSourceBecomesStatusId": "missingIsNull",
18210
18991
  "system": "nullIsAbsent"
18211
18992
  }
18993
+ },
18994
+ "internal-record-relation": {
18995
+ "authored": [
18996
+ "id",
18997
+ "relationKind",
18998
+ "sourceRecordKind",
18999
+ "sourceRecordId",
19000
+ "targetRecordKind",
19001
+ "targetRecordId",
19002
+ "orderKey"
19003
+ ],
19004
+ "derived": [],
19005
+ "volatile": [
19006
+ "projectId",
19007
+ "createdAt",
19008
+ "updatedAt"
19009
+ ],
19010
+ "normalization": {
19011
+ "orderKey": "nullIsAbsent"
19012
+ }
18212
19013
  }
18213
19014
  };
18214
19015
  }
@@ -19379,7 +20180,6 @@ function isSchemaSystemMetadata(value) {
19379
20180
  "replaceValue"
19380
20181
  ]);
19381
20182
  const worlds = /* @__PURE__ */ new Set([
19382
- "worldAssets",
19383
20183
  "tileGrid",
19384
20184
  "tileLayer",
19385
20185
  "objectLayer",
@@ -20191,6 +20991,267 @@ var init_class = __esm({
20191
20991
  }
20192
20992
  });
20193
20993
 
20994
+ // src/schema/manifest/record-adapters/internal-record-relation.ts
20995
+ function internalRecordRelationFromDocument(record2, context) {
20996
+ const data = normalizeDocumentFieldsV3(
20997
+ "internal-record-relation",
20998
+ record2.data
20999
+ );
21000
+ const relationKind = requiredString(data, "relationKind", record2);
21001
+ const sourceRecordKind = projectRecordKind2(
21002
+ requiredString(data, "sourceRecordKind", record2),
21003
+ record2,
21004
+ "sourceRecordKind"
21005
+ );
21006
+ const sourceRecordId = requiredString(data, "sourceRecordId", record2);
21007
+ const orderKey = optionalStringOrNull(data.orderKey);
21008
+ const order = orderKey === null ? null : orderedDirectGroup(context, {
21009
+ relationKind,
21010
+ sourceRecordKind,
21011
+ sourceRecordId
21012
+ }).findIndex((candidate) => candidate.recordId === record2.recordId);
21013
+ if (orderKey !== null && (order === null || order < 0)) {
21014
+ throw new Error(
21015
+ `Internal relation ${record2.recordId} could not be located in its ordered direct source set.`
21016
+ );
21017
+ }
21018
+ return {
21019
+ id: record2.recordId,
21020
+ source: context.sourceForRecord(
21021
+ "internal-record-relation",
21022
+ record2.recordId,
21023
+ "internalRecordRelation"
21024
+ ),
21025
+ relationKind,
21026
+ sourceRecordKind,
21027
+ sourceRecordId,
21028
+ targetRecordKind: projectRecordKind2(
21029
+ requiredString(data, "targetRecordKind", record2),
21030
+ record2,
21031
+ "targetRecordKind"
21032
+ ),
21033
+ targetRecordId: requiredString(data, "targetRecordId", record2),
21034
+ order
21035
+ };
21036
+ }
21037
+ function orderedDirectGroup(context, source) {
21038
+ return [...context.recordsByKind.get("internal-record-relation") ?? []].filter((candidate) => {
21039
+ const data = normalizeDocumentFieldsV3(
21040
+ "internal-record-relation",
21041
+ candidate.data
21042
+ );
21043
+ return data.relationKind === source.relationKind && data.sourceRecordKind === source.sourceRecordKind && data.sourceRecordId === source.sourceRecordId && typeof data.orderKey === "string";
21044
+ }).sort((left, right) => {
21045
+ const leftData = normalizeDocumentFieldsV3(
21046
+ "internal-record-relation",
21047
+ left.data
21048
+ );
21049
+ const rightData = normalizeDocumentFieldsV3(
21050
+ "internal-record-relation",
21051
+ right.data
21052
+ );
21053
+ return String(leftData.orderKey).localeCompare(String(rightData.orderKey)) || left.recordId.localeCompare(right.recordId);
21054
+ });
21055
+ }
21056
+ function internalRecordRelationToDocument(relation, orderKey) {
21057
+ return omitUndefined({
21058
+ id: relation.id,
21059
+ relationKind: relation.relationKind,
21060
+ sourceRecordKind: relation.sourceRecordKind,
21061
+ sourceRecordId: relation.sourceRecordId,
21062
+ targetRecordKind: relation.targetRecordKind,
21063
+ targetRecordId: relation.targetRecordId,
21064
+ orderKey
21065
+ });
21066
+ }
21067
+ function relationOrderKeys(manifest, context) {
21068
+ const result = /* @__PURE__ */ new Map();
21069
+ const groups = /* @__PURE__ */ new Map();
21070
+ for (const relation of manifest.internalRecordRelations) {
21071
+ if (relation.order === null) continue;
21072
+ const key = `${relation.relationKind}\0${relation.sourceRecordKind}\0${relation.sourceRecordId}`;
21073
+ const group = groups.get(key) ?? [];
21074
+ group.push(relation);
21075
+ groups.set(key, group);
21076
+ }
21077
+ for (const group of groups.values()) {
21078
+ group.sort(
21079
+ (left, right) => (left.order ?? 0) - (right.order ?? 0) || left.id.localeCompare(right.id)
21080
+ );
21081
+ const groupSource = group[0];
21082
+ const base = group.map(
21083
+ (relation) => context.baseRecord("internal-record-relation", relation.id)
21084
+ ).filter((record2) => record2 !== null).filter((record2) => {
21085
+ if (groupSource === void 0) return false;
21086
+ const data = normalizeDocumentFieldsV3(
21087
+ "internal-record-relation",
21088
+ record2.data
21089
+ );
21090
+ return data.relationKind === groupSource.relationKind && data.sourceRecordKind === groupSource.sourceRecordKind && data.sourceRecordId === groupSource.sourceRecordId;
21091
+ }).map((record2) => ({
21092
+ id: record2.recordId,
21093
+ key: typeof record2.data.orderKey === "string" ? record2.data.orderKey : null
21094
+ })).filter(
21095
+ (entry) => entry.key !== null
21096
+ ).sort((left, right) => left.key.localeCompare(right.key));
21097
+ const unchanged = base.length === group.length && group.every((relation, index) => relation.id === base[index]?.id);
21098
+ if (unchanged) {
21099
+ for (const entry of base) result.set(entry.id, entry.key);
21100
+ continue;
21101
+ }
21102
+ const baseIds = base.map((entry) => entry.id);
21103
+ const desiredIds = group.map((relation) => relation.id);
21104
+ const anchoredIds = new Set(longestCommonSubsequence(baseIds, desiredIds));
21105
+ const baseKeysById = new Map(base.map((entry) => [entry.id, entry.key]));
21106
+ for (const id of anchoredIds) {
21107
+ const key = baseKeysById.get(id);
21108
+ if (key !== void 0) result.set(id, key);
21109
+ }
21110
+ const assignedKeys = new Set(
21111
+ [...anchoredIds].map((id) => baseKeysById.get(id)).filter((key) => key !== void 0)
21112
+ );
21113
+ let failedToAllocate = false;
21114
+ for (const [index, relation] of group.entries()) {
21115
+ if (result.has(relation.id)) continue;
21116
+ const previousKey = findAssignedNeighborKey(group, index, -1, result);
21117
+ const nextKey = findAssignedNeighborKey(group, index, 1, result);
21118
+ const key = relationKeyBetween(previousKey, nextKey, assignedKeys);
21119
+ if (key === null) {
21120
+ failedToAllocate = true;
21121
+ break;
21122
+ }
21123
+ result.set(relation.id, key);
21124
+ assignedKeys.add(key);
21125
+ }
21126
+ if (failedToAllocate) {
21127
+ for (const relation of group) result.delete(relation.id);
21128
+ for (const [index, relation] of group.entries()) {
21129
+ result.set(relation.id, String((index + 1) * 1024).padStart(12, "0"));
21130
+ }
21131
+ }
21132
+ }
21133
+ return result;
21134
+ }
21135
+ function longestCommonSubsequence(left, right) {
21136
+ const lengths = Array.from(
21137
+ { length: left.length + 1 },
21138
+ () => Array(right.length + 1).fill(0)
21139
+ );
21140
+ for (let leftIndex2 = left.length - 1; leftIndex2 >= 0; leftIndex2 -= 1) {
21141
+ for (let rightIndex2 = right.length - 1; rightIndex2 >= 0; rightIndex2 -= 1) {
21142
+ lengths[leftIndex2][rightIndex2] = left[leftIndex2] === right[rightIndex2] ? 1 + lengths[leftIndex2 + 1][rightIndex2 + 1] : Math.max(
21143
+ lengths[leftIndex2 + 1][rightIndex2],
21144
+ lengths[leftIndex2][rightIndex2 + 1]
21145
+ );
21146
+ }
21147
+ }
21148
+ const result = [];
21149
+ let leftIndex = 0;
21150
+ let rightIndex = 0;
21151
+ while (leftIndex < left.length && rightIndex < right.length) {
21152
+ if (left[leftIndex] === right[rightIndex]) {
21153
+ result.push(left[leftIndex]);
21154
+ leftIndex += 1;
21155
+ rightIndex += 1;
21156
+ } else if (lengths[leftIndex + 1][rightIndex] >= lengths[leftIndex][rightIndex + 1]) {
21157
+ leftIndex += 1;
21158
+ } else {
21159
+ rightIndex += 1;
21160
+ }
21161
+ }
21162
+ return result;
21163
+ }
21164
+ function findAssignedNeighborKey(group, startIndex, direction, assigned) {
21165
+ for (let index = startIndex + direction; index >= 0 && index < group.length; index += direction) {
21166
+ const relation = group[index];
21167
+ if (relation === void 0) continue;
21168
+ const key = assigned.get(relation.id);
21169
+ if (key !== void 0) return key;
21170
+ }
21171
+ return null;
21172
+ }
21173
+ function relationKeyBetween(previous, next, assigned) {
21174
+ if (previous === null && next === null) return "000000001024";
21175
+ if (previous !== null && next !== null && /^\d+$/.test(previous) && /^\d+$/.test(next) && previous.length === next.length) {
21176
+ const previousNumber = BigInt(previous);
21177
+ const nextNumber = BigInt(next);
21178
+ if (nextNumber - previousNumber > 1n) {
21179
+ const midpoint = (previousNumber + nextNumber) / 2n;
21180
+ const candidate = midpoint.toString().padStart(previous.length, "0");
21181
+ if (!assigned.has(candidate)) return candidate;
21182
+ }
21183
+ }
21184
+ if (previous === null && next !== null) {
21185
+ for (let depth = 1; depth <= 32; depth += 1) {
21186
+ const candidate = `${"!".repeat(depth)}${next}`;
21187
+ if (candidate.localeCompare(next) < 0 && !assigned.has(candidate)) {
21188
+ return candidate;
21189
+ }
21190
+ }
21191
+ return null;
21192
+ }
21193
+ if (previous !== null) {
21194
+ for (let depth = 1; depth <= 32; depth += 1) {
21195
+ const candidate = `${previous}${"~".repeat(depth)}`;
21196
+ if (candidate.localeCompare(previous) > 0 && (next === null || candidate.localeCompare(next) < 0) && !assigned.has(candidate)) {
21197
+ return candidate;
21198
+ }
21199
+ }
21200
+ }
21201
+ return null;
21202
+ }
21203
+ function projectRecordKind2(value, record2, field) {
21204
+ switch (value) {
21205
+ case "project":
21206
+ case "member":
21207
+ case "value":
21208
+ case "class":
21209
+ case "internal-record-relation":
21210
+ case "enum":
21211
+ case "interface":
21212
+ case "dialogue":
21213
+ case "dialogue-node":
21214
+ case "dialogue-group":
21215
+ case "priority-group":
21216
+ case "project-file":
21217
+ case "unity-texture-template":
21218
+ case "unity-audio-clip-template":
21219
+ case "localization-config":
21220
+ case "localization-status":
21221
+ case "localized-text":
21222
+ case "migration":
21223
+ return value;
21224
+ }
21225
+ throw new Error(
21226
+ `Schema document ${record2.recordKind}:${record2.recordId} field ${field} has unknown project record kind ${JSON.stringify(value)}.`
21227
+ );
21228
+ }
21229
+ var INTERNAL_RECORD_RELATION_SCHEMA_RECORD_ADAPTER_V3;
21230
+ var init_internal_record_relation = __esm({
21231
+ "src/schema/manifest/record-adapters/internal-record-relation.ts"() {
21232
+ "use strict";
21233
+ init_contracts();
21234
+ init_codecs();
21235
+ INTERNAL_RECORD_RELATION_SCHEMA_RECORD_ADAPTER_V3 = {
21236
+ recordKind: "internal-record-relation",
21237
+ manifestCollection: "internalRecordRelations",
21238
+ cardinality: "many",
21239
+ fromDocument: internalRecordRelationFromDocument,
21240
+ toAuthoredDocuments: (manifest, context) => {
21241
+ const orderKeys = relationOrderKeys(manifest, context);
21242
+ return manifest.internalRecordRelations.map((relation) => ({
21243
+ recordKind: "internal-record-relation",
21244
+ recordId: relation.id,
21245
+ authoredFields: internalRecordRelationToDocument(
21246
+ relation,
21247
+ orderKeys.get(relation.id) ?? null
21248
+ )
21249
+ }));
21250
+ }
21251
+ };
21252
+ }
21253
+ });
21254
+
20194
21255
  // src/schema/manifest/record-adapters/registry.ts
20195
21256
  var SCHEMA_RECORD_ADAPTER_REGISTRY_V3, SCHEMA_RECORD_ADAPTERS_V3;
20196
21257
  var init_registry = __esm({
@@ -20202,6 +21263,7 @@ var init_registry = __esm({
20202
21263
  init_member();
20203
21264
  init_template();
20204
21265
  init_class();
21266
+ init_internal_record_relation();
20205
21267
  SCHEMA_RECORD_ADAPTER_REGISTRY_V3 = {
20206
21268
  member: MEMBER_SCHEMA_RECORD_ADAPTER_V3,
20207
21269
  class: CLASS_SCHEMA_RECORD_ADAPTER_V3,
@@ -20210,7 +21272,8 @@ var init_registry = __esm({
20210
21272
  "unity-texture-template": TEXTURE_TEMPLATE_SCHEMA_RECORD_ADAPTER_V3,
20211
21273
  "unity-audio-clip-template": AUDIO_TEMPLATE_SCHEMA_RECORD_ADAPTER_V3,
20212
21274
  "localization-config": LOCALIZATION_SCHEMA_RECORD_ADAPTER_V3,
20213
- "localization-status": LOCALIZATION_STATUS_SCHEMA_RECORD_ADAPTER_V3
21275
+ "localization-status": LOCALIZATION_STATUS_SCHEMA_RECORD_ADAPTER_V3,
21276
+ "internal-record-relation": INTERNAL_RECORD_RELATION_SCHEMA_RECORD_ADAPTER_V3
20214
21277
  };
20215
21278
  SCHEMA_RECORD_ADAPTERS_V3 = [
20216
21279
  SCHEMA_RECORD_ADAPTER_REGISTRY_V3.class,
@@ -20220,7 +21283,8 @@ var init_registry = __esm({
20220
21283
  SCHEMA_RECORD_ADAPTER_REGISTRY_V3["unity-texture-template"],
20221
21284
  SCHEMA_RECORD_ADAPTER_REGISTRY_V3["unity-audio-clip-template"],
20222
21285
  SCHEMA_RECORD_ADAPTER_REGISTRY_V3["localization-config"],
20223
- SCHEMA_RECORD_ADAPTER_REGISTRY_V3["localization-status"]
21286
+ SCHEMA_RECORD_ADAPTER_REGISTRY_V3["localization-status"],
21287
+ SCHEMA_RECORD_ADAPTER_REGISTRY_V3["internal-record-relation"]
20224
21288
  ];
20225
21289
  }
20226
21290
  });
@@ -20237,6 +21301,7 @@ var init_record_adapters = __esm({
20237
21301
  init_registry();
20238
21302
  init_template();
20239
21303
  init_class();
21304
+ init_internal_record_relation();
20240
21305
  }
20241
21306
  });
20242
21307
 
@@ -20255,7 +21320,8 @@ function documentsToSchemaManifestV3(records2, optionsOrContext = {}) {
20255
21320
  textureTemplates: [],
20256
21321
  audioTemplates: [],
20257
21322
  localization: void 0,
20258
- localizationStatuses: []
21323
+ localizationStatuses: [],
21324
+ internalRecordRelations: []
20259
21325
  };
20260
21326
  for (const adapter of SCHEMA_RECORD_ADAPTERS_V3) {
20261
21327
  const adapterRecords = context.recordsByKind.get(adapter.recordKind) ?? [];
@@ -20498,6 +21564,8 @@ function emitSchemaManifestV3(manifest) {
20498
21564
  }
20499
21565
  const loose = emitLooseMembers(context);
20500
21566
  if (loose !== null) append(loose);
21567
+ const genericRelations = emitGenericRelations(context);
21568
+ if (genericRelations !== null) append(genericRelations);
20501
21569
  append(emitLocalization(manifest.localization));
20502
21570
  for (const status of manifest.localizationStatuses) {
20503
21571
  append(
@@ -20875,6 +21943,14 @@ function emitNeoSchemaClass(context, schemaClass2) {
20875
21943
  if (typeBindingEntries.length > 0) {
20876
21944
  lines.push(...indent(typeBindingEntries));
20877
21945
  }
21946
+ const relationSettings = emitClassRelationSettings(context, schemaClass2);
21947
+ if (relationSettings.lines.length > 0) {
21948
+ if (typeBindingEntries.length > 0) lines.push("");
21949
+ lines.push(...indent(relationSettings.lines));
21950
+ for (const relationId of relationSettings.relationIds) {
21951
+ recordKeys.push(`internal-record-relation:${relationId}`);
21952
+ }
21953
+ }
20878
21954
  const orderedKeys2 = orderedSchemaKeys(schemaClass2);
20879
21955
  let emittedMember = false;
20880
21956
  for (const schemaKey of orderedKeys2) {
@@ -20886,7 +21962,9 @@ function emitNeoSchemaClass(context, schemaClass2) {
20886
21962
  `Class ${JSON.stringify(schemaClass2.name)} references missing member ${JSON.stringify(memberId)}.`
20887
21963
  );
20888
21964
  }
20889
- if (emittedMember || typeBindingEntries.length > 0) lines.push("");
21965
+ if (emittedMember || typeBindingEntries.length > 0 || relationSettings.lines.length > 0) {
21966
+ lines.push("");
21967
+ }
20890
21968
  const memberLines = emitMember(
20891
21969
  context,
20892
21970
  schemaClass2,
@@ -20912,6 +21990,119 @@ function emitNeoSchemaClass(context, schemaClass2) {
20912
21990
  [...new Set(recordKeys)]
20913
21991
  );
20914
21992
  }
21993
+ function emitClassRelationSettings(context, schemaClass2) {
21994
+ const relations = context.manifest.internalRecordRelations.filter(
21995
+ (relation) => relation.sourceRecordKind === "class" && relation.sourceRecordId === schemaClass2.id && relation.targetRecordKind === "class" && Object.hasOwn(SPECIALIZED_RELATION_EMISSION, relation.relationKind)
21996
+ );
21997
+ if (relations.length === 0) return { lines: [], relationIds: [] };
21998
+ const descriptors = relations.map((relation) => ({
21999
+ relation,
22000
+ descriptor: SPECIALIZED_RELATION_EMISSION[relation.relationKind]
22001
+ }));
22002
+ const settingsTypes = new Set(
22003
+ descriptors.map((entry) => entry.descriptor.settingsType)
22004
+ );
22005
+ if (settingsTypes.size !== 1) {
22006
+ throw new Error(
22007
+ `Class ${schemaClass2.id} has incompatible specialized relation families: ${[...settingsTypes].join(", ")}.`
22008
+ );
22009
+ }
22010
+ const settingsType = descriptors[0]?.descriptor.settingsType;
22011
+ if (settingsType === void 0) return { lines: [], relationIds: [] };
22012
+ const propertyOrder = [
22013
+ "TileImports",
22014
+ "ObjectImports",
22015
+ "TileLayers",
22016
+ "ObjectLayers",
22017
+ "CompatibleLayers",
22018
+ "DefaultLayer",
22019
+ "TargetLayer"
22020
+ ];
22021
+ const lines = [
22022
+ `private static readonly ${settingsType} NeoRelations = new()`,
22023
+ "{"
22024
+ ];
22025
+ for (const property2 of propertyOrder) {
22026
+ const entries = descriptors.filter(
22027
+ (entry) => entry.descriptor.property === property2
22028
+ );
22029
+ if (entries.length === 0) continue;
22030
+ const singleton = entries[0] !== void 0 && "singleton" in entries[0].descriptor && entries[0].descriptor.singleton === true;
22031
+ if (singleton) {
22032
+ if (entries.length !== 1) {
22033
+ throw new Error(
22034
+ `Class ${schemaClass2.id} declares more than one direct ${entries[0]?.relation.relationKind} relation.`
22035
+ );
22036
+ }
22037
+ lines.push(
22038
+ ` ${property2} = ${emitClassRelationDefinition(context, entries[0].relation)},`
22039
+ );
22040
+ continue;
22041
+ }
22042
+ entries.sort((left, right) => {
22043
+ if (left.descriptor.ordered) {
22044
+ return (left.relation.order ?? 0) - (right.relation.order ?? 0) || left.relation.id.localeCompare(right.relation.id);
22045
+ }
22046
+ return left.relation.targetRecordId.localeCompare(
22047
+ right.relation.targetRecordId
22048
+ ) || left.relation.id.localeCompare(right.relation.id);
22049
+ });
22050
+ lines.push(` ${property2} =`, " {");
22051
+ for (const entry of entries) {
22052
+ lines.push(
22053
+ ` ${emitClassRelationDefinition(context, entry.relation)},`
22054
+ );
22055
+ }
22056
+ lines.push(" },");
22057
+ }
22058
+ lines.push("};");
22059
+ return {
22060
+ lines,
22061
+ relationIds: relations.map((relation) => relation.id)
22062
+ };
22063
+ }
22064
+ function emitClassRelationDefinition(context, relation) {
22065
+ const target = requiredName(
22066
+ context.classNames,
22067
+ relation.targetRecordId,
22068
+ "relation target class"
22069
+ );
22070
+ return `new(${csString(relation.id)}, typeof(${target}))`;
22071
+ }
22072
+ function emitGenericRelations(context) {
22073
+ const relations = context.manifest.internalRecordRelations.filter(
22074
+ (relation) => !(relation.sourceRecordKind === "class" && relation.targetRecordKind === "class" && Object.hasOwn(SPECIALIZED_RELATION_EMISSION, relation.relationKind))
22075
+ ).sort(
22076
+ (left, right) => left.relationKind === right.relationKind ? left.id.localeCompare(right.id) : left.relationKind.localeCompare(right.relationKind)
22077
+ );
22078
+ if (relations.length === 0) return null;
22079
+ const ordered = relations.find((relation) => relation.order !== null);
22080
+ if (ordered !== void 0) {
22081
+ throw new Error(
22082
+ `Generic relation ${ordered.id} is ordered, but its relation kind has no typed source-owned settings contract. CLI contract upgrade required.`
22083
+ );
22084
+ }
22085
+ const lines = [
22086
+ "[NeoRelationConfiguration]",
22087
+ "internal static class ProjectRelations",
22088
+ "{",
22089
+ " internal static readonly NeoInternalRecordRelationDefinition[] NeoRelations =",
22090
+ " {",
22091
+ ...relations.map(
22092
+ (relation) => ` new(${csString(relation.id)}, ${csString(relation.relationKind)}, new(NeoProjectRecordKind.${enumCase(relation.sourceRecordKind)}, ${csString(relation.sourceRecordId)}), new(NeoProjectRecordKind.${enumCase(relation.targetRecordKind)}, ${csString(relation.targetRecordId)})),`
22093
+ ),
22094
+ " };",
22095
+ "}"
22096
+ ];
22097
+ return emitFile(
22098
+ "ProjectRelations.cs",
22099
+ [
22100
+ { recordKey: null, lines: HEADER.split("\n") },
22101
+ { recordKey: null, lines }
22102
+ ],
22103
+ relations.map((relation) => `internal-record-relation:${relation.id}`)
22104
+ );
22105
+ }
20915
22106
  function orderedSchemaKeys(schemaClass2) {
20916
22107
  const keys = Object.keys(schemaClass2.schema);
20917
22108
  const preferred = schemaClass2.schemaKeyOrder ?? [];
@@ -22278,7 +23469,7 @@ function emitLocalizationStatus(status, className) {
22278
23469
  [recordKey]
22279
23470
  );
22280
23471
  }
22281
- var HEADER;
23472
+ var HEADER, SPECIALIZED_RELATION_EMISSION;
22282
23473
  var init_emitter = __esm({
22283
23474
  "src/schema/emitter.ts"() {
22284
23475
  "use strict";
@@ -22295,6 +23486,62 @@ using System.Threading.Tasks;
22295
23486
 
22296
23487
  namespace ProjectSchema;
22297
23488
  `;
23489
+ SPECIALIZED_RELATION_EMISSION = {
23490
+ "world.grid.tile-import": {
23491
+ settingsType: "NeoGridRelationSettings",
23492
+ property: "TileImports",
23493
+ ordered: false
23494
+ },
23495
+ "world.grid.object-import": {
23496
+ settingsType: "NeoGridRelationSettings",
23497
+ property: "ObjectImports",
23498
+ ordered: false
23499
+ },
23500
+ "world.grid.tile-layer": {
23501
+ settingsType: "NeoGridRelationSettings",
23502
+ property: "TileLayers",
23503
+ ordered: true
23504
+ },
23505
+ "world.grid.object-layer": {
23506
+ settingsType: "NeoGridRelationSettings",
23507
+ property: "ObjectLayers",
23508
+ ordered: true
23509
+ },
23510
+ "world.tile.compatible-layer": {
23511
+ settingsType: "NeoTileRelationSettings",
23512
+ property: "CompatibleLayers",
23513
+ ordered: false
23514
+ },
23515
+ "world.tile.default-layer": {
23516
+ settingsType: "NeoTileRelationSettings",
23517
+ property: "DefaultLayer",
23518
+ ordered: false,
23519
+ singleton: true
23520
+ },
23521
+ "world.object.compatible-layer": {
23522
+ settingsType: "NeoObjectRelationSettings",
23523
+ property: "CompatibleLayers",
23524
+ ordered: false
23525
+ },
23526
+ "world.object.default-layer": {
23527
+ settingsType: "NeoObjectRelationSettings",
23528
+ property: "DefaultLayer",
23529
+ ordered: false,
23530
+ singleton: true
23531
+ },
23532
+ "world.tile-layer-link.target": {
23533
+ settingsType: "NeoTileLayerLinkRelationSettings",
23534
+ property: "TargetLayer",
23535
+ ordered: false,
23536
+ singleton: true
23537
+ },
23538
+ "world.object-layer-link.target": {
23539
+ settingsType: "NeoObjectLayerLinkRelationSettings",
23540
+ property: "TargetLayer",
23541
+ ordered: false,
23542
+ singleton: true
23543
+ }
23544
+ };
22298
23545
  }
22299
23546
  });
22300
23547
 
@@ -22375,7 +23622,8 @@ var init_project_documents = __esm({
22375
23622
  "unity-texture-template",
22376
23623
  "unity-audio-clip-template",
22377
23624
  "localization-config",
22378
- "localization-status"
23625
+ "localization-status",
23626
+ "internal-record-relation"
22379
23627
  ]);
22380
23628
  }
22381
23629
  });
@@ -22423,7 +23671,7 @@ function isSystemDisallowedOperation(value) {
22423
23671
  return value === SystemDisallowedOperation.EditRecord || value === SystemDisallowedOperation.DeleteRecord || value === SystemDisallowedOperation.SelectRecord || value === SystemDisallowedOperation.ReplaceValue;
22424
23672
  }
22425
23673
  function isNeoWorldSystemClassKind(value) {
22426
- return value === NeoWorldSystemClassKind.WorldAssets || value === NeoWorldSystemClassKind.TileGrid || value === NeoWorldSystemClassKind.TileLayer || value === NeoWorldSystemClassKind.ObjectLayer || value === NeoWorldSystemClassKind.Tile || value === NeoWorldSystemClassKind.ObjectBase || value === NeoWorldSystemClassKind.LayerGroupBase || value === NeoWorldSystemClassKind.SpriteObject || value === NeoWorldSystemClassKind.Object || value === NeoWorldSystemClassKind.TileLayerLink || value === NeoWorldSystemClassKind.ObjectLayerLink || value === NeoWorldSystemClassKind.ObjectPlacementTile || value === NeoWorldSystemClassKind.TileInstance || value === NeoWorldSystemClassKind.ObjectCollider || value === NeoWorldSystemClassKind.SortingLayer || value === NeoWorldSystemClassKind.SmartTile || value === NeoWorldSystemClassKind.SmartTileRule || value === NeoWorldSystemClassKind.SmartTileNeighbor;
23674
+ return value === NeoWorldSystemClassKind.TileGrid || value === NeoWorldSystemClassKind.TileLayer || value === NeoWorldSystemClassKind.ObjectLayer || value === NeoWorldSystemClassKind.Tile || value === NeoWorldSystemClassKind.ObjectBase || value === NeoWorldSystemClassKind.LayerGroupBase || value === NeoWorldSystemClassKind.SpriteObject || value === NeoWorldSystemClassKind.Object || value === NeoWorldSystemClassKind.TileLayerLink || value === NeoWorldSystemClassKind.ObjectLayerLink || value === NeoWorldSystemClassKind.ObjectPlacementTile || value === NeoWorldSystemClassKind.TileInstance || value === NeoWorldSystemClassKind.ObjectCollider || value === NeoWorldSystemClassKind.SortingLayer || value === NeoWorldSystemClassKind.SmartTile || value === NeoWorldSystemClassKind.SmartTileRule || value === NeoWorldSystemClassKind.SmartTileNeighbor;
22427
23675
  }
22428
23676
  function isSystemMetadata(value) {
22429
23677
  const v = value;
@@ -22465,7 +23713,6 @@ var init_core_types = __esm({
22465
23713
  ReplaceValue: "replaceValue"
22466
23714
  };
22467
23715
  NeoWorldSystemClassKind = {
22468
- WorldAssets: "worldAssets",
22469
23716
  TileGrid: "tileGrid",
22470
23717
  TileLayer: "tileLayer",
22471
23718
  ObjectLayer: "objectLayer",
@@ -27149,6 +28396,216 @@ var init_value_responses = __esm({
27149
28396
  }
27150
28397
  });
27151
28398
 
28399
+ // ../src/models/project/project-version-types.ts
28400
+ function isProjectVersionBumpKind(value) {
28401
+ if (value === ProjectVersionBumpKind.Patch) return true;
28402
+ if (value === ProjectVersionBumpKind.Minor) return true;
28403
+ if (value === ProjectVersionBumpKind.Major) return true;
28404
+ return false;
28405
+ }
28406
+ function isProjectDocumentRevisionMarker(value) {
28407
+ if (typeof value !== "object") return false;
28408
+ if (value === null) return false;
28409
+ const candidate = value;
28410
+ if (candidate.latestTransactionId !== null && typeof candidate.latestTransactionId !== "string") {
28411
+ return false;
28412
+ }
28413
+ if (typeof candidate.latestTransactionCreatedAt !== "number") return false;
28414
+ return typeof candidate.versionsStamp === "string";
28415
+ }
28416
+ function isProjectRecordKind(value) {
28417
+ if (value === ProjectRecordKind.Project) return true;
28418
+ if (value === ProjectRecordKind.Member) return true;
28419
+ if (value === ProjectRecordKind.Value) return true;
28420
+ if (value === ProjectRecordKind.Class) return true;
28421
+ if (value === ProjectRecordKind.InternalRecordRelation) return true;
28422
+ if (value === ProjectRecordKind.Enum) return true;
28423
+ if (value === ProjectRecordKind.Interface) return true;
28424
+ if (value === ProjectRecordKind.Dialogue) return true;
28425
+ if (value === ProjectRecordKind.DialogueNode) return true;
28426
+ if (value === ProjectRecordKind.DialogueGroup) return true;
28427
+ if (value === ProjectRecordKind.PriorityGroup) return true;
28428
+ if (value === ProjectRecordKind.ProjectFile) return true;
28429
+ if (value === ProjectRecordKind.UnityTextureTemplate) return true;
28430
+ if (value === ProjectRecordKind.UnityAudioClipTemplate) return true;
28431
+ if (value === ProjectRecordKind.LocalizationConfig) return true;
28432
+ if (value === ProjectRecordKind.LocalizationStatus) return true;
28433
+ if (value === ProjectRecordKind.LocalizedText) return true;
28434
+ if (value === ProjectRecordKind.Migration) return true;
28435
+ return false;
28436
+ }
28437
+ function isInternalRecordRelation(value) {
28438
+ if (!isWithId(value)) return false;
28439
+ const relation = value;
28440
+ if (!isString(relation.projectId)) return false;
28441
+ if (!isString(relation.relationKind)) return false;
28442
+ if (relation.relationKind.length === 0) return false;
28443
+ if (!isProjectRecordKind(relation.sourceRecordKind)) return false;
28444
+ if (!isString(relation.sourceRecordId)) return false;
28445
+ if (relation.sourceRecordId.length === 0) return false;
28446
+ if (!isProjectRecordKind(relation.targetRecordKind)) return false;
28447
+ if (!isString(relation.targetRecordId)) return false;
28448
+ if (relation.targetRecordId.length === 0) return false;
28449
+ if (relation.orderKey !== void 0 && (!isString(relation.orderKey) || relation.orderKey.length === 0)) {
28450
+ return false;
28451
+ }
28452
+ if (!isNumber(relation.createdAt)) return false;
28453
+ return isNumber(relation.updatedAt);
28454
+ }
28455
+ function isProjectVersionSemver(value) {
28456
+ if (!isObject(value)) return false;
28457
+ const v = value;
28458
+ if (!isNonNegativeInteger(v.major)) return false;
28459
+ if (!isNonNegativeInteger(v.minor)) return false;
28460
+ if (!isNonNegativeInteger(v.patch)) return false;
28461
+ if (!isString(v.label)) return false;
28462
+ return v.label === `${v.major}.${v.minor}.${v.patch}`;
28463
+ }
28464
+ function isProjectVersionProps(value) {
28465
+ if (!isWithId(value)) return false;
28466
+ const v = value;
28467
+ if (!isString(v.projectId)) return false;
28468
+ if (!isProjectVersionSemver(v.semver)) return false;
28469
+ if (!isString(v.statusId)) return false;
28470
+ if (!isProjectVersionBumpKind(v.bumpKind)) return false;
28471
+ if (!isNullableString(v.sourceVersionId)) return false;
28472
+ if (!isNullableString(v.sourceHeadVersionId)) return false;
28473
+ if (!isTimestamp(v.createdAt)) return false;
28474
+ if (!isTimestamp(v.updatedAt)) return false;
28475
+ if (!isOptionalNullableTimestamp(v.statusChangedAt)) return false;
28476
+ if (!isOptionalNullableTimestamp(v.releasedAt)) return false;
28477
+ if (!isOptionalNullableTimestamp(v.archivedAt)) return false;
28478
+ if (!isPositiveInteger(v.exportSchemaVersion)) return false;
28479
+ if (!isPositiveInteger(v.runtimeSchemaVersion)) return false;
28480
+ if (!isOptionalNullableString(v.codegenContractHash)) return false;
28481
+ if (v.kind !== void 0 && v.kind !== "branch" && v.kind !== "release") {
28482
+ return false;
28483
+ }
28484
+ if (!isOptionalNullableString(v.name)) return false;
28485
+ return isOptionalNullableString(v.runtimeDataContractHash);
28486
+ }
28487
+ function projectVersionDisplayLabel(version) {
28488
+ if (version.kind === "branch" && typeof version.name === "string") {
28489
+ return version.name;
28490
+ }
28491
+ return version.semver.label;
28492
+ }
28493
+ function isProjectVersion(value) {
28494
+ if (!isProjectVersionProps(value)) return false;
28495
+ return isWithId(value);
28496
+ }
28497
+ function isProjectVersionStatusProps(value) {
28498
+ if (!isWithId(value)) return false;
28499
+ const v = value;
28500
+ if (!isString(v.projectId)) return false;
28501
+ if (!isString(v.name)) return false;
28502
+ if (!isOptionalNullableString(v.description)) return false;
28503
+ if (!isNumber(v.sortOrder)) return false;
28504
+ if (!isOptionalNullableString(v.color)) return false;
28505
+ if (!isOptionalNullableTimestamp(v.archivedAt)) return false;
28506
+ if (!isTimestamp(v.createdAt)) return false;
28507
+ if (!isTimestamp(v.updatedAt)) return false;
28508
+ if (!isBoolean(v.isWritable)) return false;
28509
+ if (!isBoolean(v.canSourceNewVersions)) return false;
28510
+ if (!isStringArray(v.releaseChannelIds)) return false;
28511
+ if (!isStringArray(v.allowedNextStatusIds)) return false;
28512
+ if (v.system !== void 0 && v.system !== null) {
28513
+ return isSystemMetadata(v.system);
28514
+ }
28515
+ return true;
28516
+ }
28517
+ function isProjectVersionStatus(value) {
28518
+ if (!isProjectVersionStatusProps(value)) return false;
28519
+ return isWithId(value);
28520
+ }
28521
+ function isProjectReleaseChannelProps(value) {
28522
+ if (!isWithId(value)) return false;
28523
+ const v = value;
28524
+ if (!isString(v.projectId)) return false;
28525
+ if (!isString(v.name)) return false;
28526
+ if (!isString(v.slug)) return false;
28527
+ if (!isOptionalNullableString(v.description)) return false;
28528
+ if (!isNumber(v.sortOrder)) return false;
28529
+ if (!isTimestamp(v.createdAt)) return false;
28530
+ if (!isTimestamp(v.updatedAt)) return false;
28531
+ if (v.system !== void 0 && v.system !== null) {
28532
+ return isSystemMetadata(v.system);
28533
+ }
28534
+ return true;
28535
+ }
28536
+ function isProjectReleaseChannel(value) {
28537
+ if (!isProjectReleaseChannelProps(value)) return false;
28538
+ return isWithId(value);
28539
+ }
28540
+ function isNonNegativeInteger(value) {
28541
+ if (typeof value !== "number") return false;
28542
+ if (!Number.isInteger(value)) return false;
28543
+ return value >= 0;
28544
+ }
28545
+ function isPositiveInteger(value) {
28546
+ if (typeof value !== "number") return false;
28547
+ if (!Number.isInteger(value)) return false;
28548
+ return value > 0;
28549
+ }
28550
+ function isTimestamp(value) {
28551
+ if (typeof value !== "number") return false;
28552
+ return Number.isFinite(value);
28553
+ }
28554
+ function isNullableString(value) {
28555
+ if (value === null) return true;
28556
+ return isString(value);
28557
+ }
28558
+ function isOptionalNullableTimestamp(value) {
28559
+ if (value === void 0) return true;
28560
+ if (value === null) return true;
28561
+ return isTimestamp(value);
28562
+ }
28563
+ function isStringArray(value) {
28564
+ if (!Array.isArray(value)) return false;
28565
+ return value.every(isString);
28566
+ }
28567
+ var ProjectVersionBumpKind, ProjectRecordKind;
28568
+ var init_project_version_types = __esm({
28569
+ "../src/models/project/project-version-types.ts"() {
28570
+ "use strict";
28571
+ init_core();
28572
+ ProjectVersionBumpKind = {
28573
+ Patch: "patch",
28574
+ Minor: "minor",
28575
+ Major: "major"
28576
+ };
28577
+ ProjectRecordKind = {
28578
+ Project: "project",
28579
+ Member: "member",
28580
+ Value: "value",
28581
+ Class: "class",
28582
+ InternalRecordRelation: "internal-record-relation",
28583
+ Enum: "enum",
28584
+ Interface: "interface",
28585
+ Dialogue: "dialogue",
28586
+ DialogueNode: "dialogue-node",
28587
+ DialogueGroup: "dialogue-group",
28588
+ PriorityGroup: "priority-group",
28589
+ ProjectFile: "project-file",
28590
+ UnityTextureTemplate: "unity-texture-template",
28591
+ UnityAudioClipTemplate: "unity-audio-clip-template",
28592
+ LocalizationConfig: "localization-config",
28593
+ LocalizationStatus: "localization-status",
28594
+ LocalizedText: "localized-text",
28595
+ Migration: "migration"
28596
+ };
28597
+ }
28598
+ });
28599
+
28600
+ // ../src/models/project/initial-class-internal-record-relation.ts
28601
+ var init_initial_class_internal_record_relation = __esm({
28602
+ "../src/models/project/initial-class-internal-record-relation.ts"() {
28603
+ "use strict";
28604
+ init_core();
28605
+ init_project_version_types();
28606
+ }
28607
+ });
28608
+
27152
28609
  // ../src/models/classes/add-class-member.ts
27153
28610
  var init_add_class_member = __esm({
27154
28611
  "../src/models/classes/add-class-member.ts"() {
@@ -27156,6 +28613,8 @@ var init_add_class_member = __esm({
27156
28613
  init_members();
27157
28614
  init_enum2();
27158
28615
  init_classes();
28616
+ init_initial_class_internal_record_relation();
28617
+ init_project_version_types();
27159
28618
  }
27160
28619
  });
27161
28620
 
@@ -28884,6 +30343,12 @@ function manifestRecordSources(manifest) {
28884
30343
  for (const status of manifest.localizationStatuses) {
28885
30344
  result.set(recordStateKey("localization-status", status.id), status.source);
28886
30345
  }
30346
+ for (const relation of manifest.internalRecordRelations) {
30347
+ result.set(
30348
+ recordStateKey("internal-record-relation", relation.id),
30349
+ relation.source
30350
+ );
30351
+ }
28887
30352
  return result;
28888
30353
  }
28889
30354
  function mergeMigrationServerFields(fileFields, baseData) {
@@ -28948,187 +30413,6 @@ var init_status = __esm({
28948
30413
  }
28949
30414
  });
28950
30415
 
28951
- // ../src/models/project/project-version-types.ts
28952
- function isProjectVersionBumpKind(value) {
28953
- if (value === ProjectVersionBumpKind.Patch) return true;
28954
- if (value === ProjectVersionBumpKind.Minor) return true;
28955
- if (value === ProjectVersionBumpKind.Major) return true;
28956
- return false;
28957
- }
28958
- function isProjectDocumentRevisionMarker(value) {
28959
- if (typeof value !== "object") return false;
28960
- if (value === null) return false;
28961
- const candidate = value;
28962
- if (candidate.latestTransactionId !== null && typeof candidate.latestTransactionId !== "string") {
28963
- return false;
28964
- }
28965
- if (typeof candidate.latestTransactionCreatedAt !== "number") return false;
28966
- return typeof candidate.versionsStamp === "string";
28967
- }
28968
- function isProjectRecordKind(value) {
28969
- if (value === ProjectRecordKind.Project) return true;
28970
- if (value === ProjectRecordKind.Member) return true;
28971
- if (value === ProjectRecordKind.Value) return true;
28972
- if (value === ProjectRecordKind.Class) return true;
28973
- if (value === ProjectRecordKind.Enum) return true;
28974
- if (value === ProjectRecordKind.Interface) return true;
28975
- if (value === ProjectRecordKind.Dialogue) return true;
28976
- if (value === ProjectRecordKind.DialogueNode) return true;
28977
- if (value === ProjectRecordKind.DialogueGroup) return true;
28978
- if (value === ProjectRecordKind.PriorityGroup) return true;
28979
- if (value === ProjectRecordKind.ProjectFile) return true;
28980
- if (value === ProjectRecordKind.UnityTextureTemplate) return true;
28981
- if (value === ProjectRecordKind.UnityAudioClipTemplate) return true;
28982
- if (value === ProjectRecordKind.LocalizationConfig) return true;
28983
- if (value === ProjectRecordKind.LocalizationStatus) return true;
28984
- if (value === ProjectRecordKind.LocalizedText) return true;
28985
- if (value === ProjectRecordKind.Migration) return true;
28986
- return false;
28987
- }
28988
- function isProjectVersionSemver(value) {
28989
- if (!isObject(value)) return false;
28990
- const v = value;
28991
- if (!isNonNegativeInteger(v.major)) return false;
28992
- if (!isNonNegativeInteger(v.minor)) return false;
28993
- if (!isNonNegativeInteger(v.patch)) return false;
28994
- if (!isString(v.label)) return false;
28995
- return v.label === `${v.major}.${v.minor}.${v.patch}`;
28996
- }
28997
- function isProjectVersionProps(value) {
28998
- if (!isWithId(value)) return false;
28999
- const v = value;
29000
- if (!isString(v.projectId)) return false;
29001
- if (!isProjectVersionSemver(v.semver)) return false;
29002
- if (!isString(v.statusId)) return false;
29003
- if (!isProjectVersionBumpKind(v.bumpKind)) return false;
29004
- if (!isNullableString(v.sourceVersionId)) return false;
29005
- if (!isNullableString(v.sourceHeadVersionId)) return false;
29006
- if (!isTimestamp(v.createdAt)) return false;
29007
- if (!isTimestamp(v.updatedAt)) return false;
29008
- if (!isOptionalNullableTimestamp(v.statusChangedAt)) return false;
29009
- if (!isOptionalNullableTimestamp(v.releasedAt)) return false;
29010
- if (!isOptionalNullableTimestamp(v.archivedAt)) return false;
29011
- if (!isPositiveInteger(v.exportSchemaVersion)) return false;
29012
- if (!isPositiveInteger(v.runtimeSchemaVersion)) return false;
29013
- if (!isOptionalNullableString(v.codegenContractHash)) return false;
29014
- if (v.kind !== void 0 && v.kind !== "branch" && v.kind !== "release") {
29015
- return false;
29016
- }
29017
- if (!isOptionalNullableString(v.name)) return false;
29018
- return isOptionalNullableString(v.runtimeDataContractHash);
29019
- }
29020
- function projectVersionDisplayLabel(version) {
29021
- if (version.kind === "branch" && typeof version.name === "string") {
29022
- return version.name;
29023
- }
29024
- return version.semver.label;
29025
- }
29026
- function isProjectVersion(value) {
29027
- if (!isProjectVersionProps(value)) return false;
29028
- return isWithId(value);
29029
- }
29030
- function isProjectVersionStatusProps(value) {
29031
- if (!isWithId(value)) return false;
29032
- const v = value;
29033
- if (!isString(v.projectId)) return false;
29034
- if (!isString(v.name)) return false;
29035
- if (!isOptionalNullableString(v.description)) return false;
29036
- if (!isNumber(v.sortOrder)) return false;
29037
- if (!isOptionalNullableString(v.color)) return false;
29038
- if (!isOptionalNullableTimestamp(v.archivedAt)) return false;
29039
- if (!isTimestamp(v.createdAt)) return false;
29040
- if (!isTimestamp(v.updatedAt)) return false;
29041
- if (!isBoolean(v.isWritable)) return false;
29042
- if (!isBoolean(v.canSourceNewVersions)) return false;
29043
- if (!isStringArray(v.releaseChannelIds)) return false;
29044
- if (!isStringArray(v.allowedNextStatusIds)) return false;
29045
- if (v.system !== void 0 && v.system !== null) {
29046
- return isSystemMetadata(v.system);
29047
- }
29048
- return true;
29049
- }
29050
- function isProjectVersionStatus(value) {
29051
- if (!isProjectVersionStatusProps(value)) return false;
29052
- return isWithId(value);
29053
- }
29054
- function isProjectReleaseChannelProps(value) {
29055
- if (!isWithId(value)) return false;
29056
- const v = value;
29057
- if (!isString(v.projectId)) return false;
29058
- if (!isString(v.name)) return false;
29059
- if (!isString(v.slug)) return false;
29060
- if (!isOptionalNullableString(v.description)) return false;
29061
- if (!isNumber(v.sortOrder)) return false;
29062
- if (!isTimestamp(v.createdAt)) return false;
29063
- if (!isTimestamp(v.updatedAt)) return false;
29064
- if (v.system !== void 0 && v.system !== null) {
29065
- return isSystemMetadata(v.system);
29066
- }
29067
- return true;
29068
- }
29069
- function isProjectReleaseChannel(value) {
29070
- if (!isProjectReleaseChannelProps(value)) return false;
29071
- return isWithId(value);
29072
- }
29073
- function isNonNegativeInteger(value) {
29074
- if (typeof value !== "number") return false;
29075
- if (!Number.isInteger(value)) return false;
29076
- return value >= 0;
29077
- }
29078
- function isPositiveInteger(value) {
29079
- if (typeof value !== "number") return false;
29080
- if (!Number.isInteger(value)) return false;
29081
- return value > 0;
29082
- }
29083
- function isTimestamp(value) {
29084
- if (typeof value !== "number") return false;
29085
- return Number.isFinite(value);
29086
- }
29087
- function isNullableString(value) {
29088
- if (value === null) return true;
29089
- return isString(value);
29090
- }
29091
- function isOptionalNullableTimestamp(value) {
29092
- if (value === void 0) return true;
29093
- if (value === null) return true;
29094
- return isTimestamp(value);
29095
- }
29096
- function isStringArray(value) {
29097
- if (!Array.isArray(value)) return false;
29098
- return value.every(isString);
29099
- }
29100
- var ProjectVersionBumpKind, ProjectRecordKind;
29101
- var init_project_version_types = __esm({
29102
- "../src/models/project/project-version-types.ts"() {
29103
- "use strict";
29104
- init_core();
29105
- ProjectVersionBumpKind = {
29106
- Patch: "patch",
29107
- Minor: "minor",
29108
- Major: "major"
29109
- };
29110
- ProjectRecordKind = {
29111
- Project: "project",
29112
- Member: "member",
29113
- Value: "value",
29114
- Class: "class",
29115
- Enum: "enum",
29116
- Interface: "interface",
29117
- Dialogue: "dialogue",
29118
- DialogueNode: "dialogue-node",
29119
- DialogueGroup: "dialogue-group",
29120
- PriorityGroup: "priority-group",
29121
- ProjectFile: "project-file",
29122
- UnityTextureTemplate: "unity-texture-template",
29123
- UnityAudioClipTemplate: "unity-audio-clip-template",
29124
- LocalizationConfig: "localization-config",
29125
- LocalizationStatus: "localization-status",
29126
- LocalizedText: "localized-text",
29127
- Migration: "migration"
29128
- };
29129
- }
29130
- });
29131
-
29132
30416
  // ../convex/_generated/api.js
29133
30417
  var api_exports = {};
29134
30418
  __export(api_exports, {
@@ -29319,6 +30603,144 @@ var init_project_migration_types = __esm({
29319
30603
  }
29320
30604
  });
29321
30605
 
30606
+ // ../src/models/project/internal-record-relations.ts
30607
+ function worldClassContract(relationKind, sourceWorldKind, targetWorldKind, targetClassPolicy, merge, allowAbstractTarget) {
30608
+ return {
30609
+ relationKind,
30610
+ endpointPairs: [{ sourceRecordKind: "class", targetRecordKind: "class" }],
30611
+ sourceClassPolicy: "include-descendants",
30612
+ targetClassPolicy,
30613
+ merge,
30614
+ allowCycles: false,
30615
+ sourceWorldKind,
30616
+ targetWorldKind,
30617
+ allowAbstractTarget
30618
+ };
30619
+ }
30620
+ var InternalRecordRelationKind, INTERNAL_RECORD_RELATION_KIND_CONTRACTS;
30621
+ var init_internal_record_relations = __esm({
30622
+ "../src/models/project/internal-record-relations.ts"() {
30623
+ "use strict";
30624
+ init_classes();
30625
+ init_core();
30626
+ init_project_version_types();
30627
+ InternalRecordRelationKind = {
30628
+ WorldGridTileImport: "world.grid.tile-import",
30629
+ WorldGridObjectImport: "world.grid.object-import",
30630
+ WorldGridTileLayer: "world.grid.tile-layer",
30631
+ WorldGridObjectLayer: "world.grid.object-layer",
30632
+ WorldTileCompatibleLayer: "world.tile.compatible-layer",
30633
+ WorldTileDefaultLayer: "world.tile.default-layer",
30634
+ WorldObjectCompatibleLayer: "world.object.compatible-layer",
30635
+ WorldObjectDefaultLayer: "world.object.default-layer",
30636
+ WorldTileLayerLinkTarget: "world.tile-layer-link.target",
30637
+ WorldObjectLayerLinkTarget: "world.object-layer-link.target",
30638
+ WorldSmartTileNeighborTile: "world.smart-tile-neighbor.tile"
30639
+ };
30640
+ INTERNAL_RECORD_RELATION_KIND_CONTRACTS = [
30641
+ worldClassContract(
30642
+ InternalRecordRelationKind.WorldGridTileImport,
30643
+ NeoWorldSystemClassKind.TileGrid,
30644
+ NeoWorldSystemClassKind.Tile,
30645
+ "include-descendants",
30646
+ "union",
30647
+ true
30648
+ ),
30649
+ worldClassContract(
30650
+ InternalRecordRelationKind.WorldGridObjectImport,
30651
+ NeoWorldSystemClassKind.TileGrid,
30652
+ NeoWorldSystemClassKind.Object,
30653
+ "include-descendants",
30654
+ "union",
30655
+ true
30656
+ ),
30657
+ worldClassContract(
30658
+ InternalRecordRelationKind.WorldGridTileLayer,
30659
+ NeoWorldSystemClassKind.TileGrid,
30660
+ NeoWorldSystemClassKind.TileLayer,
30661
+ "exact",
30662
+ "ordered-union",
30663
+ false
30664
+ ),
30665
+ worldClassContract(
30666
+ InternalRecordRelationKind.WorldGridObjectLayer,
30667
+ NeoWorldSystemClassKind.TileGrid,
30668
+ NeoWorldSystemClassKind.ObjectLayer,
30669
+ "exact",
30670
+ "ordered-union",
30671
+ false
30672
+ ),
30673
+ worldClassContract(
30674
+ InternalRecordRelationKind.WorldTileCompatibleLayer,
30675
+ NeoWorldSystemClassKind.Tile,
30676
+ NeoWorldSystemClassKind.TileLayer,
30677
+ "include-descendants",
30678
+ "union",
30679
+ true
30680
+ ),
30681
+ worldClassContract(
30682
+ InternalRecordRelationKind.WorldTileDefaultLayer,
30683
+ NeoWorldSystemClassKind.Tile,
30684
+ NeoWorldSystemClassKind.TileLayer,
30685
+ "exact",
30686
+ "nearest-single",
30687
+ false
30688
+ ),
30689
+ worldClassContract(
30690
+ InternalRecordRelationKind.WorldObjectCompatibleLayer,
30691
+ NeoWorldSystemClassKind.Object,
30692
+ NeoWorldSystemClassKind.ObjectLayer,
30693
+ "include-descendants",
30694
+ "union",
30695
+ true
30696
+ ),
30697
+ worldClassContract(
30698
+ InternalRecordRelationKind.WorldObjectDefaultLayer,
30699
+ NeoWorldSystemClassKind.Object,
30700
+ NeoWorldSystemClassKind.ObjectLayer,
30701
+ "exact",
30702
+ "nearest-single",
30703
+ false
30704
+ ),
30705
+ worldClassContract(
30706
+ InternalRecordRelationKind.WorldTileLayerLinkTarget,
30707
+ NeoWorldSystemClassKind.TileLayerLink,
30708
+ NeoWorldSystemClassKind.TileLayer,
30709
+ "exact",
30710
+ "nearest-single",
30711
+ false
30712
+ ),
30713
+ worldClassContract(
30714
+ InternalRecordRelationKind.WorldObjectLayerLinkTarget,
30715
+ NeoWorldSystemClassKind.ObjectLayerLink,
30716
+ NeoWorldSystemClassKind.ObjectLayer,
30717
+ "exact",
30718
+ "nearest-single",
30719
+ false
30720
+ ),
30721
+ {
30722
+ relationKind: InternalRecordRelationKind.WorldSmartTileNeighborTile,
30723
+ endpointPairs: [{ sourceRecordKind: "value", targetRecordKind: "class" }],
30724
+ sourceClassPolicy: "exact",
30725
+ targetClassPolicy: "exact",
30726
+ merge: "nearest-single",
30727
+ allowCycles: false,
30728
+ sourceWorldKind: NeoWorldSystemClassKind.SmartTileNeighbor,
30729
+ targetWorldKind: NeoWorldSystemClassKind.Tile,
30730
+ allowAbstractTarget: true
30731
+ }
30732
+ ];
30733
+ }
30734
+ });
30735
+
30736
+ // ../src/models/project/internal-record-relation-head-projection.ts
30737
+ var init_internal_record_relation_head_projection = __esm({
30738
+ "../src/models/project/internal-record-relation-head-projection.ts"() {
30739
+ "use strict";
30740
+ init_project_version_types();
30741
+ }
30742
+ });
30743
+
29322
30744
  // ../src/models/project/index.ts
29323
30745
  var init_project2 = __esm({
29324
30746
  "../src/models/project/index.ts"() {
@@ -29328,6 +30750,9 @@ var init_project2 = __esm({
29328
30750
  init_project_root_members();
29329
30751
  init_project_version_types();
29330
30752
  init_project_migration_types();
30753
+ init_internal_record_relations();
30754
+ init_internal_record_relation_head_projection();
30755
+ init_initial_class_internal_record_relation();
29331
30756
  }
29332
30757
  });
29333
30758
 
@@ -30223,6 +31648,7 @@ var init_projectRecordBuckets = __esm({
30223
31648
  member: "members",
30224
31649
  value: "values",
30225
31650
  class: "classes",
31651
+ "internal-record-relation": "internalRecordRelations",
30226
31652
  enum: "enums",
30227
31653
  interface: "interfaces",
30228
31654
  dialogue: "dialogueRecords",
@@ -30618,6 +32044,7 @@ var init_project_record_migrations = __esm({
30618
32044
  [ProjectRecordKind.Member]: 4,
30619
32045
  [ProjectRecordKind.Value]: 1,
30620
32046
  [ProjectRecordKind.Class]: 1,
32047
+ [ProjectRecordKind.InternalRecordRelation]: 1,
30621
32048
  [ProjectRecordKind.Enum]: 2,
30622
32049
  [ProjectRecordKind.Interface]: 1,
30623
32050
  [ProjectRecordKind.Dialogue]: 4,
@@ -30641,6 +32068,7 @@ var init_project_record_migrations = __esm({
30641
32068
  },
30642
32069
  [ProjectRecordKind.Value]: {},
30643
32070
  [ProjectRecordKind.Class]: {},
32071
+ [ProjectRecordKind.InternalRecordRelation]: {},
30644
32072
  [ProjectRecordKind.Enum]: {
30645
32073
  1: migrateEnumOptionKeyOrder
30646
32074
  },
@@ -31014,6 +32442,7 @@ function assembleProjectDocumentRaw(args) {
31014
32442
  project: null,
31015
32443
  members: [],
31016
32444
  classes: [],
32445
+ internalRecordRelations: [],
31017
32446
  values: [],
31018
32447
  enums: [],
31019
32448
  interfaces: [],
@@ -31074,6 +32503,7 @@ function assembleProjectDocumentRaw(args) {
31074
32503
  project: buckets.project,
31075
32504
  members: buckets.members,
31076
32505
  classes: buckets.classes,
32506
+ internalRecordRelations: buckets.internalRecordRelations,
31077
32507
  values: buckets.values,
31078
32508
  enums: buckets.enums,
31079
32509
  interfaces: buckets.interfaces,
@@ -31121,10 +32551,15 @@ function appendBucketRecord(buckets, recordKind, data) {
31121
32551
  }
31122
32552
  buckets[bucket].push(data);
31123
32553
  }
31124
- function readProjectDocument(value) {
32554
+ function readProjectDocument(value, options = {}) {
31125
32555
  if (!isObject2(value)) {
31126
32556
  throw new Error("Convex project document query returned a non-object.");
31127
32557
  }
32558
+ const localizationConfig = options.localizationConfig === "nullable" ? readOptionalNullableField(
32559
+ value,
32560
+ "localizationConfig",
32561
+ isProjectLocalizationConfig
32562
+ ) : readField(value, "localizationConfig", isProjectLocalizationConfig);
31128
32563
  return {
31129
32564
  version: readField(value, "version", isProjectVersion),
31130
32565
  versions: readArrayField(value, "versions", isProjectVersion),
@@ -31149,6 +32584,11 @@ function readProjectDocument(value) {
31149
32584
  isAnyMember
31150
32585
  ),
31151
32586
  classes: readArrayField(value, "classes", isNeoSchemaClass),
32587
+ internalRecordRelations: readOptionalArrayField(
32588
+ value,
32589
+ "internalRecordRelations",
32590
+ isInternalRecordRelation
32591
+ ),
31152
32592
  values: readArrayField(value, "values", isMemberValue),
31153
32593
  enums: readArrayField(value, "enums", isEnum),
31154
32594
  interfaces: readArrayField(value, "interfaces", isNeoInterface),
@@ -31176,11 +32616,7 @@ function readProjectDocument(value) {
31176
32616
  "audioClipTemplates",
31177
32617
  isUnityAudioClipImportSettingsTemplate
31178
32618
  ),
31179
- localizationConfig: readField(
31180
- value,
31181
- "localizationConfig",
31182
- isProjectLocalizationConfig
31183
- ),
32619
+ localizationConfig,
31184
32620
  localizationStatuses: readArrayField(
31185
32621
  value,
31186
32622
  "localizationStatuses",
@@ -31221,6 +32657,14 @@ function readField(source, key, isValid) {
31221
32657
  `Convex project document field "${key}" has an invalid shape.`
31222
32658
  );
31223
32659
  }
32660
+ function readOptionalNullableField(source, key, isValid) {
32661
+ const value = source[key];
32662
+ if (value === void 0 || value === null) return null;
32663
+ if (isValid(value)) return value;
32664
+ throw new Error(
32665
+ `Convex project document field "${key}" has an invalid shape.`
32666
+ );
32667
+ }
31224
32668
  function readArrayField(source, key, isValid) {
31225
32669
  const value = source[key];
31226
32670
  if (!Array.isArray(value)) {
@@ -31371,6 +32815,12 @@ async function fetchProjectDocument(workspace) {
31371
32815
  entries: readRawArray(raw, "localizationStatuses"),
31372
32816
  guard: isProjectedRecord,
31373
32817
  label: "localization status"
32818
+ },
32819
+ {
32820
+ recordKind: "internal-record-relation",
32821
+ entries: readRawArray(raw, "internalRecordRelations"),
32822
+ guard: isProjectedRecord,
32823
+ label: "internal record relation"
31374
32824
  }
31375
32825
  ];
31376
32826
  const records2 = /* @__PURE__ */ new Map();
@@ -32162,6 +33612,565 @@ var init_init = __esm({
32162
33612
  }
32163
33613
  });
32164
33614
 
33615
+ // src/commands/history-selection.ts
33616
+ async function resolveHistoryScope(options) {
33617
+ const projectSelector = await readHistorySelector({
33618
+ command: options.command,
33619
+ kind: "project",
33620
+ provided: options.projectSelector
33621
+ });
33622
+ const project = await options.resolveProject(projectSelector);
33623
+ console.log(`${sym.info} Project: ${project.name} (${project.id})`);
33624
+ if (!options.requiresVersion) {
33625
+ return { project, version: null };
33626
+ }
33627
+ const versionSelector = await readHistorySelector({
33628
+ command: options.command,
33629
+ kind: "version",
33630
+ provided: options.versionSelector
33631
+ });
33632
+ const version = await options.resolveVersion(project.id, versionSelector);
33633
+ console.log(`${sym.info} Version: ${version.name} (${version.id})`);
33634
+ return { project, version };
33635
+ }
33636
+ async function readHistorySelector(args) {
33637
+ if (args.provided !== null) return normalizeHistorySelector(args.provided);
33638
+ const flag = args.kind === "project" ? "--project" : "--version";
33639
+ if (!isInteractive()) {
33640
+ throw new Error(
33641
+ `neo history ${args.command} requires ${flag} <id|@latest|@current> outside an interactive terminal.`
33642
+ );
33643
+ }
33644
+ const label = args.kind === "project" ? "Project" : "Version";
33645
+ const value = await promptInput({
33646
+ message: `${label} ID (@latest or @current; Enter for latest)`,
33647
+ nonInteractiveHint: `Pass ${flag} <id|@latest|@current>.`
33648
+ });
33649
+ return normalizeHistorySelector(value);
33650
+ }
33651
+ function normalizeHistorySelector(value) {
33652
+ const trimmed = value.trim();
33653
+ if (trimmed.length === 0 || trimmed === "@latest" || trimmed === "@current") {
33654
+ return "@latest";
33655
+ }
33656
+ if (trimmed.startsWith("@")) {
33657
+ throw new Error(
33658
+ `Unknown history selector "${trimmed}". Use a stable ID, @latest, or @current.`
33659
+ );
33660
+ }
33661
+ return trimmed;
33662
+ }
33663
+ var init_history_selection = __esm({
33664
+ "src/commands/history-selection.ts"() {
33665
+ "use strict";
33666
+ init_ui();
33667
+ }
33668
+ });
33669
+
33670
+ // src/commands/history-inspect.ts
33671
+ function inspectHistoryDataset(dataset) {
33672
+ const brokenReferences = [];
33673
+ const projectSnapshots = new Map(
33674
+ dataset["project-snapshots"].map((snapshot) => [snapshot.id, snapshot])
33675
+ );
33676
+ const projectTransactions = new Set(
33677
+ dataset["project-transactions"].map((transaction) => transaction.id)
33678
+ );
33679
+ const reachableProjectSnapshots = walkAncestors({
33680
+ roots: dataset["project-heads"].map((head) => ({
33681
+ id: head.snapshotId,
33682
+ source: `project head "${head.id}"`
33683
+ })),
33684
+ rows: projectSnapshots,
33685
+ baseId: (snapshot) => snapshot.baseSnapshotId,
33686
+ kind: "project snapshot",
33687
+ brokenReferences
33688
+ });
33689
+ for (const snapshot of projectSnapshots.values()) {
33690
+ if (!projectTransactions.has(snapshot.transactionId)) {
33691
+ brokenReferences.push(
33692
+ `Project snapshot "${snapshot.id}" references missing transaction "${snapshot.transactionId}".`
33693
+ );
33694
+ }
33695
+ }
33696
+ const reachableProjectTransactions = new Set(
33697
+ [...reachableProjectSnapshots].map((id) => projectSnapshots.get(id)?.transactionId).filter(
33698
+ (id) => id !== void 0 && projectTransactions.has(id)
33699
+ )
33700
+ );
33701
+ const wikiSnapshots = new Map(
33702
+ dataset["wiki-snapshots"].map((snapshot) => [snapshot.id, snapshot])
33703
+ );
33704
+ const wikiTransactions = new Set(
33705
+ dataset["wiki-transactions"].map((transaction) => transaction.id)
33706
+ );
33707
+ const wikiDocuments = new Map(
33708
+ dataset["wiki-documents"].map((document) => [document.id, document])
33709
+ );
33710
+ const reachableWikiSnapshots = walkAncestors({
33711
+ roots: dataset["wiki-heads"].map((head) => ({
33712
+ id: head.snapshotId,
33713
+ source: `wiki head "${head.id}"`
33714
+ })),
33715
+ rows: wikiSnapshots,
33716
+ baseId: (snapshot) => snapshot.baseSnapshotId,
33717
+ kind: "wiki snapshot",
33718
+ brokenReferences
33719
+ });
33720
+ const reachableWikiDocuments = walkAncestors({
33721
+ roots: [...reachableWikiSnapshots].flatMap((snapshotId) => {
33722
+ const snapshot = wikiSnapshots.get(snapshotId);
33723
+ return snapshot === void 0 ? [] : [
33724
+ {
33725
+ id: snapshot.yjsDocumentId,
33726
+ source: `wiki snapshot "${snapshot.id}"`
33727
+ }
33728
+ ];
33729
+ }),
33730
+ rows: wikiDocuments,
33731
+ baseId: (document) => document.forkedFromDocumentId,
33732
+ kind: "wiki document",
33733
+ brokenReferences
33734
+ });
33735
+ const reachableWikiTransactions = /* @__PURE__ */ new Set();
33736
+ for (const snapshotId of reachableWikiSnapshots) {
33737
+ const snapshot = wikiSnapshots.get(snapshotId);
33738
+ if (snapshot !== void 0 && wikiTransactions.has(snapshot.transactionId)) {
33739
+ reachableWikiTransactions.add(snapshot.transactionId);
33740
+ }
33741
+ }
33742
+ for (const snapshot of wikiSnapshots.values()) {
33743
+ if (!wikiTransactions.has(snapshot.transactionId)) {
33744
+ brokenReferences.push(
33745
+ `Wiki snapshot "${snapshot.id}" references missing transaction "${snapshot.transactionId}".`
33746
+ );
33747
+ }
33748
+ if (!wikiDocuments.has(snapshot.yjsDocumentId)) {
33749
+ brokenReferences.push(
33750
+ `Wiki snapshot "${snapshot.id}" references missing document "${snapshot.yjsDocumentId}".`
33751
+ );
33752
+ }
33753
+ }
33754
+ let unreachableWikiRevisions = 0;
33755
+ for (const revision of dataset["wiki-revisions"]) {
33756
+ const documentExists = wikiDocuments.has(revision.yjsDocumentId);
33757
+ const transactionExists = wikiTransactions.has(revision.transactionId);
33758
+ if (!documentExists) {
33759
+ brokenReferences.push(
33760
+ `Wiki revision "${revision.id}" references missing document "${revision.yjsDocumentId}".`
33761
+ );
33762
+ }
33763
+ if (!transactionExists) {
33764
+ brokenReferences.push(
33765
+ `Wiki revision "${revision.id}" references missing transaction "${revision.transactionId}".`
33766
+ );
33767
+ }
33768
+ if (transactionExists && reachableWikiDocuments.has(revision.yjsDocumentId)) {
33769
+ reachableWikiTransactions.add(revision.transactionId);
33770
+ } else {
33771
+ unreachableWikiRevisions += 1;
33772
+ }
33773
+ }
33774
+ return {
33775
+ versions: {
33776
+ total: dataset.versions.length,
33777
+ archived: dataset.versions.filter((version) => version.archived).length
33778
+ },
33779
+ projectRecords: {
33780
+ heads: dataset["project-heads"].length,
33781
+ snapshots: projectSnapshots.size,
33782
+ transactions: projectTransactions.size,
33783
+ unreachableSnapshots: projectSnapshots.size - reachableProjectSnapshots.size,
33784
+ unreachableTransactions: projectTransactions.size - reachableProjectTransactions.size,
33785
+ duplicateSemanticSnapshots: duplicateCount(
33786
+ dataset["project-snapshots"],
33787
+ (snapshot) => `${snapshot.recordKind}:${snapshot.recordId}:${snapshot.contentHash}`
33788
+ )
33789
+ },
33790
+ wiki: {
33791
+ heads: dataset["wiki-heads"].length,
33792
+ snapshots: wikiSnapshots.size,
33793
+ transactions: wikiTransactions.size,
33794
+ documents: wikiDocuments.size,
33795
+ revisions: dataset["wiki-revisions"].length,
33796
+ unreachableSnapshots: wikiSnapshots.size - reachableWikiSnapshots.size,
33797
+ unreachableTransactions: wikiTransactions.size - reachableWikiTransactions.size,
33798
+ unreachableDocuments: wikiDocuments.size - reachableWikiDocuments.size,
33799
+ unreachableRevisions: unreachableWikiRevisions,
33800
+ duplicateSemanticSnapshots: duplicateCount(
33801
+ dataset["wiki-snapshots"],
33802
+ (snapshot) => `${snapshot.pageId}:${snapshot.contentHash}`
33803
+ ),
33804
+ duplicateSemanticDocuments: duplicateCount(
33805
+ dataset["wiki-documents"],
33806
+ (document) => `${document.pageId}:${document.contentHash}`
33807
+ )
33808
+ },
33809
+ brokenReferences
33810
+ };
33811
+ }
33812
+ function walkAncestors(args) {
33813
+ const reachable = /* @__PURE__ */ new Set();
33814
+ const stack = [...args.roots];
33815
+ while (stack.length > 0) {
33816
+ const current = stack.pop();
33817
+ if (reachable.has(current.id)) continue;
33818
+ const row = args.rows.get(current.id);
33819
+ if (row === void 0) {
33820
+ args.brokenReferences.push(
33821
+ `${current.source} references missing ${args.kind} "${current.id}".`
33822
+ );
33823
+ continue;
33824
+ }
33825
+ reachable.add(current.id);
33826
+ const baseId = args.baseId(row);
33827
+ if (baseId !== null) {
33828
+ stack.push({
33829
+ id: baseId,
33830
+ source: `${args.kind} "${current.id}"`
33831
+ });
33832
+ }
33833
+ }
33834
+ return reachable;
33835
+ }
33836
+ function duplicateCount(rows, key) {
33837
+ const counts = /* @__PURE__ */ new Map();
33838
+ for (const row of rows) {
33839
+ const value = key(row);
33840
+ counts.set(value, (counts.get(value) ?? 0) + 1);
33841
+ }
33842
+ let duplicates = 0;
33843
+ for (const count of counts.values()) {
33844
+ if (count > 1) duplicates += count - 1;
33845
+ }
33846
+ return duplicates;
33847
+ }
33848
+ var HISTORY_INSPECT_PHASES;
33849
+ var init_history_inspect = __esm({
33850
+ "src/commands/history-inspect.ts"() {
33851
+ "use strict";
33852
+ HISTORY_INSPECT_PHASES = [
33853
+ "versions",
33854
+ "project-heads",
33855
+ "project-snapshots",
33856
+ "project-transactions",
33857
+ "wiki-heads",
33858
+ "wiki-snapshots",
33859
+ "wiki-transactions",
33860
+ "wiki-documents",
33861
+ "wiki-revisions"
33862
+ ];
33863
+ }
33864
+ });
33865
+
33866
+ // src/commands/history.ts
33867
+ var history_exports = {};
33868
+ __export(history_exports, {
33869
+ resolveHistoryScopeFromServer: () => resolveHistoryScopeFromServer,
33870
+ runHistory: () => runHistory
33871
+ });
33872
+ async function resolveHistoryScopeFromServer(options) {
33873
+ const convex = await createConvexClientForApi(options.apiBaseUrl);
33874
+ return await resolveHistoryScope({
33875
+ command: options.command,
33876
+ projectSelector: options.projectSelector,
33877
+ versionSelector: options.versionSelector,
33878
+ requiresVersion: options.command === "log" || options.command === "flatten",
33879
+ resolveProject: async (selector) => await convex.query(api2.projectHistory.resolveProjectSelector, {
33880
+ selector
33881
+ }),
33882
+ resolveVersion: async (projectId, selector) => await convex.query(api2.projectHistory.resolveVersionSelector, {
33883
+ projectId,
33884
+ selector
33885
+ })
33886
+ });
33887
+ }
33888
+ async function runHistory(options) {
33889
+ assertHistoryOptions(options);
33890
+ const convex = await createConvexClientForApi(options.apiBaseUrl);
33891
+ if (options.command === "inspect") {
33892
+ const scope = await resolveScopeWithClient(convex, options);
33893
+ await printHistoryInspection(convex, scope.project.id);
33894
+ return;
33895
+ }
33896
+ if (options.command === "log") {
33897
+ const scope = await resolveScopeWithClient(convex, options);
33898
+ await printVersionLog(convex, scope);
33899
+ return;
33900
+ }
33901
+ if (options.command === "flatten") {
33902
+ const scope = await resolveScopeWithClient(convex, options);
33903
+ if (scope.version === null) {
33904
+ throw new Error("Flatten requires a resolved version.");
33905
+ }
33906
+ const started = await convex.mutation(api2.projectHistory.planFlatten, {
33907
+ projectId: scope.project.id,
33908
+ versionId: scope.version.id
33909
+ });
33910
+ console.log(`${sym.info} Plan: ${started.planId}`);
33911
+ console.log(`${sym.info} Planning run: ${started.runId}`);
33912
+ const plan = await waitForPlan(convex, started.planId);
33913
+ printPlan(plan);
33914
+ return;
33915
+ }
33916
+ if (options.command === "prune") {
33917
+ const scope = await resolveScopeWithClient(convex, options);
33918
+ const started = await convex.mutation(api2.projectHistory.planPrune, {
33919
+ projectId: scope.project.id
33920
+ });
33921
+ console.log(`${sym.info} Plan: ${started.planId}`);
33922
+ console.log(`${sym.info} Planning run: ${started.runId}`);
33923
+ const plan = await waitForPlan(convex, started.planId);
33924
+ printPlan(plan);
33925
+ return;
33926
+ }
33927
+ if (options.command === "apply") {
33928
+ const plan = await convex.query(api2.projectHistory.getPlan, {
33929
+ planId: options.planId
33930
+ });
33931
+ const expectedScope = plan.versionId === null ? plan.projectId : `${plan.projectId}/${plan.versionId}`;
33932
+ let confirmScope = options.confirmScope;
33933
+ if (plan.productionConfirmationRequired && confirmScope === null) {
33934
+ if (!isInteractive()) {
33935
+ throw new Error(
33936
+ `Production history apply requires --confirm-scope ${expectedScope}.`
33937
+ );
33938
+ }
33939
+ confirmScope = await promptInput({
33940
+ message: `Type ${expectedScope} to confirm production history rewrite`,
33941
+ nonInteractiveHint: `Pass --confirm-scope ${expectedScope}.`,
33942
+ validate: (value) => value === expectedScope ? true : `Confirmation must exactly match ${expectedScope}.`
33943
+ });
33944
+ }
33945
+ const started = await convex.mutation(api2.projectHistory.applyPlan, {
33946
+ planId: plan.id,
33947
+ confirmScope
33948
+ });
33949
+ console.log(`${sym.info} Run: ${started.runId}`);
33950
+ console.log(`${sym.info} State: ${started.state}`);
33951
+ return;
33952
+ }
33953
+ if (options.command === "status") {
33954
+ const status = await convex.query(api2.projectHistory.getRunStatus, {
33955
+ runId: options.runId
33956
+ });
33957
+ printRunStatus(status);
33958
+ return;
33959
+ }
33960
+ if (options.command === "resume") {
33961
+ const resumed = await convex.mutation(api2.projectHistory.resumeRun, {
33962
+ runId: options.runId
33963
+ });
33964
+ console.log(`${sym.info} Run: ${resumed.runId}`);
33965
+ console.log(`${sym.info} State: ${resumed.state}`);
33966
+ return;
33967
+ }
33968
+ }
33969
+ async function resolveScopeWithClient(convex, options) {
33970
+ if (options.command !== "inspect" && options.command !== "log" && options.command !== "prune" && options.command !== "flatten") {
33971
+ throw new Error(`neo history ${options.command} has immutable scope.`);
33972
+ }
33973
+ return await resolveHistoryScope({
33974
+ command: options.command,
33975
+ projectSelector: options.projectSelector,
33976
+ versionSelector: options.versionSelector,
33977
+ requiresVersion: options.command === "log" || options.command === "flatten",
33978
+ resolveProject: async (selector) => await convex.query(api2.projectHistory.resolveProjectSelector, {
33979
+ selector
33980
+ }),
33981
+ resolveVersion: async (projectId, selector) => await convex.query(api2.projectHistory.resolveVersionSelector, {
33982
+ projectId,
33983
+ selector
33984
+ })
33985
+ });
33986
+ }
33987
+ async function waitForPlan(convex, planId) {
33988
+ let lastState = null;
33989
+ while (true) {
33990
+ const plan = await convex.query(api2.projectHistory.getPlan, { planId });
33991
+ if (plan.state !== lastState) {
33992
+ console.log(`${sym.info} Plan state: ${plan.state}`);
33993
+ lastState = plan.state;
33994
+ }
33995
+ if (plan.state === "sealed") return plan;
33996
+ if (plan.state === "failed") {
33997
+ throw new Error(
33998
+ `History plan "${plan.id}" failed: ${plan.error ?? "unknown error"}`
33999
+ );
34000
+ }
34001
+ await new Promise((resolve6) => setTimeout(resolve6, 500));
34002
+ }
34003
+ }
34004
+ async function printVersionLog(convex, scope) {
34005
+ if (scope.version === null) {
34006
+ throw new Error("History log requires a resolved version.");
34007
+ }
34008
+ let cursor = null;
34009
+ let count = 0;
34010
+ while (true) {
34011
+ const page = await convex.query(
34012
+ api2.projectVersionHistory.getChangelog,
34013
+ {
34014
+ projectId: scope.project.id,
34015
+ versionId: scope.version.id,
34016
+ paginationOpts: { cursor, numItems: 50 }
34017
+ }
34018
+ );
34019
+ if (page === null) {
34020
+ throw new Error(
34021
+ `Version "${scope.version.id}" no longer exists in project "${scope.project.id}".`
34022
+ );
34023
+ }
34024
+ for (const transaction of page.transactions) {
34025
+ count += 1;
34026
+ console.log(
34027
+ `${transaction.id} ${new Date(transaction.createdAt).toISOString()} ${transaction.operation} ${transaction.summary ?? ""}`.trimEnd()
34028
+ );
34029
+ }
34030
+ if (page.isDone) break;
34031
+ cursor = page.continueCursor;
34032
+ }
34033
+ if (count === 0) console.log("No retained history entries.");
34034
+ }
34035
+ async function printHistoryInspection(convex, projectId) {
34036
+ const dataset = {
34037
+ versions: [],
34038
+ "project-heads": [],
34039
+ "project-snapshots": [],
34040
+ "project-transactions": [],
34041
+ "wiki-heads": [],
34042
+ "wiki-snapshots": [],
34043
+ "wiki-transactions": [],
34044
+ "wiki-documents": [],
34045
+ "wiki-revisions": []
34046
+ };
34047
+ for (const phase of HISTORY_INSPECT_PHASES) {
34048
+ let cursor = null;
34049
+ while (true) {
34050
+ const page = await convex.query(api2.projectHistory.inspectPage, {
34051
+ projectId,
34052
+ phase,
34053
+ paginationOpts: { cursor, numItems: 100 }
34054
+ });
34055
+ if (page.phase !== phase) {
34056
+ throw new Error(
34057
+ `History inspection requested phase "${phase}" but received "${page.phase}".`
34058
+ );
34059
+ }
34060
+ dataset[phase].push(...page.rows);
34061
+ if (page.isDone) break;
34062
+ cursor = page.continueCursor;
34063
+ }
34064
+ }
34065
+ const report = inspectHistoryDataset(dataset);
34066
+ console.log(
34067
+ `${sym.info} Versions: ${report.versions.total} total, ${report.versions.archived} archived`
34068
+ );
34069
+ console.log(
34070
+ `${sym.info} Project records: ${report.projectRecords.heads} heads, ${report.projectRecords.snapshots} snapshots, ${report.projectRecords.transactions} transactions`
34071
+ );
34072
+ console.log(
34073
+ `${sym.info} Project cleanup candidates: ${report.projectRecords.unreachableSnapshots} unreachable snapshots, ${report.projectRecords.unreachableTransactions} unreachable transactions, ${report.projectRecords.duplicateSemanticSnapshots} duplicate semantic snapshots`
34074
+ );
34075
+ console.log(
34076
+ `${sym.info} Wiki: ${report.wiki.heads} heads, ${report.wiki.snapshots} snapshots, ${report.wiki.transactions} transactions, ${report.wiki.documents} documents, ${report.wiki.revisions} revisions`
34077
+ );
34078
+ console.log(
34079
+ `${sym.info} Wiki cleanup candidates: ${report.wiki.unreachableSnapshots} unreachable snapshots, ${report.wiki.unreachableTransactions} unreachable transactions, ${report.wiki.unreachableDocuments} unreachable documents, ${report.wiki.unreachableRevisions} unreachable revisions`
34080
+ );
34081
+ console.log(
34082
+ `${sym.info} Wiki duplicates: ${report.wiki.duplicateSemanticSnapshots} semantic snapshots, ${report.wiki.duplicateSemanticDocuments} semantic documents`
34083
+ );
34084
+ console.log(
34085
+ `${sym.info} Broken references: ${report.brokenReferences.length}`
34086
+ );
34087
+ for (const broken of report.brokenReferences.slice(0, 20)) {
34088
+ console.log(` ${broken}`);
34089
+ }
34090
+ if (report.brokenReferences.length > 20) {
34091
+ console.log(
34092
+ ` ... ${report.brokenReferences.length - 20} additional broken references`
34093
+ );
34094
+ }
34095
+ }
34096
+ function printPlan(plan) {
34097
+ const scope = plan.versionId === null ? plan.projectId : `${plan.projectId}/${plan.versionId}`;
34098
+ console.log(`${sym.info} Scope: ${scope}`);
34099
+ console.log(`${sym.info} Mode: ${plan.mode}`);
34100
+ console.log(
34101
+ `${sym.info} Project records: ${plan.counts.removableSnapshots} snapshots and ${plan.counts.removableTransactions} transactions removable`
34102
+ );
34103
+ if (plan.wikiCounts !== null) {
34104
+ console.log(
34105
+ `${sym.info} Wiki: ${plan.wikiCounts.removableSnapshots} snapshots, ${plan.wikiCounts.removableTransactions} transactions, and ${plan.wikiCounts.removableDocuments} documents removable`
34106
+ );
34107
+ }
34108
+ console.log(
34109
+ `${sym.info} Current baseline: project=${String(plan.compactionCurrent)} wiki=${String(plan.wikiCompactionCurrent)}`
34110
+ );
34111
+ console.log(`Apply with: neo history apply --plan ${plan.id}`);
34112
+ }
34113
+ function printRunStatus(status) {
34114
+ const scope = status.versionId === null ? status.projectId : `${status.projectId}/${status.versionId}`;
34115
+ console.log(`${sym.info} Run: ${status.runId}`);
34116
+ console.log(`${sym.info} Plan: ${status.planId}`);
34117
+ console.log(`${sym.info} Scope: ${scope}`);
34118
+ console.log(`${sym.info} State: ${status.state} (${status.phase})`);
34119
+ console.log(
34120
+ `${sym.info} Correctness verification: ${status.verified ? "verified" : "pending"}`
34121
+ );
34122
+ if (status.lastError !== null) {
34123
+ console.log(`${sym.info} Error: ${status.lastError}`);
34124
+ }
34125
+ }
34126
+ function assertHistoryOptions(options) {
34127
+ const planningOrRead = options.command === "inspect" || options.command === "log" || options.command === "prune" || options.command === "flatten";
34128
+ if (planningOrRead) {
34129
+ if (options.planId !== null || options.runId !== null) {
34130
+ throw new Error(
34131
+ `neo history ${options.command} does not accept --plan or --run.`
34132
+ );
34133
+ }
34134
+ if (options.confirmScope !== null) {
34135
+ throw new Error(
34136
+ `neo history ${options.command} does not accept --confirm-scope.`
34137
+ );
34138
+ }
34139
+ if ((options.command === "inspect" || options.command === "prune") && options.versionSelector !== null) {
34140
+ throw new Error(
34141
+ `neo history ${options.command} is project-scoped and does not accept --version.`
34142
+ );
34143
+ }
34144
+ return;
34145
+ }
34146
+ if (options.command === "apply") {
34147
+ if (options.planId === null) {
34148
+ throw new Error("neo history apply requires --plan <plan-id>.");
34149
+ }
34150
+ if (options.runId !== null) {
34151
+ throw new Error("neo history apply does not accept --run.");
34152
+ }
34153
+ return;
34154
+ }
34155
+ if (options.runId === null) {
34156
+ throw new Error(`neo history ${options.command} requires --run <run-id>.`);
34157
+ }
34158
+ if (options.planId !== null || options.confirmScope !== null) {
34159
+ throw new Error(
34160
+ `neo history ${options.command} does not accept --plan or --confirm-scope.`
34161
+ );
34162
+ }
34163
+ }
34164
+ var init_history = __esm({
34165
+ "src/commands/history.ts"() {
34166
+ "use strict";
34167
+ init_convex();
34168
+ init_ui();
34169
+ init_history_selection();
34170
+ init_history_inspect();
34171
+ }
34172
+ });
34173
+
32165
34174
  // src/commands/doctor.ts
32166
34175
  var doctor_exports = {};
32167
34176
  __export(doctor_exports, {
@@ -32464,6 +34473,9 @@ var init_project_version_intents = __esm({
32464
34473
  ClassUpdate: "class.update",
32465
34474
  ClassDelete: "class.delete",
32466
34475
  ClassAddSchemaEntry: "class.add-schema-entry",
34476
+ InternalRecordRelationCreate: "internal-record-relation.create",
34477
+ InternalRecordRelationUpdate: "internal-record-relation.update",
34478
+ InternalRecordRelationDelete: "internal-record-relation.delete",
32467
34479
  EnumCreate: "enum.create",
32468
34480
  EnumUpdate: "enum.update",
32469
34481
  EnumDelete: "enum.delete",
@@ -44574,16 +46586,19 @@ var KNOWN_FLAGS = /* @__PURE__ */ new Set([
44574
46586
  "out",
44575
46587
  "profile",
44576
46588
  "project",
46589
+ "plan",
44577
46590
  "push",
44578
46591
  "replace",
44579
46592
  "reset",
44580
46593
  "returns",
46594
+ "run",
44581
46595
  "save",
44582
46596
  "save-project",
44583
46597
  "server",
44584
46598
  "skip-invalid",
44585
46599
  "status",
44586
46600
  "summary",
46601
+ "confirm-scope",
44587
46602
  "target",
44588
46603
  "template",
44589
46604
  "theirs",
@@ -44681,6 +46696,7 @@ ${h("Branches & releases")}
44681
46696
  merge ${d("[source] [--dry-run] [--migrate] [--mine|--theirs <kind>:<id>,...]")}
44682
46697
  release ${d("cut [--bump ...] | publish <ref> | archive <ref> | restore <ref>")}
44683
46698
  channel ${d("list | create | edit | delete")}
46699
+ history ${d("inspect | log | prune | flatten | apply | status | resume")}
44684
46700
 
44685
46701
  ${h("Content & scripts")}
44686
46702
  values ${d("list | get | set | create | delete | ...")} loc ${d("locales | list | set | archive | restore | ...")}
@@ -44748,6 +46764,46 @@ ${h("Usage")}
44748
46764
  Neo checks Unity 6000's existing runtime first, then a compatible system
44749
46765
  dotnet. It never downloads or installs a runtime. Set NEO_DOTNET_HOST to use
44750
46766
  an explicit host.
46767
+ `;
46768
+ }
46769
+ if (command === "history") {
46770
+ return `${h("neo history")} \u2014 inspect and deliberately rewrite project history
46771
+
46772
+ ${h("Usage")}
46773
+ neo history inspect ${d("[--project <id|@latest|@current>]")}
46774
+ neo history log ${d("[--project <id|@latest|@current>] [--version <id|@latest|@current>]")}
46775
+ neo history prune ${d("[--project <id|@latest|@current>]")}
46776
+ neo history flatten ${d("[--project <id|@latest|@current>] [--version <id|@latest|@current>]")}
46777
+ neo history apply --plan <plan-id> ${d("[--confirm-scope <project-id>[/<version-id>]]")}
46778
+ neo history status --run <run-id>
46779
+ neo history resume --run <run-id>
46780
+
46781
+ ${h("Commands")}
46782
+ inspect ${d("Read-only project history audit; creates no plan.")}
46783
+ log ${d("Show retained history for exactly one version.")}
46784
+ prune ${d("Create an immutable plan for rows proven unreachable; writes nothing.")}
46785
+ flatten ${d("Create an immutable one-version baseline plan; writes nothing.")}
46786
+ apply ${d("Begin the destructive execution of an immutable plan.")}
46787
+ status ${d("Report execution phase, result, and correctness verification.")}
46788
+ resume ${d("Restart a stopped retryable execution.")}
46789
+
46790
+ ${h("Arguments")}
46791
+ --project <id|@latest|@current>
46792
+ ${d("Stable project ID, or latest authorized project. Blank interactive input uses latest.")}
46793
+ --version <id|@latest|@current>
46794
+ ${d("Stable version ID in the resolved project, or latest unarchived version. Blank interactive input uses latest.")}
46795
+ --plan <id>
46796
+ ${d("Immutable plan returned by prune or flatten.")}
46797
+ --run <id>
46798
+ ${d("Execution returned by apply.")}
46799
+ --confirm-scope <project>[/<version>]
46800
+ ${d("Exact resolved production scope acknowledgement.")}
46801
+
46802
+ ${h("Selection")}
46803
+ ${d("Project resolution always happens before version resolution. @latest and @current are aliases. apply, status, and resume use immutable plan/run scope and do not accept project or version selectors.")}
46804
+
46805
+ There is no --dry-run: prune and flatten are always non-mutating until apply.
46806
+ There is no --keep-current: preserving current semantic state defines flatten.
44751
46807
  `;
44752
46808
  }
44753
46809
  return usage();
@@ -44788,6 +46844,30 @@ async function main() {
44788
46844
  });
44789
46845
  }
44790
46846
  return;
46847
+ case "history": {
46848
+ const sub = args.positional[0];
46849
+ if (sub !== "inspect" && sub !== "log" && sub !== "prune" && sub !== "flatten" && sub !== "apply" && sub !== "status" && sub !== "resume") {
46850
+ throw new Error(
46851
+ "neo history requires a subcommand: inspect | log | prune | flatten | apply | status | resume."
46852
+ );
46853
+ }
46854
+ if ((sub === "apply" || sub === "status" || sub === "resume") && (stringFlag(args, "project") !== null || stringFlag(args, "version") !== null)) {
46855
+ throw new Error(
46856
+ `neo history ${sub} derives its immutable scope from the plan or run and does not accept --project or --version.`
46857
+ );
46858
+ }
46859
+ const { runHistory: runHistory2 } = await Promise.resolve().then(() => (init_history(), history_exports));
46860
+ await runHistory2({
46861
+ apiBaseUrl,
46862
+ command: sub,
46863
+ projectSelector: stringFlag(args, "project"),
46864
+ versionSelector: stringFlag(args, "version"),
46865
+ planId: stringFlag(args, "plan"),
46866
+ runId: stringFlag(args, "run"),
46867
+ confirmScope: stringFlag(args, "confirm-scope")
46868
+ });
46869
+ return;
46870
+ }
44791
46871
  case "pull": {
44792
46872
  const workspace = loadWorkspaceForCommand(args);
44793
46873
  const { runPull: runPull2 } = await Promise.resolve().then(() => (init_pull(), pull_exports));