@objectstack/objectql 6.7.1 → 6.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +93 -202
- package/README.md +1 -1
- package/dist/index.d.mts +194 -1
- package/dist/index.d.ts +194 -1
- package/dist/index.js +605 -36
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +596 -27
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -6
package/dist/index.mjs
CHANGED
|
@@ -622,9 +622,26 @@ var SchemaRegistry = class {
|
|
|
622
622
|
// src/sys-metadata-repository.ts
|
|
623
623
|
import { hashSpec, ConflictError } from "@objectstack/metadata-core";
|
|
624
624
|
import { DEFAULT_METADATA_TYPE_REGISTRY } from "@objectstack/spec/kernel";
|
|
625
|
+
import { PLURAL_TO_SINGULAR, SINGULAR_TO_PLURAL } from "@objectstack/spec/shared";
|
|
625
626
|
var OVERLAY_ALLOWED_TYPES = new Set(
|
|
626
627
|
DEFAULT_METADATA_TYPE_REGISTRY.filter((e) => e.allowOrgOverride).map((e) => e.type)
|
|
627
628
|
);
|
|
629
|
+
var _envWritableMetadataTypes = null;
|
|
630
|
+
function envWritableMetadataTypes() {
|
|
631
|
+
if (_envWritableMetadataTypes !== null) return _envWritableMetadataTypes;
|
|
632
|
+
const raw = typeof process !== "undefined" && process?.env?.OBJECTSTACK_METADATA_WRITABLE || "";
|
|
633
|
+
const set = /* @__PURE__ */ new Set();
|
|
634
|
+
for (const tok of raw.split(",")) {
|
|
635
|
+
const t = tok.trim();
|
|
636
|
+
if (!t) continue;
|
|
637
|
+
const singular = PLURAL_TO_SINGULAR[t] ?? t;
|
|
638
|
+
set.add(singular);
|
|
639
|
+
const plural = SINGULAR_TO_PLURAL[singular];
|
|
640
|
+
if (plural) set.add(plural);
|
|
641
|
+
}
|
|
642
|
+
_envWritableMetadataTypes = set;
|
|
643
|
+
return set;
|
|
644
|
+
}
|
|
628
645
|
var SysMetadataRepository = class {
|
|
629
646
|
constructor(opts) {
|
|
630
647
|
/**
|
|
@@ -1017,14 +1034,21 @@ var SysMetadataRepository = class {
|
|
|
1017
1034
|
if (this.closed) throw new Error("SysMetadataRepository is closed");
|
|
1018
1035
|
}
|
|
1019
1036
|
assertAllowed(type) {
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1037
|
+
const singular = PLURAL_TO_SINGULAR[type] ?? type;
|
|
1038
|
+
const allowedByRegistry = OVERLAY_ALLOWED_TYPES.has(singular) || OVERLAY_ALLOWED_TYPES.has(type);
|
|
1039
|
+
if (allowedByRegistry) return;
|
|
1040
|
+
const env = envWritableMetadataTypes();
|
|
1041
|
+
if (env.has(singular) || env.has(type)) return;
|
|
1042
|
+
const allowed = [
|
|
1043
|
+
...OVERLAY_ALLOWED_TYPES,
|
|
1044
|
+
...envWritableMetadataTypes()
|
|
1045
|
+
];
|
|
1046
|
+
const err = new Error(
|
|
1047
|
+
`[not_overridable] '${type}' is not allowOrgOverride in the registry. Allowed: ${Array.from(new Set(allowed)).join(", ") || "(none)"}. Set OBJECTSTACK_METADATA_WRITABLE to enable additional types at runtime.`
|
|
1048
|
+
);
|
|
1049
|
+
err.code = "not_overridable";
|
|
1050
|
+
err.status = 403;
|
|
1051
|
+
throw err;
|
|
1028
1052
|
}
|
|
1029
1053
|
whereFor(ref) {
|
|
1030
1054
|
return {
|
|
@@ -1127,13 +1151,171 @@ var SysMetadataRepository = class {
|
|
|
1127
1151
|
|
|
1128
1152
|
// src/protocol.ts
|
|
1129
1153
|
import { ConflictError as ConflictError2 } from "@objectstack/metadata-core";
|
|
1130
|
-
import { parseFilterAST, isFilterAST } from "@objectstack/spec/data";
|
|
1131
|
-
import { PLURAL_TO_SINGULAR, SINGULAR_TO_PLURAL } from "@objectstack/spec/shared";
|
|
1132
|
-
import { ListViewSchema, FormViewSchema, DashboardSchema } from "@objectstack/spec/ui";
|
|
1154
|
+
import { parseFilterAST, isFilterAST, objectForm, fieldForm, hookForm, ObjectSchema as ObjectSchema2, FieldSchema, HookSchema } from "@objectstack/spec/data";
|
|
1155
|
+
import { PLURAL_TO_SINGULAR as PLURAL_TO_SINGULAR2, SINGULAR_TO_PLURAL as SINGULAR_TO_PLURAL2 } from "@objectstack/spec/shared";
|
|
1156
|
+
import { ListViewSchema, FormViewSchema, DashboardSchema, AppSchema as AppSchema2, PageSchema, ReportSchema, ActionSchema, reportForm, viewForm, appForm, dashboardForm, actionForm, pageForm } from "@objectstack/spec/ui";
|
|
1157
|
+
import { RoleSchema, roleForm } from "@objectstack/spec/identity";
|
|
1158
|
+
import { PermissionSetSchema, permissionForm } from "@objectstack/spec/security";
|
|
1159
|
+
import { EmailTemplateSchema, emailTemplateForm } from "@objectstack/spec/system";
|
|
1160
|
+
import { ToolSchema, SkillSchema, AgentSchema, agentForm, toolForm, skillForm } from "@objectstack/spec/ai";
|
|
1161
|
+
import { FlowSchema, WorkflowRuleSchema, ApprovalProcessSchema, flowForm, workflowForm, approvalForm } from "@objectstack/spec/automation";
|
|
1133
1162
|
import { DEFAULT_METADATA_TYPE_REGISTRY as DEFAULT_METADATA_TYPE_REGISTRY2 } from "@objectstack/spec/kernel";
|
|
1163
|
+
import { z } from "zod";
|
|
1164
|
+
var TYPE_TO_SCHEMA = {
|
|
1165
|
+
object: ObjectSchema2,
|
|
1166
|
+
field: FieldSchema,
|
|
1167
|
+
dashboard: DashboardSchema,
|
|
1168
|
+
app: AppSchema2,
|
|
1169
|
+
page: PageSchema,
|
|
1170
|
+
report: ReportSchema,
|
|
1171
|
+
action: ActionSchema,
|
|
1172
|
+
role: RoleSchema,
|
|
1173
|
+
permission: PermissionSetSchema,
|
|
1174
|
+
profile: PermissionSetSchema,
|
|
1175
|
+
email_template: EmailTemplateSchema,
|
|
1176
|
+
tool: ToolSchema,
|
|
1177
|
+
skill: SkillSchema,
|
|
1178
|
+
agent: AgentSchema,
|
|
1179
|
+
flow: FlowSchema,
|
|
1180
|
+
workflow: WorkflowRuleSchema,
|
|
1181
|
+
approval: ApprovalProcessSchema,
|
|
1182
|
+
hook: HookSchema
|
|
1183
|
+
};
|
|
1184
|
+
var TYPE_TO_FORM = {
|
|
1185
|
+
object: objectForm,
|
|
1186
|
+
field: fieldForm,
|
|
1187
|
+
hook: hookForm,
|
|
1188
|
+
report: reportForm,
|
|
1189
|
+
view: viewForm,
|
|
1190
|
+
app: appForm,
|
|
1191
|
+
dashboard: dashboardForm,
|
|
1192
|
+
role: roleForm,
|
|
1193
|
+
action: actionForm,
|
|
1194
|
+
page: pageForm,
|
|
1195
|
+
agent: agentForm,
|
|
1196
|
+
tool: toolForm,
|
|
1197
|
+
skill: skillForm,
|
|
1198
|
+
flow: flowForm,
|
|
1199
|
+
workflow: workflowForm,
|
|
1200
|
+
approval: approvalForm,
|
|
1201
|
+
permission: permissionForm,
|
|
1202
|
+
profile: permissionForm,
|
|
1203
|
+
email_template: emailTemplateForm
|
|
1204
|
+
};
|
|
1205
|
+
var _jsonSchemaCache = /* @__PURE__ */ new WeakMap();
|
|
1206
|
+
function toJsonSchemaSafe(schema) {
|
|
1207
|
+
const cached = _jsonSchemaCache.get(schema);
|
|
1208
|
+
if (cached !== void 0) return cached ?? void 0;
|
|
1209
|
+
try {
|
|
1210
|
+
const result = z.toJSONSchema(schema, { unrepresentable: "any" });
|
|
1211
|
+
_jsonSchemaCache.set(schema, result);
|
|
1212
|
+
return result;
|
|
1213
|
+
} catch {
|
|
1214
|
+
_jsonSchemaCache.set(schema, null);
|
|
1215
|
+
return void 0;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
var HAND_CRAFTED_SCHEMAS = {
|
|
1219
|
+
object: {
|
|
1220
|
+
type: "object",
|
|
1221
|
+
properties: {
|
|
1222
|
+
name: { type: "string" },
|
|
1223
|
+
label: { type: "string" },
|
|
1224
|
+
pluralLabel: { type: "string" },
|
|
1225
|
+
icon: { type: "string" },
|
|
1226
|
+
description: { type: "string" },
|
|
1227
|
+
tags: { type: "array", items: { type: "string" } },
|
|
1228
|
+
active: { type: "boolean", default: true },
|
|
1229
|
+
isSystem: { type: "boolean", default: false },
|
|
1230
|
+
abstract: { type: "boolean", default: false },
|
|
1231
|
+
datasource: { type: "string" },
|
|
1232
|
+
fields: {
|
|
1233
|
+
type: "array",
|
|
1234
|
+
default: [],
|
|
1235
|
+
items: {
|
|
1236
|
+
type: "object",
|
|
1237
|
+
properties: {
|
|
1238
|
+
name: { type: "string" },
|
|
1239
|
+
label: { type: "string" },
|
|
1240
|
+
type: { type: "string" },
|
|
1241
|
+
required: { type: "boolean", default: false },
|
|
1242
|
+
unique: { type: "boolean", default: false },
|
|
1243
|
+
defaultValue: {},
|
|
1244
|
+
description: { type: "string" }
|
|
1245
|
+
},
|
|
1246
|
+
required: ["name", "type"]
|
|
1247
|
+
}
|
|
1248
|
+
},
|
|
1249
|
+
capabilities: { type: "object", additionalProperties: true }
|
|
1250
|
+
},
|
|
1251
|
+
required: ["name"],
|
|
1252
|
+
additionalProperties: true
|
|
1253
|
+
},
|
|
1254
|
+
action: {
|
|
1255
|
+
type: "object",
|
|
1256
|
+
properties: {
|
|
1257
|
+
name: { type: "string" },
|
|
1258
|
+
label: { type: "string" },
|
|
1259
|
+
objectName: { type: "string" },
|
|
1260
|
+
icon: { type: "string" },
|
|
1261
|
+
type: { type: "string", enum: ["url", "flow", "api", "script"] },
|
|
1262
|
+
variant: { type: "string", enum: ["primary", "secondary", "danger", "ghost", "outline"] },
|
|
1263
|
+
target: { type: "string" },
|
|
1264
|
+
method: { type: "string", enum: ["GET", "POST", "PUT", "PATCH", "DELETE"] },
|
|
1265
|
+
body: {
|
|
1266
|
+
type: "array",
|
|
1267
|
+
default: [],
|
|
1268
|
+
items: {
|
|
1269
|
+
type: "object",
|
|
1270
|
+
properties: {
|
|
1271
|
+
line: { type: "string" }
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
},
|
|
1275
|
+
params: {
|
|
1276
|
+
type: "array",
|
|
1277
|
+
default: [],
|
|
1278
|
+
items: {
|
|
1279
|
+
type: "object",
|
|
1280
|
+
properties: {
|
|
1281
|
+
name: { type: "string" },
|
|
1282
|
+
label: { type: "string" },
|
|
1283
|
+
type: { type: "string" },
|
|
1284
|
+
required: { type: "boolean", default: false }
|
|
1285
|
+
},
|
|
1286
|
+
required: ["name"]
|
|
1287
|
+
}
|
|
1288
|
+
},
|
|
1289
|
+
confirmText: { type: "string" },
|
|
1290
|
+
successMessage: { type: "string" },
|
|
1291
|
+
refreshAfter: { type: "boolean", default: true },
|
|
1292
|
+
locations: {
|
|
1293
|
+
type: "array",
|
|
1294
|
+
default: [],
|
|
1295
|
+
items: {
|
|
1296
|
+
type: "object",
|
|
1297
|
+
properties: {
|
|
1298
|
+
location: { type: "string" }
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
},
|
|
1302
|
+
component: { type: "string" },
|
|
1303
|
+
visible: { type: "string" },
|
|
1304
|
+
disabled: { type: "string" },
|
|
1305
|
+
shortcut: { type: "string" },
|
|
1306
|
+
bulkEnabled: { type: "boolean", default: false },
|
|
1307
|
+
aiExposed: { type: "boolean", default: false },
|
|
1308
|
+
recordIdParam: { type: "string" },
|
|
1309
|
+
recordIdField: { type: "string" },
|
|
1310
|
+
bodyShape: { type: "string", enum: ["flat", "nested"] }
|
|
1311
|
+
},
|
|
1312
|
+
required: ["name", "label", "type"],
|
|
1313
|
+
additionalProperties: true
|
|
1314
|
+
}
|
|
1315
|
+
};
|
|
1134
1316
|
var FORM_VIEW_TYPES = /* @__PURE__ */ new Set(["simple", "tabbed", "wizard", "split", "drawer", "modal"]);
|
|
1135
1317
|
function resolveOverlaySchema(type, item) {
|
|
1136
|
-
const singular =
|
|
1318
|
+
const singular = PLURAL_TO_SINGULAR2[type] ?? type;
|
|
1137
1319
|
switch (singular) {
|
|
1138
1320
|
case "view": {
|
|
1139
1321
|
const t = item && typeof item === "object" && "type" in item ? String(item.type) : void 0;
|
|
@@ -1190,6 +1372,140 @@ var SERVICE_CONFIG = {
|
|
|
1190
1372
|
"file-storage": { route: "/api/v1/storage", plugin: "plugin-storage" },
|
|
1191
1373
|
search: { route: "/api/v1/search", plugin: "plugin-search" }
|
|
1192
1374
|
};
|
|
1375
|
+
var REFERENCE_PATHS = {
|
|
1376
|
+
object: [
|
|
1377
|
+
{ fromType: "view", paths: ["object", "objectName"], kind: "view" },
|
|
1378
|
+
{ fromType: "dashboard", paths: ["widgets[].object", "widgets[].objectName"], kind: "dashboard widget" },
|
|
1379
|
+
{ fromType: "flow", paths: ["object", "context.object", "trigger.object", "targetObject"], kind: "flow" },
|
|
1380
|
+
{ fromType: "workflow", paths: ["object", "targetObject"], kind: "workflow" },
|
|
1381
|
+
{ fromType: "permission", paths: ["objects[].name", "objects[].object"], kind: "permission" },
|
|
1382
|
+
{ fromType: "app", paths: ["navItems[].objectName", "navItems[].object", "tabs[].objectName", "tabs[].object"], kind: "app nav" },
|
|
1383
|
+
{ fromType: "page", paths: ["object", "objectName"], kind: "page" },
|
|
1384
|
+
{ fromType: "report", paths: ["object", "objectName"], kind: "report" },
|
|
1385
|
+
{ fromType: "action", paths: ["object", "objectName"], kind: "action" },
|
|
1386
|
+
{ fromType: "validation", paths: ["object", "objectName"], kind: "validation" },
|
|
1387
|
+
{ fromType: "hook", paths: ["object", "objectName"], kind: "hook" },
|
|
1388
|
+
{ fromType: "object", paths: ["fields[].referenceTo", "fields{}.referenceTo", "fields{}.reference"], kind: "field reference" }
|
|
1389
|
+
],
|
|
1390
|
+
view: [
|
|
1391
|
+
{ fromType: "dashboard", paths: ["widgets[].view", "widgets[].viewName"], kind: "dashboard widget" },
|
|
1392
|
+
{ fromType: "app", paths: ["navItems[].viewName", "tabs[].viewName"], kind: "app nav" },
|
|
1393
|
+
{ fromType: "page", paths: ["viewName"], kind: "page" }
|
|
1394
|
+
],
|
|
1395
|
+
tool: [
|
|
1396
|
+
{ fromType: "agent", paths: ["tools[]", "tools[].name"], kind: "agent tool" }
|
|
1397
|
+
],
|
|
1398
|
+
skill: [
|
|
1399
|
+
{ fromType: "agent", paths: ["skills[]", "skills[].name"], kind: "agent skill" }
|
|
1400
|
+
],
|
|
1401
|
+
flow: [
|
|
1402
|
+
{ fromType: "app", paths: ["navItems[].flowName", "tabs[].flowName"], kind: "app nav" }
|
|
1403
|
+
],
|
|
1404
|
+
dashboard: [
|
|
1405
|
+
{ fromType: "app", paths: ["navItems[].dashboardName", "tabs[].dashboardName"], kind: "app nav" }
|
|
1406
|
+
],
|
|
1407
|
+
page: [
|
|
1408
|
+
{ fromType: "app", paths: ["navItems[].pageName", "tabs[].pageName"], kind: "app nav" }
|
|
1409
|
+
]
|
|
1410
|
+
};
|
|
1411
|
+
function extractPathValues(item, path) {
|
|
1412
|
+
if (!item || typeof item !== "object") return [];
|
|
1413
|
+
const segments = path.split(".");
|
|
1414
|
+
let current = [item];
|
|
1415
|
+
for (const rawSeg of segments) {
|
|
1416
|
+
let kind = "value";
|
|
1417
|
+
let seg = rawSeg;
|
|
1418
|
+
if (seg.endsWith("[]")) {
|
|
1419
|
+
kind = "array";
|
|
1420
|
+
seg = seg.slice(0, -2);
|
|
1421
|
+
} else if (seg.endsWith("{}")) {
|
|
1422
|
+
kind = "record";
|
|
1423
|
+
seg = seg.slice(0, -2);
|
|
1424
|
+
}
|
|
1425
|
+
const next = [];
|
|
1426
|
+
for (const node of current) {
|
|
1427
|
+
if (!node || typeof node !== "object") continue;
|
|
1428
|
+
let value;
|
|
1429
|
+
if (seg === "") {
|
|
1430
|
+
value = node;
|
|
1431
|
+
} else {
|
|
1432
|
+
value = node[seg];
|
|
1433
|
+
}
|
|
1434
|
+
if (value === void 0 || value === null) continue;
|
|
1435
|
+
if (kind === "array") {
|
|
1436
|
+
if (Array.isArray(value)) {
|
|
1437
|
+
for (const v of value) next.push(v);
|
|
1438
|
+
}
|
|
1439
|
+
} else if (kind === "record") {
|
|
1440
|
+
if (Array.isArray(value)) {
|
|
1441
|
+
for (const v of value) next.push(v);
|
|
1442
|
+
} else if (typeof value === "object") {
|
|
1443
|
+
for (const v of Object.values(value)) next.push(v);
|
|
1444
|
+
}
|
|
1445
|
+
} else {
|
|
1446
|
+
next.push(value);
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
current = next;
|
|
1450
|
+
if (current.length === 0) return [];
|
|
1451
|
+
}
|
|
1452
|
+
const out = [];
|
|
1453
|
+
for (const v of current) {
|
|
1454
|
+
if (typeof v === "string" && v.length > 0) out.push(v);
|
|
1455
|
+
else if (v && typeof v === "object" && "name" in v && typeof v.name === "string") {
|
|
1456
|
+
out.push(v.name);
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
return out;
|
|
1460
|
+
}
|
|
1461
|
+
function detectDestructiveObjectChanges(prev, next) {
|
|
1462
|
+
if (!prev || typeof prev !== "object" || !next || typeof next !== "object") return [];
|
|
1463
|
+
const prevFields = prev.fields && typeof prev.fields === "object" ? prev.fields : {};
|
|
1464
|
+
const nextFields = next.fields && typeof next.fields === "object" ? next.fields : {};
|
|
1465
|
+
const issues = [];
|
|
1466
|
+
for (const fname of Object.keys(prevFields)) {
|
|
1467
|
+
if (prevFields[fname]?.system) continue;
|
|
1468
|
+
if (!(fname in nextFields)) {
|
|
1469
|
+
issues.push({
|
|
1470
|
+
code: "field_removed",
|
|
1471
|
+
field: fname,
|
|
1472
|
+
message: `Field '${fname}' removed \u2014 existing data in this column will become inaccessible.`
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
const TYPE_COMPATIBILITY = {
|
|
1477
|
+
text: /* @__PURE__ */ new Set(["textarea", "markdown", "html", "code"]),
|
|
1478
|
+
number: /* @__PURE__ */ new Set([]),
|
|
1479
|
+
boolean: /* @__PURE__ */ new Set([]),
|
|
1480
|
+
date: /* @__PURE__ */ new Set(["datetime"]),
|
|
1481
|
+
datetime: /* @__PURE__ */ new Set(["date"])
|
|
1482
|
+
};
|
|
1483
|
+
for (const fname of Object.keys(nextFields)) {
|
|
1484
|
+
const prevField = prevFields[fname];
|
|
1485
|
+
const nextField = nextFields[fname];
|
|
1486
|
+
if (!prevField) continue;
|
|
1487
|
+
const prevType = prevField.type;
|
|
1488
|
+
const nextType = nextField.type;
|
|
1489
|
+
if (prevType && nextType && prevType !== nextType) {
|
|
1490
|
+
const compatible = TYPE_COMPATIBILITY[prevType]?.has(nextType);
|
|
1491
|
+
if (!compatible) {
|
|
1492
|
+
issues.push({
|
|
1493
|
+
code: "field_type_change",
|
|
1494
|
+
field: fname,
|
|
1495
|
+
message: `Field '${fname}' type changed from '${prevType}' to '${nextType}' \u2014 existing values may not convert cleanly.`
|
|
1496
|
+
});
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
if (!prevField.required && nextField.required && nextField.defaultValue === void 0) {
|
|
1500
|
+
issues.push({
|
|
1501
|
+
code: "field_required_no_default",
|
|
1502
|
+
field: fname,
|
|
1503
|
+
message: `Field '${fname}' is now required but has no default value \u2014 existing rows with null values may fail validation.`
|
|
1504
|
+
});
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
return issues;
|
|
1508
|
+
}
|
|
1193
1509
|
var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementation {
|
|
1194
1510
|
constructor(engine, getServicesRegistry, getFeedService, environmentId) {
|
|
1195
1511
|
/**
|
|
@@ -1391,7 +1707,52 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
|
|
|
1391
1707
|
} catch {
|
|
1392
1708
|
}
|
|
1393
1709
|
const allTypes = Array.from(/* @__PURE__ */ new Set([...schemaTypes, ...runtimeTypes]));
|
|
1394
|
-
|
|
1710
|
+
const writableOverrides = _ObjectStackProtocolImplementation.envWritableTypes();
|
|
1711
|
+
const registryByType = new Map(
|
|
1712
|
+
DEFAULT_METADATA_TYPE_REGISTRY2.map((e) => [e.type, e])
|
|
1713
|
+
);
|
|
1714
|
+
const entries = allTypes.map((type) => {
|
|
1715
|
+
const singular = PLURAL_TO_SINGULAR2[type] ?? type;
|
|
1716
|
+
const zodSchema = singular === "view" ? ListViewSchema : TYPE_TO_SCHEMA[singular];
|
|
1717
|
+
const schema = (zodSchema ? toJsonSchemaSafe(zodSchema) : void 0) ?? HAND_CRAFTED_SCHEMAS[singular];
|
|
1718
|
+
const form = TYPE_TO_FORM[singular];
|
|
1719
|
+
const base = registryByType.get(singular);
|
|
1720
|
+
if (base) {
|
|
1721
|
+
const isEnvOverridden = writableOverrides.has(singular);
|
|
1722
|
+
return {
|
|
1723
|
+
...base,
|
|
1724
|
+
type: singular,
|
|
1725
|
+
schemaId: singular,
|
|
1726
|
+
// API client expects schemaId field
|
|
1727
|
+
allowOrgOverride: base.allowOrgOverride || isEnvOverridden,
|
|
1728
|
+
overrideSource: isEnvOverridden && !base.allowOrgOverride ? "env" : "registry",
|
|
1729
|
+
schema,
|
|
1730
|
+
form
|
|
1731
|
+
};
|
|
1732
|
+
}
|
|
1733
|
+
return {
|
|
1734
|
+
type: singular,
|
|
1735
|
+
schemaId: singular,
|
|
1736
|
+
// API client expects schemaId field
|
|
1737
|
+
label: singular,
|
|
1738
|
+
description: void 0,
|
|
1739
|
+
filePatterns: [],
|
|
1740
|
+
supportsOverlay: false,
|
|
1741
|
+
allowOrgOverride: writableOverrides.has(singular),
|
|
1742
|
+
allowRuntimeCreate: true,
|
|
1743
|
+
supportsVersioning: false,
|
|
1744
|
+
executionPinned: false,
|
|
1745
|
+
loadOrder: 1e3,
|
|
1746
|
+
domain: "system",
|
|
1747
|
+
overrideSource: writableOverrides.has(singular) ? "env" : "registry",
|
|
1748
|
+
schema,
|
|
1749
|
+
form
|
|
1750
|
+
};
|
|
1751
|
+
}).sort((a, b) => {
|
|
1752
|
+
if (a.domain !== b.domain) return a.domain.localeCompare(b.domain);
|
|
1753
|
+
return a.type.localeCompare(b.type);
|
|
1754
|
+
});
|
|
1755
|
+
return { types: allTypes, entries };
|
|
1395
1756
|
}
|
|
1396
1757
|
async getMetaItems(request) {
|
|
1397
1758
|
const { packageId } = request;
|
|
@@ -1399,13 +1760,13 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
|
|
|
1399
1760
|
if (this.environmentId === void 0) {
|
|
1400
1761
|
items = [...this.engine.registry.listItems(request.type, packageId)];
|
|
1401
1762
|
if (items.length === 0) {
|
|
1402
|
-
const alt =
|
|
1763
|
+
const alt = PLURAL_TO_SINGULAR2[request.type] ?? SINGULAR_TO_PLURAL2[request.type];
|
|
1403
1764
|
if (alt) items = [...this.engine.registry.listItems(alt, packageId)];
|
|
1404
1765
|
}
|
|
1405
1766
|
} else {
|
|
1406
1767
|
items = [...this.engine.registry.listItems(request.type, packageId)];
|
|
1407
1768
|
if (items.length === 0) {
|
|
1408
|
-
const alt =
|
|
1769
|
+
const alt = PLURAL_TO_SINGULAR2[request.type] ?? SINGULAR_TO_PLURAL2[request.type];
|
|
1409
1770
|
if (alt) items = [...this.engine.registry.listItems(alt, packageId)];
|
|
1410
1771
|
}
|
|
1411
1772
|
}
|
|
@@ -1420,7 +1781,7 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
|
|
|
1420
1781
|
if (packageId) whereClause._packageId = packageId;
|
|
1421
1782
|
let rs = await this.engine.find("sys_metadata", { where: whereClause });
|
|
1422
1783
|
if (!rs || rs.length === 0) {
|
|
1423
|
-
const alt =
|
|
1784
|
+
const alt = PLURAL_TO_SINGULAR2[request.type] ?? SINGULAR_TO_PLURAL2[request.type];
|
|
1424
1785
|
if (alt) {
|
|
1425
1786
|
const altWhere = { type: alt, state: "active", organization_id: oid };
|
|
1426
1787
|
if (packageId) altWhere._packageId = packageId;
|
|
@@ -1503,7 +1864,7 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
|
|
|
1503
1864
|
};
|
|
1504
1865
|
const rec = await this.engine.findOne("sys_metadata", { where });
|
|
1505
1866
|
if (rec) return rec;
|
|
1506
|
-
const alt =
|
|
1867
|
+
const alt = PLURAL_TO_SINGULAR2[request.type] ?? SINGULAR_TO_PLURAL2[request.type];
|
|
1507
1868
|
if (alt) {
|
|
1508
1869
|
const altWhere = {
|
|
1509
1870
|
type: alt,
|
|
@@ -1530,7 +1891,7 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
|
|
|
1530
1891
|
if (fromService !== void 0 && fromService !== null) {
|
|
1531
1892
|
item = fromService;
|
|
1532
1893
|
} else {
|
|
1533
|
-
const alt =
|
|
1894
|
+
const alt = PLURAL_TO_SINGULAR2[request.type] ?? SINGULAR_TO_PLURAL2[request.type];
|
|
1534
1895
|
if (alt) {
|
|
1535
1896
|
const altFromService = await metadataService.get(alt, request.name);
|
|
1536
1897
|
if (altFromService !== void 0 && altFromService !== null) {
|
|
@@ -1545,7 +1906,7 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
|
|
|
1545
1906
|
if (item === void 0) {
|
|
1546
1907
|
item = this.engine.registry.getItem(request.type, request.name);
|
|
1547
1908
|
if (item === void 0) {
|
|
1548
|
-
const alt =
|
|
1909
|
+
const alt = PLURAL_TO_SINGULAR2[request.type] ?? SINGULAR_TO_PLURAL2[request.type];
|
|
1549
1910
|
if (alt) item = this.engine.registry.getItem(alt, request.name);
|
|
1550
1911
|
}
|
|
1551
1912
|
}
|
|
@@ -1555,6 +1916,91 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
|
|
|
1555
1916
|
item
|
|
1556
1917
|
};
|
|
1557
1918
|
}
|
|
1919
|
+
/**
|
|
1920
|
+
* Phase 3a-layered-get: return the 3 layers of a metadata item
|
|
1921
|
+
* separately — `code` (artifact-loaded baseline), `overlay` (per-org
|
|
1922
|
+
* customisation row, if any), and `effective` (what `getMetaItem`
|
|
1923
|
+
* would return, i.e. overlay-wins merge).
|
|
1924
|
+
*
|
|
1925
|
+
* Drives the "Code default vs Overlay vs Effective" diff tab in the
|
|
1926
|
+
* generic Metadata Resource Edit page. Admins can see exactly what
|
|
1927
|
+
* was customised and reset selectively.
|
|
1928
|
+
*
|
|
1929
|
+
* `code` is null if no artifact baseline exists; `overlay` is null if
|
|
1930
|
+
* no sys_metadata row exists for the requested scope; `effective` is
|
|
1931
|
+
* never null when either layer exists.
|
|
1932
|
+
*/
|
|
1933
|
+
async getMetaItemLayered(request) {
|
|
1934
|
+
const orgId = request.organizationId;
|
|
1935
|
+
let code = null;
|
|
1936
|
+
try {
|
|
1937
|
+
const services = this.getServicesRegistry?.();
|
|
1938
|
+
const metadataService = services?.get("metadata");
|
|
1939
|
+
if (metadataService && typeof metadataService.get === "function") {
|
|
1940
|
+
let fromService = await metadataService.get(request.type, request.name);
|
|
1941
|
+
if (fromService === void 0 || fromService === null) {
|
|
1942
|
+
const alt = PLURAL_TO_SINGULAR2[request.type] ?? SINGULAR_TO_PLURAL2[request.type];
|
|
1943
|
+
if (alt) fromService = await metadataService.get(alt, request.name);
|
|
1944
|
+
}
|
|
1945
|
+
if (fromService !== void 0 && fromService !== null) code = fromService;
|
|
1946
|
+
}
|
|
1947
|
+
} catch {
|
|
1948
|
+
}
|
|
1949
|
+
if (code === null) {
|
|
1950
|
+
let regItem = this.engine.registry.getItem(request.type, request.name);
|
|
1951
|
+
if (regItem === void 0) {
|
|
1952
|
+
const alt = PLURAL_TO_SINGULAR2[request.type] ?? SINGULAR_TO_PLURAL2[request.type];
|
|
1953
|
+
if (alt) regItem = this.engine.registry.getItem(alt, request.name);
|
|
1954
|
+
}
|
|
1955
|
+
if (regItem !== void 0) code = regItem;
|
|
1956
|
+
}
|
|
1957
|
+
let overlay = null;
|
|
1958
|
+
let overlayScope = null;
|
|
1959
|
+
try {
|
|
1960
|
+
const findOverlay = async (oid) => {
|
|
1961
|
+
const where = {
|
|
1962
|
+
type: request.type,
|
|
1963
|
+
name: request.name,
|
|
1964
|
+
state: "active",
|
|
1965
|
+
organization_id: oid
|
|
1966
|
+
};
|
|
1967
|
+
let rec = await this.engine.findOne("sys_metadata", { where });
|
|
1968
|
+
if (!rec) {
|
|
1969
|
+
const alt = PLURAL_TO_SINGULAR2[request.type] ?? SINGULAR_TO_PLURAL2[request.type];
|
|
1970
|
+
if (alt) {
|
|
1971
|
+
rec = await this.engine.findOne("sys_metadata", {
|
|
1972
|
+
where: { ...where, type: alt }
|
|
1973
|
+
});
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
return rec;
|
|
1977
|
+
};
|
|
1978
|
+
if (orgId) {
|
|
1979
|
+
const rec = await findOverlay(orgId);
|
|
1980
|
+
if (rec) {
|
|
1981
|
+
overlay = typeof rec.metadata === "string" ? JSON.parse(rec.metadata) : rec.metadata;
|
|
1982
|
+
overlayScope = "org";
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
if (overlay === null) {
|
|
1986
|
+
const rec = await findOverlay(null);
|
|
1987
|
+
if (rec) {
|
|
1988
|
+
overlay = typeof rec.metadata === "string" ? JSON.parse(rec.metadata) : rec.metadata;
|
|
1989
|
+
overlayScope = "env";
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
} catch {
|
|
1993
|
+
}
|
|
1994
|
+
const effective = overlay ?? code;
|
|
1995
|
+
return {
|
|
1996
|
+
type: request.type,
|
|
1997
|
+
name: request.name,
|
|
1998
|
+
code,
|
|
1999
|
+
overlay,
|
|
2000
|
+
overlayScope,
|
|
2001
|
+
effective
|
|
2002
|
+
};
|
|
2003
|
+
}
|
|
1558
2004
|
async getUiView(request) {
|
|
1559
2005
|
const schema = this.engine.registry.getObject(request.object);
|
|
1560
2006
|
if (!schema) throw new Error(`Object ${request.object} not found`);
|
|
@@ -2426,10 +2872,33 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
|
|
|
2426
2872
|
...request.options
|
|
2427
2873
|
});
|
|
2428
2874
|
}
|
|
2875
|
+
static envWritableTypes() {
|
|
2876
|
+
if (this._envWritableTypes !== null) return this._envWritableTypes;
|
|
2877
|
+
const raw = typeof process !== "undefined" && process?.env?.OBJECTSTACK_METADATA_WRITABLE || "";
|
|
2878
|
+
const set = /* @__PURE__ */ new Set();
|
|
2879
|
+
for (const tok of raw.split(",")) {
|
|
2880
|
+
const t = tok.trim();
|
|
2881
|
+
if (!t) continue;
|
|
2882
|
+
const singular = PLURAL_TO_SINGULAR2[t] ?? t;
|
|
2883
|
+
set.add(singular);
|
|
2884
|
+
const plural = SINGULAR_TO_PLURAL2[singular];
|
|
2885
|
+
if (plural) set.add(plural);
|
|
2886
|
+
}
|
|
2887
|
+
this._envWritableTypes = set;
|
|
2888
|
+
return set;
|
|
2889
|
+
}
|
|
2890
|
+
/** Test hook — clear the memoised env-writable cache. */
|
|
2891
|
+
static resetEnvWritableCache() {
|
|
2892
|
+
this._envWritableTypes = null;
|
|
2893
|
+
}
|
|
2429
2894
|
/** Normalize plural→singular before consulting the allow-list. */
|
|
2430
2895
|
static isOverlayAllowed(type) {
|
|
2431
|
-
const singular =
|
|
2432
|
-
|
|
2896
|
+
const singular = PLURAL_TO_SINGULAR2[type] ?? type;
|
|
2897
|
+
if (this.OVERLAY_ALLOWED_TYPES.has(singular) || this.OVERLAY_ALLOWED_TYPES.has(type)) {
|
|
2898
|
+
return true;
|
|
2899
|
+
}
|
|
2900
|
+
const env = this.envWritableTypes();
|
|
2901
|
+
return env.has(singular) || env.has(type);
|
|
2433
2902
|
}
|
|
2434
2903
|
/**
|
|
2435
2904
|
* Mirror an object-type overlay write into the in-memory engine
|
|
@@ -2459,12 +2928,38 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
|
|
|
2459
2928
|
if (this.environmentId !== void 0 && !_ObjectStackProtocolImplementation.isOverlayAllowed(request.type)) {
|
|
2460
2929
|
const allowed = Array.from(_ObjectStackProtocolImplementation.OVERLAY_ALLOWED_TYPES).join(", ");
|
|
2461
2930
|
const err = new Error(
|
|
2462
|
-
`[not_overridable] Metadata type '${request.type}' has not opted into per-org overlay writes. Set allowOrgOverride: true on its DEFAULT_METADATA_TYPE_REGISTRY entry to enable. Currently allowed: ${allowed}. See docs/adr/0005-metadata-customization-overlay.md.`
|
|
2931
|
+
`[not_overridable] Metadata type '${request.type}' has not opted into per-org overlay writes. Set allowOrgOverride: true on its DEFAULT_METADATA_TYPE_REGISTRY entry to enable, or set the OBJECTSTACK_METADATA_WRITABLE env var (comma-separated singular type names) to opt in at runtime. Currently allowed: ${allowed}. See docs/adr/0005-metadata-customization-overlay.md.`
|
|
2463
2932
|
);
|
|
2464
2933
|
err.code = "not_overridable";
|
|
2465
2934
|
err.status = 403;
|
|
2466
2935
|
throw err;
|
|
2467
2936
|
}
|
|
2937
|
+
const singularType = PLURAL_TO_SINGULAR2[request.type] ?? request.type;
|
|
2938
|
+
if (!request.force && (singularType === "object" || singularType === "field")) {
|
|
2939
|
+
try {
|
|
2940
|
+
const existing = await this.getMetaItem({
|
|
2941
|
+
type: request.type,
|
|
2942
|
+
name: request.name,
|
|
2943
|
+
...request.organizationId ? { organizationId: request.organizationId } : {}
|
|
2944
|
+
});
|
|
2945
|
+
const prev = existing?.item;
|
|
2946
|
+
if (prev) {
|
|
2947
|
+
const issues = detectDestructiveObjectChanges(prev, request.item);
|
|
2948
|
+
if (issues.length > 0) {
|
|
2949
|
+
const summary = issues.slice(0, 3).map((i) => i.message).join("; ");
|
|
2950
|
+
const err = new Error(
|
|
2951
|
+
`[destructive_change] ${request.type}/${request.name} would drop or transform existing data: ${summary}` + (issues.length > 3 ? ` (+${issues.length - 3} more)` : "") + ` \u2014 re-submit with ?force=true to proceed.`
|
|
2952
|
+
);
|
|
2953
|
+
err.code = "destructive_change";
|
|
2954
|
+
err.status = 409;
|
|
2955
|
+
err.issues = issues;
|
|
2956
|
+
throw err;
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2959
|
+
} catch (err) {
|
|
2960
|
+
if (err?.code === "destructive_change") throw err;
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2468
2963
|
{
|
|
2469
2964
|
const schema = resolveOverlaySchema(request.type, request.item);
|
|
2470
2965
|
if (schema) {
|
|
@@ -2487,7 +2982,7 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
|
|
|
2487
2982
|
}
|
|
2488
2983
|
}
|
|
2489
2984
|
await this.ensureOverlayIndex();
|
|
2490
|
-
const singularTypeForRepo =
|
|
2985
|
+
const singularTypeForRepo = PLURAL_TO_SINGULAR2[request.type] ?? request.type;
|
|
2491
2986
|
if (_ObjectStackProtocolImplementation.isOverlayAllowed(singularTypeForRepo)) {
|
|
2492
2987
|
const orgId = request.organizationId ?? null;
|
|
2493
2988
|
const repo = this.getOverlayRepo(orgId);
|
|
@@ -2598,7 +3093,7 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
|
|
|
2598
3093
|
* "no history" uniformly.
|
|
2599
3094
|
*/
|
|
2600
3095
|
async historyMetaItem(request) {
|
|
2601
|
-
const singularType =
|
|
3096
|
+
const singularType = PLURAL_TO_SINGULAR2[request.type] ?? request.type;
|
|
2602
3097
|
if (!_ObjectStackProtocolImplementation.isOverlayAllowed(singularType)) {
|
|
2603
3098
|
return { events: [] };
|
|
2604
3099
|
}
|
|
@@ -2631,7 +3126,7 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
|
|
|
2631
3126
|
err.status = 403;
|
|
2632
3127
|
throw err;
|
|
2633
3128
|
}
|
|
2634
|
-
const singularTypeForRepo =
|
|
3129
|
+
const singularTypeForRepo = PLURAL_TO_SINGULAR2[request.type] ?? request.type;
|
|
2635
3130
|
const useRepoPath = _ObjectStackProtocolImplementation.isOverlayAllowed(singularTypeForRepo);
|
|
2636
3131
|
if (useRepoPath) {
|
|
2637
3132
|
const orgId = request.organizationId ?? null;
|
|
@@ -2751,7 +3246,7 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
|
|
|
2751
3246
|
for (const record of records) {
|
|
2752
3247
|
try {
|
|
2753
3248
|
const data = typeof record.metadata === "string" ? JSON.parse(record.metadata) : record.metadata;
|
|
2754
|
-
const normalizedType =
|
|
3249
|
+
const normalizedType = PLURAL_TO_SINGULAR2[record.type] ?? record.type;
|
|
2755
3250
|
if (normalizedType === "object") {
|
|
2756
3251
|
this.engine.registry.registerObject(data, record.packageId || "sys_metadata");
|
|
2757
3252
|
} else {
|
|
@@ -2771,6 +3266,68 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
|
|
|
2771
3266
|
return { loaded, errors };
|
|
2772
3267
|
}
|
|
2773
3268
|
// ==========================================
|
|
3269
|
+
// Metadata References (Phase 3a-references)
|
|
3270
|
+
// ==========================================
|
|
3271
|
+
/**
|
|
3272
|
+
* Scan all loaded metadata for references pointing at the given
|
|
3273
|
+
* `{type, name}` target. Returns one row per referring artifact with
|
|
3274
|
+
* the path that produced the hit, so the admin UI can render an
|
|
3275
|
+
* "Used by" panel before destructive actions (rename / delete /
|
|
3276
|
+
* type-narrowing).
|
|
3277
|
+
*
|
|
3278
|
+
* Coverage is driven by the hand-curated {@link REFERENCE_PATHS}
|
|
3279
|
+
* registry. Types not present in the registry simply return no hits
|
|
3280
|
+
* — the engine never throws.
|
|
3281
|
+
*/
|
|
3282
|
+
async findReferencesToMeta(request) {
|
|
3283
|
+
const singularTarget = PLURAL_TO_SINGULAR2[request.type] ?? request.type;
|
|
3284
|
+
const targetName = request.name;
|
|
3285
|
+
const matchers = REFERENCE_PATHS[singularTarget];
|
|
3286
|
+
if (!matchers || matchers.length === 0) {
|
|
3287
|
+
return { references: [] };
|
|
3288
|
+
}
|
|
3289
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3290
|
+
const out = [];
|
|
3291
|
+
await Promise.all(
|
|
3292
|
+
matchers.map(async (matcher) => {
|
|
3293
|
+
let items = [];
|
|
3294
|
+
try {
|
|
3295
|
+
const result = await this.getMetaItems({
|
|
3296
|
+
type: matcher.fromType,
|
|
3297
|
+
...request.organizationId ? { organizationId: request.organizationId } : {}
|
|
3298
|
+
});
|
|
3299
|
+
items = result?.items ?? [];
|
|
3300
|
+
} catch {
|
|
3301
|
+
return;
|
|
3302
|
+
}
|
|
3303
|
+
for (const raw of items) {
|
|
3304
|
+
if (!raw || typeof raw !== "object") continue;
|
|
3305
|
+
const sourceName = raw.name;
|
|
3306
|
+
if (!sourceName) continue;
|
|
3307
|
+
const isSelfReference = matcher.fromType === singularTarget && sourceName === targetName;
|
|
3308
|
+
for (const path of matcher.paths) {
|
|
3309
|
+
const values = extractPathValues(raw, path);
|
|
3310
|
+
if (!values.includes(targetName)) continue;
|
|
3311
|
+
if (isSelfReference && !path.includes("[]") && !path.includes("{}")) continue;
|
|
3312
|
+
const key = `${matcher.fromType}|${sourceName}|${path}`;
|
|
3313
|
+
if (seen.has(key)) continue;
|
|
3314
|
+
seen.add(key);
|
|
3315
|
+
const label = raw.label;
|
|
3316
|
+
out.push({
|
|
3317
|
+
type: matcher.fromType,
|
|
3318
|
+
name: sourceName,
|
|
3319
|
+
...label ? { label } : {},
|
|
3320
|
+
path,
|
|
3321
|
+
kind: matcher.kind
|
|
3322
|
+
});
|
|
3323
|
+
}
|
|
3324
|
+
}
|
|
3325
|
+
})
|
|
3326
|
+
);
|
|
3327
|
+
out.sort((a, b) => a.type.localeCompare(b.type) || a.name.localeCompare(b.name));
|
|
3328
|
+
return { references: out };
|
|
3329
|
+
}
|
|
3330
|
+
// ==========================================
|
|
2774
3331
|
// Feed Operations
|
|
2775
3332
|
// ==========================================
|
|
2776
3333
|
async listFeed(request) {
|
|
@@ -2917,11 +3474,23 @@ _ObjectStackProtocolImplementation.OVERLAY_ALLOWED_TYPES = (() => {
|
|
|
2917
3474
|
for (const entry of DEFAULT_METADATA_TYPE_REGISTRY2) {
|
|
2918
3475
|
if (!entry.allowOrgOverride) continue;
|
|
2919
3476
|
out.add(entry.type);
|
|
2920
|
-
const plural =
|
|
3477
|
+
const plural = SINGULAR_TO_PLURAL2[entry.type];
|
|
2921
3478
|
if (plural) out.add(plural);
|
|
2922
3479
|
}
|
|
2923
3480
|
return out;
|
|
2924
3481
|
})();
|
|
3482
|
+
/**
|
|
3483
|
+
* Phase 3a-env-writable: parse `OBJECTSTACK_METADATA_WRITABLE` once.
|
|
3484
|
+
* Comma-separated singular type names. When the env var is set, the
|
|
3485
|
+
* listed types get treated as `allowOrgOverride: true` regardless of
|
|
3486
|
+
* their static registry entry. This is the runtime escape hatch admins
|
|
3487
|
+
* use to enable Studio-side editing of types whose protocol-level flag
|
|
3488
|
+
* is still false (object, field, permission, …).
|
|
3489
|
+
*
|
|
3490
|
+
* Memoised at first call. Tests can override by clearing the cache via
|
|
3491
|
+
* {@link ObjectStackProtocolImplementation.resetEnvWritableCache}.
|
|
3492
|
+
*/
|
|
3493
|
+
_ObjectStackProtocolImplementation._envWritableTypes = null;
|
|
2925
3494
|
var ObjectStackProtocolImplementation = _ObjectStackProtocolImplementation;
|
|
2926
3495
|
|
|
2927
3496
|
// src/engine.ts
|