@neocompose/cli 0.71.3 → 0.72.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.
@@ -880,10 +880,10 @@ import { parentPort } from "node:worker_threads";
880
880
 
881
881
  // ../src/models/members/member-storage.ts
882
882
  function isMemberStorageKind(value) {
883
- return value === 0 /* Inherit */ || value === 1 /* Immutable */ || value === 2 /* Save */ || value === 3 /* Session */ || value === 4 /* Writable */;
883
+ return value === 0 /* Inherit */ || value === 1 /* Immutable */ || value === 2 /* Save */ || value === 3 /* Session */ || value === 4 /* Writable */ || value === 5 /* User */;
884
884
  }
885
885
  function isEffectiveMemberStorage(value) {
886
- return value === 1 /* Immutable */ || value === 2 /* Save */ || value === 3 /* Session */;
886
+ return value === 1 /* Immutable */ || value === 2 /* Save */ || value === 3 /* Session */ || value === 5 /* User */;
887
887
  }
888
888
  function isClassAllowedStorage(value) {
889
889
  if (!isMemberStorageKind(value)) return false;
@@ -894,7 +894,7 @@ function childEffectiveStorage(declared, parent) {
894
894
  case 0 /* Inherit */:
895
895
  return parent;
896
896
  case 4 /* Writable */:
897
- return parent === 2 /* Save */ ? 2 /* Save */ : 3 /* Session */;
897
+ return parent === 2 /* Save */ || parent === 5 /* User */ ? parent : 3 /* Session */;
898
898
  default:
899
899
  return declared;
900
900
  }
@@ -1124,6 +1124,176 @@ function memberKindSupportsDistribution(kind) {
1124
1124
  return memberKindSupportsStorage(kind);
1125
1125
  }
1126
1126
 
1127
+ // node_modules/uuid/dist-node/regex.js
1128
+ var regex_default = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/i;
1129
+
1130
+ // node_modules/uuid/dist-node/validate.js
1131
+ function validate(uuid) {
1132
+ return typeof uuid === "string" && regex_default.test(uuid);
1133
+ }
1134
+ var validate_default = validate;
1135
+
1136
+ // node_modules/uuid/dist-node/parse.js
1137
+ function parse(uuid) {
1138
+ if (!validate_default(uuid)) {
1139
+ throw TypeError("Invalid UUID");
1140
+ }
1141
+ let v;
1142
+ return Uint8Array.of((v = parseInt(uuid.slice(0, 8), 16)) >>> 24, v >>> 16 & 255, v >>> 8 & 255, v & 255, (v = parseInt(uuid.slice(9, 13), 16)) >>> 8, v & 255, (v = parseInt(uuid.slice(14, 18), 16)) >>> 8, v & 255, (v = parseInt(uuid.slice(19, 23), 16)) >>> 8, v & 255, (v = parseInt(uuid.slice(24, 36), 16)) / 1099511627776 & 255, v / 4294967296 & 255, v >>> 24 & 255, v >>> 16 & 255, v >>> 8 & 255, v & 255);
1143
+ }
1144
+ var parse_default = parse;
1145
+
1146
+ // node_modules/uuid/dist-node/stringify.js
1147
+ var byteToHex = [];
1148
+ for (let i = 0; i < 256; ++i) {
1149
+ byteToHex.push((i + 256).toString(16).slice(1));
1150
+ }
1151
+ function unsafeStringify(arr, offset = 0) {
1152
+ return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
1153
+ }
1154
+
1155
+ // node_modules/uuid/dist-node/rng.js
1156
+ var rnds8 = new Uint8Array(16);
1157
+ function rng() {
1158
+ return crypto.getRandomValues(rnds8);
1159
+ }
1160
+
1161
+ // node_modules/uuid/dist-node/v35.js
1162
+ function stringToBytes(str) {
1163
+ str = unescape(encodeURIComponent(str));
1164
+ const bytes = new Uint8Array(str.length);
1165
+ for (let i = 0; i < str.length; ++i) {
1166
+ bytes[i] = str.charCodeAt(i);
1167
+ }
1168
+ return bytes;
1169
+ }
1170
+ var DNS = "6ba7b810-9dad-11d1-80b4-00c04fd430c8";
1171
+ var URL2 = "6ba7b811-9dad-11d1-80b4-00c04fd430c8";
1172
+ function v35(version, hash, value, namespace, buf, offset) {
1173
+ const valueBytes = typeof value === "string" ? stringToBytes(value) : value;
1174
+ const namespaceBytes = typeof namespace === "string" ? parse_default(namespace) : namespace;
1175
+ if (typeof namespace === "string") {
1176
+ namespace = parse_default(namespace);
1177
+ }
1178
+ if (namespace?.length !== 16) {
1179
+ throw TypeError("Namespace must be array-like (16 iterable integer values, 0-255)");
1180
+ }
1181
+ let bytes = new Uint8Array(16 + valueBytes.length);
1182
+ bytes.set(namespaceBytes);
1183
+ bytes.set(valueBytes, namespaceBytes.length);
1184
+ bytes = hash(bytes);
1185
+ bytes[6] = bytes[6] & 15 | version;
1186
+ bytes[8] = bytes[8] & 63 | 128;
1187
+ if (buf) {
1188
+ offset ??= 0;
1189
+ if (offset < 0 || offset + 16 > buf.length) {
1190
+ throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
1191
+ }
1192
+ for (let i = 0; i < 16; ++i) {
1193
+ buf[offset + i] = bytes[i];
1194
+ }
1195
+ return buf;
1196
+ }
1197
+ return unsafeStringify(bytes);
1198
+ }
1199
+
1200
+ // node_modules/uuid/dist-node/v4.js
1201
+ function v4(options, buf, offset) {
1202
+ if (!buf && !options && crypto.randomUUID) {
1203
+ return crypto.randomUUID();
1204
+ }
1205
+ return _v4(options, buf, offset);
1206
+ }
1207
+ function _v4(options, buf, offset) {
1208
+ options = options || {};
1209
+ const rnds = options.random ?? options.rng?.() ?? rng();
1210
+ if (rnds.length < 16) {
1211
+ throw new Error("Random bytes length must be >= 16");
1212
+ }
1213
+ rnds[6] = rnds[6] & 15 | 64;
1214
+ rnds[8] = rnds[8] & 63 | 128;
1215
+ if (buf) {
1216
+ offset = offset || 0;
1217
+ if (offset < 0 || offset + 16 > buf.length) {
1218
+ throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
1219
+ }
1220
+ for (let i = 0; i < 16; ++i) {
1221
+ buf[offset + i] = rnds[i];
1222
+ }
1223
+ return buf;
1224
+ }
1225
+ return unsafeStringify(rnds);
1226
+ }
1227
+ var v4_default = v4;
1228
+
1229
+ // node_modules/uuid/dist-node/sha1.js
1230
+ import { createHash } from "node:crypto";
1231
+ function sha1(bytes) {
1232
+ if (Array.isArray(bytes)) {
1233
+ bytes = Buffer.from(bytes);
1234
+ } else if (typeof bytes === "string") {
1235
+ bytes = Buffer.from(bytes, "utf8");
1236
+ }
1237
+ return createHash("sha1").update(bytes).digest();
1238
+ }
1239
+ var sha1_default = sha1;
1240
+
1241
+ // node_modules/uuid/dist-node/v5.js
1242
+ function v5(value, namespace, buf, offset) {
1243
+ return v35(80, sha1_default, value, namespace, buf, offset);
1244
+ }
1245
+ v5.DNS = DNS;
1246
+ v5.URL = URL2;
1247
+ var v5_default = v5;
1248
+
1249
+ // ../src/models/project/project-root-members.ts
1250
+ var PROJECT_ROOTS = [
1251
+ {
1252
+ key: "Assets",
1253
+ choice: "assets",
1254
+ projectField: "rootAssetsMemberId",
1255
+ label: "Assets",
1256
+ storage: 1 /* Immutable */
1257
+ },
1258
+ {
1259
+ key: "Save",
1260
+ choice: "save",
1261
+ projectField: "rootSaveFileMemberId",
1262
+ label: "Save",
1263
+ storage: 2 /* Save */
1264
+ },
1265
+ {
1266
+ key: "Session",
1267
+ choice: "session",
1268
+ projectField: "rootSessionMemberId",
1269
+ label: "Session",
1270
+ storage: 3 /* Session */
1271
+ },
1272
+ {
1273
+ key: "User",
1274
+ choice: "user",
1275
+ projectField: "rootUserMemberId",
1276
+ label: "User",
1277
+ storage: 5 /* User */
1278
+ }
1279
+ ];
1280
+ function projectRootMemberIds(project) {
1281
+ return PROJECT_ROOTS.map((root) => project[root.projectField]);
1282
+ }
1283
+ function buildRootStorageMap(project) {
1284
+ const map = /* @__PURE__ */ new Map();
1285
+ for (const root of PROJECT_ROOTS) {
1286
+ map.set(project[root.projectField], root.storage);
1287
+ }
1288
+ return map;
1289
+ }
1290
+ function projectRootMembersInDisplayOrder(project) {
1291
+ return PROJECT_ROOTS.map((root) => ({
1292
+ root,
1293
+ memberId: project[root.projectField]
1294
+ }));
1295
+ }
1296
+
1127
1297
  // ../src/models/core/structural-narrow.ts
1128
1298
  function isObject(value) {
1129
1299
  if (typeof value !== "object") return false;
@@ -5291,128 +5461,6 @@ function descendantClassIdsInChildIndex(classId, childrenByClassId) {
5291
5461
  return result;
5292
5462
  }
5293
5463
 
5294
- // node_modules/uuid/dist-node/regex.js
5295
- var regex_default = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/i;
5296
-
5297
- // node_modules/uuid/dist-node/validate.js
5298
- function validate(uuid) {
5299
- return typeof uuid === "string" && regex_default.test(uuid);
5300
- }
5301
- var validate_default = validate;
5302
-
5303
- // node_modules/uuid/dist-node/parse.js
5304
- function parse(uuid) {
5305
- if (!validate_default(uuid)) {
5306
- throw TypeError("Invalid UUID");
5307
- }
5308
- let v;
5309
- return Uint8Array.of((v = parseInt(uuid.slice(0, 8), 16)) >>> 24, v >>> 16 & 255, v >>> 8 & 255, v & 255, (v = parseInt(uuid.slice(9, 13), 16)) >>> 8, v & 255, (v = parseInt(uuid.slice(14, 18), 16)) >>> 8, v & 255, (v = parseInt(uuid.slice(19, 23), 16)) >>> 8, v & 255, (v = parseInt(uuid.slice(24, 36), 16)) / 1099511627776 & 255, v / 4294967296 & 255, v >>> 24 & 255, v >>> 16 & 255, v >>> 8 & 255, v & 255);
5310
- }
5311
- var parse_default = parse;
5312
-
5313
- // node_modules/uuid/dist-node/stringify.js
5314
- var byteToHex = [];
5315
- for (let i = 0; i < 256; ++i) {
5316
- byteToHex.push((i + 256).toString(16).slice(1));
5317
- }
5318
- function unsafeStringify(arr, offset = 0) {
5319
- return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
5320
- }
5321
-
5322
- // node_modules/uuid/dist-node/rng.js
5323
- var rnds8 = new Uint8Array(16);
5324
- function rng() {
5325
- return crypto.getRandomValues(rnds8);
5326
- }
5327
-
5328
- // node_modules/uuid/dist-node/v35.js
5329
- function stringToBytes(str) {
5330
- str = unescape(encodeURIComponent(str));
5331
- const bytes = new Uint8Array(str.length);
5332
- for (let i = 0; i < str.length; ++i) {
5333
- bytes[i] = str.charCodeAt(i);
5334
- }
5335
- return bytes;
5336
- }
5337
- var DNS = "6ba7b810-9dad-11d1-80b4-00c04fd430c8";
5338
- var URL2 = "6ba7b811-9dad-11d1-80b4-00c04fd430c8";
5339
- function v35(version, hash, value, namespace, buf, offset) {
5340
- const valueBytes = typeof value === "string" ? stringToBytes(value) : value;
5341
- const namespaceBytes = typeof namespace === "string" ? parse_default(namespace) : namespace;
5342
- if (typeof namespace === "string") {
5343
- namespace = parse_default(namespace);
5344
- }
5345
- if (namespace?.length !== 16) {
5346
- throw TypeError("Namespace must be array-like (16 iterable integer values, 0-255)");
5347
- }
5348
- let bytes = new Uint8Array(16 + valueBytes.length);
5349
- bytes.set(namespaceBytes);
5350
- bytes.set(valueBytes, namespaceBytes.length);
5351
- bytes = hash(bytes);
5352
- bytes[6] = bytes[6] & 15 | version;
5353
- bytes[8] = bytes[8] & 63 | 128;
5354
- if (buf) {
5355
- offset ??= 0;
5356
- if (offset < 0 || offset + 16 > buf.length) {
5357
- throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
5358
- }
5359
- for (let i = 0; i < 16; ++i) {
5360
- buf[offset + i] = bytes[i];
5361
- }
5362
- return buf;
5363
- }
5364
- return unsafeStringify(bytes);
5365
- }
5366
-
5367
- // node_modules/uuid/dist-node/v4.js
5368
- function v4(options, buf, offset) {
5369
- if (!buf && !options && crypto.randomUUID) {
5370
- return crypto.randomUUID();
5371
- }
5372
- return _v4(options, buf, offset);
5373
- }
5374
- function _v4(options, buf, offset) {
5375
- options = options || {};
5376
- const rnds = options.random ?? options.rng?.() ?? rng();
5377
- if (rnds.length < 16) {
5378
- throw new Error("Random bytes length must be >= 16");
5379
- }
5380
- rnds[6] = rnds[6] & 15 | 64;
5381
- rnds[8] = rnds[8] & 63 | 128;
5382
- if (buf) {
5383
- offset = offset || 0;
5384
- if (offset < 0 || offset + 16 > buf.length) {
5385
- throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
5386
- }
5387
- for (let i = 0; i < 16; ++i) {
5388
- buf[offset + i] = rnds[i];
5389
- }
5390
- return buf;
5391
- }
5392
- return unsafeStringify(rnds);
5393
- }
5394
- var v4_default = v4;
5395
-
5396
- // node_modules/uuid/dist-node/sha1.js
5397
- import { createHash } from "node:crypto";
5398
- function sha1(bytes) {
5399
- if (Array.isArray(bytes)) {
5400
- bytes = Buffer.from(bytes);
5401
- } else if (typeof bytes === "string") {
5402
- bytes = Buffer.from(bytes, "utf8");
5403
- }
5404
- return createHash("sha1").update(bytes).digest();
5405
- }
5406
- var sha1_default = sha1;
5407
-
5408
- // node_modules/uuid/dist-node/v5.js
5409
- function v5(value, namespace, buf, offset) {
5410
- return v35(80, sha1_default, value, namespace, buf, offset);
5411
- }
5412
- v5.DNS = DNS;
5413
- v5.URL = URL2;
5414
- var v5_default = v5;
5415
-
5416
5464
  // ../src/models/system-record-id.ts
5417
5465
  var SYSTEM_RECORD_ID_PREFIX = "system_";
5418
5466
  function isSystemRecordId(id) {
@@ -11753,6 +11801,38 @@ var EffectiveStorageResolver = class {
11753
11801
  (id) => this.schemaIndex.member(id)
11754
11802
  );
11755
11803
  resolving = /* @__PURE__ */ new Set();
11804
+ allowedByClassId = /* @__PURE__ */ new Map();
11805
+ userClasses;
11806
+ /**
11807
+ * `resolveAllowedStorage` over this context, memoized along each extends
11808
+ * chain so a whole-document pass stays O(classes).
11809
+ */
11810
+ allowedStorage(classId) {
11811
+ const cached = this.allowedByClassId.get(classId);
11812
+ if (cached !== void 0) return cached;
11813
+ this.allowedByClassId.set(classId, null);
11814
+ const schemaClass2 = this.schemaIndex.classesById.get(classId);
11815
+ let resolved = null;
11816
+ if (schemaClass2?.allowedStorage != null) {
11817
+ resolved = schemaClass2.allowedStorage;
11818
+ } else if (schemaClass2?.extendsClassId !== void 0) {
11819
+ resolved = this.allowedStorage(schemaClass2.extendsClassId);
11820
+ }
11821
+ this.allowedByClassId.set(classId, resolved);
11822
+ return resolved;
11823
+ }
11824
+ /** P104 §1.3: classes whose resolved `allowedStorage` is `User`. */
11825
+ userClassIds() {
11826
+ if (this.userClasses === void 0) {
11827
+ this.userClasses = /* @__PURE__ */ new Set();
11828
+ for (const schemaClass2 of this.ctx.classes) {
11829
+ if (this.allowedStorage(schemaClass2.id) === 5 /* User */) {
11830
+ this.userClasses.add(schemaClass2.id);
11831
+ }
11832
+ }
11833
+ }
11834
+ return this.userClasses;
11835
+ }
11756
11836
  /** Declared storage of `memberId`, resolved through the extends chain. */
11757
11837
  declaredStorage(memberId) {
11758
11838
  return declaredMemberStorageFromIndex(
@@ -11796,7 +11876,7 @@ var EffectiveStorageResolver = class {
11796
11876
  result.add(declared);
11797
11877
  } else if (isStaticMember(this.schemaIndex.member(memberId))) {
11798
11878
  const declaringClassId = this.schemaIndex.firstSchemaPlacement(memberId)?.ownerClass.id;
11799
- const constraint = declaringClassId === void 0 ? null : resolveAllowedStorage(declaringClassId, this.ctx.classes);
11879
+ const constraint = declaringClassId === void 0 ? null : this.allowedStorage(declaringClassId);
11800
11880
  result.add(
11801
11881
  childEffectiveStorage(
11802
11882
  declared,
@@ -11899,6 +11979,11 @@ var EffectiveStorageResolver = class {
11899
11979
  member(memberId) {
11900
11980
  return this.schemaIndex.member(memberId);
11901
11981
  }
11982
+ /** `Owner.Member` for a schema member, else the member's own name. */
11983
+ placementLabel(member) {
11984
+ const owner = this.schemaIndex.firstSchemaPlacement(member.id)?.ownerClass;
11985
+ return owner === void 0 ? member.name : `${owner.name}.${member.name}`;
11986
+ }
11902
11987
  buildParentEdges() {
11903
11988
  const addEdge = (childId, parentId) => {
11904
11989
  const existing = this.parentIdsByMemberId.get(childId);
@@ -12831,44 +12916,6 @@ function visitTypedAggregateRowReferences(value, rawType, visitor) {
12831
12916
  }
12832
12917
  }
12833
12918
 
12834
- // ../src/models/project/project-root-members.ts
12835
- var PROJECT_ROOTS = [
12836
- {
12837
- key: "Assets",
12838
- choice: "assets",
12839
- projectField: "rootAssetsMemberId",
12840
- label: "Assets",
12841
- storage: 1 /* Immutable */
12842
- },
12843
- {
12844
- key: "Save",
12845
- choice: "save",
12846
- projectField: "rootSaveFileMemberId",
12847
- label: "Save",
12848
- storage: 2 /* Save */
12849
- },
12850
- {
12851
- key: "Session",
12852
- choice: "session",
12853
- projectField: "rootSessionMemberId",
12854
- label: "Session",
12855
- storage: 3 /* Session */
12856
- }
12857
- ];
12858
- function buildRootStorageMap(project) {
12859
- const map = /* @__PURE__ */ new Map();
12860
- for (const root of PROJECT_ROOTS) {
12861
- map.set(project[root.projectField], root.storage);
12862
- }
12863
- return map;
12864
- }
12865
- function projectRootMembersInDisplayOrder(project) {
12866
- return PROJECT_ROOTS.map((root) => ({
12867
- root,
12868
- memberId: project[root.projectField]
12869
- }));
12870
- }
12871
-
12872
12919
  // ../src/runtime/neoscript/initializer-dependencies.ts
12873
12920
  var PendingInitializerDependency = class extends NSGetterRuntimeError {
12874
12921
  constructor(valueId) {
@@ -18751,6 +18798,11 @@ function effectiveStaticStorage(memberId, ctx) {
18751
18798
  `Static member '${memberId}' did not resolve to exactly one effective storage class.`
18752
18799
  );
18753
18800
  }
18801
+ if (storage === 5 /* User */) {
18802
+ throw new NSGetterRuntimeError(
18803
+ `Static member '${memberId}' belongs to a User class. User classes can't declare static stored members.`
18804
+ );
18805
+ }
18754
18806
  return storage;
18755
18807
  }
18756
18808
  function staticBindingTargetId(member, ctx) {
@@ -19503,6 +19555,10 @@ function listenerReceiverOnOwner(memberId, ownerReceiver, ctx) {
19503
19555
  return ownerReceiver;
19504
19556
  }
19505
19557
  function invokeActionListeners(listeners, args, ctx, frame, ownerReceiver, callSiteId) {
19558
+ if (ctx.assertActionInvocable !== void 0) {
19559
+ const ownerRowId = findKnownRowIdByValueReference(ownerReceiver, ctx);
19560
+ if (ownerRowId !== null) ctx.assertActionInvocable(ownerRowId);
19561
+ }
19506
19562
  for (let index = 0; index < listeners.length; index += 1) {
19507
19563
  try {
19508
19564
  invokeDelegateValue(
@@ -21167,17 +21223,9 @@ function classIdForValueRow(row, ctx) {
21167
21223
  function rootMemberForValueRow(row, ctx) {
21168
21224
  if (typeof ctx.rootValue !== "object" || ctx.rootValue === null) return null;
21169
21225
  const rootRecord = ctx.rootValue;
21170
- const project = ctx.vm.project;
21171
- if (rootRecord.Assets === row.value) {
21172
- return evalMemberById(ctx.vm, project.rootAssetsMemberId);
21173
- }
21174
- if (rootRecord.Save === row.value) {
21175
- return evalMemberById(ctx.vm, project.rootSaveFileMemberId);
21176
- }
21177
- if (rootRecord.Session === row.value) {
21178
- return evalMemberById(ctx.vm, project.rootSessionMemberId);
21179
- }
21180
- return null;
21226
+ const root = PROJECT_ROOTS.find((root2) => rootRecord[root2.key] === row.value);
21227
+ if (root === void 0) return null;
21228
+ return evalMemberById(ctx.vm, ctx.vm.project[root.projectField]);
21181
21229
  }
21182
21230
  function memberForCustomSchemaValue(classId, schemaKey, ctx) {
21183
21231
  const memberId = cachedMergedSchemaMemberId(classId, schemaKey, ctx);
@@ -33328,6 +33376,136 @@ function isCollectionQueryName(name) {
33328
33376
  return Object.hasOwn(COLLECTION_QUERY_METHODS, name);
33329
33377
  }
33330
33378
 
33379
+ // ../packages/neoscript-language/src/project-source-lifecycle-hooks.ts
33380
+ var UNITY_LIFECYCLE_INTERFACE_PREFIX = "INeoUnity";
33381
+ function isUnityLifecycleInterfaceName(name) {
33382
+ return name.startsWith(UNITY_LIFECYCLE_INTERFACE_PREFIX);
33383
+ }
33384
+
33385
+ // ../packages/neoscript-language/src/user-safe-types.ts
33386
+ var USER_DATA_WRITE_ERROR = "Only User code can change User data. C# changes it through the generated user client.";
33387
+ function userUnsafeTypeName(type, project) {
33388
+ return userSafeTypeCheck(project).unsafeName(type);
33389
+ }
33390
+ var checks = /* @__PURE__ */ new WeakMap();
33391
+ function userSafeTypeCheck(project) {
33392
+ let check2 = checks.get(project);
33393
+ if (check2 === void 0) {
33394
+ check2 = new UserSafeTypeCheck(project);
33395
+ checks.set(project, check2);
33396
+ }
33397
+ return check2;
33398
+ }
33399
+ function userUnsafeTypeMessage(typeName, site) {
33400
+ return `User code can't use \`${typeName}\` in ${site}, because it isn't a User class.`;
33401
+ }
33402
+ var UserSafeTypeCheck = class {
33403
+ constructor(project) {
33404
+ this.project = project;
33405
+ }
33406
+ project;
33407
+ /** Final interface verdicts. */
33408
+ interfaces = /* @__PURE__ */ new Map();
33409
+ /**
33410
+ * Interfaces whose verdict waits on their cycle (Tarjan's SCC stack),
33411
+ * by stack position. One on the stack reads as safe until its cycle ends.
33412
+ */
33413
+ pending = /* @__PURE__ */ new Map();
33414
+ stack = [];
33415
+ /** The lowest stack position the current interface's check consulted. */
33416
+ lowestConsulted = Infinity;
33417
+ symbolUnsafeName(symbol) {
33418
+ for (const parameter3 of symbol.parameters ?? []) {
33419
+ const name = this.unsafeName(parameter3.type);
33420
+ if (name !== null) return name;
33421
+ }
33422
+ if (symbol.returnType !== void 0) {
33423
+ const name = this.unsafeName(symbol.returnType);
33424
+ if (name !== null) return name;
33425
+ }
33426
+ return this.unsafeName(symbol.type);
33427
+ }
33428
+ unsafeName(type) {
33429
+ switch (type.kind) {
33430
+ case "primitive":
33431
+ return null;
33432
+ case "typeParameter":
33433
+ return type.constraint === void 0 ? null : this.unsafeName(type.constraint);
33434
+ case "list":
33435
+ case "set":
33436
+ return this.unsafeName(type.elementType);
33437
+ case "dictionary":
33438
+ return this.unsafeName(type.keyType) ?? this.unsafeName(type.valueType);
33439
+ case "delegate":
33440
+ return this.firstUnsafe(type.parameterTypes) ?? this.unsafeName(type.returnType);
33441
+ case "action":
33442
+ return this.firstUnsafe(type.parameterTypes);
33443
+ case "named":
33444
+ return this.namedUnsafeName(type.typeId) ?? this.firstUnsafe(type.typeArguments ?? []);
33445
+ }
33446
+ }
33447
+ firstUnsafe(types) {
33448
+ for (const type of types) {
33449
+ const name = this.unsafeName(type);
33450
+ if (name !== null) return name;
33451
+ }
33452
+ return null;
33453
+ }
33454
+ namedUnsafeName(typeId) {
33455
+ const named = this.project.typeById.get(typeId);
33456
+ if (named === void 0) return null;
33457
+ switch (named.kind) {
33458
+ case "enum":
33459
+ case "builtin":
33460
+ return null;
33461
+ case "class":
33462
+ return named.userClass === true ? null : named.name;
33463
+ case "synthetic":
33464
+ return named.name;
33465
+ case "interface":
33466
+ return this.interfaceUnsafeName(named);
33467
+ }
33468
+ }
33469
+ // An interface is unsafe when it reaches an unsafe type, so a cycle alone
33470
+ // proves nothing. Interfaces in one cycle reach each other and share a
33471
+ // verdict, settled once the cycle's first interface finishes; each
33472
+ // interface is scanned once.
33473
+ interfaceUnsafeName(named) {
33474
+ if (isUnityLifecycleInterfaceName(named.name)) return named.name;
33475
+ const cached = this.interfaces.get(named.id);
33476
+ if (cached !== void 0) return cached;
33477
+ const pendingAt = this.pending.get(named.id);
33478
+ if (pendingAt !== void 0) {
33479
+ this.lowestConsulted = Math.min(this.lowestConsulted, pendingAt);
33480
+ return null;
33481
+ }
33482
+ const position = this.stack.length;
33483
+ this.stack.push(named);
33484
+ this.pending.set(named.id, position);
33485
+ const outerLowest = this.lowestConsulted;
33486
+ this.lowestConsulted = Infinity;
33487
+ let unsafe = false;
33488
+ for (const member of named.members) {
33489
+ if (this.symbolUnsafeName(member) === null) continue;
33490
+ unsafe = true;
33491
+ break;
33492
+ }
33493
+ if (unsafe || this.lowestConsulted >= position) {
33494
+ this.settleFrom(position, unsafe);
33495
+ this.lowestConsulted = outerLowest;
33496
+ } else {
33497
+ this.lowestConsulted = Math.min(outerLowest, this.lowestConsulted);
33498
+ }
33499
+ return unsafe ? named.name : null;
33500
+ }
33501
+ settleFrom(position, unsafe) {
33502
+ for (const settled of this.stack.splice(position)) {
33503
+ this.pending.delete(settled.id);
33504
+ this.interfaces.set(settled.id, unsafe ? settled.name : null);
33505
+ }
33506
+ }
33507
+ };
33508
+
33331
33509
  // ../packages/neoscript-language/src/tile-conversion.ts
33332
33510
  var NEO_TILE_CONVERT_MEMBER_ID = "system_54ecc5a7-874d-54ed-92f4-38100a4b7465";
33333
33511
  var NEO_TILE_CLASS_ID = "system_d7fa161b-b269-4bf1-9ec9-3aebec67e6e0";
@@ -33503,6 +33681,9 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
33503
33681
  };
33504
33682
  this.debugPosition = sourcePositionMapper(options.source);
33505
33683
  this.project = projectIndex ?? createProjectIndex(context.project);
33684
+ const lexicalClass = context.declaringType === void 0 ? context.thisClass : context.declaringType;
33685
+ this.userClassCode = lexicalClass?.kind === "named" && this.project.typeById.get(lexicalClass.typeId)?.userClass === true;
33686
+ this.writesUserData = this.userClassCode || context.projectSourceKind === "spec";
33506
33687
  this.listenerParameters = createListenerAnnotationProvider(
33507
33688
  this.project,
33508
33689
  context
@@ -33519,6 +33700,7 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
33519
33700
  const rootSymbol = context.project.roots.find(
33520
33701
  (candidate) => candidate.name === "root"
33521
33702
  );
33703
+ this.rootTypeId = rootSymbol?.type.kind === "named" ? rootSymbol.type.typeId : null;
33522
33704
  this.rootVariable = variable(
33523
33705
  "__root__",
33524
33706
  rootSymbol?.type ?? unknown,
@@ -33574,6 +33756,11 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
33574
33756
  functionControlBoundaries = [];
33575
33757
  debugSource;
33576
33758
  inferredExpressionReturnType;
33759
+ /** P104 §3.1. User class bodies reach only `root.User`. */
33760
+ userClassCode;
33761
+ rootTypeId;
33762
+ /** P104 §3.1. User class and test bodies may write User data. */
33763
+ writesUserData;
33577
33764
  initialScope() {
33578
33765
  const scope = new Scope(null, this.options.onFlowScope !== void 0);
33579
33766
  scope.define(
@@ -35103,6 +35290,9 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
35103
35290
  statement.pos
35104
35291
  );
35105
35292
  }
35293
+ if (writability === "readOnly" /* ReadOnly */ && target.userData) {
35294
+ throw new CompileError(USER_DATA_WRITE_ERROR, statement.pos);
35295
+ }
35106
35296
  if (writability === "readOnly" /* ReadOnly */) {
35107
35297
  throw new CompileError(
35108
35298
  `Cannot assign to a read-only ${this.describe(target.type)} value.`,
@@ -35296,7 +35486,7 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
35296
35486
  statement.pos
35297
35487
  );
35298
35488
  }
35299
- const writability = observed.writability;
35489
+ const writability = observed.userData ? "runtime" /* Runtime */ : observed.writability;
35300
35490
  if (writability !== "save" /* Save */ && writability !== "session" /* Session */ && writability !== "runtime" /* Runtime */ && writability !== "local" /* Local */) {
35301
35491
  throw new CompileError(
35302
35492
  "OnChanged requires a member whose storage is writable at gameplay runtime.",
@@ -35906,6 +36096,16 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
35906
36096
  expression.pos
35907
36097
  );
35908
36098
  const declaredTarget = this.project.typeByName.get(className);
36099
+ if (declaredTarget !== void 0) {
36100
+ this.assertUserSafe(
36101
+ { kind: "named", typeId: declaredTarget.id },
36102
+ "a `new` expression",
36103
+ expression.pos
36104
+ );
36105
+ }
36106
+ if (declaredTarget?.userClass === true && !this.writesUserData && this.context.storedConstructionReplay !== true) {
36107
+ throw new CompileError(USER_DATA_WRITE_ERROR, expression.pos);
36108
+ }
35909
36109
  if (declaredTarget?.kind === "class" && (expression.initializer?.length ?? 0) > 0 && this.context.storedConstructionReplay !== true) {
35910
36110
  return this.resolveExecutableObjectInitializer(
35911
36111
  declaredTarget,
@@ -37094,7 +37294,7 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
37094
37294
  type: member.type,
37095
37295
  symbol: member,
37096
37296
  ...member.writable ? {
37097
- writability: member.writability ? toWritability(member.writability) : "setter" /* Setter */
37297
+ writability: member.writability ? toWritability(member.writability, this.writesUserData) : "setter" /* Setter */
37098
37298
  } : {}
37099
37299
  };
37100
37300
  }
@@ -37105,7 +37305,9 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
37105
37305
  },
37106
37306
  type: member.type,
37107
37307
  symbol: member,
37108
- ...member.writability ? { writability: toWritability(member.writability) } : {}
37308
+ ...member.writability ? {
37309
+ writability: toWritability(member.writability, this.writesUserData)
37310
+ } : {}
37109
37311
  };
37110
37312
  }
37111
37313
  resolveMemberNatural(receiverAst, name, scope, pos, optional2, receiverValue) {
@@ -37175,6 +37377,11 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
37175
37377
  }
37176
37378
  const staticType = this.project.typeByName.get(receiverAst.name);
37177
37379
  if (staticType && staticType.kind !== "enum") {
37380
+ this.assertUserSafe(
37381
+ { kind: "named", typeId: staticType.id },
37382
+ "a static receiver",
37383
+ pos
37384
+ );
37178
37385
  if (optional2) {
37179
37386
  throw new CompileError(
37180
37387
  `Optional chaining is not valid on type receiver '${staticType.name}'.`,
@@ -37353,6 +37560,9 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
37353
37560
  );
37354
37561
  }
37355
37562
  this.assertMemberAccessible(type, member, pos);
37563
+ if (this.userClassCode && type.id === this.rootTypeId && member.writability !== "user") {
37564
+ throw new CompileError("User code can only read `root.User`.", pos);
37565
+ }
37356
37566
  if (member.static === true) {
37357
37567
  throw new CompileError(
37358
37568
  `Static member '${type.name}.${name}' must be accessed through the type name, not an instance.`,
@@ -37382,12 +37592,14 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
37382
37592
  ...nullPropagating ? { optionalChainType: memberType2 } : {},
37383
37593
  symbol: member,
37384
37594
  ...member.writable ? {
37385
- writability: member.writability ? toWritability(member.writability) : "setter" /* Setter */
37595
+ writability: member.writability ? toWritability(member.writability, this.writesUserData) : "setter" /* Setter */
37386
37596
  } : {}
37387
37597
  };
37388
37598
  }
37389
37599
  const writability = this.memberWritability(member, receiver);
37390
37600
  const entryWritability = member.lookup ? this.lookupEntryWritability(member, receiver) : void 0;
37601
+ const userData = this.memberUserData(member, receiver);
37602
+ const entryUserData = member.lookup ? this.lookupEntryUserData(member) : false;
37391
37603
  const lookupWire = member.lookup?.multiselect === true && memberType2.kind === "set" ? {
37392
37604
  type: 9 /* Lookup */,
37393
37605
  required: !isNullable(memberType2),
@@ -37409,7 +37621,9 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
37409
37621
  symbol: member,
37410
37622
  ...writability ? { writability } : {},
37411
37623
  ...lookupWire ? { wireType: lookupWire } : {},
37412
- ...entryWritability ? { entryWritability } : {}
37624
+ ...entryWritability ? { entryWritability } : {},
37625
+ userData,
37626
+ entryUserData
37413
37627
  };
37414
37628
  }
37415
37629
  throw new CompileError(
@@ -37495,20 +37709,33 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
37495
37709
  }
37496
37710
  memberWritability(member, receiver) {
37497
37711
  if (member.writability && !member.lookup) {
37498
- return toWritability(member.writability);
37712
+ return toWritability(member.writability, this.writesUserData);
37499
37713
  }
37500
37714
  if (!member.writable) return void 0;
37501
37715
  if (!member.lookup) {
37502
- return member.writability ? toWritability(member.writability) : receiver.entryWritability ?? receiver.writability ?? "save" /* Save */;
37716
+ return member.writability ? toWritability(member.writability, this.writesUserData) : receiver.entryWritability ?? receiver.writability ?? "save" /* Save */;
37717
+ }
37718
+ return member.writability ? toWritability(member.writability, this.writesUserData) : receiver.writability ?? "save" /* Save */;
37719
+ }
37720
+ /** Mirrors {@link memberWritability}'s inheritance for P104 User data. */
37721
+ memberUserData(member, receiver) {
37722
+ if (member.writability) return member.writability === "user";
37723
+ if (member.lookup || receiver.entryWritability === void 0) {
37724
+ return receiver.userData === true;
37503
37725
  }
37504
- return member.writability ? toWritability(member.writability) : receiver.writability ?? "save" /* Save */;
37726
+ return receiver.entryUserData === true;
37727
+ }
37728
+ lookupEntryUserData(member) {
37729
+ if (!member.lookup) return false;
37730
+ const collectionWritability = member.lookup.collectionWritability ?? this.project.symbolById.get(member.lookup.collectionMemberId)?.writability;
37731
+ return collectionWritability === "user";
37505
37732
  }
37506
37733
  lookupEntryWritability(member, receiver) {
37507
37734
  if (!member.lookup) return "readOnly" /* ReadOnly */;
37508
37735
  const target = this.project.symbolById.get(
37509
37736
  member.lookup.collectionMemberId
37510
37737
  );
37511
- const targetWritability = member.lookup.collectionWritability ? toWritability(member.lookup.collectionWritability) : target?.writability ? toWritability(target.writability) : target?.writable ? "runtime" /* Runtime */ : "readOnly" /* ReadOnly */;
37738
+ const targetWritability = member.lookup.collectionWritability ? toWritability(member.lookup.collectionWritability, this.writesUserData) : target?.writability ? toWritability(target.writability, this.writesUserData) : target?.writable ? "runtime" /* Runtime */ : "readOnly" /* ReadOnly */;
37512
37739
  if (receiver.writability === "immutable" /* Immutable */) {
37513
37740
  if (targetWritability === "save" /* Save */) {
37514
37741
  return "immutableToSaveLookup" /* ImmutableToSaveLookup */;
@@ -37529,7 +37756,8 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
37529
37756
  },
37530
37757
  type: optional2 ? { ...type, nullable: true } : type,
37531
37758
  ...optional2 ? { optionalChainType: type } : {},
37532
- ...receiver.writability ? { writability: receiver.writability } : {}
37759
+ ...receiver.writability ? { writability: receiver.writability } : {},
37760
+ ...receiver.userData ? { userData: true } : {}
37533
37761
  };
37534
37762
  }
37535
37763
  resolveIndex(receiverAst, indexAst, scope, pos, optional2) {
@@ -37590,7 +37818,8 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
37590
37818
  },
37591
37819
  type: nullPropagating ? { ...receiver.type.elementType, nullable: true } : receiver.type.elementType,
37592
37820
  ...nullPropagating ? { optionalChainType: receiver.type.elementType } : {},
37593
- ...receiver.writability ? { writability: receiver.writability } : {}
37821
+ ...receiver.writability ? { writability: receiver.writability } : {},
37822
+ ...receiver.userData ? { userData: true } : {}
37594
37823
  };
37595
37824
  }
37596
37825
  keyType = { kind: "primitive", name: "int" };
@@ -37624,7 +37853,8 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
37624
37853
  },
37625
37854
  type: nullPropagating ? { ...valueType, nullable: true } : valueType,
37626
37855
  ...nullPropagating ? { optionalChainType: valueType } : {},
37627
- ...receiver.writability ? { writability: receiver.writability } : {}
37856
+ ...receiver.writability ? { writability: receiver.writability } : {},
37857
+ ...receiver.userData ? { userData: true } : {}
37628
37858
  };
37629
37859
  }
37630
37860
  resolveDictionaryKey(expectedKey, keyAst, scope, pos, requireNonOptional, dictionaryLiteral = false) {
@@ -38028,6 +38258,11 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
38028
38258
  }
38029
38259
  const staticType = this.project.typeByName.get(callee.receiver.name);
38030
38260
  if (staticType && staticType.kind !== "enum") {
38261
+ this.assertUserSafe(
38262
+ { kind: "named", typeId: staticType.id },
38263
+ "a static receiver",
38264
+ pos
38265
+ );
38031
38266
  if (callee.optional) {
38032
38267
  throw new CompileError(
38033
38268
  `Optional chaining is not valid on type receiver '${staticType.name}'.`,
@@ -40176,6 +40411,9 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
40176
40411
  */
40177
40412
  resolveActionCall(action, actionType, expression, scope) {
40178
40413
  this.assertActionControlAccessible(action, "invoke", expression.pos);
40414
+ if (action.userData && action.writability === "readOnly" /* ReadOnly */) {
40415
+ throw new CompileError(USER_DATA_WRITE_ERROR, expression.pos);
40416
+ }
40179
40417
  if (expression.callee.kind === "member" && expression.callee.optional) {
40180
40418
  throw new CompileError(
40181
40419
  `${this.describe(actionType)} is never null, so optional-chained invocation is not supported.`,
@@ -40333,6 +40571,11 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
40333
40571
  expression.callee.receiver.name
40334
40572
  );
40335
40573
  if (staticType && staticType.kind !== "enum") {
40574
+ this.assertUserSafe(
40575
+ { kind: "named", typeId: staticType.id },
40576
+ "a static receiver",
40577
+ expression.pos
40578
+ );
40336
40579
  if (expression.callee.optional) {
40337
40580
  throw new CompileError(
40338
40581
  `Optional chaining is not valid on type receiver '${staticType.name}'.`,
@@ -40431,6 +40674,9 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
40431
40674
  pos
40432
40675
  );
40433
40676
  }
40677
+ if (writability === "readOnly" /* ReadOnly */ && receiver.userData) {
40678
+ throw new CompileError(USER_DATA_WRITE_ERROR, pos);
40679
+ }
40434
40680
  throw new CompileError(
40435
40681
  writability === "immutable" /* Immutable */ ? "Cannot mutate a collection whose effective storage is immutable." : "Cannot mutate a read-only collection value.",
40436
40682
  pos
@@ -40525,6 +40771,9 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
40525
40771
  pos
40526
40772
  );
40527
40773
  }
40774
+ if (writability === "readOnly" /* ReadOnly */ && receiver.userData) {
40775
+ throw new CompileError(USER_DATA_WRITE_ERROR, pos);
40776
+ }
40528
40777
  if (writability === "readOnly" /* ReadOnly */) {
40529
40778
  throw new CompileError("Cannot clear a read-only action.", pos);
40530
40779
  }
@@ -40872,7 +41121,17 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
40872
41121
  );
40873
41122
  }
40874
41123
  resolveType(type) {
40875
- return resolveNeoScriptType(type, this.context, this.project);
41124
+ const resolved = resolveNeoScriptType(type, this.context, this.project);
41125
+ this.assertUserSafe(resolved, "a type annotation", type.pos);
41126
+ return resolved;
41127
+ }
41128
+ /** P104 §3.2. User code names only User-safe types. */
41129
+ assertUserSafe(type, site, pos) {
41130
+ if (!this.userClassCode) return;
41131
+ const unsafe = userUnsafeTypeName(type, this.project);
41132
+ if (unsafe !== null) {
41133
+ throw new CompileError(userUnsafeTypeMessage(unsafe, site), pos);
41134
+ }
40876
41135
  }
40877
41136
  /**
40878
41137
  * P43 §7.1. A constructor body builds the value it is handed; every other
@@ -41038,7 +41297,12 @@ var StrictNeoScriptResolver = class _StrictNeoScriptResolver {
41038
41297
  pointer: { type: "variable" /* Variable */, variableId: symbol.id },
41039
41298
  type: symbol.type,
41040
41299
  symbol,
41041
- ...symbol.writability ? { writability: toWritability(symbol.writability) } : {}
41300
+ ...symbol.writability ? {
41301
+ writability: toWritability(
41302
+ symbol.writability,
41303
+ this.writesUserData
41304
+ )
41305
+ } : {}
41042
41306
  };
41043
41307
  }
41044
41308
  return literal(symbol.type, null);
@@ -42383,8 +42647,10 @@ function namedPrimitive(name) {
42383
42647
  return null;
42384
42648
  }
42385
42649
  }
42386
- function toWritability(value) {
42650
+ function toWritability(value, writesUserData) {
42387
42651
  switch (value) {
42652
+ case "user":
42653
+ return writesUserData ? "runtime" /* Runtime */ : "readOnly" /* ReadOnly */;
42388
42654
  case "save":
42389
42655
  return "save" /* Save */;
42390
42656
  case "session":
@@ -43085,10 +43351,10 @@ function resolvedAllowedStorage(classId, environment) {
43085
43351
  return null;
43086
43352
  }
43087
43353
  function constraintDefaultWritability(allowed) {
43088
- return allowed === "immutable" || allowed === "save" || allowed === "session" ? allowed : "session";
43354
+ return allowed === "immutable" || allowed === "save" || allowed === "session" || allowed === "user" ? allowed : "session";
43089
43355
  }
43090
43356
  function declaredStorageWritability(storage, staticOwnerClassId, environment) {
43091
- if (storage === "immutable" || storage === "save" || storage === "session") {
43357
+ if (storage === "immutable" || storage === "save" || storage === "session" || storage === "user") {
43092
43358
  return storage;
43093
43359
  }
43094
43360
  if (storage === "writable") return "runtime";
@@ -43102,12 +43368,13 @@ function staticDefaultWritability(classId, environment) {
43102
43368
  }
43103
43369
  function createRootType(ids, environment) {
43104
43370
  const definitions = [
43105
- ["Assets", ids.assets, false],
43106
- ["Save", ids.save, true],
43107
- ["Session", ids.session, true]
43371
+ ["Assets", ids.assets, false, "immutable"],
43372
+ ["Save", ids.save, true, "save"],
43373
+ ["Session", ids.session, true, "session"],
43374
+ ["User", ids.user, true, "user"]
43108
43375
  ];
43109
43376
  const members = [];
43110
- for (const [name, memberId, writable] of definitions) {
43377
+ for (const [name, memberId, writable, writability] of definitions) {
43111
43378
  if (!memberId) continue;
43112
43379
  const member = environment.members.get(memberId);
43113
43380
  if (!member) continue;
@@ -43117,7 +43384,7 @@ function createRootType(ids, environment) {
43117
43384
  kind: "property",
43118
43385
  type: memberType(member, environment, /* @__PURE__ */ new Set(), `root.${name}`),
43119
43386
  writable,
43120
- writability: name === "Assets" ? "immutable" : name === "Session" ? "session" : "save",
43387
+ writability,
43121
43388
  documentation: `Project ${name.toLowerCase()} root (${memberId}).`,
43122
43389
  ...sourceLocation(member.source, environment, `root.${name}.source`)
43123
43390
  });
@@ -43140,7 +43407,8 @@ function inferRootMemberIds(members) {
43140
43407
  return {
43141
43408
  assets: unique("immutable"),
43142
43409
  save: unique("save"),
43143
- session: unique("session")
43410
+ session: unique("session"),
43411
+ user: unique("user")
43144
43412
  };
43145
43413
  }
43146
43414
  function effectiveMemberWritability(members, classes, roots) {
@@ -43161,7 +43429,7 @@ function effectiveMemberWritability(members, classes, roots) {
43161
43429
  seen.add(memberId);
43162
43430
  const member = memberById.get(memberId);
43163
43431
  if (!member) return null;
43164
- if (member.storage === "immutable" || member.storage === "save" || member.storage === "session" || member.storage === "writable") {
43432
+ if (member.storage === "immutable" || member.storage === "save" || member.storage === "session" || member.storage === "writable" || member.storage === "user") {
43165
43433
  return member.storage;
43166
43434
  }
43167
43435
  return typeof member.overrideOf === "string" ? declaredStorage(member.overrideOf, seen) : null;
@@ -43229,6 +43497,7 @@ function effectiveMemberWritability(members, classes, roots) {
43229
43497
  if (typeof roots.session === "string") {
43230
43498
  rootStorage.set(roots.session, "session");
43231
43499
  }
43500
+ if (typeof roots.user === "string") rootStorage.set(roots.user, "user");
43232
43501
  for (const [memberId, member] of memberById) {
43233
43502
  const declared = rootStorage.get(memberId) ?? declaredStorage(memberId);
43234
43503
  if (declared === "writable") {
@@ -43300,6 +43569,7 @@ function schemaClass(value, environment, field) {
43300
43569
  ),
43301
43570
  ...value.declarationModifier === "abstract" ? { abstract: true } : {},
43302
43571
  ...value.declarationModifier === "sealed" ? { sealed: true } : {},
43572
+ ...resolvedAllowedStorage(id, environment) === "user" ? { userClass: true } : {},
43303
43573
  ...sourceLocation(value.source, environment, `${field}.source`),
43304
43574
  typeParameters: records(
43305
43575
  value.genericParameters ?? [],
@@ -45081,6 +45351,7 @@ function classToLanguageType(schemaClass2, context) {
45081
45351
  ...schemaClass2.docsText ? { documentation: schemaClass2.docsText } : {},
45082
45352
  kind: "class",
45083
45353
  abstract: isAbstractClass(schemaClass2),
45354
+ ...resolveAllowedStorage(schemaClass2.id, context.vm.classes) === 5 /* User */ ? { userClass: true } : {},
45084
45355
  location: virtualIdentifierLocation(
45085
45356
  context,
45086
45357
  "Classes",
@@ -45356,6 +45627,8 @@ function symbolWritability(memberId, context, staticOwnerClassId) {
45356
45627
  return { writability: "session" };
45357
45628
  case 4 /* Writable */:
45358
45629
  return { writability: "runtime" };
45630
+ case 5 /* User */:
45631
+ return { writability: "user" };
45359
45632
  }
45360
45633
  }
45361
45634
  function uniqueEffectiveWritability(memberId, context) {
@@ -45377,6 +45650,8 @@ function uniqueEffectiveWritability(memberId, context) {
45377
45650
  return "save";
45378
45651
  case 3 /* Session */:
45379
45652
  return "session";
45653
+ case 5 /* User */:
45654
+ return "user";
45380
45655
  }
45381
45656
  }
45382
45657
  function interfaceToLanguageType(neoInterface, context) {
@@ -45476,14 +45751,17 @@ function interfaceMemberToSymbol(neoInterface, key, member, context, inherited)
45476
45751
  ...inheritedFrom ? { inheritedFrom } : {}
45477
45752
  };
45478
45753
  }
45754
+ var ROOT_WRITABILITY = {
45755
+ [1 /* Immutable */]: "immutable",
45756
+ [2 /* Save */]: "save",
45757
+ [3 /* Session */]: "session",
45758
+ [5 /* User */]: "user"
45759
+ };
45479
45760
  function createRootType2(context) {
45480
- const definitions = [
45481
- ["Assets", context.vm.project.rootAssetsMemberId],
45482
- ["Save", context.vm.project.rootSaveFileMemberId],
45483
- ["Session", context.vm.project.rootSessionMemberId]
45484
- ];
45485
45761
  const members = [];
45486
- for (const [index, [name, memberId]] of definitions.entries()) {
45762
+ for (const [index, root] of PROJECT_ROOTS.entries()) {
45763
+ const name = root.key;
45764
+ const memberId = context.vm.project[root.projectField];
45487
45765
  const member = analyzerMemberById(context, memberId);
45488
45766
  if (!member) continue;
45489
45767
  members.push({
@@ -45491,8 +45769,8 @@ function createRootType2(context) {
45491
45769
  name,
45492
45770
  kind: "property",
45493
45771
  type: memberRuntimeType(member, context),
45494
- writable: name !== "Assets",
45495
- writability: name === "Assets" ? "immutable" : name === "Session" ? "session" : "save",
45772
+ writable: root.storage !== 1 /* Immutable */,
45773
+ writability: ROOT_WRITABILITY[root.storage],
45496
45774
  documentation: `Project ${name.toLowerCase()} root (${memberId}).`,
45497
45775
  location: virtualIdentifierLocation(
45498
45776
  context,
@@ -46059,9 +46337,7 @@ function projectLanguageVersion(context) {
46059
46337
  `adapter:${NEOSCRIPT_COMPILER_ADAPTER_REVISION}`,
46060
46338
  context.vm.project.id,
46061
46339
  context.vm.project.updatedAt,
46062
- context.vm.project.rootAssetsMemberId,
46063
- context.vm.project.rootSaveFileMemberId,
46064
- context.vm.project.rootSessionMemberId,
46340
+ ...projectRootMemberIds(context.vm.project),
46065
46341
  ...records2.map((record4) => `${record4.id}:${record4.updatedAt}`).sort()
46066
46342
  ].join("|");
46067
46343
  }
@@ -47037,7 +47313,7 @@ async function restartProjectRead(read) {
47037
47313
  // ../src/models/project/project-types.ts
47038
47314
  function isProject(value) {
47039
47315
  const v = value;
47040
- return typeof v?.name === "string" && typeof v?.id === "string" && isProjectOwner(v.owner) && isActorIdentifier(v.author) && typeof v?.createdAt === "number" && typeof v?.updatedAt === "number" && typeof v?.rootAssetsMemberId === "string" && typeof v?.rootSaveFileMemberId === "string" && typeof v?.rootSessionMemberId === "string" && isOptionalNullableString2(v.defaultPriorityGroupId) && isOptionalNullableString2(v.defaultTextureTemplateId) && isOptionalNullableString2(v.defaultAudioClipTemplateId) && isProjectExportSettingsOrEmpty(v.exportSettings);
47316
+ return typeof v?.name === "string" && typeof v?.id === "string" && isProjectOwner(v.owner) && isActorIdentifier(v.author) && typeof v?.createdAt === "number" && typeof v?.updatedAt === "number" && typeof v?.rootAssetsMemberId === "string" && typeof v?.rootSaveFileMemberId === "string" && typeof v?.rootSessionMemberId === "string" && typeof v?.rootUserMemberId === "string" && isOptionalNullableString2(v.defaultPriorityGroupId) && isOptionalNullableString2(v.defaultTextureTemplateId) && isOptionalNullableString2(v.defaultAudioClipTemplateId) && isProjectExportSettingsOrEmpty(v.exportSettings);
47041
47317
  }
47042
47318
  function isProjectOwner(value) {
47043
47319
  if (!isActorIdentifier(value)) return false;
@@ -51305,7 +51581,9 @@ var commonMemberEntries = {
51305
51581
  defaultValue: nullable(schemaDefaultValueSchema),
51306
51582
  overrideOf: nullableId,
51307
51583
  system: nullable(schemaSystemMetadataSchema),
51308
- storage: nullable(picklist(["immutable", "save", "session", "writable"])),
51584
+ storage: nullable(
51585
+ picklist(["immutable", "save", "session", "writable", "user"])
51586
+ ),
51309
51587
  distribution: nullable(picklist(["sparse", "packed"])),
51310
51588
  storageKey: nullableId
51311
51589
  };
@@ -51495,7 +51773,8 @@ var MEMBER_STORAGE = [
51495
51773
  "immutable",
51496
51774
  "save",
51497
51775
  "session",
51498
- "writable"
51776
+ "writable",
51777
+ "user"
51499
51778
  ];
51500
51779
  var MEMBER_DISTRIBUTION = ["sparse", "packed"];
51501
51780
  var STRING_FORMAT = ["localized", "plain"];
@@ -53387,7 +53666,8 @@ var CLASS_ALLOWED_STORAGE = [
53387
53666
  "immutable",
53388
53667
  "save",
53389
53668
  "session",
53390
- "writable"
53669
+ "writable",
53670
+ "user"
53391
53671
  ];
53392
53672
  var CLASS_SCHEMA_RECORD_ADAPTER = {
53393
53673
  recordKind: "class",