@open-mercato/shared 0.6.7-develop.6860.1.f796b22e80 → 0.6.7
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/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +6 -57
- package/build.mjs +4 -2
- package/dist/lib/auth/server.js +2 -15
- package/dist/lib/auth/server.js.map +2 -2
- package/dist/lib/bootstrap/dynamicLoader.js +43 -117
- package/dist/lib/bootstrap/dynamicLoader.js.map +2 -2
- package/dist/lib/commands/command-bus.js +1 -5
- package/dist/lib/commands/command-bus.js.map +2 -2
- package/dist/lib/commands/command-interceptor-runner.js +2 -2
- package/dist/lib/commands/command-interceptor-runner.js.map +2 -2
- package/dist/lib/crud/enricher-runner.js +11 -2
- package/dist/lib/crud/enricher-runner.js.map +2 -2
- package/dist/lib/crud/factory.js +4 -63
- package/dist/lib/crud/factory.js.map +2 -2
- package/dist/lib/crud/interceptor-runner.js +2 -2
- package/dist/lib/crud/interceptor-runner.js.map +2 -2
- package/dist/lib/crud/mutation-guard-registry.js +2 -2
- package/dist/lib/crud/mutation-guard-registry.js.map +2 -2
- package/dist/lib/crud/types.js +0 -4
- package/dist/lib/crud/types.js.map +3 -3
- package/dist/lib/data/engine.js +11 -37
- package/dist/lib/data/engine.js.map +2 -2
- package/dist/lib/db/mikro.js +12 -3
- package/dist/lib/db/mikro.js.map +2 -2
- package/dist/lib/db/pg-errors.js +0 -42
- package/dist/lib/db/pg-errors.js.map +2 -2
- package/dist/lib/di/container.js +6 -48
- package/dist/lib/di/container.js.map +2 -2
- package/dist/lib/email/send.js +0 -56
- package/dist/lib/email/send.js.map +2 -2
- package/dist/lib/encryption/subscriber.js +10 -2
- package/dist/lib/encryption/subscriber.js.map +2 -2
- package/dist/lib/encryption/tenantDataEncryptionService.js +17 -52
- package/dist/lib/encryption/tenantDataEncryptionService.js.map +2 -2
- package/dist/lib/i18n/config.js +1 -1
- package/dist/lib/i18n/config.js.map +2 -2
- package/dist/lib/i18n/server.js +4 -4
- package/dist/lib/i18n/server.js.map +3 -3
- package/dist/lib/logger/index.js +3 -47
- package/dist/lib/logger/index.js.map +3 -3
- package/dist/lib/modules/registry.js +1 -5
- package/dist/lib/modules/registry.js.map +2 -2
- package/dist/lib/query/engine.js +40 -54
- package/dist/lib/query/engine.js.map +2 -2
- package/dist/lib/query/types.js.map +1 -1
- package/dist/lib/redis/connection.js +2 -7
- package/dist/lib/redis/connection.js.map +2 -2
- package/dist/lib/search/config.js +2 -61
- package/dist/lib/search/config.js.map +2 -2
- package/dist/lib/search/tokenize.js +11 -23
- package/dist/lib/search/tokenize.js.map +2 -2
- package/dist/lib/version.js +1 -1
- package/dist/lib/version.js.map +1 -1
- package/dist/lib/webhooks/index.js +1 -13
- package/dist/lib/webhooks/index.js.map +2 -2
- package/dist/lib/webhooks/verify.js +7 -12
- package/dist/lib/webhooks/verify.js.map +2 -2
- package/dist/modules/analytics.js.map +2 -2
- package/dist/modules/overrides.js +1 -50
- package/dist/modules/overrides.js.map +2 -2
- package/dist/modules/payment_gateways/types.js +1 -8
- package/dist/modules/payment_gateways/types.js.map +2 -2
- package/dist/modules/registry.js +0 -1
- package/dist/modules/registry.js.map +2 -2
- package/dist/security/enabledModulesRegistry.js +11 -56
- package/dist/security/enabledModulesRegistry.js.map +2 -2
- package/jest.config.cjs +1 -2
- package/package.json +9 -19
- package/src/lib/ai/llm-provider.ts +0 -33
- package/src/lib/auth/__tests__/server.test.ts +0 -23
- package/src/lib/auth/server.ts +2 -23
- package/src/lib/bootstrap/__tests__/dynamicLoader.commandInterceptors.test.ts +8 -63
- package/src/lib/bootstrap/__tests__/dynamicLoader.tsconfig.test.ts +1 -21
- package/src/lib/bootstrap/dynamicLoader.ts +65 -213
- package/src/lib/commands/__tests__/command-interceptor-runner.test.ts +0 -28
- package/src/lib/commands/command-bus.ts +1 -5
- package/src/lib/commands/command-interceptor-runner.ts +2 -2
- package/src/lib/crud/__tests__/crud-factory.test.ts +0 -298
- package/src/lib/crud/__tests__/mutation-guard-registry.test.ts +0 -26
- package/src/lib/crud/enricher-runner.ts +11 -2
- package/src/lib/crud/factory.ts +1 -69
- package/src/lib/crud/interceptor-runner.ts +2 -2
- package/src/lib/crud/mutation-guard-registry.ts +2 -2
- package/src/lib/crud/types.ts +0 -3
- package/src/lib/data/__tests__/engine.bulk-suppress.test.ts +3 -18
- package/src/lib/data/engine.ts +16 -50
- package/src/lib/db/__tests__/mikro.test.ts +25 -0
- package/src/lib/db/mikro.ts +38 -13
- package/src/lib/db/pg-errors.ts +0 -58
- package/src/lib/di/container.ts +8 -72
- package/src/lib/email/__tests__/send.test.ts +1 -37
- package/src/lib/email/send.ts +0 -79
- package/src/lib/encryption/__tests__/subscriber.test.ts +2 -54
- package/src/lib/encryption/__tests__/tenantDataEncryptionService.test.ts +0 -58
- package/src/lib/encryption/subscriber.ts +14 -2
- package/src/lib/encryption/tenantDataEncryptionService.ts +18 -64
- package/src/lib/i18n/config.ts +2 -2
- package/src/lib/i18n/server.ts +2 -6
- package/src/lib/logger/__tests__/logger.test.ts +0 -77
- package/src/lib/logger/index.ts +1 -59
- package/src/lib/modules/registry.ts +0 -10
- package/src/lib/query/__tests__/engine.test.ts +13 -65
- package/src/lib/query/engine.ts +58 -75
- package/src/lib/query/types.ts +5 -4
- package/src/lib/redis/__tests__/connection.test.ts +0 -16
- package/src/lib/redis/connection.ts +7 -10
- package/src/lib/search/__tests__/config.test.ts +0 -177
- package/src/lib/search/config.ts +8 -106
- package/src/lib/search/tokenize.ts +11 -33
- package/src/lib/webhooks/__tests__/verify.test.ts +1 -20
- package/src/lib/webhooks/index.ts +1 -19
- package/src/lib/webhooks/verify.ts +9 -17
- package/src/modules/__tests__/registry.test.ts +2 -10
- package/src/modules/analytics.ts +0 -7
- package/src/modules/customer-auth.ts +0 -1
- package/src/modules/encryption.ts +0 -3
- package/src/modules/navigation/backendChrome.ts +0 -15
- package/src/modules/overrides.ts +0 -103
- package/src/modules/payment_gateways/types.ts +0 -41
- package/src/modules/registry.ts +0 -3
- package/src/security/enabledModulesRegistry.ts +12 -64
- package/dist/lib/ai/safety-identifier.js +0 -32
- package/dist/lib/ai/safety-identifier.js.map +0 -7
- package/dist/lib/auth/organizationScope.js +0 -34
- package/dist/lib/auth/organizationScope.js.map +0 -7
- package/dist/lib/bootstrap/clientOnlyModules.js +0 -55
- package/dist/lib/bootstrap/clientOnlyModules.js.map +0 -7
- package/dist/lib/data/consistency.js +0 -19
- package/dist/lib/data/consistency.js.map +0 -7
- package/dist/lib/db/entityMetadata.js +0 -26
- package/dist/lib/db/entityMetadata.js.map +0 -7
- package/dist/lib/logger/extension.js +0 -29
- package/dist/lib/logger/extension.js.map +0 -7
- package/dist/lib/modules/surfaceFingerprint.js +0 -47
- package/dist/lib/modules/surfaceFingerprint.js.map +0 -7
- package/dist/lib/query/ciphertext-search-warning.js +0 -45
- package/dist/lib/query/ciphertext-search-warning.js.map +0 -7
- package/dist/lib/search/auto-indexing.js +0 -14
- package/dist/lib/search/auto-indexing.js.map +0 -7
- package/dist/lib/search/availability.js +0 -107
- package/dist/lib/search/availability.js.map +0 -7
- package/dist/lib/search/tokenLookup.js +0 -46
- package/dist/lib/search/tokenLookup.js.map +0 -7
- package/dist/lib/telemetry/runtime.js +0 -35
- package/dist/lib/telemetry/runtime.js.map +0 -7
- package/dist/lib/webhooks/body.js +0 -57
- package/dist/lib/webhooks/body.js.map +0 -7
- package/dist/lib/webhooks/inbound-types.js +0 -1
- package/dist/lib/webhooks/inbound-types.js.map +0 -7
- package/dist/modules/widgets/extension-points.js +0 -96
- package/dist/modules/widgets/extension-points.js.map +0 -7
- package/dist/security/featurePolicy.js +0 -62
- package/dist/security/featurePolicy.js.map +0 -7
- package/scripts/versionSource.cjs +0 -65
- package/src/lib/__tests__/versionSource.test.ts +0 -46
- package/src/lib/ai/__tests__/llm-provider-contract.test.ts +0 -59
- package/src/lib/ai/__tests__/safety-identifier.test.ts +0 -71
- package/src/lib/ai/safety-identifier.ts +0 -73
- package/src/lib/auth/__tests__/organizationScope.test.ts +0 -107
- package/src/lib/auth/organizationScope.ts +0 -65
- package/src/lib/bootstrap/__tests__/clientOnlyModules.test.ts +0 -189
- package/src/lib/bootstrap/__tests__/dynamicLoader.aliasResolver.test.ts +0 -98
- package/src/lib/bootstrap/__tests__/dynamicLoader.appDi.test.ts +0 -211
- package/src/lib/bootstrap/__tests__/dynamicLoader.generatedCacheRecovery.test.ts +0 -192
- package/src/lib/bootstrap/clientOnlyModules.ts +0 -85
- package/src/lib/crud/__tests__/crud-factory.cache-user-scope.test.ts +0 -171
- package/src/lib/data/__tests__/consistency.test.ts +0 -38
- package/src/lib/data/consistency.ts +0 -17
- package/src/lib/db/__tests__/entityMetadata.test.ts +0 -38
- package/src/lib/db/__tests__/pg-errors.test.ts +0 -48
- package/src/lib/db/entityMetadata.ts +0 -43
- package/src/lib/di/__tests__/container-app-di-absent.test.ts +0 -155
- package/src/lib/di/__tests__/container-app-di.test.ts +0 -173
- package/src/lib/di/__tests__/registrar-error-log.test.ts +0 -154
- package/src/lib/i18n/__tests__/config.test.ts +0 -8
- package/src/lib/i18n/__tests__/server-dictionary-cache.test.ts +0 -30
- package/src/lib/i18n/__tests__/server-unbootstrapped.test.ts +0 -29
- package/src/lib/logger/extension.ts +0 -59
- package/src/lib/modules/__tests__/surfaceFingerprint.test.ts +0 -122
- package/src/lib/modules/surfaceFingerprint.ts +0 -87
- package/src/lib/query/__tests__/ciphertext-search-warning.test.ts +0 -178
- package/src/lib/query/ciphertext-search-warning.ts +0 -95
- package/src/lib/search/__tests__/availability.test.ts +0 -211
- package/src/lib/search/__tests__/tokenLookup.test.ts +0 -206
- package/src/lib/search/__tests__/tokenize.test.ts +0 -49
- package/src/lib/search/auto-indexing.ts +0 -22
- package/src/lib/search/availability.ts +0 -232
- package/src/lib/search/tokenLookup.ts +0 -133
- package/src/lib/telemetry/runtime.ts +0 -72
- package/src/lib/webhooks/__tests__/body.test.ts +0 -95
- package/src/lib/webhooks/body.ts +0 -70
- package/src/lib/webhooks/inbound-types.ts +0 -125
- package/src/modules/__tests__/nav-group-order-override.test.ts +0 -183
- package/src/modules/payment_gateways/__tests__/types.test.ts +0 -37
- package/src/modules/widgets/__tests__/extension-points.test.ts +0 -101
- package/src/modules/widgets/extension-points.ts +0 -524
- package/src/security/__tests__/featurePolicy.test.ts +0 -166
- package/src/security/featurePolicy.ts +0 -89
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
const GLOBAL_KEY = /* @__PURE__ */ Symbol.for("@open-mercato/shared.telemetryRuntime");
|
|
2
|
-
const ENABLED_BACKENDS = /* @__PURE__ */ new Set(["console", "signoz", "newrelic", "otlp"]);
|
|
3
|
-
function store() {
|
|
4
|
-
const globalStore = globalThis;
|
|
5
|
-
let current = globalStore[GLOBAL_KEY];
|
|
6
|
-
if (!current) {
|
|
7
|
-
current = {};
|
|
8
|
-
globalStore[GLOBAL_KEY] = current;
|
|
9
|
-
}
|
|
10
|
-
return current;
|
|
11
|
-
}
|
|
12
|
-
function isTelemetryBackendEnabled(raw) {
|
|
13
|
-
const value = raw ?? (typeof process === "undefined" ? void 0 : process.env.TELEMETRY_BACKEND);
|
|
14
|
-
return ENABLED_BACKENDS.has((value ?? "").trim().toLowerCase());
|
|
15
|
-
}
|
|
16
|
-
function registerTelemetryRuntime(runtime) {
|
|
17
|
-
store().active = runtime;
|
|
18
|
-
return () => {
|
|
19
|
-
const current = store();
|
|
20
|
-
if (current.active === runtime) current.active = void 0;
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
function getTelemetryRuntime() {
|
|
24
|
-
return store().active;
|
|
25
|
-
}
|
|
26
|
-
function resetTelemetryRuntime() {
|
|
27
|
-
store().active = void 0;
|
|
28
|
-
}
|
|
29
|
-
export {
|
|
30
|
-
getTelemetryRuntime,
|
|
31
|
-
isTelemetryBackendEnabled,
|
|
32
|
-
registerTelemetryRuntime,
|
|
33
|
-
resetTelemetryRuntime
|
|
34
|
-
};
|
|
35
|
-
//# sourceMappingURL=runtime.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/lib/telemetry/runtime.ts"],
|
|
4
|
-
"sourcesContent": ["export type TelemetryTraceCarrier = Record<string, string>\n\nexport type TelemetryRuntime = {\n /**\n * True only when the active SDK may safely use the process-global W3C\n * propagator for cross-boundary extraction.\n */\n canUseGlobalTracePropagation(): boolean\n captureTraceContext(): TelemetryTraceCarrier\n continueTrace<T>(\n carrier: TelemetryTraceCarrier | undefined,\n name: string,\n fn: () => T,\n options?: { kind?: 'internal' | 'server' | 'client' | 'producer' | 'consumer' },\n ): T\n recordHttpDuration(method: string, route: string, status: number, startedAt: number): void\n reportError(\n error: unknown,\n context?: {\n module?: string\n attributes?: Record<string, string | number | boolean | undefined>\n },\n ): void\n shutdown(): Promise<void>\n}\n\nconst GLOBAL_KEY = Symbol.for('@open-mercato/shared.telemetryRuntime')\nconst ENABLED_BACKENDS = new Set(['console', 'signoz', 'newrelic', 'otlp'])\n\ntype TelemetryRuntimeStore = {\n active?: TelemetryRuntime\n}\n\nfunction store(): TelemetryRuntimeStore {\n const globalStore = globalThis as unknown as Record<symbol, TelemetryRuntimeStore | undefined>\n let current = globalStore[GLOBAL_KEY]\n if (!current) {\n current = {}\n globalStore[GLOBAL_KEY] = current\n }\n return current\n}\n\n/**\n * This check is intentionally owned by shared code so hosts can decide whether\n * to dynamically import the telemetry package without evaluating that package.\n */\nexport function isTelemetryBackendEnabled(raw?: string): boolean {\n const value = raw ?? (\n typeof process === 'undefined'\n ? undefined\n : process.env.TELEMETRY_BACKEND\n )\n return ENABLED_BACKENDS.has((value ?? '').trim().toLowerCase())\n}\n\nexport function registerTelemetryRuntime(runtime: TelemetryRuntime): () => void {\n store().active = runtime\n return () => {\n const current = store()\n if (current.active === runtime) current.active = undefined\n }\n}\n\nexport function getTelemetryRuntime(): TelemetryRuntime | undefined {\n return store().active\n}\n\n/** Test-only: clear the process-wide telemetry bridge. */\nexport function resetTelemetryRuntime(): void {\n store().active = undefined\n}\n"],
|
|
5
|
-
"mappings": "AA0BA,MAAM,aAAa,uBAAO,IAAI,uCAAuC;AACrE,MAAM,mBAAmB,oBAAI,IAAI,CAAC,WAAW,UAAU,YAAY,MAAM,CAAC;AAM1E,SAAS,QAA+B;AACtC,QAAM,cAAc;AACpB,MAAI,UAAU,YAAY,UAAU;AACpC,MAAI,CAAC,SAAS;AACZ,cAAU,CAAC;AACX,gBAAY,UAAU,IAAI;AAAA,EAC5B;AACA,SAAO;AACT;AAMO,SAAS,0BAA0B,KAAuB;AAC/D,QAAM,QAAQ,QACZ,OAAO,YAAY,cACf,SACA,QAAQ,IAAI;AAElB,SAAO,iBAAiB,KAAK,SAAS,IAAI,KAAK,EAAE,YAAY,CAAC;AAChE;AAEO,SAAS,yBAAyB,SAAuC;AAC9E,QAAM,EAAE,SAAS;AACjB,SAAO,MAAM;AACX,UAAM,UAAU,MAAM;AACtB,QAAI,QAAQ,WAAW,QAAS,SAAQ,SAAS;AAAA,EACnD;AACF;AAEO,SAAS,sBAAoD;AAClE,SAAO,MAAM,EAAE;AACjB;AAGO,SAAS,wBAA8B;AAC5C,QAAM,EAAE,SAAS;AACnB;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { parseNumberWithDefault } from "../number.js";
|
|
2
|
-
const DEFAULT_WEBHOOK_BODY_LIMIT_BYTES = 1024 * 1024;
|
|
3
|
-
class WebhookBodyTooLargeError extends Error {
|
|
4
|
-
constructor(limitBytes) {
|
|
5
|
-
super(`Webhook body exceeds the ${limitBytes}-byte limit`);
|
|
6
|
-
this.name = "WebhookBodyTooLargeError";
|
|
7
|
-
this.limitBytes = limitBytes;
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
function resolveWebhookBodyLimitBytes(raw = typeof process === "undefined" ? void 0 : process.env.OM_WEBHOOK_MAX_BODY_BYTES, fallbackBytes = DEFAULT_WEBHOOK_BODY_LIMIT_BYTES) {
|
|
11
|
-
const fallback = Number.isSafeInteger(fallbackBytes) && fallbackBytes > 0 ? fallbackBytes : DEFAULT_WEBHOOK_BODY_LIMIT_BYTES;
|
|
12
|
-
const parsed = parseNumberWithDefault(raw, fallback, { min: 1 });
|
|
13
|
-
return Number.isSafeInteger(parsed) ? parsed : fallback;
|
|
14
|
-
}
|
|
15
|
-
async function readBoundedRequestBody(request, options) {
|
|
16
|
-
const configuredLimit = options?.maxBytes ?? resolveWebhookBodyLimitBytes();
|
|
17
|
-
const maxBytes = Number.isSafeInteger(configuredLimit) && configuredLimit > 0 ? configuredLimit : DEFAULT_WEBHOOK_BODY_LIMIT_BYTES;
|
|
18
|
-
const declaredLength = request.headers.get("content-length")?.trim();
|
|
19
|
-
if (declaredLength && /^\d+$/.test(declaredLength)) {
|
|
20
|
-
const declaredBytes = Number(declaredLength);
|
|
21
|
-
if (!Number.isSafeInteger(declaredBytes) || declaredBytes > maxBytes) {
|
|
22
|
-
throw new WebhookBodyTooLargeError(maxBytes);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
if (!request.body) return "";
|
|
26
|
-
const reader = request.body.getReader();
|
|
27
|
-
const chunks = [];
|
|
28
|
-
let totalBytes = 0;
|
|
29
|
-
try {
|
|
30
|
-
while (true) {
|
|
31
|
-
const { done, value } = await reader.read();
|
|
32
|
-
if (done) break;
|
|
33
|
-
totalBytes += value.byteLength;
|
|
34
|
-
if (totalBytes > maxBytes) {
|
|
35
|
-
await reader.cancel().catch(() => void 0);
|
|
36
|
-
throw new WebhookBodyTooLargeError(maxBytes);
|
|
37
|
-
}
|
|
38
|
-
chunks.push(value);
|
|
39
|
-
}
|
|
40
|
-
} finally {
|
|
41
|
-
reader.releaseLock();
|
|
42
|
-
}
|
|
43
|
-
const body = new Uint8Array(totalBytes);
|
|
44
|
-
let offset = 0;
|
|
45
|
-
for (const chunk of chunks) {
|
|
46
|
-
body.set(chunk, offset);
|
|
47
|
-
offset += chunk.byteLength;
|
|
48
|
-
}
|
|
49
|
-
return new TextDecoder().decode(body);
|
|
50
|
-
}
|
|
51
|
-
export {
|
|
52
|
-
DEFAULT_WEBHOOK_BODY_LIMIT_BYTES,
|
|
53
|
-
WebhookBodyTooLargeError,
|
|
54
|
-
readBoundedRequestBody,
|
|
55
|
-
resolveWebhookBodyLimitBytes
|
|
56
|
-
};
|
|
57
|
-
//# sourceMappingURL=body.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/lib/webhooks/body.ts"],
|
|
4
|
-
"sourcesContent": ["import { parseNumberWithDefault } from '../number'\n\nexport const DEFAULT_WEBHOOK_BODY_LIMIT_BYTES = 1024 * 1024\n\nexport class WebhookBodyTooLargeError extends Error {\n readonly limitBytes: number\n\n constructor(limitBytes: number) {\n super(`Webhook body exceeds the ${limitBytes}-byte limit`)\n this.name = 'WebhookBodyTooLargeError'\n this.limitBytes = limitBytes\n }\n}\n\nexport function resolveWebhookBodyLimitBytes(\n raw = typeof process === 'undefined' ? undefined : process.env.OM_WEBHOOK_MAX_BODY_BYTES,\n fallbackBytes = DEFAULT_WEBHOOK_BODY_LIMIT_BYTES,\n): number {\n const fallback = Number.isSafeInteger(fallbackBytes) && fallbackBytes > 0\n ? fallbackBytes\n : DEFAULT_WEBHOOK_BODY_LIMIT_BYTES\n const parsed = parseNumberWithDefault(raw, fallback, { min: 1 })\n return Number.isSafeInteger(parsed) ? parsed : fallback\n}\n\nexport async function readBoundedRequestBody(\n request: Request,\n options?: { maxBytes?: number },\n): Promise<string> {\n const configuredLimit = options?.maxBytes ?? resolveWebhookBodyLimitBytes()\n const maxBytes = Number.isSafeInteger(configuredLimit) && configuredLimit > 0\n ? configuredLimit\n : DEFAULT_WEBHOOK_BODY_LIMIT_BYTES\n const declaredLength = request.headers.get('content-length')?.trim()\n if (declaredLength && /^\\d+$/.test(declaredLength)) {\n const declaredBytes = Number(declaredLength)\n if (!Number.isSafeInteger(declaredBytes) || declaredBytes > maxBytes) {\n throw new WebhookBodyTooLargeError(maxBytes)\n }\n }\n\n if (!request.body) return ''\n\n const reader = request.body.getReader()\n const chunks: Uint8Array[] = []\n let totalBytes = 0\n\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n totalBytes += value.byteLength\n if (totalBytes > maxBytes) {\n await reader.cancel().catch(() => undefined)\n throw new WebhookBodyTooLargeError(maxBytes)\n }\n chunks.push(value)\n }\n } finally {\n reader.releaseLock()\n }\n\n const body = new Uint8Array(totalBytes)\n let offset = 0\n for (const chunk of chunks) {\n body.set(chunk, offset)\n offset += chunk.byteLength\n }\n return new TextDecoder().decode(body)\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,8BAA8B;AAEhC,MAAM,mCAAmC,OAAO;AAEhD,MAAM,iCAAiC,MAAM;AAAA,EAGlD,YAAY,YAAoB;AAC9B,UAAM,4BAA4B,UAAU,aAAa;AACzD,SAAK,OAAO;AACZ,SAAK,aAAa;AAAA,EACpB;AACF;AAEO,SAAS,6BACd,MAAM,OAAO,YAAY,cAAc,SAAY,QAAQ,IAAI,2BAC/D,gBAAgB,kCACR;AACR,QAAM,WAAW,OAAO,cAAc,aAAa,KAAK,gBAAgB,IACpE,gBACA;AACJ,QAAM,SAAS,uBAAuB,KAAK,UAAU,EAAE,KAAK,EAAE,CAAC;AAC/D,SAAO,OAAO,cAAc,MAAM,IAAI,SAAS;AACjD;AAEA,eAAsB,uBACpB,SACA,SACiB;AACjB,QAAM,kBAAkB,SAAS,YAAY,6BAA6B;AAC1E,QAAM,WAAW,OAAO,cAAc,eAAe,KAAK,kBAAkB,IACxE,kBACA;AACJ,QAAM,iBAAiB,QAAQ,QAAQ,IAAI,gBAAgB,GAAG,KAAK;AACnE,MAAI,kBAAkB,QAAQ,KAAK,cAAc,GAAG;AAClD,UAAM,gBAAgB,OAAO,cAAc;AAC3C,QAAI,CAAC,OAAO,cAAc,aAAa,KAAK,gBAAgB,UAAU;AACpE,YAAM,IAAI,yBAAyB,QAAQ;AAAA,IAC7C;AAAA,EACF;AAEA,MAAI,CAAC,QAAQ,KAAM,QAAO;AAE1B,QAAM,SAAS,QAAQ,KAAK,UAAU;AACtC,QAAM,SAAuB,CAAC;AAC9B,MAAI,aAAa;AAEjB,MAAI;AACF,WAAO,MAAM;AACX,YAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,UAAI,KAAM;AACV,oBAAc,MAAM;AACpB,UAAI,aAAa,UAAU;AACzB,cAAM,OAAO,OAAO,EAAE,MAAM,MAAM,MAAS;AAC3C,cAAM,IAAI,yBAAyB,QAAQ;AAAA,MAC7C;AACA,aAAO,KAAK,KAAK;AAAA,IACnB;AAAA,EACF,UAAE;AACA,WAAO,YAAY;AAAA,EACrB;AAEA,QAAM,OAAO,IAAI,WAAW,UAAU;AACtC,MAAI,SAAS;AACb,aAAW,SAAS,QAAQ;AAC1B,SAAK,IAAI,OAAO,MAAM;AACtB,cAAU,MAAM;AAAA,EAClB;AACA,SAAO,IAAI,YAAY,EAAE,OAAO,IAAI;AACtC;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
//# sourceMappingURL=inbound-types.js.map
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
function defineModuleExtensionPoints(declaration) {
|
|
2
|
-
return Object.freeze({
|
|
3
|
-
...declaration,
|
|
4
|
-
hosts: Object.freeze({ ...declaration.hosts })
|
|
5
|
-
});
|
|
6
|
-
}
|
|
7
|
-
function injectionExtensionHost(declaration) {
|
|
8
|
-
const hasExactId = typeof declaration.spotId === "string" && declaration.spotId.length > 0;
|
|
9
|
-
const hasPattern = typeof declaration.pattern === "string" && declaration.pattern.length > 0;
|
|
10
|
-
if (hasExactId === hasPattern) {
|
|
11
|
-
throw new Error("[internal] injection extension hosts require exactly one of spotId or pattern");
|
|
12
|
-
}
|
|
13
|
-
if (hasPattern && (!declaration.parameters || Object.keys(declaration.parameters).length === 0)) {
|
|
14
|
-
throw new Error("[internal] patterned injection extension hosts require named parameters");
|
|
15
|
-
}
|
|
16
|
-
return Object.freeze({ ...declaration });
|
|
17
|
-
}
|
|
18
|
-
function dataTableExtensionHost(declaration) {
|
|
19
|
-
return Object.freeze({ family: "data-table", ...declaration });
|
|
20
|
-
}
|
|
21
|
-
function crudFormExtensionHost(declaration) {
|
|
22
|
-
return Object.freeze({ family: "crud-form", ...declaration });
|
|
23
|
-
}
|
|
24
|
-
function componentExtensionHost(declaration) {
|
|
25
|
-
return Object.freeze({ family: "component-handle", ...declaration });
|
|
26
|
-
}
|
|
27
|
-
const DATA_TABLE_EXTENSION_SURFACES = [
|
|
28
|
-
{ key: "header", suffix: "header", capabilities: ["render-widget"], bound: true },
|
|
29
|
-
{ key: "footer", suffix: "footer", capabilities: ["render-widget"], bound: true },
|
|
30
|
-
{ key: "toolbar", suffix: "toolbar", capabilities: ["toolbar-widget"], bound: true },
|
|
31
|
-
{ key: "searchTrailing", suffix: "search-trailing", capabilities: ["render-widget"], bound: true },
|
|
32
|
-
{ key: "columns", suffix: "columns", capabilities: ["column-widget"], bound: true },
|
|
33
|
-
{ key: "rowActions", suffix: "row-actions", capabilities: ["row-action"], bound: true },
|
|
34
|
-
{ key: "bulkActions", suffix: "bulk-actions", capabilities: ["bulk-action"], bound: true },
|
|
35
|
-
{ key: "filters", suffix: "filters", capabilities: ["filter-widget"], bound: true },
|
|
36
|
-
{ key: "replacement", suffix: null, capabilities: ["component-replacement"], bound: true },
|
|
37
|
-
{ key: "emptyState", suffix: "empty-state", capabilities: ["render-widget"], bound: false }
|
|
38
|
-
];
|
|
39
|
-
const CRUD_FORM_EXTENSION_SURFACES = [
|
|
40
|
-
{ key: "base", suffix: null, capabilities: ["render-widget", "lifecycle-handler"], bound: true },
|
|
41
|
-
{ key: "header", suffix: "header", capabilities: ["render-widget"], bound: true },
|
|
42
|
-
{ key: "fields", suffix: "fields", capabilities: ["field-widget"], bound: true },
|
|
43
|
-
{ key: "replacement", suffix: null, capabilities: ["component-replacement"], bound: true },
|
|
44
|
-
{ key: "beforeFields", suffix: "before-fields", capabilities: ["render-widget"], bound: false },
|
|
45
|
-
{ key: "afterFields", suffix: "after-fields", capabilities: ["render-widget"], bound: false },
|
|
46
|
-
{ key: "footer", suffix: "footer", capabilities: ["render-widget"], bound: false },
|
|
47
|
-
{ key: "sidebar", suffix: "sidebar", capabilities: ["render-widget"], bound: false },
|
|
48
|
-
{ key: "group", suffix: "group:{groupId}", capabilities: ["render-widget"], bound: false },
|
|
49
|
-
{ key: "fieldBefore", suffix: "field:{fieldId}:before", capabilities: ["render-widget"], bound: false },
|
|
50
|
-
{ key: "fieldAfter", suffix: "field:{fieldId}:after", capabilities: ["render-widget"], bound: false }
|
|
51
|
-
];
|
|
52
|
-
const CRUD_FORM_LIFECYCLE_PHASES = [
|
|
53
|
-
"transformValidation",
|
|
54
|
-
"transformDisplayData",
|
|
55
|
-
"onBeforeNavigate",
|
|
56
|
-
"onAppEvent",
|
|
57
|
-
"onVisibilityChange",
|
|
58
|
-
"onBeforeDelete",
|
|
59
|
-
"onDelete",
|
|
60
|
-
"onAfterDelete",
|
|
61
|
-
"onDeleteError",
|
|
62
|
-
"onFieldChange",
|
|
63
|
-
"transformFormData",
|
|
64
|
-
"onBeforeSave",
|
|
65
|
-
"onSave",
|
|
66
|
-
"onAfterSave"
|
|
67
|
-
];
|
|
68
|
-
const CRUD_FORM_OPERATIONS = ["create", "update", "delete"];
|
|
69
|
-
function dataTableExtensionSpotId(tableId, suffix) {
|
|
70
|
-
return suffix ? `data-table:${tableId}:${suffix}` : `data-table:${tableId}`;
|
|
71
|
-
}
|
|
72
|
-
function crudFormExtensionSpotId(entityId, suffix) {
|
|
73
|
-
return suffix ? `crud-form:${entityId}:${suffix}` : `crud-form:${entityId}`;
|
|
74
|
-
}
|
|
75
|
-
function extensionSpotChildId(spotId, suffix) {
|
|
76
|
-
return `${spotId}:${suffix}`;
|
|
77
|
-
}
|
|
78
|
-
function resolveExtensionPointPattern(pattern, parameters) {
|
|
79
|
-
return pattern.replace(/\{([^}]+)\}/g, (token, parameterName) => parameters[parameterName] ?? token);
|
|
80
|
-
}
|
|
81
|
-
export {
|
|
82
|
-
CRUD_FORM_EXTENSION_SURFACES,
|
|
83
|
-
CRUD_FORM_LIFECYCLE_PHASES,
|
|
84
|
-
CRUD_FORM_OPERATIONS,
|
|
85
|
-
DATA_TABLE_EXTENSION_SURFACES,
|
|
86
|
-
componentExtensionHost,
|
|
87
|
-
crudFormExtensionHost,
|
|
88
|
-
crudFormExtensionSpotId,
|
|
89
|
-
dataTableExtensionHost,
|
|
90
|
-
dataTableExtensionSpotId,
|
|
91
|
-
defineModuleExtensionPoints,
|
|
92
|
-
extensionSpotChildId,
|
|
93
|
-
injectionExtensionHost,
|
|
94
|
-
resolveExtensionPointPattern
|
|
95
|
-
};
|
|
96
|
-
//# sourceMappingURL=extension-points.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/modules/widgets/extension-points.ts"],
|
|
4
|
-
"sourcesContent": ["export type ExtensionHostFamily =\n | 'generic'\n | 'menu'\n | 'data-table'\n | 'crud-form'\n | 'detail'\n | 'portal-page'\n | 'component-handle'\n | 'entity'\n | 'api-route'\n | 'command'\n | 'event'\n | 'query-lifecycle'\n | 'dashboard'\n | 'notification'\n | 'integration'\n | 'specialized-registry'\n | 'module-override'\n\nexport type ExtensionHostCapability =\n | 'render-widget'\n | 'headless-widget'\n | 'menu-item'\n | 'column-widget'\n | 'row-action'\n | 'bulk-action'\n | 'filter-widget'\n | 'toolbar-widget'\n | 'field-widget'\n | 'lifecycle-handler'\n | 'component-replacement'\n | 'response-enricher'\n | 'query-enricher'\n | 'api-interceptor'\n | 'command-interceptor'\n | 'mutation-guard'\n | 'entity-extension'\n | 'async-subscriber'\n | 'sync-subscriber'\n | 'browser-client'\n | 'browser-portal'\n | 'registry-contribution'\n | 'module-override'\n\nexport type ExtensionHostActivation = 'always' | 'host-opt-in' | 'caller-opt-in' | 'feature-gated'\n\nexport type ExtensionPointPatternParameter = {\n source: string\n pattern?: string\n}\n\ntype ExtensionHostDeclarationBase = {\n source: string\n contextContract?: string\n dataContract?: string\n scopeContract?: string\n runtimeContract?: string\n activation?: ExtensionHostActivation\n aliases?: readonly string[]\n fallbacks?: readonly string[]\n}\n\ntype InjectionExtensionHostDeclarationBase = ExtensionHostDeclarationBase & {\n family: Exclude<ExtensionHostFamily, 'data-table' | 'crud-form' | 'component-handle'>\n supported: readonly ExtensionHostCapability[]\n}\n\nexport type InjectionExtensionHostDeclaration = InjectionExtensionHostDeclarationBase & (\n | {\n spotId: string\n pattern?: never\n parameters?: never\n }\n | {\n spotId?: never\n pattern: string\n parameters: Readonly<Record<string, ExtensionPointPatternParameter>>\n }\n)\n\nexport type DataTableExtensionHostDeclaration = ExtensionHostDeclarationBase & {\n family: 'data-table'\n tableId: string\n baseSpotId?: string\n}\n\nexport type CrudFormExtensionHostDeclaration = ExtensionHostDeclarationBase & {\n family: 'crud-form'\n entityId: string\n spotId?: string\n}\n\nexport type ComponentExtensionHostDeclaration = ExtensionHostDeclarationBase & {\n family: 'component-handle'\n componentId: string\n propsContract?: string\n}\n\nexport type ModuleExtensionHostDeclaration =\n | InjectionExtensionHostDeclaration\n | DataTableExtensionHostDeclaration\n | CrudFormExtensionHostDeclaration\n | ComponentExtensionHostDeclaration\n\nexport type ModuleExtensionPoints<\n TModuleId extends string = string,\n THosts extends Readonly<Record<string, ModuleExtensionHostDeclaration>> = Readonly<Record<string, ModuleExtensionHostDeclaration>>,\n> = {\n moduleId: TModuleId\n hosts: THosts\n}\n\nexport function defineModuleExtensionPoints<\n const TModuleId extends string,\n const THosts extends Readonly<Record<string, ModuleExtensionHostDeclaration>>,\n>(declaration: ModuleExtensionPoints<TModuleId, THosts>): ModuleExtensionPoints<TModuleId, THosts> {\n return Object.freeze({\n ...declaration,\n hosts: Object.freeze({ ...declaration.hosts }),\n })\n}\n\nexport function injectionExtensionHost<const TDeclaration extends InjectionExtensionHostDeclaration>(\n declaration: TDeclaration,\n): Readonly<TDeclaration> {\n const hasExactId = typeof declaration.spotId === 'string' && declaration.spotId.length > 0\n const hasPattern = typeof declaration.pattern === 'string' && declaration.pattern.length > 0\n if (hasExactId === hasPattern) {\n throw new Error('[internal] injection extension hosts require exactly one of spotId or pattern')\n }\n if (hasPattern && (!declaration.parameters || Object.keys(declaration.parameters).length === 0)) {\n throw new Error('[internal] patterned injection extension hosts require named parameters')\n }\n return Object.freeze({ ...declaration })\n}\n\nexport function dataTableExtensionHost<\n const TDeclaration extends Omit<DataTableExtensionHostDeclaration, 'family'>,\n>(\n declaration: TDeclaration,\n): Readonly<TDeclaration & { family: 'data-table' }> {\n return Object.freeze({ family: 'data-table', ...declaration })\n}\n\nexport function crudFormExtensionHost<\n const TDeclaration extends Omit<CrudFormExtensionHostDeclaration, 'family'>,\n>(\n declaration: TDeclaration,\n): Readonly<TDeclaration & { family: 'crud-form' }> {\n return Object.freeze({ family: 'crud-form', ...declaration })\n}\n\nexport function componentExtensionHost<\n const TDeclaration extends Omit<ComponentExtensionHostDeclaration, 'family'>,\n>(\n declaration: TDeclaration,\n): Readonly<TDeclaration & { family: 'component-handle' }> {\n return Object.freeze({ family: 'component-handle', ...declaration })\n}\n\nexport type BoundExtensionSurface = {\n key: string\n suffix: string | null\n capabilities: readonly ExtensionHostCapability[]\n bound: boolean\n phases?: readonly string[]\n operations?: readonly string[]\n}\n\nexport const DATA_TABLE_EXTENSION_SURFACES = [\n { key: 'header', suffix: 'header', capabilities: ['render-widget'], bound: true },\n { key: 'footer', suffix: 'footer', capabilities: ['render-widget'], bound: true },\n { key: 'toolbar', suffix: 'toolbar', capabilities: ['toolbar-widget'], bound: true },\n { key: 'searchTrailing', suffix: 'search-trailing', capabilities: ['render-widget'], bound: true },\n { key: 'columns', suffix: 'columns', capabilities: ['column-widget'], bound: true },\n { key: 'rowActions', suffix: 'row-actions', capabilities: ['row-action'], bound: true },\n { key: 'bulkActions', suffix: 'bulk-actions', capabilities: ['bulk-action'], bound: true },\n { key: 'filters', suffix: 'filters', capabilities: ['filter-widget'], bound: true },\n { key: 'replacement', suffix: null, capabilities: ['component-replacement'], bound: true },\n { key: 'emptyState', suffix: 'empty-state', capabilities: ['render-widget'], bound: false },\n] as const satisfies readonly BoundExtensionSurface[]\n\nexport const CRUD_FORM_EXTENSION_SURFACES = [\n { key: 'base', suffix: null, capabilities: ['render-widget', 'lifecycle-handler'], bound: true },\n { key: 'header', suffix: 'header', capabilities: ['render-widget'], bound: true },\n { key: 'fields', suffix: 'fields', capabilities: ['field-widget'], bound: true },\n { key: 'replacement', suffix: null, capabilities: ['component-replacement'], bound: true },\n { key: 'beforeFields', suffix: 'before-fields', capabilities: ['render-widget'], bound: false },\n { key: 'afterFields', suffix: 'after-fields', capabilities: ['render-widget'], bound: false },\n { key: 'footer', suffix: 'footer', capabilities: ['render-widget'], bound: false },\n { key: 'sidebar', suffix: 'sidebar', capabilities: ['render-widget'], bound: false },\n { key: 'group', suffix: 'group:{groupId}', capabilities: ['render-widget'], bound: false },\n { key: 'fieldBefore', suffix: 'field:{fieldId}:before', capabilities: ['render-widget'], bound: false },\n { key: 'fieldAfter', suffix: 'field:{fieldId}:after', capabilities: ['render-widget'], bound: false },\n] as const satisfies readonly BoundExtensionSurface[]\n\nexport const CRUD_FORM_LIFECYCLE_PHASES = [\n 'transformValidation',\n 'transformDisplayData',\n 'onBeforeNavigate',\n 'onAppEvent',\n 'onVisibilityChange',\n 'onBeforeDelete',\n 'onDelete',\n 'onAfterDelete',\n 'onDeleteError',\n 'onFieldChange',\n 'transformFormData',\n 'onBeforeSave',\n 'onSave',\n 'onAfterSave',\n] as const\n\nexport const CRUD_FORM_OPERATIONS = ['create', 'update', 'delete'] as const\n\nexport function dataTableExtensionSpotId(tableId: string, suffix?: string): string {\n return suffix ? `data-table:${tableId}:${suffix}` : `data-table:${tableId}`\n}\n\nexport function crudFormExtensionSpotId(entityId: string, suffix?: string): string {\n return suffix ? `crud-form:${entityId}:${suffix}` : `crud-form:${entityId}`\n}\n\nexport function extensionSpotChildId(spotId: string, suffix: string): string {\n return `${spotId}:${suffix}`\n}\n\nexport function resolveExtensionPointPattern(\n pattern: string,\n parameters: Readonly<Record<string, string>>,\n): string {\n return pattern.replace(/\\{([^}]+)\\}/g, (token, parameterName: string) => parameters[parameterName] ?? token)\n}\n\n/**\n * Portable, structural copy of the CLI provenance source reference\n * (`ModuleFactSourceRef` in `packages/cli/.../module-facts.ts`). Redefined here\n * \u2014 rather than imported \u2014 so `@open-mercato/shared` keeps zero dependency on\n * `@open-mercato/cli`. Field names are kept byte-identical so the two are\n * assignable across the package boundary.\n */\nexport type ModuleFactSourceRef = {\n sourcePath: string\n exportName?: string\n line?: number\n}\n\n/** Portable, structural copy of the CLI provenance fact reference. */\nexport type ModuleFactRef = {\n factSection: string\n factKey: string\n}\n\nexport type ModuleExtensionContributionKind = ModuleExtensionContributionFact['kind']\n\nexport type ModuleExtensionActivationKind =\n | 'crud-response-enricher'\n | 'query-enricher'\n | 'mutation-guard'\n | 'api-interceptor-bridge'\n | 'command-interceptor-bridge'\n | 'widget-injection-consumer'\n | 'component-extension-consumer'\n | 'dashboard-host-consumer'\n\nexport type ModuleExtensionTargetRef = {\n kind:\n | 'module'\n | 'entity'\n | 'api-route'\n | 'command'\n | 'widget-spot'\n | 'component'\n | 'event'\n | 'notification'\n | 'wildcard'\n id: string\n moduleId?: string\n method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'\n}\n\nexport type ModuleExtensionActivation = {\n id: string\n kind: ModuleExtensionActivationKind\n host: ModuleExtensionTargetRef\n contributionKinds: ModuleExtensionContributionKind[]\n phases?: string[]\n /**\n * Mutation operations the call site guards, when statically declared. A\n * contribution declaring only other operations is not bound by this activation.\n */\n operations?: string[]\n source: ModuleFactSourceRef\n bridge?: ModuleFactRef\n}\n\nexport type ModuleExtensionResolution =\n | 'bound'\n | 'capability-only'\n | 'optional-target-missing'\n | 'wildcard'\n | 'unresolved'\n\nexport type ModuleIncomingExtensionRef = {\n contributionId: string\n contributionKind: ModuleExtensionContributionKind\n contributorModuleId: string\n target: ModuleExtensionTargetRef\n activationId?: string\n resolution: ModuleExtensionResolution\n source: ModuleFactSourceRef\n}\n\nexport type ModuleContributionResolution = {\n contributionId: string\n target: ModuleExtensionTargetRef\n resolution: ModuleExtensionResolution\n activationIds: string[]\n}\n\n/**\n * Additive, optional bidirectional-topology fields layered on top of the\n * existing extension-surface facts. Every field is optional so legacy consumers\n * and pre-existing generated output remain byte-compatible.\n */\nexport type ModuleExtensionSurfaceFactsAdditions = {\n activations?: ModuleExtensionActivation[]\n incoming?: ModuleIncomingExtensionRef[]\n contributionResolutions?: ModuleContributionResolution[]\n}\n\nexport type ModuleExtensionSurfaceFacts = {\n hosts: ModuleExtensionHostFact[]\n contributions: ModuleExtensionContributionFact[]\n unresolved: ModuleExtensionUnresolvedFact[]\n} & ModuleExtensionSurfaceFactsAdditions\n\nexport type ModuleExtensionHostFact = {\n key: string\n id: string\n resolution: 'exact' | 'pattern' | 'framework' | 'fact-ref'\n family: ExtensionHostFamily\n ownerModule: string\n capabilities: ExtensionHostCapability[]\n phases?: string[]\n operations?: string[]\n contextContract?: string\n dataContract?: string\n scopeContract?: string\n runtimeContract?: string\n activation?: ExtensionHostActivation\n bound: boolean\n stability: 'frozen' | 'stable'\n source:\n | { kind: 'declaration'; path: string; symbol: string }\n | { kind: 'fact-ref'; factSection: string; factKey: string }\n | { kind: 'framework'; path: string; symbol: string }\n aliases?: string[]\n patternParameters?: Record<string, ExtensionPointPatternParameter>\n fallbacks?: string[]\n}\n\nexport type ModuleExtensionTargetFact = {\n id: string\n resolution: 'exact' | 'pattern' | 'framework' | 'fact-ref' | 'optional-external' | 'unresolved'\n factRef?: { factSection: string; factKey: string }\n optionalOwnerPackage?: string\n}\n\nexport type ModuleExtensionContributionBase = {\n id: string\n targets: ModuleExtensionTargetFact[]\n phases?: string[]\n operations?: string[]\n features?: string[]\n scopeContract: string\n activation?: ExtensionHostActivation\n placement?: { relativeTo?: string; position?: 'first' | 'last' | 'before' | 'after'; priority?: number }\n roundTripId?: string\n override?: { domain: string; key: string; mode: 'disable-replace' | 'replace' | 'additive' }\n source: { path: string; symbol?: string }\n}\n\nexport type ModuleExtensionContributionFact = ModuleExtensionContributionBase & (\n | {\n kind: 'widget'\n details: {\n payload: 'render' | 'headless' | 'menu' | 'dashboard' | 'notification' | 'integration'\n registryKey: string\n itemIds?: string[]\n labelKeys?: string[]\n contextContract?: string\n dataContract?: string\n executionGuard: 'host' | 'contribution' | 'both'\n }\n }\n | {\n kind: 'data-table'\n details: {\n payload: 'column' | 'row-action' | 'bulk-action' | 'filter' | 'toolbar' | 'render'\n tableId: string\n executionGuard: 'host' | 'contribution' | 'both'\n }\n }\n | {\n kind: 'crud-form'\n details: {\n payload: 'render' | 'field' | 'lifecycle-handler'\n entityId: string\n fieldIds?: string[]\n groupIds?: string[]\n requestHeaderCapability: boolean\n }\n }\n | {\n kind: 'component-override'\n details: { handle: string; mode: 'replace' | 'wrapper' | 'props'; propsContract: string }\n }\n | {\n kind: 'response-enricher'\n details: {\n targetEntity: string\n surfaces: Array<'list' | 'detail'>\n timeoutMs: number\n fallback: 'none' | 'configured'\n critical: boolean\n cachePosture: 'record-pure' | 'rerun-on-list-cache-hit'\n queryEngine?: {\n engines: string[]\n applyOn: Array<'list' | 'detail'>\n activation: 'caller-opt-in'\n }\n }\n }\n | {\n kind: 'api-interceptor'\n details: {\n route: string\n methods: string[]\n phases: Array<'before' | 'after'>\n activation: 'crud-pipeline' | 'custom-route-bridge'\n timeoutMs: number\n failurePosture: 'fail-closed' | 'fallback'\n }\n }\n | {\n kind: 'command-interceptor'\n details: {\n targetCommand: string\n phases: Array<'before-execute' | 'after-execute' | 'before-undo' | 'after-undo'>\n }\n }\n | {\n kind: 'mutation-guard'\n details: {\n entityId: string\n operations: Array<'create' | 'update' | 'delete'>\n capabilities: Array<'block' | 'rewrite' | 'after-success'>\n optimisticLock: 'preserved'\n }\n }\n | {\n kind: 'entity-extension'\n details: {\n hostEntityId: string\n extensionEntityId: string\n linkId: string\n scopeContract: string\n orphanContract: string\n }\n }\n | {\n kind: 'subscriber'\n details: {\n event: string\n subscriberId: string\n persistent: boolean\n sync: boolean\n priority?: number\n }\n }\n | {\n kind: 'browser-reaction'\n details: {\n transports: Array<'client' | 'portal' | 'notification-effect'>\n hooks: string[]\n audienceScopeContract: string\n maxPayloadBytes?: number\n dedupWindowMs?: number\n /**\n * Statically declared handler id. Runtime keys `notifications.handlers`\n * overrides by it, so it is absent when the module declares no id (such a\n * handler cannot be overridden).\n */\n overrideKey?: string\n }\n }\n | {\n kind: 'specialized-registry'\n details: {\n registry: 'notification' | 'integration' | 'search' | 'vector' | 'ai' | 'payment' | 'shipping' | 'currency' | 'workflow'\n registryId: string\n specialistRoute: string\n }\n }\n | {\n kind: 'module-override'\n details: {\n domain: string\n key: string\n mode: 'disable-replace' | 'replace' | 'additive'\n }\n }\n)\n\nexport type ModuleExtensionUnresolvedFact = {\n key: string\n source: { path: string; symbol?: string }\n reason:\n | 'unclassified-binding'\n | 'unbound-declaration'\n | 'dynamic-without-pattern'\n | 'unresolved-first-party-target'\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { hasAllFeatures as matchesAllFeatures } from "./features.js";
|
|
2
|
-
import {
|
|
3
|
-
filterGrantsByEnabledModules,
|
|
4
|
-
getConcreteFeatureIds,
|
|
5
|
-
getEnabledModuleIds,
|
|
6
|
-
getOwningModuleId,
|
|
7
|
-
hasEnabledModulesRegistry
|
|
8
|
-
} from "./enabledModulesRegistry.js";
|
|
9
|
-
import { composeAclFeatureOverrides } from "../modules/overrides.js";
|
|
10
|
-
function getRemovedAclFeatureIds() {
|
|
11
|
-
return Object.entries(composeAclFeatureOverrides()).filter(([, override]) => override === null).map(([featureId]) => featureId);
|
|
12
|
-
}
|
|
13
|
-
function isAclFeatureRemoved(featureId) {
|
|
14
|
-
return composeAclFeatureOverrides()[featureId] === null;
|
|
15
|
-
}
|
|
16
|
-
function isFeatureEnabled(featureId) {
|
|
17
|
-
if (!hasEnabledModulesRegistry()) return true;
|
|
18
|
-
const enabledModuleIds = getEnabledModuleIds();
|
|
19
|
-
return enabledModuleIds.includes(getOwningModuleId(featureId));
|
|
20
|
-
}
|
|
21
|
-
function authorizeFeatures(required, subject) {
|
|
22
|
-
if (required.length === 0) return true;
|
|
23
|
-
if (subject.scopeAllowed === false) return false;
|
|
24
|
-
if (required.some((featureId) => isAclFeatureRemoved(featureId) || !isFeatureEnabled(featureId))) {
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
if (subject.unrestricted === true) return true;
|
|
28
|
-
return matchesAllFeatures(
|
|
29
|
-
filterGrantsByEnabledModules(subject.grantedFeatures),
|
|
30
|
-
required
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
function resolveEffectiveFeatures(grantedFeatures) {
|
|
34
|
-
const filteredGrants = filterGrantsByEnabledModules(grantedFeatures).filter((featureId) => !isAclFeatureRemoved(featureId));
|
|
35
|
-
if (!hasEnabledModulesRegistry()) {
|
|
36
|
-
return filteredGrants.filter((featureId, index, features) => featureId !== "*" && !featureId.endsWith(".*") && features.indexOf(featureId) === index);
|
|
37
|
-
}
|
|
38
|
-
const result = [];
|
|
39
|
-
const seen = /* @__PURE__ */ new Set();
|
|
40
|
-
const addFeature = (featureId) => {
|
|
41
|
-
if (seen.has(featureId) || isAclFeatureRemoved(featureId) || !isFeatureEnabled(featureId)) {
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
seen.add(featureId);
|
|
45
|
-
result.push(featureId);
|
|
46
|
-
};
|
|
47
|
-
for (const featureId of getConcreteFeatureIds()) {
|
|
48
|
-
if (matchesAllFeatures(filteredGrants, [featureId])) addFeature(featureId);
|
|
49
|
-
}
|
|
50
|
-
for (const featureId of filteredGrants) {
|
|
51
|
-
if (featureId === "*" || featureId.endsWith(".*")) continue;
|
|
52
|
-
addFeature(featureId);
|
|
53
|
-
}
|
|
54
|
-
return result;
|
|
55
|
-
}
|
|
56
|
-
export {
|
|
57
|
-
authorizeFeatures,
|
|
58
|
-
getRemovedAclFeatureIds,
|
|
59
|
-
isAclFeatureRemoved,
|
|
60
|
-
resolveEffectiveFeatures
|
|
61
|
-
};
|
|
62
|
-
//# sourceMappingURL=featurePolicy.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/security/featurePolicy.ts"],
|
|
4
|
-
"sourcesContent": ["import { hasAllFeatures as matchesAllFeatures } from './features'\nimport {\n filterGrantsByEnabledModules,\n getConcreteFeatureIds,\n getEnabledModuleIds,\n getOwningModuleId,\n hasEnabledModulesRegistry,\n} from './enabledModulesRegistry'\nimport { composeAclFeatureOverrides } from '../modules/overrides'\n\nexport type FeaturePolicySubject = {\n grantedFeatures: readonly string[]\n unrestricted?: boolean\n scopeAllowed?: boolean\n}\n\nexport function getRemovedAclFeatureIds(): string[] {\n return Object.entries(composeAclFeatureOverrides())\n .filter(([, override]) => override === null)\n .map(([featureId]) => featureId)\n}\n\nexport function isAclFeatureRemoved(featureId: string): boolean {\n return composeAclFeatureOverrides()[featureId] === null\n}\n\nfunction isFeatureEnabled(featureId: string): boolean {\n if (!hasEnabledModulesRegistry()) return true\n const enabledModuleIds = getEnabledModuleIds()\n return enabledModuleIds.includes(getOwningModuleId(featureId))\n}\n\nexport function authorizeFeatures(\n required: readonly string[],\n subject: FeaturePolicySubject,\n): boolean {\n if (required.length === 0) return true\n if (subject.scopeAllowed === false) return false\n if (required.some((featureId) => (\n isAclFeatureRemoved(featureId) || !isFeatureEnabled(featureId)\n ))) {\n return false\n }\n if (subject.unrestricted === true) return true\n return matchesAllFeatures(\n filterGrantsByEnabledModules(subject.grantedFeatures),\n required,\n )\n}\n\nexport function resolveEffectiveFeatures(\n grantedFeatures: readonly string[],\n): string[] {\n const filteredGrants = filterGrantsByEnabledModules(grantedFeatures)\n .filter((featureId) => !isAclFeatureRemoved(featureId))\n\n if (!hasEnabledModulesRegistry()) {\n return filteredGrants.filter((featureId, index, features) => (\n featureId !== '*'\n && !featureId.endsWith('.*')\n && features.indexOf(featureId) === index\n ))\n }\n\n const result: string[] = []\n const seen = new Set<string>()\n const addFeature = (featureId: string) => {\n if (\n seen.has(featureId)\n || isAclFeatureRemoved(featureId)\n || !isFeatureEnabled(featureId)\n ) {\n return\n }\n seen.add(featureId)\n result.push(featureId)\n }\n\n for (const featureId of getConcreteFeatureIds()) {\n if (matchesAllFeatures(filteredGrants, [featureId])) addFeature(featureId)\n }\n\n for (const featureId of filteredGrants) {\n if (featureId === '*' || featureId.endsWith('.*')) continue\n addFeature(featureId)\n }\n\n return result\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,kBAAkB,0BAA0B;AACrD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,kCAAkC;AAQpC,SAAS,0BAAoC;AAClD,SAAO,OAAO,QAAQ,2BAA2B,CAAC,EAC/C,OAAO,CAAC,CAAC,EAAE,QAAQ,MAAM,aAAa,IAAI,EAC1C,IAAI,CAAC,CAAC,SAAS,MAAM,SAAS;AACnC;AAEO,SAAS,oBAAoB,WAA4B;AAC9D,SAAO,2BAA2B,EAAE,SAAS,MAAM;AACrD;AAEA,SAAS,iBAAiB,WAA4B;AACpD,MAAI,CAAC,0BAA0B,EAAG,QAAO;AACzC,QAAM,mBAAmB,oBAAoB;AAC7C,SAAO,iBAAiB,SAAS,kBAAkB,SAAS,CAAC;AAC/D;AAEO,SAAS,kBACd,UACA,SACS;AACT,MAAI,SAAS,WAAW,EAAG,QAAO;AAClC,MAAI,QAAQ,iBAAiB,MAAO,QAAO;AAC3C,MAAI,SAAS,KAAK,CAAC,cACjB,oBAAoB,SAAS,KAAK,CAAC,iBAAiB,SAAS,CAC9D,GAAG;AACF,WAAO;AAAA,EACT;AACA,MAAI,QAAQ,iBAAiB,KAAM,QAAO;AAC1C,SAAO;AAAA,IACL,6BAA6B,QAAQ,eAAe;AAAA,IACpD;AAAA,EACF;AACF;AAEO,SAAS,yBACd,iBACU;AACV,QAAM,iBAAiB,6BAA6B,eAAe,EAChE,OAAO,CAAC,cAAc,CAAC,oBAAoB,SAAS,CAAC;AAExD,MAAI,CAAC,0BAA0B,GAAG;AAChC,WAAO,eAAe,OAAO,CAAC,WAAW,OAAO,aAC9C,cAAc,OACX,CAAC,UAAU,SAAS,IAAI,KACxB,SAAS,QAAQ,SAAS,MAAM,KACpC;AAAA,EACH;AAEA,QAAM,SAAmB,CAAC;AAC1B,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,aAAa,CAAC,cAAsB;AACxC,QACE,KAAK,IAAI,SAAS,KACf,oBAAoB,SAAS,KAC7B,CAAC,iBAAiB,SAAS,GAC9B;AACA;AAAA,IACF;AACA,SAAK,IAAI,SAAS;AAClB,WAAO,KAAK,SAAS;AAAA,EACvB;AAEA,aAAW,aAAa,sBAAsB,GAAG;AAC/C,QAAI,mBAAmB,gBAAgB,CAAC,SAAS,CAAC,EAAG,YAAW,SAAS;AAAA,EAC3E;AAEA,aAAW,aAAa,gBAAgB;AACtC,QAAI,cAAc,OAAO,UAAU,SAAS,IAAI,EAAG;AACnD,eAAW,SAAS;AAAA,EACtB;AAEA,SAAO;AACT;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
// Pure source emitter for the build-time replacement of src/lib/version.ts.
|
|
2
|
-
// Kept side-effect free, and CommonJS so both build.mjs and the Jest suite can load it.
|
|
3
|
-
|
|
4
|
-
const { Project, QuoteKind, ScriptKind, StructureKind, VariableDeclarationKind } = require('ts-morph')
|
|
5
|
-
|
|
6
|
-
const VIRTUAL_FILE_NAME = 'version.ts'
|
|
7
|
-
const BANNER = '// Build-time generated version\n'
|
|
8
|
-
|
|
9
|
-
let sharedProject = null
|
|
10
|
-
|
|
11
|
-
function getProject() {
|
|
12
|
-
if (!sharedProject) {
|
|
13
|
-
sharedProject = new Project({
|
|
14
|
-
useInMemoryFileSystem: true,
|
|
15
|
-
manipulationSettings: { quoteKind: QuoteKind.Single },
|
|
16
|
-
})
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return sharedProject
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function assertNoSyntacticDiagnostics(project, sourceFile) {
|
|
23
|
-
const diagnostics = project.getProgram().getSyntacticDiagnostics(sourceFile)
|
|
24
|
-
if (diagnostics.length === 0) return
|
|
25
|
-
|
|
26
|
-
const details = diagnostics
|
|
27
|
-
.map((diagnostic) => ` line ${diagnostic.getLineNumber() ?? 0}: ${diagnostic.getMessageText()}`)
|
|
28
|
-
.join('\n')
|
|
29
|
-
throw new Error(`[internal] Generated ${VIRTUAL_FILE_NAME} is not syntactically valid:\n${details}`)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function normalizeSourceText(text) {
|
|
33
|
-
const normalized = text.replace(/\r\n/g, '\n')
|
|
34
|
-
return normalized.endsWith('\n') ? normalized : `${normalized}\n`
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function buildVersionSource(version) {
|
|
38
|
-
const project = getProject()
|
|
39
|
-
const sourceFile = project.createSourceFile(VIRTUAL_FILE_NAME, '', {
|
|
40
|
-
overwrite: true,
|
|
41
|
-
scriptKind: ScriptKind.TS,
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
sourceFile.addVariableStatement({
|
|
45
|
-
kind: StructureKind.VariableStatement,
|
|
46
|
-
isExported: true,
|
|
47
|
-
declarationKind: VariableDeclarationKind.Const,
|
|
48
|
-
declarations: [{ name: 'APP_VERSION', initializer: (writer) => writer.quote(String(version)) }],
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
sourceFile.addVariableStatement({
|
|
52
|
-
kind: StructureKind.VariableStatement,
|
|
53
|
-
isExported: true,
|
|
54
|
-
declarationKind: VariableDeclarationKind.Const,
|
|
55
|
-
declarations: [{ name: 'appVersion', initializer: 'APP_VERSION' }],
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
sourceFile.formatText({ indentSize: 2, convertTabsToSpaces: true })
|
|
59
|
-
sourceFile.insertText(0, BANNER)
|
|
60
|
-
assertNoSyntacticDiagnostics(project, sourceFile)
|
|
61
|
-
|
|
62
|
-
return normalizeSourceText(sourceFile.getFullText())
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
module.exports = { buildVersionSource }
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { Project, SyntaxKind } from 'ts-morph'
|
|
2
|
-
import { buildVersionSource } from '../../../scripts/versionSource.cjs'
|
|
3
|
-
|
|
4
|
-
function parse(source: string) {
|
|
5
|
-
const project = new Project({ useInMemoryFileSystem: true })
|
|
6
|
-
const sourceFile = project.createSourceFile('version.ts', source, { overwrite: true })
|
|
7
|
-
return { project, sourceFile }
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function exportedLiteral(source: string, name: string): string {
|
|
11
|
-
const { sourceFile } = parse(source)
|
|
12
|
-
return sourceFile.getVariableDeclarationOrThrow(name).getInitializerOrThrow().getText()
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
describe('buildVersionSource', () => {
|
|
16
|
-
it('exports both APP_VERSION and appVersion', () => {
|
|
17
|
-
const { sourceFile } = parse(buildVersionSource('1.2.3'))
|
|
18
|
-
expect([...sourceFile.getExportedDeclarations().keys()].sort()).toEqual(['APP_VERSION', 'appVersion'])
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
it('writes the supplied version as the APP_VERSION literal', () => {
|
|
22
|
-
expect(exportedLiteral(buildVersionSource('1.2.3'), 'APP_VERSION')).toBe("'1.2.3'")
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
it('aliases appVersion to APP_VERSION rather than repeating the literal', () => {
|
|
26
|
-
expect(exportedLiteral(buildVersionSource('1.2.3'), 'appVersion')).toBe('APP_VERSION')
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
it('escapes a version string that would otherwise break the literal', () => {
|
|
30
|
-
const source = buildVersionSource("0.0.0-dev'; drop")
|
|
31
|
-
const { sourceFile } = parse(source)
|
|
32
|
-
const literal = sourceFile
|
|
33
|
-
.getVariableDeclarationOrThrow('APP_VERSION')
|
|
34
|
-
.getInitializerIfKindOrThrow(SyntaxKind.StringLiteral)
|
|
35
|
-
expect(literal.getLiteralValue()).toBe("0.0.0-dev'; drop")
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
it('emits zero syntactic diagnostics', () => {
|
|
39
|
-
const { project, sourceFile } = parse(buildVersionSource('1.2.3'))
|
|
40
|
-
expect(project.getProgram().getSyntacticDiagnostics(sourceFile)).toEqual([])
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
it('keeps the build-time banner comment', () => {
|
|
44
|
-
expect(buildVersionSource('1.2.3').startsWith('// Build-time generated version\n')).toBe(true)
|
|
45
|
-
})
|
|
46
|
-
})
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import type { EnvLookup, LlmProvider, LlmCreateModelOptions } from '../llm-provider'
|
|
2
|
-
|
|
3
|
-
function makeBaseProvider(overrides: Partial<LlmProvider> = {}): LlmProvider {
|
|
4
|
-
const id = overrides.id ?? 'contract-provider'
|
|
5
|
-
const envKeys = overrides.envKeys ?? [`${id.toUpperCase()}_API_KEY`]
|
|
6
|
-
const base: LlmProvider = {
|
|
7
|
-
id,
|
|
8
|
-
name: `Contract Provider ${id}`,
|
|
9
|
-
envKeys,
|
|
10
|
-
defaultModel: 'contract-model',
|
|
11
|
-
defaultModels: [{ id: 'contract-model', name: 'Contract Model', contextWindow: 8192 }],
|
|
12
|
-
isConfigured(env?: EnvLookup): boolean {
|
|
13
|
-
const lookup = env ?? process.env
|
|
14
|
-
return envKeys.some((key) => {
|
|
15
|
-
const value = lookup[key]
|
|
16
|
-
return typeof value === 'string' && value.trim().length > 0
|
|
17
|
-
})
|
|
18
|
-
},
|
|
19
|
-
resolveApiKey(): string | null {
|
|
20
|
-
return null
|
|
21
|
-
},
|
|
22
|
-
getConfiguredEnvKey(): string {
|
|
23
|
-
return envKeys[0]
|
|
24
|
-
},
|
|
25
|
-
createModel(options: LlmCreateModelOptions) {
|
|
26
|
-
return { __kind: 'contract-model', modelId: options.modelId }
|
|
27
|
-
},
|
|
28
|
-
}
|
|
29
|
-
return { ...base, ...overrides }
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
describe('LlmProvider contract — moderation/safety-identifier additive members', () => {
|
|
33
|
-
it('treats mapEndUserIdentifier and supportsInputModeration as optional (legacy adapters)', () => {
|
|
34
|
-
const provider = makeBaseProvider()
|
|
35
|
-
expect(provider.mapEndUserIdentifier).toBeUndefined()
|
|
36
|
-
expect(provider.supportsInputModeration).toBeUndefined()
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
it('accepts an optional endUserIdentifier on createModel options without changing behavior', () => {
|
|
40
|
-
const provider = makeBaseProvider()
|
|
41
|
-
const withIdentifier: LlmCreateModelOptions = {
|
|
42
|
-
modelId: 'contract-model',
|
|
43
|
-
apiKey: 'sk-test',
|
|
44
|
-
endUserIdentifier: 'hashed-identifier',
|
|
45
|
-
}
|
|
46
|
-
expect(provider.createModel(withIdentifier)).toEqual({ __kind: 'contract-model', modelId: 'contract-model' })
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
it('exposes a providerOptions fragment when mapEndUserIdentifier is implemented', () => {
|
|
50
|
-
const provider = makeBaseProvider({
|
|
51
|
-
mapEndUserIdentifier(identifier: string) {
|
|
52
|
-
return { contract: { user_id: identifier } }
|
|
53
|
-
},
|
|
54
|
-
supportsInputModeration: true,
|
|
55
|
-
})
|
|
56
|
-
expect(provider.supportsInputModeration).toBe(true)
|
|
57
|
-
expect(provider.mapEndUserIdentifier?.('abc123')).toEqual({ contract: { user_id: 'abc123' } })
|
|
58
|
-
})
|
|
59
|
-
})
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { computeEndUserIdentifier, deriveAiSafetyIdentifierSecret } from '../safety-identifier'
|
|
2
|
-
|
|
3
|
-
const BASE_SECRET = 'unit-test-base-secret'
|
|
4
|
-
const HEX_64 = /^[0-9a-f]{64}$/
|
|
5
|
-
|
|
6
|
-
describe('safety-identifier', () => {
|
|
7
|
-
describe('deriveAiSafetyIdentifierSecret', () => {
|
|
8
|
-
it('is deterministic and memoized for a given base secret', () => {
|
|
9
|
-
const first = deriveAiSafetyIdentifierSecret(BASE_SECRET)
|
|
10
|
-
const second = deriveAiSafetyIdentifierSecret(BASE_SECRET)
|
|
11
|
-
expect(first).toBe(second)
|
|
12
|
-
expect(first).toMatch(HEX_64)
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
it('produces a different key for a different base secret', () => {
|
|
16
|
-
expect(deriveAiSafetyIdentifierSecret(BASE_SECRET)).not.toBe(
|
|
17
|
-
deriveAiSafetyIdentifierSecret('other-base-secret'),
|
|
18
|
-
)
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
it('throws when no base secret is available', () => {
|
|
22
|
-
const previous = process.env.JWT_SECRET
|
|
23
|
-
delete process.env.JWT_SECRET
|
|
24
|
-
try {
|
|
25
|
-
expect(() => deriveAiSafetyIdentifierSecret()).toThrow(/JWT_SECRET/)
|
|
26
|
-
} finally {
|
|
27
|
-
if (previous !== undefined) process.env.JWT_SECRET = previous
|
|
28
|
-
}
|
|
29
|
-
})
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
describe('computeEndUserIdentifier', () => {
|
|
33
|
-
it('is stable for the same (tenant, user) pair', () => {
|
|
34
|
-
const a = computeEndUserIdentifier('tenant-1', 'user-1', { baseSecret: BASE_SECRET })
|
|
35
|
-
const b = computeEndUserIdentifier('tenant-1', 'user-1', { baseSecret: BASE_SECRET })
|
|
36
|
-
expect(a).toBe(b)
|
|
37
|
-
expect(a).toMatch(HEX_64)
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
it('separates the same user across tenants (tenant-salted)', () => {
|
|
41
|
-
const t1 = computeEndUserIdentifier('tenant-1', 'user-1', { baseSecret: BASE_SECRET })
|
|
42
|
-
const t2 = computeEndUserIdentifier('tenant-2', 'user-1', { baseSecret: BASE_SECRET })
|
|
43
|
-
expect(t1).not.toBe(t2)
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
it('separates different users within the same tenant', () => {
|
|
47
|
-
const u1 = computeEndUserIdentifier('tenant-1', 'user-1', { baseSecret: BASE_SECRET })
|
|
48
|
-
const u2 = computeEndUserIdentifier('tenant-1', 'user-2', { baseSecret: BASE_SECRET })
|
|
49
|
-
expect(u1).not.toBe(u2)
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
it('never leaks the raw tenant or user id', () => {
|
|
53
|
-
const id = computeEndUserIdentifier('tenant-1', 'user-1', { baseSecret: BASE_SECRET })
|
|
54
|
-
expect(id).not.toContain('tenant-1')
|
|
55
|
-
expect(id).not.toContain('user-1')
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
it('treats a null tenant as an empty salt without throwing', () => {
|
|
59
|
-
const withNull = computeEndUserIdentifier(null, 'user-1', { baseSecret: BASE_SECRET })
|
|
60
|
-
const withEmpty = computeEndUserIdentifier('', 'user-1', { baseSecret: BASE_SECRET })
|
|
61
|
-
expect(withNull).toMatch(HEX_64)
|
|
62
|
-
expect(withNull).toBe(withEmpty)
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
it('throws when userId is empty', () => {
|
|
66
|
-
expect(() => computeEndUserIdentifier('tenant-1', ' ', { baseSecret: BASE_SECRET })).toThrow(
|
|
67
|
-
/userId/,
|
|
68
|
-
)
|
|
69
|
-
})
|
|
70
|
-
})
|
|
71
|
-
})
|