@objectstack/core 17.0.0-rc.6 → 17.0.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/CHANGELOG.md +2823 -0
- package/dist/index.cjs +153 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +184 -4
- package/dist/index.d.ts +184 -4
- package/dist/index.js +135 -11
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1385,6 +1385,7 @@ function createMemoryJob() {
|
|
|
1385
1385
|
}
|
|
1386
1386
|
|
|
1387
1387
|
// src/fallbacks/memory-i18n.ts
|
|
1388
|
+
import { normalizeSupportedLocales } from "@objectstack/spec/system";
|
|
1388
1389
|
function deepMerge(target, source) {
|
|
1389
1390
|
const result = { ...target };
|
|
1390
1391
|
for (const key of Object.keys(source)) {
|
|
@@ -1418,6 +1419,7 @@ function createMemoryI18n() {
|
|
|
1418
1419
|
const translations = /* @__PURE__ */ new Map();
|
|
1419
1420
|
const authored = /* @__PURE__ */ new Map();
|
|
1420
1421
|
let defaultLocale = "en";
|
|
1422
|
+
let supportedLocales;
|
|
1421
1423
|
function resolveKey(data, key) {
|
|
1422
1424
|
const parts = key.split(".");
|
|
1423
1425
|
let current = data;
|
|
@@ -1483,9 +1485,29 @@ function createMemoryI18n() {
|
|
|
1483
1485
|
authored.set(locale, { ...data });
|
|
1484
1486
|
}
|
|
1485
1487
|
},
|
|
1488
|
+
/**
|
|
1489
|
+
* Report the locales this stack offers.
|
|
1490
|
+
*
|
|
1491
|
+
* [#7679] When the app declared `i18n.supportedLocales`, that declaration
|
|
1492
|
+
* IS the answer — in declared order, and including a declared locale no
|
|
1493
|
+
* bundle was ever loaded for (declared-but-unserved). Reporting the
|
|
1494
|
+
* declaration rather than an intersection is what gives a client the
|
|
1495
|
+
* signal that the locale it is being offered has nothing behind it yet;
|
|
1496
|
+
* quietly dropping it would leave the gap invisible on both sides. It is
|
|
1497
|
+
* also the only answer that does not depend on how much had loaded by the
|
|
1498
|
+
* time this was called.
|
|
1499
|
+
*
|
|
1500
|
+
* With nothing declared, the loaded set — the behaviour every app that
|
|
1501
|
+
* never opted in already has.
|
|
1502
|
+
*/
|
|
1486
1503
|
getLocales() {
|
|
1504
|
+
if (supportedLocales) return [...supportedLocales];
|
|
1487
1505
|
return [.../* @__PURE__ */ new Set([...translations.keys(), ...authored.keys()])];
|
|
1488
1506
|
},
|
|
1507
|
+
/** @see II18nService.setSupportedLocales — [#7679] */
|
|
1508
|
+
setSupportedLocales(locales) {
|
|
1509
|
+
supportedLocales = normalizeSupportedLocales(locales);
|
|
1510
|
+
},
|
|
1489
1511
|
getDefaultLocale() {
|
|
1490
1512
|
return defaultLocale;
|
|
1491
1513
|
},
|
|
@@ -1495,14 +1517,42 @@ function createMemoryI18n() {
|
|
|
1495
1517
|
};
|
|
1496
1518
|
}
|
|
1497
1519
|
|
|
1520
|
+
// src/metadata-service-contract.ts
|
|
1521
|
+
import { pluralToSingular } from "@objectstack/spec/shared";
|
|
1522
|
+
var REGISTER_REFUSAL_CODE = "VALIDATION_ERROR";
|
|
1523
|
+
function canonicalMetadataServiceType(type) {
|
|
1524
|
+
return pluralToSingular(type);
|
|
1525
|
+
}
|
|
1526
|
+
function registerRefusal(message) {
|
|
1527
|
+
const err = new Error(message);
|
|
1528
|
+
err.code = REGISTER_REFUSAL_CODE;
|
|
1529
|
+
err.status = 400;
|
|
1530
|
+
return err;
|
|
1531
|
+
}
|
|
1532
|
+
function assertMetadataRegisterContract(type, name, data) {
|
|
1533
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
1534
|
+
const shape = data === null ? "null" : Array.isArray(data) ? "an array" : `a ${typeof data}`;
|
|
1535
|
+
throw registerRefusal(
|
|
1536
|
+
`IMetadataService.register('${type}', '${name}'): data is ${shape}, not a metadata document. register() stores plain-object documents only \u2014 accepting a value the service cannot key was measured as accept-then-drop on document-keyed stores (#7378 row 3: refuse loudly, never coerce into storability). Wrap the value in a document object whose shape the '${type}' type's schema accepts, or store it under a type that declares one.`
|
|
1537
|
+
);
|
|
1538
|
+
}
|
|
1539
|
+
const documentName = data.name;
|
|
1540
|
+
if (documentName !== void 0 && documentName !== name) {
|
|
1541
|
+
throw registerRefusal(
|
|
1542
|
+
`IMetadataService.register('${type}', '${name}'): data.name is '${String(documentName)}', which disagrees with the name argument '${name}'. A disagreement is almost always an authoring bug, and resolving it silently in either direction can file the item under a key the caller never wrote (#7378 row 1: refuse loudly, locate the mismatch). Register under one name: pass the intended key as the argument and make data.name match it, or omit data.name.`
|
|
1543
|
+
);
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1498
1547
|
// src/fallbacks/memory-metadata.ts
|
|
1499
1548
|
function createMemoryMetadata() {
|
|
1500
1549
|
const store = /* @__PURE__ */ new Map();
|
|
1501
1550
|
function getTypeMap(type) {
|
|
1502
|
-
|
|
1551
|
+
const canonical = canonicalMetadataServiceType(type);
|
|
1552
|
+
let map = store.get(canonical);
|
|
1503
1553
|
if (!map) {
|
|
1504
1554
|
map = /* @__PURE__ */ new Map();
|
|
1505
|
-
store.set(
|
|
1555
|
+
store.set(canonical, map);
|
|
1506
1556
|
}
|
|
1507
1557
|
return map;
|
|
1508
1558
|
}
|
|
@@ -1517,6 +1567,7 @@ function createMemoryMetadata() {
|
|
|
1517
1567
|
},
|
|
1518
1568
|
_serviceName: "metadata",
|
|
1519
1569
|
async register(type, name, data) {
|
|
1570
|
+
assertMetadataRegisterContract(type, name, data);
|
|
1520
1571
|
getTypeMap(type).set(name, data);
|
|
1521
1572
|
},
|
|
1522
1573
|
// Mirror MetadataManager.registerInMemory (synchronous, no persistence).
|
|
@@ -1528,7 +1579,11 @@ function createMemoryMetadata() {
|
|
|
1528
1579
|
// so `defineStack({ datasources })` entries silently never reached the
|
|
1529
1580
|
// registry and were absent from GET /api/v1/datasources and
|
|
1530
1581
|
// GET /api/v1/meta/datasource (ADR-0015 §18). This store is already
|
|
1531
|
-
// in-memory only, so registerInMemory and register share
|
|
1582
|
+
// in-memory only, so registerInMemory and register share a store — but
|
|
1583
|
+
// NOT the [#7378] refusals: the ruling names `register`, and this member
|
|
1584
|
+
// is a boot-time seeding primitive for source-control-owned artefacts
|
|
1585
|
+
// (see assertMetadataRegisterContract's header for the boundary). It does
|
|
1586
|
+
// share the row-2 canonical type fold, via getTypeMap.
|
|
1532
1587
|
registerInMemory(type, name, data) {
|
|
1533
1588
|
getTypeMap(type).set(name, data);
|
|
1534
1589
|
},
|
|
@@ -2524,11 +2579,29 @@ var TestRunner = class {
|
|
|
2524
2579
|
};
|
|
2525
2580
|
|
|
2526
2581
|
// src/qa/http-adapter.ts
|
|
2582
|
+
import { RestApiConfigSchema, CrudEndpointsConfigSchema } from "@objectstack/spec/api";
|
|
2583
|
+
var dataPathCache;
|
|
2584
|
+
function defaultDataPath() {
|
|
2585
|
+
if (dataPathCache === void 0) {
|
|
2586
|
+
const api = RestApiConfigSchema.parse({});
|
|
2587
|
+
const crud = CrudEndpointsConfigSchema.parse({});
|
|
2588
|
+
dataPathCache = `${api.apiPath ?? `${api.basePath}/${api.version}`}${crud.dataPrefix}`;
|
|
2589
|
+
}
|
|
2590
|
+
return dataPathCache;
|
|
2591
|
+
}
|
|
2527
2592
|
var HttpTestAdapter = class {
|
|
2528
2593
|
constructor(baseUrl, authToken) {
|
|
2529
2594
|
this.baseUrl = baseUrl;
|
|
2530
2595
|
this.authToken = authToken;
|
|
2531
2596
|
}
|
|
2597
|
+
/** `{baseUrl}{apiBasePath}{dataPrefix}/{object}` — the collection URL. */
|
|
2598
|
+
collectionUrl(objectName) {
|
|
2599
|
+
return `${this.baseUrl}${defaultDataPath()}/${encodeURIComponent(objectName)}`;
|
|
2600
|
+
}
|
|
2601
|
+
/** `{collection}/{id}` — the single-record URL. */
|
|
2602
|
+
recordUrl(objectName, id) {
|
|
2603
|
+
return `${this.collectionUrl(objectName)}/${encodeURIComponent(String(id))}`;
|
|
2604
|
+
}
|
|
2532
2605
|
async execute(action, _context) {
|
|
2533
2606
|
const headers = {
|
|
2534
2607
|
"Content-Type": "application/json"
|
|
@@ -2560,7 +2633,7 @@ var HttpTestAdapter = class {
|
|
|
2560
2633
|
}
|
|
2561
2634
|
}
|
|
2562
2635
|
async createRecord(objectName, data, headers) {
|
|
2563
|
-
const response = await fetch(
|
|
2636
|
+
const response = await fetch(this.collectionUrl(objectName), {
|
|
2564
2637
|
method: "POST",
|
|
2565
2638
|
headers,
|
|
2566
2639
|
body: JSON.stringify(data)
|
|
@@ -2568,19 +2641,19 @@ var HttpTestAdapter = class {
|
|
|
2568
2641
|
return this.handleResponse(response);
|
|
2569
2642
|
}
|
|
2570
2643
|
async updateRecord(objectName, data, headers) {
|
|
2571
|
-
const id = data
|
|
2644
|
+
const { id, ...fields } = data;
|
|
2572
2645
|
if (!id) throw new Error("Update record requires id in payload");
|
|
2573
|
-
const response = await fetch(
|
|
2574
|
-
method: "
|
|
2646
|
+
const response = await fetch(this.recordUrl(objectName, id), {
|
|
2647
|
+
method: "PATCH",
|
|
2575
2648
|
headers,
|
|
2576
|
-
body: JSON.stringify(
|
|
2649
|
+
body: JSON.stringify(fields)
|
|
2577
2650
|
});
|
|
2578
2651
|
return this.handleResponse(response);
|
|
2579
2652
|
}
|
|
2580
2653
|
async deleteRecord(objectName, data, headers) {
|
|
2581
2654
|
const id = data.id;
|
|
2582
2655
|
if (!id) throw new Error("Delete record requires id in payload");
|
|
2583
|
-
const response = await fetch(
|
|
2656
|
+
const response = await fetch(this.recordUrl(objectName, id), {
|
|
2584
2657
|
method: "DELETE",
|
|
2585
2658
|
headers
|
|
2586
2659
|
});
|
|
@@ -2589,14 +2662,14 @@ var HttpTestAdapter = class {
|
|
|
2589
2662
|
async readRecord(objectName, data, headers) {
|
|
2590
2663
|
const id = data.id;
|
|
2591
2664
|
if (!id) throw new Error("Read record requires id in payload");
|
|
2592
|
-
const response = await fetch(
|
|
2665
|
+
const response = await fetch(this.recordUrl(objectName, id), {
|
|
2593
2666
|
method: "GET",
|
|
2594
2667
|
headers
|
|
2595
2668
|
});
|
|
2596
2669
|
return this.handleResponse(response);
|
|
2597
2670
|
}
|
|
2598
2671
|
async queryRecords(objectName, data, headers) {
|
|
2599
|
-
const response = await fetch(`${this.
|
|
2672
|
+
const response = await fetch(`${this.collectionUrl(objectName)}/query`, {
|
|
2600
2673
|
method: "POST",
|
|
2601
2674
|
headers,
|
|
2602
2675
|
body: JSON.stringify(data)
|
|
@@ -4546,6 +4619,18 @@ function shouldDenyAnonymous(input) {
|
|
|
4546
4619
|
return true;
|
|
4547
4620
|
}
|
|
4548
4621
|
|
|
4622
|
+
// src/security/audience-binding-suggestion-status.ts
|
|
4623
|
+
var AUDIENCE_BINDING_SUGGESTION_STATUSES = {
|
|
4624
|
+
pending: true,
|
|
4625
|
+
confirmed: true,
|
|
4626
|
+
dismissed: true
|
|
4627
|
+
};
|
|
4628
|
+
var AUDIENCE_BINDING_SUGGESTION_STATUS_VALUES = Object.keys(
|
|
4629
|
+
AUDIENCE_BINDING_SUGGESTION_STATUSES
|
|
4630
|
+
);
|
|
4631
|
+
var isAudienceBindingSuggestionStatus = (value) => Object.prototype.hasOwnProperty.call(AUDIENCE_BINDING_SUGGESTION_STATUSES, value);
|
|
4632
|
+
var unknownAudienceBindingSuggestionStatusMessage = (value) => `Unknown status filter '${value}' \u2014 expected one of: ${AUDIENCE_BINDING_SUGGESTION_STATUS_VALUES.join(", ")}`;
|
|
4633
|
+
|
|
4549
4634
|
// src/security/operation-private-keys.ts
|
|
4550
4635
|
var OPERATION_PRIVATE_KEY_PREFIX = "__";
|
|
4551
4636
|
function withoutOperationPrivateKeys(exec) {
|
|
@@ -4799,6 +4884,27 @@ async function bulkWrite(rows, opts) {
|
|
|
4799
4884
|
return results;
|
|
4800
4885
|
}
|
|
4801
4886
|
|
|
4887
|
+
// src/utils/internal-write-response.ts
|
|
4888
|
+
function collectInternalWriteResponseFields(schema) {
|
|
4889
|
+
const fields = schema?.fields;
|
|
4890
|
+
if (!fields || typeof fields !== "object") return [];
|
|
4891
|
+
const out = [];
|
|
4892
|
+
for (const [name, def] of Object.entries(fields)) {
|
|
4893
|
+
if (def && def.internal === true) out.push(name);
|
|
4894
|
+
}
|
|
4895
|
+
return out;
|
|
4896
|
+
}
|
|
4897
|
+
function omitInternalFieldsFromWriteResponse(schema, records) {
|
|
4898
|
+
if (!records) return;
|
|
4899
|
+
const internalFields = collectInternalWriteResponseFields(schema);
|
|
4900
|
+
if (internalFields.length === 0) return;
|
|
4901
|
+
const list = Array.isArray(records) ? records : [records];
|
|
4902
|
+
for (const row of list) {
|
|
4903
|
+
if (!row || typeof row !== "object") continue;
|
|
4904
|
+
for (const field of internalFields) delete row[field];
|
|
4905
|
+
}
|
|
4906
|
+
}
|
|
4907
|
+
|
|
4802
4908
|
// src/utils/migration-journal.ts
|
|
4803
4909
|
import { createHash as createHash2, randomUUID } from "crypto";
|
|
4804
4910
|
import {
|
|
@@ -5394,6 +5500,15 @@ function filterTokenContextFrom(execCtx, now) {
|
|
|
5394
5500
|
};
|
|
5395
5501
|
}
|
|
5396
5502
|
|
|
5503
|
+
// src/utils/record-not-found.ts
|
|
5504
|
+
function recordNotFoundError(object, id) {
|
|
5505
|
+
const err = new Error(`Record ${id} not found in ${object}`);
|
|
5506
|
+
err.code = "RECORD_NOT_FOUND";
|
|
5507
|
+
err.status = 404;
|
|
5508
|
+
err.object = object;
|
|
5509
|
+
return err;
|
|
5510
|
+
}
|
|
5511
|
+
|
|
5397
5512
|
// src/health-monitor.ts
|
|
5398
5513
|
var PluginHealthMonitor = class {
|
|
5399
5514
|
constructor(logger) {
|
|
@@ -6376,6 +6491,8 @@ export {
|
|
|
6376
6491
|
ANONYMOUS_DENY_MESSAGE,
|
|
6377
6492
|
ANONYMOUS_DENY_STATUS,
|
|
6378
6493
|
API_KEY_PREFIX,
|
|
6494
|
+
AUDIENCE_BINDING_SUGGESTION_STATUSES,
|
|
6495
|
+
AUDIENCE_BINDING_SUGGESTION_STATUS_VALUES,
|
|
6379
6496
|
CORE_FALLBACK_FACTORIES,
|
|
6380
6497
|
DependencyResolver,
|
|
6381
6498
|
ENTRY_EXECUTION_CONTEXT_FIELDS,
|
|
@@ -6409,11 +6526,14 @@ export {
|
|
|
6409
6526
|
assembleExecutionContext,
|
|
6410
6527
|
assembleExecutionContextOrGuest,
|
|
6411
6528
|
assertInitServiceRequirements,
|
|
6529
|
+
assertMetadataRegisterContract,
|
|
6412
6530
|
bucketKeyToCalendarRange,
|
|
6413
6531
|
buildPermissionsFromGrants,
|
|
6414
6532
|
bulkWrite,
|
|
6415
6533
|
calendarPartsInTz,
|
|
6416
6534
|
calendarPartsInTzOrUtc,
|
|
6535
|
+
canonicalMetadataServiceType,
|
|
6536
|
+
collectInternalWriteResponseFields,
|
|
6417
6537
|
counterSignPayload,
|
|
6418
6538
|
createLogger,
|
|
6419
6539
|
createMemoryCache,
|
|
@@ -6438,6 +6558,7 @@ export {
|
|
|
6438
6558
|
getMemoryUsage,
|
|
6439
6559
|
hashApiKey,
|
|
6440
6560
|
hashMigrationPlan,
|
|
6561
|
+
isAudienceBindingSuggestionStatus,
|
|
6441
6562
|
isAuthGateAllowlisted,
|
|
6442
6563
|
isExpired,
|
|
6443
6564
|
isGrantActive,
|
|
@@ -6445,12 +6566,14 @@ export {
|
|
|
6445
6566
|
isNode,
|
|
6446
6567
|
nextUtcCalendarDay,
|
|
6447
6568
|
normalizeAuthGate,
|
|
6569
|
+
omitInternalFieldsFromWriteResponse,
|
|
6448
6570
|
parseScopes,
|
|
6449
6571
|
parseSignature,
|
|
6450
6572
|
planChunks,
|
|
6451
6573
|
postureVisibleRows,
|
|
6452
6574
|
readAuthoredTranslationLayer,
|
|
6453
6575
|
readRunJournal,
|
|
6576
|
+
recordNotFoundError,
|
|
6454
6577
|
resolveApiKeyPrincipal,
|
|
6455
6578
|
resolveAuthzContext,
|
|
6456
6579
|
resolveFilterToken,
|
|
@@ -6464,6 +6587,7 @@ export {
|
|
|
6464
6587
|
safeExit,
|
|
6465
6588
|
shouldDenyAnonymous,
|
|
6466
6589
|
signPayload,
|
|
6590
|
+
unknownAudienceBindingSuggestionStatusMessage,
|
|
6467
6591
|
utcInstantMs,
|
|
6468
6592
|
validateInitServiceContract,
|
|
6469
6593
|
verifyPayload,
|