@lssm/lib.contracts-runtime-core 3.0.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/README.md +110 -0
- package/dist/approval.d.ts +13 -0
- package/dist/approval.js +2 -0
- package/dist/approval.test.d.ts +1 -0
- package/dist/browser/approval.js +1 -0
- package/dist/browser/collaboration.js +0 -0
- package/dist/browser/contracts-runtime-core.feature.js +1 -0
- package/dist/browser/engine/backoff.js +1 -0
- package/dist/browser/engine/cache-store.js +1 -0
- package/dist/browser/engine/conflict-registry.js +1 -0
- package/dist/browser/engine/conflict-resolution.js +1 -0
- package/dist/browser/engine/data-engine-host.js +1 -0
- package/dist/browser/engine/data-engine-test-helpers.js +1 -0
- package/dist/browser/engine/data-engine.js +1 -0
- package/dist/browser/engine/dedup.js +1 -0
- package/dist/browser/engine/events.js +1 -0
- package/dist/browser/engine/index.js +1 -0
- package/dist/browser/engine/maintenance.js +1 -0
- package/dist/browser/engine/mutation-engine.js +1 -0
- package/dist/browser/engine/mutation-queue.js +1 -0
- package/dist/browser/engine/mutation-types.js +0 -0
- package/dist/browser/engine/no-cache-store.js +1 -0
- package/dist/browser/engine/online-only-data-engine.js +1 -0
- package/dist/browser/engine/online-only-mutation-engine.js +1 -0
- package/dist/browser/engine/query-engine.js +1 -0
- package/dist/browser/engine/state.js +1 -0
- package/dist/browser/engine/types.js +0 -0
- package/dist/browser/forms-platform-csv-columns.js +1 -0
- package/dist/browser/forms-platform-csv.js +2 -0
- package/dist/browser/forms-platform-management.js +1 -0
- package/dist/browser/forms-platform-navigation.js +1 -0
- package/dist/browser/forms-platform-submission.js +1 -0
- package/dist/browser/forms-platform-types.js +1 -0
- package/dist/browser/forms-platform-utils.js +1 -0
- package/dist/browser/forms-platform.js +2 -0
- package/dist/browser/index.js +3 -0
- package/dist/browser/local-offline.js +1 -0
- package/dist/browser/observability.js +1 -0
- package/dist/browser/reachability.js +1 -0
- package/dist/browser/redaction.js +1 -0
- package/dist/browser/storage/indexeddb.web.js +1 -0
- package/dist/browser/storage/keyed-store.js +1 -0
- package/dist/browser/storage/memory.js +1 -0
- package/dist/browser/storage/port.js +1 -0
- package/dist/browser/transport.js +2 -0
- package/dist/collaboration.d.ts +57 -0
- package/dist/collaboration.js +1 -0
- package/dist/contracts-runtime-core.feature.d.ts +1 -0
- package/dist/contracts-runtime-core.feature.js +2 -0
- package/dist/engine/backoff.d.ts +16 -0
- package/dist/engine/backoff.js +2 -0
- package/dist/engine/backoff.test.d.ts +1 -0
- package/dist/engine/cache-store.d.ts +19 -0
- package/dist/engine/cache-store.js +2 -0
- package/dist/engine/conflict-registry.d.ts +10 -0
- package/dist/engine/conflict-registry.js +2 -0
- package/dist/engine/conflict-registry.test.d.ts +1 -0
- package/dist/engine/conflict-resolution.d.ts +24 -0
- package/dist/engine/conflict-resolution.js +2 -0
- package/dist/engine/conflict-resolution.test.d.ts +1 -0
- package/dist/engine/data-engine-host.d.ts +30 -0
- package/dist/engine/data-engine-host.js +2 -0
- package/dist/engine/data-engine-local-offline.test.d.ts +1 -0
- package/dist/engine/data-engine-test-helpers.d.ts +30 -0
- package/dist/engine/data-engine-test-helpers.js +2 -0
- package/dist/engine/data-engine.d.ts +46 -0
- package/dist/engine/data-engine.js +2 -0
- package/dist/engine/data-engine.test.d.ts +1 -0
- package/dist/engine/dedup.d.ts +9 -0
- package/dist/engine/dedup.js +2 -0
- package/dist/engine/events.d.ts +76 -0
- package/dist/engine/events.economic-evidence.test.d.ts +1 -0
- package/dist/engine/events.js +2 -0
- package/dist/engine/index.d.ts +17 -0
- package/dist/engine/index.js +2 -0
- package/dist/engine/maintenance.d.ts +18 -0
- package/dist/engine/maintenance.js +2 -0
- package/dist/engine/maintenance.test.d.ts +1 -0
- package/dist/engine/mutation-engine.d.ts +2 -0
- package/dist/engine/mutation-engine.js +2 -0
- package/dist/engine/mutation-queue.d.ts +16 -0
- package/dist/engine/mutation-queue.js +2 -0
- package/dist/engine/mutation-queue.test.d.ts +1 -0
- package/dist/engine/mutation-types.d.ts +118 -0
- package/dist/engine/mutation-types.js +1 -0
- package/dist/engine/no-cache-store.d.ts +3 -0
- package/dist/engine/no-cache-store.js +2 -0
- package/dist/engine/online-only-data-engine.d.ts +9 -0
- package/dist/engine/online-only-data-engine.js +2 -0
- package/dist/engine/online-only-mutation-engine.d.ts +3 -0
- package/dist/engine/online-only-mutation-engine.js +2 -0
- package/dist/engine/query-engine.d.ts +7 -0
- package/dist/engine/query-engine.js +2 -0
- package/dist/engine/query-engine.test.d.ts +1 -0
- package/dist/engine/state.d.ts +37 -0
- package/dist/engine/state.js +2 -0
- package/dist/engine/types.d.ts +70 -0
- package/dist/engine/types.js +1 -0
- package/dist/forms-platform-choice.test.d.ts +1 -0
- package/dist/forms-platform-csv-columns.d.ts +7 -0
- package/dist/forms-platform-csv-columns.js +2 -0
- package/dist/forms-platform-csv.d.ts +12 -0
- package/dist/forms-platform-csv.js +3 -0
- package/dist/forms-platform-management.d.ts +12 -0
- package/dist/forms-platform-management.js +2 -0
- package/dist/forms-platform-navigation.d.ts +2 -0
- package/dist/forms-platform-navigation.js +2 -0
- package/dist/forms-platform-submission.d.ts +7 -0
- package/dist/forms-platform-submission.js +2 -0
- package/dist/forms-platform-types.d.ts +51 -0
- package/dist/forms-platform-types.js +2 -0
- package/dist/forms-platform-utils.d.ts +6 -0
- package/dist/forms-platform-utils.js +2 -0
- package/dist/forms-platform-utils.test.d.ts +1 -0
- package/dist/forms-platform.d.ts +19 -0
- package/dist/forms-platform.js +3 -0
- package/dist/forms-platform.test.d.ts +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +4 -0
- package/dist/local-offline.d.ts +17 -0
- package/dist/local-offline.js +2 -0
- package/dist/local-offline.test.d.ts +1 -0
- package/dist/native/approval.js +130 -0
- package/dist/native/collaboration.js +0 -0
- package/dist/native/contracts-runtime-core.feature.js +17 -0
- package/dist/native/engine/backoff.js +16 -0
- package/dist/native/engine/cache-store.js +49 -0
- package/dist/native/engine/conflict-registry.js +51 -0
- package/dist/native/engine/conflict-resolution.js +81 -0
- package/dist/native/engine/data-engine-host.js +1119 -0
- package/dist/native/engine/data-engine-test-helpers.js +117 -0
- package/dist/native/engine/data-engine.js +1106 -0
- package/dist/native/engine/dedup.js +22 -0
- package/dist/native/engine/events.js +19 -0
- package/dist/native/engine/index.js +1189 -0
- package/dist/native/engine/maintenance.js +36 -0
- package/dist/native/engine/mutation-engine.js +360 -0
- package/dist/native/engine/mutation-queue.js +26 -0
- package/dist/native/engine/mutation-types.js +0 -0
- package/dist/native/engine/no-cache-store.js +15 -0
- package/dist/native/engine/online-only-data-engine.js +422 -0
- package/dist/native/engine/online-only-mutation-engine.js +70 -0
- package/dist/native/engine/query-engine.js +360 -0
- package/dist/native/engine/state.js +116 -0
- package/dist/native/engine/types.js +0 -0
- package/dist/native/forms-platform-csv-columns.js +31 -0
- package/dist/native/forms-platform-csv.js +150 -0
- package/dist/native/forms-platform-management.js +179 -0
- package/dist/native/forms-platform-navigation.js +27 -0
- package/dist/native/forms-platform-submission.js +207 -0
- package/dist/native/forms-platform-types.js +12 -0
- package/dist/native/forms-platform-utils.js +69 -0
- package/dist/native/forms-platform.js +429 -0
- package/dist/native/index.js +1879 -0
- package/dist/native/local-offline.js +81 -0
- package/dist/native/observability.js +182 -0
- package/dist/native/reachability.js +28 -0
- package/dist/native/redaction.js +44 -0
- package/dist/native/storage/async-storage.native.js +91 -0
- package/dist/native/storage/keyed-store.js +74 -0
- package/dist/native/storage/memory.js +26 -0
- package/dist/native/storage/port.js +16 -0
- package/dist/native/transport.js +27 -0
- package/dist/node/approval.js +1 -0
- package/dist/node/collaboration.js +0 -0
- package/dist/node/contracts-runtime-core.feature.js +1 -0
- package/dist/node/engine/backoff.js +1 -0
- package/dist/node/engine/cache-store.js +1 -0
- package/dist/node/engine/conflict-registry.js +1 -0
- package/dist/node/engine/conflict-resolution.js +1 -0
- package/dist/node/engine/data-engine-host.js +1 -0
- package/dist/node/engine/data-engine-test-helpers.js +1 -0
- package/dist/node/engine/data-engine.js +1 -0
- package/dist/node/engine/dedup.js +1 -0
- package/dist/node/engine/events.js +1 -0
- package/dist/node/engine/index.js +1 -0
- package/dist/node/engine/maintenance.js +1 -0
- package/dist/node/engine/mutation-engine.js +1 -0
- package/dist/node/engine/mutation-queue.js +1 -0
- package/dist/node/engine/mutation-types.js +0 -0
- package/dist/node/engine/no-cache-store.js +1 -0
- package/dist/node/engine/online-only-data-engine.js +1 -0
- package/dist/node/engine/online-only-mutation-engine.js +1 -0
- package/dist/node/engine/query-engine.js +1 -0
- package/dist/node/engine/state.js +1 -0
- package/dist/node/engine/types.js +0 -0
- package/dist/node/forms-platform-csv-columns.js +1 -0
- package/dist/node/forms-platform-csv.js +2 -0
- package/dist/node/forms-platform-management.js +1 -0
- package/dist/node/forms-platform-navigation.js +1 -0
- package/dist/node/forms-platform-submission.js +1 -0
- package/dist/node/forms-platform-types.js +1 -0
- package/dist/node/forms-platform-utils.js +1 -0
- package/dist/node/forms-platform.js +2 -0
- package/dist/node/index.js +3 -0
- package/dist/node/local-offline.js +1 -0
- package/dist/node/observability.js +1 -0
- package/dist/node/reachability.js +1 -0
- package/dist/node/redaction.js +1 -0
- package/dist/node/storage/keyed-store.js +1 -0
- package/dist/node/storage/memory.js +1 -0
- package/dist/node/storage/port.js +1 -0
- package/dist/node/transport.js +2 -0
- package/dist/observability.d.ts +34 -0
- package/dist/observability.js +2 -0
- package/dist/observability.test.d.ts +1 -0
- package/dist/reachability.d.ts +63 -0
- package/dist/reachability.js +2 -0
- package/dist/reachability.test.d.ts +1 -0
- package/dist/redaction.d.ts +34 -0
- package/dist/redaction.js +2 -0
- package/dist/redaction.test.d.ts +1 -0
- package/dist/storage/async-storage.native.d.ts +23 -0
- package/dist/storage/async-storage.native.test.d.ts +1 -0
- package/dist/storage/indexeddb.web.d.ts +13 -0
- package/dist/storage/indexeddb.web.js +2 -0
- package/dist/storage/keyed-store.d.ts +25 -0
- package/dist/storage/keyed-store.js +2 -0
- package/dist/storage/keyed-store.test.d.ts +1 -0
- package/dist/storage/memory.d.ts +11 -0
- package/dist/storage/memory.js +2 -0
- package/dist/storage/memory.test.d.ts +1 -0
- package/dist/storage/port.d.ts +34 -0
- package/dist/storage/port.js +2 -0
- package/dist/transport.d.ts +53 -0
- package/dist/transport.js +3 -0
- package/package.json +219 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// src/local-offline.ts
|
|
2
|
+
var MODES = new Set([
|
|
3
|
+
"online-only",
|
|
4
|
+
"memory-cache",
|
|
5
|
+
"offline-capable"
|
|
6
|
+
]);
|
|
7
|
+
var MODE_ALIASES = {
|
|
8
|
+
"online-only": "online-only",
|
|
9
|
+
online: "online-only",
|
|
10
|
+
disabled: "online-only",
|
|
11
|
+
false: "online-only",
|
|
12
|
+
off: "online-only",
|
|
13
|
+
"memory-cache": "memory-cache",
|
|
14
|
+
memory: "memory-cache",
|
|
15
|
+
cache: "memory-cache",
|
|
16
|
+
"offline-capable": "offline-capable",
|
|
17
|
+
offline: "offline-capable",
|
|
18
|
+
enabled: "offline-capable",
|
|
19
|
+
true: "offline-capable",
|
|
20
|
+
on: "offline-capable"
|
|
21
|
+
};
|
|
22
|
+
function isLocalOfflineMode(value) {
|
|
23
|
+
return typeof value === "string" && MODES.has(value);
|
|
24
|
+
}
|
|
25
|
+
function resolveLocalOfflinePolicy(options = {}) {
|
|
26
|
+
const fallback = options.defaultMode === undefined ? {
|
|
27
|
+
mode: "online-only",
|
|
28
|
+
source: "default",
|
|
29
|
+
reason: "No local/offline policy input was supplied."
|
|
30
|
+
} : normalizePolicy(options.defaultMode, "explicit") ?? {
|
|
31
|
+
mode: "online-only",
|
|
32
|
+
source: "explicit"
|
|
33
|
+
};
|
|
34
|
+
const env = normalizePolicy(readEnvValue(options.env), "env", {
|
|
35
|
+
allowUnknown: true
|
|
36
|
+
});
|
|
37
|
+
const flag = normalizePolicy(options.flag, "feature-flag");
|
|
38
|
+
return flag ?? env ?? fallback;
|
|
39
|
+
}
|
|
40
|
+
function readEnvValue(input) {
|
|
41
|
+
if (typeof input !== "object" || input === null)
|
|
42
|
+
return input;
|
|
43
|
+
if ("CONTRACTSPEC_LOCAL_OFFLINE" in input) {
|
|
44
|
+
return input.CONTRACTSPEC_LOCAL_OFFLINE;
|
|
45
|
+
}
|
|
46
|
+
if ("mode" in input)
|
|
47
|
+
return input;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
function normalizePolicy(input, source, options = {}) {
|
|
51
|
+
if (input === undefined || input === null || input === "")
|
|
52
|
+
return;
|
|
53
|
+
if (typeof input === "boolean") {
|
|
54
|
+
return { mode: input ? "offline-capable" : "online-only", source };
|
|
55
|
+
}
|
|
56
|
+
if (typeof input === "string") {
|
|
57
|
+
const mode = MODE_ALIASES[input.trim().toLowerCase()];
|
|
58
|
+
if (mode !== undefined)
|
|
59
|
+
return { mode, source };
|
|
60
|
+
if (options.allowUnknown)
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (typeof input === "object" && input !== null && "mode" in input) {
|
|
64
|
+
const policy = input;
|
|
65
|
+
const mode = typeof policy.mode === "string" ? MODE_ALIASES[policy.mode.trim().toLowerCase()] : undefined;
|
|
66
|
+
if (mode !== undefined) {
|
|
67
|
+
return {
|
|
68
|
+
mode,
|
|
69
|
+
source,
|
|
70
|
+
...typeof policy.reason === "string" ? { reason: policy.reason } : {}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
if (options.allowUnknown)
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
throw new Error(`Unsupported localOffline policy value: ${String(input)}`);
|
|
77
|
+
}
|
|
78
|
+
export {
|
|
79
|
+
isLocalOfflineMode,
|
|
80
|
+
resolveLocalOfflinePolicy
|
|
81
|
+
};
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// src/observability.ts
|
|
2
|
+
import { isSafeEconomicEvidenceRefId } from "@lssm/lib.contracts-spec/operations";
|
|
3
|
+
import {
|
|
4
|
+
createCounter,
|
|
5
|
+
createHistogram,
|
|
6
|
+
logger as defaultLogger
|
|
7
|
+
} from "@lssm/lib.observability";
|
|
8
|
+
|
|
9
|
+
// src/redaction.ts
|
|
10
|
+
var SECRET_KEY_PATTERN = /(?:token|secret|password|passwd|authorization|api[_-]?key|credential|cookie|session)/i;
|
|
11
|
+
var REDACTED = "[REDACTED]";
|
|
12
|
+
function redactSecrets(value) {
|
|
13
|
+
if (Array.isArray(value))
|
|
14
|
+
return value.map(redactSecrets);
|
|
15
|
+
if (value === null || typeof value !== "object")
|
|
16
|
+
return value;
|
|
17
|
+
const out = {};
|
|
18
|
+
for (const [key, child] of Object.entries(value)) {
|
|
19
|
+
out[key] = SECRET_KEY_PATTERN.test(key) ? REDACTED : redactSecrets(child);
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
var identityEncryption = {
|
|
24
|
+
encrypt: (value) => value,
|
|
25
|
+
decrypt: (value) => value
|
|
26
|
+
};
|
|
27
|
+
function hashKey(key) {
|
|
28
|
+
let hash = 2166136261;
|
|
29
|
+
for (let index = 0;index < key.length; index += 1) {
|
|
30
|
+
hash ^= key.charCodeAt(index);
|
|
31
|
+
hash = Math.imul(hash, 16777619);
|
|
32
|
+
}
|
|
33
|
+
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
34
|
+
}
|
|
35
|
+
var redactionDoc = {
|
|
36
|
+
id: "docs.contracts-runtime-core.redaction",
|
|
37
|
+
title: "Persisted-state redaction + encryption-at-rest seam",
|
|
38
|
+
summary: "Secret redaction for durable state plus an encryption-at-rest seam and a PII-safe key hash for logs.",
|
|
39
|
+
kind: "reference",
|
|
40
|
+
visibility: "internal",
|
|
41
|
+
route: "/docs/contracts-runtime-core/redaction",
|
|
42
|
+
domain: "contracts-runtime-core",
|
|
43
|
+
owners: ["@contractspec-core"],
|
|
44
|
+
tags: ["contracts-runtime-core", "security", "redaction", "encryption"],
|
|
45
|
+
body: "redactSecrets recursively strips secret-ish keys (token/secret/password/authorization/api-key/credential/cookie/session) so durable cache+queue state never persists secrets (AC12). EncryptionSeam is the encryption-at-rest seam — default identityEncryption (no-op); sensitive entities plug a real cipher transparently to the storage core. hashKey (FNV-1a) lets observability correlate on a stable key id without logging the raw query key (which embeds input/PII)."
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// src/observability.ts
|
|
49
|
+
function attachObservability(events, options = {}) {
|
|
50
|
+
const log = options.logger ?? defaultLogger;
|
|
51
|
+
const hash = options.hashKeys === false ? (key) => key : hashKey;
|
|
52
|
+
const cacheHits = createCounter("contracts_runtime_cache_hits_total", "Cache hits");
|
|
53
|
+
const cacheMisses = createCounter("contracts_runtime_cache_misses_total", "Cache misses");
|
|
54
|
+
const fetchLatency = createHistogram("contracts_runtime_fetch_latency_ms", "Fetch latency (ms)");
|
|
55
|
+
const conflicts = createCounter("contracts_runtime_conflicts_total", "Mutation conflicts");
|
|
56
|
+
const economicEvidenceRefs = createCounter("contracts_runtime_economic_evidence_refs_total", "Safe economic evidence refs attached to runtime operations");
|
|
57
|
+
const base = options.correlationId === undefined ? {} : { correlationId: options.correlationId };
|
|
58
|
+
return events.subscribe((event) => {
|
|
59
|
+
switch (event.type) {
|
|
60
|
+
case "cache-hit":
|
|
61
|
+
cacheHits.add(1);
|
|
62
|
+
log.debug("cache.hit", { ...base, key: hash(event.key) });
|
|
63
|
+
break;
|
|
64
|
+
case "cache-miss":
|
|
65
|
+
cacheMisses.add(1);
|
|
66
|
+
log.debug("cache.miss", { ...base, key: hash(event.key) });
|
|
67
|
+
break;
|
|
68
|
+
case "fetch-success":
|
|
69
|
+
fetchLatency.record(event.latencyMs);
|
|
70
|
+
log.info("fetch.success", {
|
|
71
|
+
...base,
|
|
72
|
+
key: hash(event.key),
|
|
73
|
+
latencyMs: event.latencyMs
|
|
74
|
+
});
|
|
75
|
+
break;
|
|
76
|
+
case "fetch-error":
|
|
77
|
+
fetchLatency.record(event.latencyMs);
|
|
78
|
+
log.warn("fetch.error", {
|
|
79
|
+
...base,
|
|
80
|
+
key: hash(event.key),
|
|
81
|
+
code: event.code,
|
|
82
|
+
latencyMs: event.latencyMs
|
|
83
|
+
});
|
|
84
|
+
break;
|
|
85
|
+
case "mutation-enqueued":
|
|
86
|
+
log.info("mutation.enqueued", {
|
|
87
|
+
...base,
|
|
88
|
+
id: event.id,
|
|
89
|
+
queueDepth: event.queueDepth
|
|
90
|
+
});
|
|
91
|
+
break;
|
|
92
|
+
case "mutation-replayed":
|
|
93
|
+
log.info("mutation.replayed", {
|
|
94
|
+
...base,
|
|
95
|
+
id: event.id,
|
|
96
|
+
queueDepth: event.queueDepth
|
|
97
|
+
});
|
|
98
|
+
break;
|
|
99
|
+
case "mutation-conflict":
|
|
100
|
+
conflicts.add(1, { strategy: event.strategy, outcome: event.outcome });
|
|
101
|
+
log.warn("mutation.conflict", {
|
|
102
|
+
...base,
|
|
103
|
+
id: event.id,
|
|
104
|
+
strategy: event.strategy,
|
|
105
|
+
outcome: event.outcome
|
|
106
|
+
});
|
|
107
|
+
break;
|
|
108
|
+
case "mutation-retry":
|
|
109
|
+
log.warn("mutation.retry", {
|
|
110
|
+
...base,
|
|
111
|
+
id: event.id,
|
|
112
|
+
attempt: event.attempt,
|
|
113
|
+
delayMs: event.delayMs
|
|
114
|
+
});
|
|
115
|
+
break;
|
|
116
|
+
case "mutation-failed":
|
|
117
|
+
log.error("mutation.failed", {
|
|
118
|
+
...base,
|
|
119
|
+
id: event.id,
|
|
120
|
+
code: event.code
|
|
121
|
+
});
|
|
122
|
+
break;
|
|
123
|
+
case "economic-evidence-attached": {
|
|
124
|
+
const evidenceRefs = serializeEconomicEvidenceRuntimeRefs(event.evidenceRefs);
|
|
125
|
+
economicEvidenceRefs.add(evidenceRefs.length, {
|
|
126
|
+
operationKey: event.operationKey
|
|
127
|
+
});
|
|
128
|
+
log.info("economic.evidence.attached", {
|
|
129
|
+
...base,
|
|
130
|
+
operationKey: event.operationKey,
|
|
131
|
+
evidenceRefCount: evidenceRefs.length,
|
|
132
|
+
evidenceRefs,
|
|
133
|
+
latencyMs: event.latencyMs
|
|
134
|
+
});
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
case "auth-expired":
|
|
138
|
+
log.warn("auth.expired", { ...base, id: event.id });
|
|
139
|
+
break;
|
|
140
|
+
case "queue-drained":
|
|
141
|
+
log.info("queue.drained", { ...base, remaining: event.remaining });
|
|
142
|
+
break;
|
|
143
|
+
case "gc-collected":
|
|
144
|
+
log.info("cache.gc", { ...base, evicted: event.evicted });
|
|
145
|
+
break;
|
|
146
|
+
case "revalidated":
|
|
147
|
+
log.debug("cache.revalidated", { ...base, key: hash(event.key) });
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
var ECONOMIC_EVIDENCE_RUNTIME_REF_KINDS = new Set(["usage", "cost", "budget", "replay", "projection"]);
|
|
153
|
+
function serializeEconomicEvidenceRuntimeRefs(refs) {
|
|
154
|
+
return refs.flatMap((ref) => {
|
|
155
|
+
if (typeof ref.id !== "string" || !isSafeEconomicEvidenceRefId(ref.id) || !ECONOMIC_EVIDENCE_RUNTIME_REF_KINDS.has(ref.kind)) {
|
|
156
|
+
return [];
|
|
157
|
+
}
|
|
158
|
+
return [
|
|
159
|
+
{
|
|
160
|
+
id: ref.id,
|
|
161
|
+
kind: ref.kind,
|
|
162
|
+
...typeof ref.source === "string" ? { source: ref.source } : {}
|
|
163
|
+
}
|
|
164
|
+
];
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
var observabilityDoc = {
|
|
168
|
+
id: "docs.contracts-runtime-core.observability",
|
|
169
|
+
title: "Engine observability binding (no PII)",
|
|
170
|
+
summary: "Binds the engine EngineEvents stream to the observability lib with hashed keys, safe economic evidence refs, and structured metrics.",
|
|
171
|
+
kind: "reference",
|
|
172
|
+
visibility: "internal",
|
|
173
|
+
route: "/docs/contracts-runtime-core/observability",
|
|
174
|
+
domain: "contracts-runtime-core",
|
|
175
|
+
owners: ["@contractspec-core"],
|
|
176
|
+
tags: ["contracts-runtime-core", "observability", "metrics", "logging"],
|
|
177
|
+
body: "attachObservability subscribes to the framework-neutral EngineEvents stream (emitted by the engine, zero deps) and forwards it to @lssm/lib.observability: structured logs via Logger (debug/info/warn/error) and metrics (cache hit/miss counters, fetch-latency histogram, conflict counter tagged by strategy+outcome, economic evidence ref counter). Query keys embed input, so they are hashed (FNV-1a via hashKey) before logging — never raw (AC12, no PII). economic-evidence-attached events are serialized through a deny-by-default allowlist: ref id, kind, and optional source only; raw provider receipts, prompts, payloads, and unrecognized fields are dropped before log/replay sinks consume them. Optional correlationId is attached to every entry. Lives behind the ./observability subpath so the OTel-backed lib stays out of the main entry bundle; no console.* — logging is the lib's concern."
|
|
178
|
+
};
|
|
179
|
+
export {
|
|
180
|
+
attachObservability,
|
|
181
|
+
observabilityDoc
|
|
182
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// src/reachability.ts
|
|
2
|
+
function isNetworkAllowed(signal) {
|
|
3
|
+
return signal !== "offline";
|
|
4
|
+
}
|
|
5
|
+
function createStaticReachabilitySource(signal = "online") {
|
|
6
|
+
const snapshot = { signal, since: Date.now() };
|
|
7
|
+
return {
|
|
8
|
+
getSnapshot: () => snapshot,
|
|
9
|
+
subscribe: () => () => {}
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
var reachabilityPortDoc = {
|
|
13
|
+
id: "docs.contracts-runtime-core.reachability",
|
|
14
|
+
title: "Reachability input port (tri-state)",
|
|
15
|
+
summary: "Engine-owned reachability input port; integrations feed it, the engine never imports them.",
|
|
16
|
+
kind: "reference",
|
|
17
|
+
visibility: "internal",
|
|
18
|
+
route: "/docs/contracts-runtime-core/reachability",
|
|
19
|
+
domain: "contracts-runtime-core",
|
|
20
|
+
owners: ["@contractspec-core"],
|
|
21
|
+
tags: ["contracts-runtime-core", "reachability", "offline", "port"],
|
|
22
|
+
body: "Tri-state OnlineSignal ('online' | 'restricted' | 'offline'). 'online' allows normal fetch + queue drain. 'restricted' is online-degraded: the engine fetches and drains the queue as if online but may mark results stale-eligible; it never suspends network nor activates the offline queue. Only 'offline' suspends outbound network and activates the durable offline mutation queue. The engine reads reachability solely through ReachabilitySource and MUST NOT import integrations/* — concrete sources (runtime-local networkReachability) are wired via an integration→port adapter (plan §2.3/§4.2, Architect finding 1). isNetworkAllowed mirrors runtime-local's outboundNetworkAllowed = reachability !== 'offline'. createStaticReachabilitySource('online') is the B1a default and online test fixture."
|
|
23
|
+
};
|
|
24
|
+
export {
|
|
25
|
+
createStaticReachabilitySource,
|
|
26
|
+
isNetworkAllowed,
|
|
27
|
+
reachabilityPortDoc
|
|
28
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// src/redaction.ts
|
|
2
|
+
var SECRET_KEY_PATTERN = /(?:token|secret|password|passwd|authorization|api[_-]?key|credential|cookie|session)/i;
|
|
3
|
+
var REDACTED = "[REDACTED]";
|
|
4
|
+
function redactSecrets(value) {
|
|
5
|
+
if (Array.isArray(value))
|
|
6
|
+
return value.map(redactSecrets);
|
|
7
|
+
if (value === null || typeof value !== "object")
|
|
8
|
+
return value;
|
|
9
|
+
const out = {};
|
|
10
|
+
for (const [key, child] of Object.entries(value)) {
|
|
11
|
+
out[key] = SECRET_KEY_PATTERN.test(key) ? REDACTED : redactSecrets(child);
|
|
12
|
+
}
|
|
13
|
+
return out;
|
|
14
|
+
}
|
|
15
|
+
var identityEncryption = {
|
|
16
|
+
encrypt: (value) => value,
|
|
17
|
+
decrypt: (value) => value
|
|
18
|
+
};
|
|
19
|
+
function hashKey(key) {
|
|
20
|
+
let hash = 2166136261;
|
|
21
|
+
for (let index = 0;index < key.length; index += 1) {
|
|
22
|
+
hash ^= key.charCodeAt(index);
|
|
23
|
+
hash = Math.imul(hash, 16777619);
|
|
24
|
+
}
|
|
25
|
+
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
26
|
+
}
|
|
27
|
+
var redactionDoc = {
|
|
28
|
+
id: "docs.contracts-runtime-core.redaction",
|
|
29
|
+
title: "Persisted-state redaction + encryption-at-rest seam",
|
|
30
|
+
summary: "Secret redaction for durable state plus an encryption-at-rest seam and a PII-safe key hash for logs.",
|
|
31
|
+
kind: "reference",
|
|
32
|
+
visibility: "internal",
|
|
33
|
+
route: "/docs/contracts-runtime-core/redaction",
|
|
34
|
+
domain: "contracts-runtime-core",
|
|
35
|
+
owners: ["@contractspec-core"],
|
|
36
|
+
tags: ["contracts-runtime-core", "security", "redaction", "encryption"],
|
|
37
|
+
body: "redactSecrets recursively strips secret-ish keys (token/secret/password/authorization/api-key/credential/cookie/session) so durable cache+queue state never persists secrets (AC12). EncryptionSeam is the encryption-at-rest seam — default identityEncryption (no-op); sensitive entities plug a real cipher transparently to the storage core. hashKey (FNV-1a) lets observability correlate on a stable key id without logging the raw query key (which embeds input/PII)."
|
|
38
|
+
};
|
|
39
|
+
export {
|
|
40
|
+
hashKey,
|
|
41
|
+
identityEncryption,
|
|
42
|
+
redactSecrets,
|
|
43
|
+
redactionDoc
|
|
44
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// src/redaction.ts
|
|
2
|
+
var SECRET_KEY_PATTERN = /(?:token|secret|password|passwd|authorization|api[_-]?key|credential|cookie|session)/i;
|
|
3
|
+
var REDACTED = "[REDACTED]";
|
|
4
|
+
function redactSecrets(value) {
|
|
5
|
+
if (Array.isArray(value))
|
|
6
|
+
return value.map(redactSecrets);
|
|
7
|
+
if (value === null || typeof value !== "object")
|
|
8
|
+
return value;
|
|
9
|
+
const out = {};
|
|
10
|
+
for (const [key, child] of Object.entries(value)) {
|
|
11
|
+
out[key] = SECRET_KEY_PATTERN.test(key) ? REDACTED : redactSecrets(child);
|
|
12
|
+
}
|
|
13
|
+
return out;
|
|
14
|
+
}
|
|
15
|
+
var identityEncryption = {
|
|
16
|
+
encrypt: (value) => value,
|
|
17
|
+
decrypt: (value) => value
|
|
18
|
+
};
|
|
19
|
+
function hashKey(key) {
|
|
20
|
+
let hash = 2166136261;
|
|
21
|
+
for (let index = 0;index < key.length; index += 1) {
|
|
22
|
+
hash ^= key.charCodeAt(index);
|
|
23
|
+
hash = Math.imul(hash, 16777619);
|
|
24
|
+
}
|
|
25
|
+
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
26
|
+
}
|
|
27
|
+
var redactionDoc = {
|
|
28
|
+
id: "docs.contracts-runtime-core.redaction",
|
|
29
|
+
title: "Persisted-state redaction + encryption-at-rest seam",
|
|
30
|
+
summary: "Secret redaction for durable state plus an encryption-at-rest seam and a PII-safe key hash for logs.",
|
|
31
|
+
kind: "reference",
|
|
32
|
+
visibility: "internal",
|
|
33
|
+
route: "/docs/contracts-runtime-core/redaction",
|
|
34
|
+
domain: "contracts-runtime-core",
|
|
35
|
+
owners: ["@contractspec-core"],
|
|
36
|
+
tags: ["contracts-runtime-core", "security", "redaction", "encryption"],
|
|
37
|
+
body: "redactSecrets recursively strips secret-ish keys (token/secret/password/authorization/api-key/credential/cookie/session) so durable cache+queue state never persists secrets (AC12). EncryptionSeam is the encryption-at-rest seam — default identityEncryption (no-op); sensitive entities plug a real cipher transparently to the storage core. hashKey (FNV-1a) lets observability correlate on a stable key id without logging the raw query key (which embeds input/PII)."
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// src/storage/keyed-store.ts
|
|
41
|
+
function createKeyedStorage(driver, options = {}) {
|
|
42
|
+
const encryption = options.encryption ?? identityEncryption;
|
|
43
|
+
const redact = options.redact;
|
|
44
|
+
const prefix = options.namespace ?? "";
|
|
45
|
+
const toDriverKey = (key) => `${prefix}${key}`;
|
|
46
|
+
const fromDriverKey = (key) => prefix !== "" && key.startsWith(prefix) ? key.slice(prefix.length) : key;
|
|
47
|
+
return {
|
|
48
|
+
async get(key) {
|
|
49
|
+
const raw = await driver.getItem(toDriverKey(key));
|
|
50
|
+
if (raw === null)
|
|
51
|
+
return;
|
|
52
|
+
const json = await encryption.decrypt(raw);
|
|
53
|
+
return JSON.parse(json);
|
|
54
|
+
},
|
|
55
|
+
async set(key, value) {
|
|
56
|
+
const json = JSON.stringify(redact === undefined ? value : redact(value));
|
|
57
|
+
await driver.setItem(toDriverKey(key), await encryption.encrypt(json));
|
|
58
|
+
},
|
|
59
|
+
async remove(key) {
|
|
60
|
+
await driver.removeItem(toDriverKey(key));
|
|
61
|
+
},
|
|
62
|
+
async keys(filter) {
|
|
63
|
+
const raw = await driver.keys();
|
|
64
|
+
const scoped = prefix === "" ? raw : raw.filter((key) => key.startsWith(prefix)).map(fromDriverKey);
|
|
65
|
+
return filter === undefined ? scoped : scoped.filter((key) => key.startsWith(filter));
|
|
66
|
+
},
|
|
67
|
+
async clear() {
|
|
68
|
+
await driver.clear();
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/storage/async-storage.native.ts
|
|
74
|
+
function asyncStorageDriver(store) {
|
|
75
|
+
return {
|
|
76
|
+
getItem: (key) => store.getItem(key),
|
|
77
|
+
setItem: (key, value) => store.setItem(key, value),
|
|
78
|
+
removeItem: (key) => store.removeItem(key),
|
|
79
|
+
keys: async () => [...await store.getAllKeys()],
|
|
80
|
+
clear: () => store.clear()
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function createAsyncStorage(store, options = {}) {
|
|
84
|
+
return createKeyedStorage(asyncStorageDriver(store), {
|
|
85
|
+
redact: redactSecrets,
|
|
86
|
+
...options
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
export {
|
|
90
|
+
createAsyncStorage
|
|
91
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// src/redaction.ts
|
|
2
|
+
var SECRET_KEY_PATTERN = /(?:token|secret|password|passwd|authorization|api[_-]?key|credential|cookie|session)/i;
|
|
3
|
+
var REDACTED = "[REDACTED]";
|
|
4
|
+
function redactSecrets(value) {
|
|
5
|
+
if (Array.isArray(value))
|
|
6
|
+
return value.map(redactSecrets);
|
|
7
|
+
if (value === null || typeof value !== "object")
|
|
8
|
+
return value;
|
|
9
|
+
const out = {};
|
|
10
|
+
for (const [key, child] of Object.entries(value)) {
|
|
11
|
+
out[key] = SECRET_KEY_PATTERN.test(key) ? REDACTED : redactSecrets(child);
|
|
12
|
+
}
|
|
13
|
+
return out;
|
|
14
|
+
}
|
|
15
|
+
var identityEncryption = {
|
|
16
|
+
encrypt: (value) => value,
|
|
17
|
+
decrypt: (value) => value
|
|
18
|
+
};
|
|
19
|
+
function hashKey(key) {
|
|
20
|
+
let hash = 2166136261;
|
|
21
|
+
for (let index = 0;index < key.length; index += 1) {
|
|
22
|
+
hash ^= key.charCodeAt(index);
|
|
23
|
+
hash = Math.imul(hash, 16777619);
|
|
24
|
+
}
|
|
25
|
+
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
26
|
+
}
|
|
27
|
+
var redactionDoc = {
|
|
28
|
+
id: "docs.contracts-runtime-core.redaction",
|
|
29
|
+
title: "Persisted-state redaction + encryption-at-rest seam",
|
|
30
|
+
summary: "Secret redaction for durable state plus an encryption-at-rest seam and a PII-safe key hash for logs.",
|
|
31
|
+
kind: "reference",
|
|
32
|
+
visibility: "internal",
|
|
33
|
+
route: "/docs/contracts-runtime-core/redaction",
|
|
34
|
+
domain: "contracts-runtime-core",
|
|
35
|
+
owners: ["@contractspec-core"],
|
|
36
|
+
tags: ["contracts-runtime-core", "security", "redaction", "encryption"],
|
|
37
|
+
body: "redactSecrets recursively strips secret-ish keys (token/secret/password/authorization/api-key/credential/cookie/session) so durable cache+queue state never persists secrets (AC12). EncryptionSeam is the encryption-at-rest seam — default identityEncryption (no-op); sensitive entities plug a real cipher transparently to the storage core. hashKey (FNV-1a) lets observability correlate on a stable key id without logging the raw query key (which embeds input/PII)."
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// src/storage/keyed-store.ts
|
|
41
|
+
function createKeyedStorage(driver, options = {}) {
|
|
42
|
+
const encryption = options.encryption ?? identityEncryption;
|
|
43
|
+
const redact = options.redact;
|
|
44
|
+
const prefix = options.namespace ?? "";
|
|
45
|
+
const toDriverKey = (key) => `${prefix}${key}`;
|
|
46
|
+
const fromDriverKey = (key) => prefix !== "" && key.startsWith(prefix) ? key.slice(prefix.length) : key;
|
|
47
|
+
return {
|
|
48
|
+
async get(key) {
|
|
49
|
+
const raw = await driver.getItem(toDriverKey(key));
|
|
50
|
+
if (raw === null)
|
|
51
|
+
return;
|
|
52
|
+
const json = await encryption.decrypt(raw);
|
|
53
|
+
return JSON.parse(json);
|
|
54
|
+
},
|
|
55
|
+
async set(key, value) {
|
|
56
|
+
const json = JSON.stringify(redact === undefined ? value : redact(value));
|
|
57
|
+
await driver.setItem(toDriverKey(key), await encryption.encrypt(json));
|
|
58
|
+
},
|
|
59
|
+
async remove(key) {
|
|
60
|
+
await driver.removeItem(toDriverKey(key));
|
|
61
|
+
},
|
|
62
|
+
async keys(filter) {
|
|
63
|
+
const raw = await driver.keys();
|
|
64
|
+
const scoped = prefix === "" ? raw : raw.filter((key) => key.startsWith(prefix)).map(fromDriverKey);
|
|
65
|
+
return filter === undefined ? scoped : scoped.filter((key) => key.startsWith(filter));
|
|
66
|
+
},
|
|
67
|
+
async clear() {
|
|
68
|
+
await driver.clear();
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
export {
|
|
73
|
+
createKeyedStorage
|
|
74
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// src/storage/memory.ts
|
|
2
|
+
function createMemoryStorage() {
|
|
3
|
+
const store = new Map;
|
|
4
|
+
return {
|
|
5
|
+
get: async (key) => {
|
|
6
|
+
const value = store.get(key);
|
|
7
|
+
return value === undefined ? undefined : structuredClone(value);
|
|
8
|
+
},
|
|
9
|
+
set: async (key, value) => {
|
|
10
|
+
store.set(key, structuredClone(value));
|
|
11
|
+
},
|
|
12
|
+
remove: async (key) => {
|
|
13
|
+
store.delete(key);
|
|
14
|
+
},
|
|
15
|
+
keys: async (prefix) => {
|
|
16
|
+
const all = [...store.keys()];
|
|
17
|
+
return prefix === undefined ? all : all.filter((key) => key.startsWith(prefix));
|
|
18
|
+
},
|
|
19
|
+
clear: async () => {
|
|
20
|
+
store.clear();
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export {
|
|
25
|
+
createMemoryStorage
|
|
26
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// src/storage/port.ts
|
|
2
|
+
var storagePortDoc = {
|
|
3
|
+
id: "docs.contracts-runtime-core.storage-port",
|
|
4
|
+
title: "Durable storage port (I/O-free contract)",
|
|
5
|
+
summary: "Value-agnostic durable key/value port; platform impls own serialization, redaction, and encryption-at-rest.",
|
|
6
|
+
kind: "reference",
|
|
7
|
+
visibility: "internal",
|
|
8
|
+
route: "/docs/contracts-runtime-core/storage-port",
|
|
9
|
+
domain: "contracts-runtime-core",
|
|
10
|
+
owners: ["@contractspec-core"],
|
|
11
|
+
tags: ["contracts-runtime-core", "storage", "persistence", "port"],
|
|
12
|
+
body: "Durable key/value StoragePort: get/set/remove/keys/clear. Intentionally value-agnostic — the engine layers CacheEntry and MutationQueueEntry shapes on top and namespaces by key prefix. keys(prefix?) supports GC and offline-queue scans. Implementations persist JSON-serializable values and own serialization, per-contract secret redaction, and the encryption-at-rest seam (AC12). B1a ships an in-memory default (createMemoryStorage); IndexedDB (web) and SQLite/AsyncStorage (native) platform variants land in B1b/#7 behind this same contract."
|
|
13
|
+
};
|
|
14
|
+
export {
|
|
15
|
+
storagePortDoc
|
|
16
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// src/transport.ts
|
|
2
|
+
var transportPortDoc = {
|
|
3
|
+
id: "docs.contracts-runtime-core.transport",
|
|
4
|
+
title: "Transport port + absorbed data-transmission concept map",
|
|
5
|
+
summary: "One Transport port carries the canonical protocol; each former data-transmission concept has an explicit home.",
|
|
6
|
+
kind: "reference",
|
|
7
|
+
visibility: "internal",
|
|
8
|
+
route: "/docs/contracts-runtime-core/transport",
|
|
9
|
+
domain: "contracts-runtime-core",
|
|
10
|
+
owners: ["@contractspec-core"],
|
|
11
|
+
tags: ["contracts-runtime-core", "transport", "data-transmission", "port"],
|
|
12
|
+
body: [
|
|
13
|
+
"The single Transport port: execute(envelope, ctx) → Promise<ContractResult<QueryResultEnvelope>>, plus an optional subscribe(tags, handler) realtime seam. REST/MCP/WebSocket/in-memory are interchangeable adapters behind it.",
|
|
14
|
+
"",
|
|
15
|
+
"Absorbed data-transmission concept map (each former concept has exactly one explicit home — no parallel transport types):",
|
|
16
|
+
"- idempotency → ContractResult success field (ContractResultIdempotency{key,replayed?,firstSeenAt?} on ContractSuccess.idempotency). NOT a transport argument.",
|
|
17
|
+
"- subscriptions → Transport.subscribe(tags, handler) seam (no spec type, by design); the version token doubles as the realtime invalidation token. Full delivery is a follow-on; the realtime worker migrates onto this seam in Phase 7.",
|
|
18
|
+
"- invalidation → the InvalidationTag{entity,id?} vocabulary (contracts-spec/query/invalidation) shared by optimistic updates, offline-queue replay, and realtime broadcast; mutations declare the tags they invalidate.",
|
|
19
|
+
"- consistency levels → the typed QueryEnvelope.consistency request field and ContractSuccess.consistency applied field (QueryConsistency = eventual | read-your-writes | strong, default read-your-writes). Typed, never prose. Subsumes the former DataFetchConsistency incl. read-your-writes.",
|
|
20
|
+
"",
|
|
21
|
+
"TransportContext.principal is opaque and carried end-to-end unchanged (AC14, no-strip); policy.auth enforcement stays in authos/identity-rbac."
|
|
22
|
+
].join(`
|
|
23
|
+
`)
|
|
24
|
+
};
|
|
25
|
+
export {
|
|
26
|
+
transportPortDoc
|
|
27
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{OPERATION_EXECUTION_EFFECTS as f}from"@lssm/lib.contracts-spec/operations";function l(e=()=>new Date){let n=new Map;return{async consume(r,t){let o=e().getTime();for(let[p,a]of n)if(a<=o)n.delete(p);if(n.has(r))return!1;return n.set(r,Date.parse(t)),!0}}}function b(e={}){let n=e.now??(()=>new Date),r=e.nonceStore??l(n);return{async authorize(t){let o=await m(t,n(),e.verifyEvidence);if(o)return o;if(!await r.consume(t.receipt.nonce,t.receipt.expiresAt))return{effect:"deny",reason:"replayed_receipt"};return{effect:"allow",receiptId:t.receipt.id,nonce:t.receipt.nonce}}}}async function d(e){let n=new TextEncoder().encode(s(e)),r=await globalThis.crypto.subtle.digest("SHA-256",n);return`sha256:${Array.from(new Uint8Array(r),(t)=>t.toString(16).padStart(2,"0")).join("")}`}async function m(e,n,r){let{receipt:t}=e,o=Date.parse(t.issuedAt),p=Date.parse(t.expiresAt);if(!t.id||!t.nonce||!t.issuer||!t.evidenceRef||!y(t.subject)||t.effects.some((c)=>!f.includes(c))||!Number.isFinite(o)||!Number.isFinite(p)||p<=o)return i("malformed_receipt");if(p<=n.getTime())return i("expired_receipt");if(o>n.getTime())return i("not_yet_valid");if(!u(t.subject,e.subject,["actor","userId"]))return i("wrong_subject");if(!u(t.subject,e.subject,["tenantId","workspaceId","organizationId"]))return i("wrong_tenant");if(t.operation.key!==e.operation.key||t.operation.version!==e.operation.version)return i("wrong_operation");if(t.inputDigest!==await d(e.input))return i("wrong_input");let a=new Set(t.effects);if(e.requiredEffects.some((c)=>!a.has(c)))return i("insufficient_effects");if(!A(t,e))return i("insufficient_cost_ceiling");if(r&&!await r(t))return i("invalid_evidence");return}function u(e,n,r){return r.every((t)=>{let o=n[t];return o==null||e[t]===o})}function A(e,n){if(!n.requestedCost)return!0;if(!Number.isFinite(n.requestedCost.amount)||n.requestedCost.amount<0)return!1;let r=e.costCeiling;if(!r||!Number.isFinite(r.amount)||r.amount<0)return!1;return r.amount>=n.requestedCost.amount&&(!n.requestedCost.currency||r.currency===n.requestedCost.currency)&&(!n.requestedCost.unit||r.unit===n.requestedCost.unit)}function y(e){return Object.values(e).some((n)=>typeof n==="string"&&n.length>0)}function i(e){return{effect:"deny",reason:e}}function s(e){if(e===null||typeof e==="string"||typeof e==="boolean")return JSON.stringify(e);if(typeof e==="number"){if(!Number.isFinite(e))throw TypeError("Approval input is not JSON.");return JSON.stringify(e)}if(Array.isArray(e))return`[${e.map(s).join(",")}]`;if(e instanceof Date)return JSON.stringify(e.toISOString());if(typeof e==="object")return`{${Object.entries(e).filter(([,r])=>r!==void 0).sort(([r],[t])=>r.localeCompare(t)).map(([r,t])=>`${JSON.stringify(r)}:${s(t)}`).join(",")}}`;throw TypeError("Approval input is not JSON.")}export{l as createMemoryApprovalNonceStore,b as createOperationApprovalPort,d as operationApprovalInputDigest};
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{defineFeature as e}from"@lssm/lib.contracts-spec/features";var r=e({meta:{key:"libs.contracts-runtime-core",version:"1.0.0",title:"Contracts Runtime Core",description:"Framework-neutral in-house query/cache/offline engine and Transport port for ContractSpec data fetching.",domain:"contracts-runtime-core",owners:["@contractspec-core"],tags:["package","libs","contracts-runtime-core"],stability:"experimental"}});export{r as ContractsRuntimeCoreFeature};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function M(n,t={}){let a=t.baseMs??200,e=t.factor??2,r=t.maxMs??30000,c=t.jitter??Math.random,o=Math.max(0,n-1),s=Math.min(r,a*e**o);return Math.round(s*(0.5+c()*0.5))}export{M as backoffDelayMs};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{invalidationTagMatches as h}from"@lssm/lib.contracts-spec/query/invalidation";var c="cache:";function l(e){let i=new Map,a=(t)=>`${c}${t}`;return{read:(t)=>e.get(a(t)),write:async(t)=>{i.set(t.key,t.tags),await e.set(a(t.key),t)},remove:async(t)=>{i.delete(t),await e.remove(a(t))},entries:async()=>{let t=await e.keys(c),r=[];for(let o of t){let n=await e.get(o);if(n!==void 0)r.push(n)}return r},invalidate:async(t,r)=>{let o=[];for(let[n,y]of i.entries()){if(!y.some((d)=>t.some((m)=>h(d,m))))continue;let s=await e.get(a(n));if(s===void 0)continue;await e.set(a(n),{...s,status:"stale",staleAt:r}),o.push(n)}return o}}}export{l as createCacheStore};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function i(o){let{serverData:e,localData:r}=o,t=o.mergeFields??[];if(typeof e!=="object"||e===null||typeof r!=="object"||r===null)return{outcome:"surface"};let s={...e},c=r;for(let n of t)if(n in c)s[n]=c[n];return{outcome:"merged",data:s}}function l(){let o=new Map;return{register(e,r){o.set(e,r)},has(e){return o.has(e)},async resolve(e){switch(e.strategy){case"server-wins":return{outcome:"use-server",data:e.serverData};case"client-wins":return{outcome:"use-client",data:e.localData};case"field-merge":return i(e);case"surface":return{outcome:"surface"};default:{let r=e.policy.resolver,t=r===void 0?void 0:o.get(r);if(t===void 0)return{outcome:"surface"};return t(e)}}}}}export{l as createConflictResolverRegistry};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{defaultConflictPolicy as u}from"@lssm/lib.contracts-spec/query/conflict-policy";async function l(t,o){for(let e of t)if(e.previous===void 0)await o.remove(e.key);else await o.write(e.previous)}async function m(t,o,e){let i=t.conflictPolicy??u(),n=t.rollback[0]?.key,r=n===void 0?void 0:await e.cache.read(n),s=o.metadata?.serverData,a=await e.resolvers.resolve({strategy:i.strategy,policy:i,localData:r?.data,serverData:s,localVersion:t.versionToken,serverVersion:o.versionToken,mergeFields:i.mergeFields});switch(e.events.emit({type:"mutation-conflict",id:t.id,strategy:i.strategy,outcome:a.outcome}),a.outcome){case"use-server":{await l(t.rollback,e.cache),await e.cache.invalidate(t.tags,e.now());for(let c of t.rollback)await e.queryEngine?.refresh(c.key);return"resolved"}case"use-client":return await e.queue.update({...t,versionToken:o.versionToken,status:"pending"}),"re-enqueued";case"merged":{if(n!==void 0&&r!==void 0)await e.cache.write({...r,data:a.data,versionToken:o.versionToken,status:"warm",storedAt:e.now()}),await e.queryEngine?.refresh(n);return await e.queue.update({...t,versionToken:o.versionToken,status:"pending"}),"re-enqueued"}default:{if(n!==void 0)await e.queryEngine?.emitConflict(n,{versionToken:o.versionToken,serverData:s,clientData:r?.data,strategy:i.strategy});return"resolved"}}}export{m as resolveQueuedConflict,l as restoreSnapshots};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var ue=new Set(["online-only","memory-cache","offline-capable"]),Y={"online-only":"online-only",online:"online-only",disabled:"online-only",false:"online-only",off:"online-only","memory-cache":"memory-cache",memory:"memory-cache",cache:"memory-cache","offline-capable":"offline-capable",offline:"offline-capable",enabled:"offline-capable",true:"offline-capable",on:"offline-capable"};function ke(e){return typeof e==="string"&&ue.has(e)}function I(e={}){let t=e.defaultMode===void 0?{mode:"online-only",source:"default",reason:"No local/offline policy input was supplied."}:z(e.defaultMode,"explicit")??{mode:"online-only",source:"explicit"},n=z(le(e.env),"env",{allowUnknown:!0});return z(e.flag,"feature-flag")??n??t}function le(e){if(typeof e!=="object"||e===null)return e;if("CONTRACTSPEC_LOCAL_OFFLINE"in e)return e.CONTRACTSPEC_LOCAL_OFFLINE;if("mode"in e)return e;return}function z(e,t,n={}){if(e===void 0||e===null||e==="")return;if(typeof e==="boolean")return{mode:e?"offline-capable":"online-only",source:t};if(typeof e==="string"){let a=Y[e.trim().toLowerCase()];if(a!==void 0)return{mode:a,source:t};if(n.allowUnknown)return}if(typeof e==="object"&&e!==null&&"mode"in e){let a=e,i=typeof a.mode==="string"?Y[a.mode.trim().toLowerCase()]:void 0;if(i!==void 0)return{mode:i,source:t,...typeof a.reason==="string"?{reason:a.reason}:{}};if(n.allowUnknown)return}throw Error(`Unsupported localOffline policy value: ${String(e)}`)}function C(e){return e!=="offline"}function R(e="online"){let t={signal:e,since:Date.now()};return{getSnapshot:()=>t,subscribe:()=>()=>{}}}var Me={id:"docs.contracts-runtime-core.reachability",title:"Reachability input port (tri-state)",summary:"Engine-owned reachability input port; integrations feed it, the engine never imports them.",kind:"reference",visibility:"internal",route:"/docs/contracts-runtime-core/reachability",domain:"contracts-runtime-core",owners:["@contractspec-core"],tags:["contracts-runtime-core","reachability","offline","port"],body:"Tri-state OnlineSignal ('online' | 'restricted' | 'offline'). 'online' allows normal fetch + queue drain. 'restricted' is online-degraded: the engine fetches and drains the queue as if online but may mark results stale-eligible; it never suspends network nor activates the offline queue. Only 'offline' suspends outbound network and activates the durable offline mutation queue. The engine reads reachability solely through ReachabilitySource and MUST NOT import integrations/* — concrete sources (runtime-local networkReachability) are wired via an integration→port adapter (plan §2.3/§4.2, Architect finding 1). isNetworkAllowed mirrors runtime-local's outboundNetworkAllowed = reachability !== 'offline'. createStaticReachabilitySource('online') is the B1a default and online test fixture."};function _(){let e=new Map;return{get:async(t)=>{let n=e.get(t);return n===void 0?void 0:structuredClone(n)},set:async(t,n)=>{e.set(t,structuredClone(n))},remove:async(t)=>{e.delete(t)},keys:async(t)=>{let n=[...e.keys()];return t===void 0?n:n.filter((a)=>a.startsWith(t))},clear:async()=>{e.clear()}}}import{invalidationTagMatches as fe}from"@lssm/lib.contracts-spec/query/invalidation";var Z="cache:";function U(e){let t=new Map,n=(a)=>`${Z}${a}`;return{read:(a)=>e.get(n(a)),write:async(a)=>{t.set(a.key,a.tags),await e.set(n(a.key),a)},remove:async(a)=>{t.delete(a),await e.remove(n(a))},entries:async()=>{let a=await e.keys(Z),i=[];for(let u of a){let r=await e.get(u);if(r!==void 0)i.push(r)}return i},invalidate:async(a,i)=>{let u=[];for(let[r,y]of t.entries()){if(!y.some((h)=>a.some((E)=>fe(h,E))))continue;let T=await e.get(n(r));if(T===void 0)continue;await e.set(n(r),{...T,status:"stale",staleAt:i}),u.push(r)}return u}}}function de(e){let{serverData:t,localData:n}=e,a=e.mergeFields??[];if(typeof t!=="object"||t===null||typeof n!=="object"||n===null)return{outcome:"surface"};let i={...t},u=n;for(let r of a)if(r in u)i[r]=u[r];return{outcome:"merged",data:i}}function F(){let e=new Map;return{register(t,n){e.set(t,n)},has(t){return e.has(t)},async resolve(t){switch(t.strategy){case"server-wins":return{outcome:"use-server",data:t.serverData};case"client-wins":return{outcome:"use-client",data:t.localData};case"field-merge":return de(t);case"surface":return{outcome:"surface"};default:{let n=t.policy.resolver,a=n===void 0?void 0:e.get(n);if(a===void 0)return{outcome:"surface"};return a(t)}}}}}function A(){let e=new Set;return{emit(t){for(let n of e)n(t)},subscribe(t){return e.add(t),()=>{e.delete(t)}}}}function ee(e){return{async collectGarbage(){let t=e.now(),n=0;for(let a of await e.cache.entries())if(a.expiresAt!==void 0&&t>=a.expiresAt)await e.cache.remove(a.key),n+=1;return e.events.emit({type:"gc-collected",evicted:n}),n},async revalidateStale(){let t=e.revalidate;if(t===void 0)return 0;let n=e.now(),a=0;for(let i of await e.cache.entries()){if(!(i.staleAt!==void 0&&n>=i.staleAt)||i.envelope===void 0)continue;await t(i.envelope),e.events.emit({type:"revalidated",key:i.key}),a+=1}return a}}}import{mergeSafeEconomicEvidenceRefIds as me,parseSafeEconomicEvidenceRefId as ye}from"@lssm/lib.contracts-spec/operations";import{defaultConflictPolicy as ge}from"@lssm/lib.contracts-spec/query/conflict-policy";function te(e,t={}){let n=t.baseMs??200,a=t.factor??2,i=t.maxMs??30000,u=t.jitter??Math.random,r=Math.max(0,e-1),y=Math.min(i,n*a**r);return Math.round(y*(0.5+u()*0.5))}import{defaultConflictPolicy as pe}from"@lssm/lib.contracts-spec/query/conflict-policy";async function N(e,t){for(let n of e)if(n.previous===void 0)await t.remove(n.key);else await t.write(n.previous)}async function ne(e,t,n){let a=e.conflictPolicy??pe(),i=e.rollback[0]?.key,u=i===void 0?void 0:await n.cache.read(i),r=t.metadata?.serverData,y=await n.resolvers.resolve({strategy:a.strategy,policy:a,localData:u?.data,serverData:r,localVersion:e.versionToken,serverVersion:t.versionToken,mergeFields:a.mergeFields});switch(n.events.emit({type:"mutation-conflict",id:e.id,strategy:a.strategy,outcome:y.outcome}),y.outcome){case"use-server":{await N(e.rollback,n.cache),await n.cache.invalidate(e.tags,n.now());for(let M of e.rollback)await n.queryEngine?.refresh(M.key);return"resolved"}case"use-client":return await n.queue.update({...e,versionToken:t.versionToken,status:"pending"}),"re-enqueued";case"merged":{if(i!==void 0&&u!==void 0)await n.cache.write({...u,data:y.data,versionToken:t.versionToken,status:"warm",storedAt:n.now()}),await n.queryEngine?.refresh(i);return await n.queue.update({...e,versionToken:t.versionToken,status:"pending"}),"re-enqueued"}default:{if(i!==void 0)await n.queryEngine?.emitConflict(i,{versionToken:t.versionToken,serverData:r,clientData:u?.data,strategy:a.strategy});return"resolved"}}}var ve=5;function W(...e){return me(...e)}function he(e){let t=ye(e);if(!t)throw Error("EconomicEvidenceInvalidRef: unsafe or invalid economic evidence ref");return t}function ae(e){let{transport:t,cache:n,queue:a,reachability:i,resolvers:u,events:r}=e,y=e.now??(()=>Date.now()),M=e.maxAttempts??ve,T={cache:n,queue:a,resolvers:u,events:r,now:y,queryEngine:e.queryEngine},h=!1,E=0,b=null,D=new Map,q=(o,l=o.economicEvidenceRefs)=>({operation:o.operation,input:o.input,tags:o.tags,economicEvidenceRefs:l});async function v(o){let l=[];for(let f of o){let g=await n.read(f.key);l.push({key:f.key,previous:g}),await n.write({key:f.key,data:f.data,versionToken:f.versionToken??g?.versionToken,status:"warm",tags:g?.tags??[],storedAt:y(),staleAt:g?.staleAt,expiresAt:g?.expiresAt}),await e.queryEngine?.refresh(f.key)}return l}async function w(o,l){for(let f of o.rollback){let g=await n.read(f.key);if(g!==void 0)await n.write({...g,versionToken:l.versionToken??g.versionToken,status:"stale",staleAt:y()});await e.queryEngine?.refresh(f.key)}await n.invalidate(o.tags,y())}async function p(o){let l;try{l=W(o.economicEvidenceRefs)}catch{await N(o.rollback,n);for(let S of o.rollback)await e.queryEngine?.refresh(S.key);return await a.remove(o.id),r.emit({type:"mutation-failed",id:o.id,code:"ECONOMIC_EVIDENCE_INVALID_REF"}),D.set(o.id,{id:o.id,status:"failed",problem:{type:"about:blank",title:"Economic evidence ref rejected",status:422,code:"ECONOMIC_EVIDENCE_INVALID_REF",category:"validation",severity:"error",retryable:!1}}),!0}let f=o.attempts+1;await a.update({...o,status:"in-flight",attempts:f});let g=await t.execute(q(o,l),e.contextProvider?.());if(g.ok){let S=W(l,g.data.economicEvidenceRefs);if(await w(o,g.data),await a.remove(o.id),r.emit({type:"mutation-replayed",id:o.id,queueDepth:await a.depth()}),S.length>0)r.emit({type:"economic-evidence-attached",operationKey:o.operation.key,evidenceRefs:S.map(he)});return D.set(o.id,{id:o.id,status:"done",data:g.data.data,economicEvidenceRefs:S}),!0}let{problem:k}=g;if(k.category==="auth"){if(h=!0,await a.update({...o,status:"paused-auth",attempts:f}),r.emit({type:"auth-expired",id:o.id}),D.set(o.id,{id:o.id,status:"paused-auth",problem:k}),e.refreshAuth!==void 0&&await e.refreshAuth())return h=!1,await a.update({...o,status:"pending",attempts:f}),!0;return!1}if(k.category==="conflict"){if(await ne(o,k,T)==="resolved")await a.remove(o.id),D.set(o.id,{id:o.id,status:"done",problem:k});return!0}if(k.retryable&&f<M){let S=te(f,e.backoff);return await a.update({...o,status:"failed",attempts:f}),r.emit({type:"mutation-retry",id:o.id,attempt:f,delayMs:S}),D.set(o.id,{id:o.id,status:"failed",problem:k}),e.schedule?.(()=>{d()},S),!0}await N(o.rollback,n);for(let S of o.rollback)await e.queryEngine?.refresh(S.key);return await a.remove(o.id),r.emit({type:"mutation-failed",id:o.id,code:g.code}),D.set(o.id,{id:o.id,status:"failed",problem:k}),!0}async function c(){if(h)return;if(!C(i.getSnapshot().signal))return;for(let o of await a.list()){if(o.status==="paused-auth")continue;if(!await p(o))return}r.emit({type:"queue-drained",remaining:await a.depth()})}function d(){if(b!==null)return b;return b=c().finally(()=>{b=null}),b}async function s(o,l={}){W(o.economicEvidenceRefs);let f=y();E+=1;let g=`${f}-${E}`,k=l.optimistic?await v(l.optimistic):[];if(await a.enqueue({...o,conflictPolicy:o.conflictPolicy??ge(),id:g,enqueuedAt:f,attempts:0,status:"pending",rollback:k}),r.emit({type:"mutation-enqueued",id:g,queueDepth:await a.depth()}),!h&&C(i.getSnapshot().signal))await d();return D.get(g)??{id:g,status:"pending"}}async function m(){h=!1;for(let o of await a.list())if(o.status==="paused-auth")await a.update({...o,status:"pending"});await d()}let O=i.subscribe((o)=>{if(!h&&C(o.signal))d()});return{mutate:s,drain:d,resume:m,dispose:O}}function oe(e){let t=(a)=>`queue:${a}`;async function n(){let a=await e.keys("queue:"),i=[];for(let u of a){let r=await e.get(u);if(r!==void 0)i.push(r)}return i.sort((u,r)=>u.enqueuedAt===r.enqueuedAt?u.id.localeCompare(r.id):u.enqueuedAt-r.enqueuedAt),i}return{enqueue:(a)=>e.set(t(a.id),a),update:(a)=>e.set(t(a.id),a),remove:(a)=>e.remove(t(a)),list:()=>n(),depth:async()=>(await e.keys("queue:")).length}}import{createQueryKey as G}from"@lssm/lib.contracts-spec/query/key";function x(e,t){if(e?.staleAt===void 0)return!1;return t>=e.staleAt}function L(){return{status:"idle",fetchStatus:"idle",cacheStatus:"empty",isStale:!1,isOffline:!1}}function V(e,t){if(e===void 0)return{status:"loading",fetchStatus:"fetching",cacheStatus:"empty",isStale:!1,isOffline:!1};let n=x(e,t);return{status:"success",fetchStatus:"fetching",data:e.data,cacheStatus:n?"stale":e.status,isStale:n,isOffline:!1,versionToken:e.versionToken,dataUpdatedAt:e.storedAt,...e.pageInfo===void 0?{}:{pageInfo:e.pageInfo}}}function Q(e,t){let n=x(e,t);return{status:"success",fetchStatus:"idle",data:e.data,cacheStatus:n?"stale":e.status,isStale:n,isOffline:!1,versionToken:e.versionToken,dataUpdatedAt:e.storedAt,...e.pageInfo===void 0?{}:{pageInfo:e.pageInfo}}}function j(e,t,n){return{status:"error",fetchStatus:"idle",data:t?.data,error:e,cacheStatus:t===void 0?"empty":t.status,isStale:x(t,n),isOffline:!1,versionToken:t?.versionToken,dataUpdatedAt:t?.storedAt,errorUpdatedAt:n}}function B(e,t,n){return{status:"auth-expired",fetchStatus:"idle",data:t?.data,error:e,cacheStatus:t===void 0?"empty":t.status,isStale:x(t,n),isOffline:!1,versionToken:t?.versionToken,dataUpdatedAt:t?.storedAt,errorUpdatedAt:n}}function K(e,t,n){return{status:e===void 0?"idle":"success",fetchStatus:"idle",data:e?.data,cacheStatus:"conflict",isStale:x(e,n),isOffline:!1,conflict:t,versionToken:t.versionToken??e?.versionToken,dataUpdatedAt:e?.storedAt}}function X(e,t){return{status:e===void 0?"idle":"success",fetchStatus:"idle",data:e?.data,cacheStatus:"offline",isStale:x(e,t),isOffline:!0,versionToken:e?.versionToken,dataUpdatedAt:e?.storedAt,...e?.pageInfo===void 0?{}:{pageInfo:e.pageInfo}}}function ie(e,t){let{transport:n}=e,a=e.advanced??{},i=a.now??(()=>Date.now()),u=a.reachability??R("online"),r=a.events??A(),y=a.resolvers??F(),M=t??I({defaultMode:e.localOffline,env:e.env,flag:e.flag}),T=new Map,h=new Map,E=0;function b(v,w){T.set(v,w);for(let p of h.get(v)??[])p(w)}return{...{async query(v,w){let p=G(v),c=u.getSnapshot().signal;if(!C(c)){let l=X(void 0,i());return b(p,l),l}b(p,V(void 0,i()));let d=i(),s=await n.execute(v,w),m=i();if(!s.ok){r.emit({type:"fetch-error",key:p,code:s.code,latencyMs:m-d});let l=s.problem.category==="auth"?B(s.problem,void 0,m):j(s.problem,void 0,m);return b(p,l),l}r.emit({type:"fetch-success",key:p,latencyMs:m-d});let O={key:p,data:s.data.data,versionToken:s.data.versionToken??s.versionToken,status:c==="restricted"?"warm":"fresh",tags:[],storedAt:m,...s.data.pageInfo===void 0?{}:{pageInfo:s.data.pageInfo}},o=Q(O,m);return b(p,o),o},getState(v){return T.get(G(v))??L()},subscribe(v,w){let p=G(v),c=h.get(p);if(c===void 0)c=new Set,h.set(p,c);return c.add(w),()=>c?.delete(w)},async invalidateTags(){return[]},async refresh(v){let w=T.get(v)??L();return b(v,w),w},async emitConflict(v,w){let p=K(void 0,w,i());return b(v,p),p}},...{async mutate(v,w={}){E+=1;let p=`${i()}-${E}`;if(!C(u.getSnapshot().signal))return{id:p,status:"disabled-offline",problem:Ee()};let c=await n.execute({operation:v.operation,input:v.input,tags:v.tags},w.ctx??a.contextProvider?.());if(c.ok)return{id:p,status:"done",data:c.data.data};if(c.problem.category==="auth")return{id:p,status:"paused-auth",problem:c.problem};return{id:p,status:"failed",problem:c.problem}},async drain(){},async resume(){},dispose(){}},maintenance:{collectGarbage:async()=>0,revalidateStale:async()=>0},events:r,resolvers:y,localOffline:M}}function Ee(){return{type:"about:blank",title:"Offline mutation queue disabled",status:409,code:"OFFLINE_QUEUE_DISABLED",category:"conflict",severity:"warning",retryable:!1,detail:"localOffline=online-only does not cache local data or queue offline mutations."}}import{createQueryKey as H}from"@lssm/lib.contracts-spec/query/key";function re(){let e=new Map;return{run(t,n){let a=e.get(t);if(a!==void 0)return a;let i=n().finally(()=>{e.delete(t)});return e.set(t,i),i},size(){return e.size}}}var we=0;function se(e){let{transport:t}=e,n=e.storage??_(),a=e.reachability??R("online"),i=e.now??(()=>Date.now()),u=e.defaultStaleTimeMs??we,r=e.cacheStore??U(n),y=e.events??A(),M=re(),T=new Map,h=new Map;function E(c,d){T.set(c,d);let s=h.get(c);if(s===void 0)return;for(let m of s)m(d)}async function b(c,d){let s=H(c),m=a.getSnapshot().signal,O=await r.read(s);if(!C(m)){let P=X(O,i());return E(s,P),P}if(O!==void 0&&!x(O,i())){y.emit({type:"cache-hit",key:s});let P=Q(O,i());return E(s,P),P}y.emit({type:"cache-miss",key:s}),E(s,V(O,i()));let o=i(),l=await M.run(s,()=>t.execute(c,d)),f=i();if(!l.ok){y.emit({type:"fetch-error",key:s,code:l.code,latencyMs:f-o});let P=l.problem.category==="auth"?B(l.problem,O,f):j(l.problem,O,f);return E(s,P),P}y.emit({type:"fetch-success",key:s,latencyMs:f-o});let g=c.cache?.staleTimeMs??u,k=c.cache?.cacheTimeMs,S={key:s,data:l.data.data,versionToken:l.data.versionToken??l.versionToken,status:m==="restricted"?"warm":"fresh",tags:c.tags??[],storedAt:f,staleAt:f+g,envelope:c,...k===void 0?{}:{expiresAt:f+k},...l.data.pageInfo===void 0?{}:{pageInfo:l.data.pageInfo}};await r.write(S);let J=Q(S,f);return E(s,J),J}async function D(c){let d=await r.read(c),s=d===void 0?L():Q(d,i());return E(c,s),s}async function q(c,d){let s=await r.read(c),m=K(s,d,i());return E(c,m),m}function v(c){return T.get(H(c))??L()}function w(c,d){let s=H(c),m=h.get(s);if(m===void 0)m=new Set,h.set(s,m);return m.add(d),()=>{h.get(s)?.delete(d)}}async function p(c){let d=await r.invalidate(c,i());for(let s of d){let m=await r.read(s);if(m!==void 0)E(s,Q(m,i()))}return d}return{query:b,getState:v,subscribe:w,invalidateTags:p,refresh:D,emitConflict:q}}function ce(e){Se(e);let t=e.advanced??{},n=I({defaultMode:e.localOffline,env:e.env,flag:e.flag});if(be(n.mode,t),n.mode==="online-only")return ie(e,n);let a=t.now??(()=>Date.now()),i=t.storage??_(),u=t.events??A(),r=t.cacheStore??U(i),y=t.reachability??R("online"),M=t.resolvers??F(),T=oe(i),h=se({...t,transport:e.transport,now:a,storage:i,events:u,cacheStore:r,reachability:y}),E=ae({transport:e.transport,cache:r,queue:T,reachability:y,resolvers:M,events:u,now:a,maxAttempts:t.maxAttempts,queryEngine:h,contextProvider:t.contextProvider,refreshAuth:t.refreshAuth,schedule:t.schedule,backoff:t.backoff}),b=ee({cache:r,events:u,now:a,revalidate:(D)=>h.query(D)});return{...h,...E,maintenance:b,events:u,resolvers:M,localOffline:n}}function be(e,t){if(e==="offline-capable"){if(t.storage===void 0)throw Error("localOffline=offline-capable requires advanced.storage; pass a durable StoragePort or explicit platform storage adapter.");return}if(t.storage!==void 0)throw Error(`localOffline=${e} rejects advanced.storage; this mode must not use durable local persistence.`);if(t.cacheStore!==void 0)throw Error(`localOffline=${e} rejects advanced.cacheStore; this mode owns cache semantics.`)}function Se(e){let t=e;if(t.storage!==void 0)throw Error("Use advanced.storage instead of top-level storage.");if(t.cacheStore!==void 0)throw Error("Use advanced.cacheStore instead of top-level cacheStore.")}function Te(){return globalThis.process?.env}function wt(e){return ce({...e,env:e.env??Te()})}export{wt as createDataEngineFromEnv,Te as readHostEnv};
|