@remnic/core 9.25.6 → 9.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-admin-ops-surface.d.ts +2 -2
- package/dist/access-admin-ops-surface.js +41 -41
- package/dist/access-audit.js +2 -2
- package/dist/access-authorization-probe.d.ts +2 -2
- package/dist/access-authorization-probe.js +42 -42
- package/dist/access-boundary.d.ts +2 -2
- package/dist/access-boundary.js +41 -41
- package/dist/access-cli.js +98 -97
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http-lcm-compaction.d.ts +130 -0
- package/dist/access-http-lcm-compaction.js +10 -0
- package/dist/access-http.d.ts +2 -2
- package/dist/access-http.js +53 -52
- package/dist/access-identity-continuity-surface.d.ts +1 -1
- package/dist/access-identity-continuity-surface.js +41 -41
- package/dist/access-lcm-surface.d.ts +2 -2
- package/dist/access-lcm-surface.js +41 -41
- package/dist/access-mcp.d.ts +2 -2
- package/dist/access-mcp.js +47 -47
- package/dist/access-namespace-preflight.js +41 -41
- package/dist/access-observe-write-surface.d.ts +2 -2
- package/dist/access-observe-write-surface.js +41 -41
- package/dist/access-operations-batch.js +43 -43
- package/dist/access-operations.d.ts +2 -2
- package/dist/access-operations.js +45 -45
- package/dist/access-recall-concurrency.d.ts +2 -2
- package/dist/access-recall-concurrency.js +41 -41
- package/dist/access-recall-response.d.ts +2 -2
- package/dist/access-recall-response.js +41 -41
- package/dist/access-recall-surface.d.ts +2 -2
- package/dist/access-recall-surface.js +41 -41
- package/dist/access-schema.d.ts +16 -1
- package/dist/access-schema.js +8 -8
- package/dist/{access-service-BJQ22URi.d.ts → access-service-DL8FJnTj.d.ts} +2 -2
- package/dist/access-service.d.ts +2 -2
- package/dist/access-service.js +41 -41
- package/dist/access-surface-catalog.d.ts +2 -2
- package/dist/adapters/index.js +7 -7
- package/dist/adapters/registry.js +3 -3
- package/dist/{auto-sync-5OE5QOZB.js → auto-sync-UPTUVE4X.js} +3 -3
- package/dist/bootstrap.d.ts +1 -1
- package/dist/briefing.js +5 -5
- package/dist/{capsule-crypto-7FJQINUR.js → capsule-crypto-YO5QJ6L3.js} +2 -2
- package/dist/capsule-crypto-YO5QJ6L3.js.map +1 -0
- package/dist/causal-behavior.js +2 -2
- package/dist/causal-chain.js +2 -2
- package/dist/causal-consolidation.js +11 -11
- package/dist/causal-retrieval.js +2 -2
- package/dist/causal-trajectory.js +1 -1
- package/dist/{chunk-JBPKEARU.js → chunk-2QSZNTDO.js} +7 -7
- package/dist/{chunk-BL4JHZSO.js → chunk-2XP3PWL3.js} +40 -40
- package/dist/{chunk-AQ2ZQYDI.js → chunk-4KKV7W62.js} +6 -6
- package/dist/chunk-4Z4XNEWZ.js +86 -0
- package/dist/chunk-4Z4XNEWZ.js.map +1 -0
- package/dist/{chunk-32XTXKIA.js → chunk-67DSDRUB.js} +22 -14
- package/dist/chunk-67DSDRUB.js.map +1 -0
- package/dist/{chunk-2PHX3I5C.js → chunk-6NNNURLK.js} +8 -8
- package/dist/{chunk-BLAKS3CP.js → chunk-77CNV7DY.js} +4 -4
- package/dist/{chunk-TVVEYCNW.js → chunk-7K5Q6COX.js} +4 -4
- package/dist/{chunk-227UFSEQ.js → chunk-7RZJWZ46.js} +5 -5
- package/dist/{chunk-WIGBTWXA.js → chunk-7XXCIO6X.js} +2 -2
- package/dist/{chunk-7CPOZH6Y.js → chunk-AJ7QCSIS.js} +2 -2
- package/dist/{chunk-2NLLXCJG.js → chunk-BXLOS5AJ.js} +2 -2
- package/dist/{chunk-X7Y7WX73.js → chunk-DQEMWVMT.js} +1 -1
- package/dist/{chunk-UMFLAACL.js → chunk-E6EAB4J6.js} +9 -9
- package/dist/{chunk-WSEA52RI.js → chunk-EXM2Q555.js} +2 -2
- package/dist/{chunk-G6MGAE36.js → chunk-F6LSNPPB.js} +5 -5
- package/dist/{chunk-7KXQ44PA.js → chunk-FE5FKTCI.js} +5 -5
- package/dist/{chunk-5J7HJ7RT.js → chunk-H2PBM562.js} +2 -2
- package/dist/{chunk-OIE242WM.js → chunk-HBVOJNV4.js} +3 -3
- package/dist/{chunk-PS5OG75R.js → chunk-IHMXPG4P.js} +2 -2
- package/dist/{chunk-3UOSVTFR.js → chunk-JCVQV6QX.js} +11 -11
- package/dist/{chunk-P25IPRXP.js → chunk-KGTD2KZJ.js} +28 -8
- package/dist/chunk-KGTD2KZJ.js.map +1 -0
- package/dist/{chunk-NY6W6CJW.js → chunk-KPWLZBXZ.js} +2 -2
- package/dist/{chunk-W33DEUKR.js → chunk-LIIQPDFN.js} +2 -2
- package/dist/{chunk-D7MAWMMK.js → chunk-M2KGXXTL.js} +2 -2
- package/dist/{chunk-UOURUMBN.js → chunk-MIFF37NN.js} +2 -2
- package/dist/{chunk-37VPMR4E.js → chunk-NKDVXEFU.js} +6 -6
- package/dist/{chunk-ZPUFQGUF.js → chunk-PQPSZICJ.js} +5 -5
- package/dist/{chunk-ES36HTSC.js → chunk-PTN6UAUA.js} +4 -4
- package/dist/{chunk-FR75B4UU.js → chunk-PX7VDW7R.js} +408 -721
- package/dist/chunk-PX7VDW7R.js.map +1 -0
- package/dist/{chunk-S2BBISHU.js → chunk-RYHWZ52A.js} +4 -4
- package/dist/{chunk-ZN4UZWMT.js → chunk-SK3EYPKC.js} +2 -2
- package/dist/{chunk-TPYZ727G.js → chunk-SN3ZESWQ.js} +6 -6
- package/dist/{chunk-BUK2FXOL.js → chunk-TKBDJTXG.js} +4 -4
- package/dist/{chunk-4B77XSG5.js → chunk-UV72DEZD.js} +5 -5
- package/dist/{chunk-K4DP5KZA.js → chunk-UZK7VVPD.js} +14 -14
- package/dist/{chunk-KQAFEZQX.js → chunk-VDX2J7OX.js} +2 -2
- package/dist/{chunk-SSYBI43M.js → chunk-VGEH3STZ.js} +6 -6
- package/dist/{chunk-NRV4Y3PM.js → chunk-W4FBGYGK.js} +1 -1
- package/dist/{chunk-IR4LGIH6.js → chunk-WMDRLTNA.js} +2 -2
- package/dist/{chunk-MU7ZHSZE.js → chunk-WZFAPHIK.js} +2 -2
- package/dist/{chunk-JA4HEXV7.js → chunk-XAAIUXJO.js} +2 -2
- package/dist/{chunk-VPSAVXHM.js → chunk-XNUBF6RF.js} +7 -7
- package/dist/{chunk-3CHQZVAL.js → chunk-Y6E37WRG.js} +5 -5
- package/dist/{chunk-E5A6C4A5.js → chunk-YN4AUGNF.js} +48 -48
- package/dist/{chunk-EZ25VE3G.js → chunk-YNDLCWXS.js} +4 -4
- package/dist/{chunk-7IGWPXVC.js → chunk-ZHP4SUPB.js} +2 -2
- package/dist/{cli-1W7cNjJJ.d.ts → cli-BoouX6LX.d.ts} +2 -2
- package/dist/cli.d.ts +3 -3
- package/dist/cli.js +75 -74
- package/dist/compounding/engine.js +5 -5
- package/dist/connectors/codex-materialize-runner.js +5 -5
- package/dist/connectors/index.js +5 -5
- package/dist/conversation-index/backend.js +2 -2
- package/dist/corpus-watermark.js +16 -16
- package/dist/dashboard-runtime.js +2 -2
- package/dist/entity-retrieval.js +5 -5
- package/dist/explicit-capture.d.ts +1 -1
- package/dist/extraction.js +3 -3
- package/dist/{first-start-migration-BKNGOFED.js → first-start-migration-LEXL3XYL.js} +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +177 -176
- package/dist/lcm/engine.js +3 -3
- package/dist/lcm/index.js +9 -9
- package/dist/maintenance/memory-governance.js +6 -6
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +5 -5
- package/dist/maintenance/rebuild-memory-projection.js +7 -7
- package/dist/mcp-memory-inspector-app.d.ts +2 -2
- package/dist/namespaces/migrate.js +15 -15
- package/dist/namespaces/search.js +9 -9
- package/dist/namespaces/storage.js +5 -5
- package/dist/operator-doctor-corpus.js +17 -17
- package/dist/operator-toolkit.js +28 -28
- package/dist/orchestration/maintenance.js +11 -11
- package/dist/{orchestrator-BtA1wC5K.d.ts → orchestrator-CFJ_TG6D.d.ts} +15 -10
- package/dist/orchestrator.d.ts +1 -1
- package/dist/orchestrator.js +98 -97
- package/dist/resume-bundles.js +1 -1
- package/dist/search/factory.js +8 -8
- package/dist/search/index.js +13 -13
- package/dist/search/lancedb-backend.js +2 -2
- package/dist/search/meilisearch-backend.js +2 -2
- package/dist/search/orama-backend.js +2 -2
- package/dist/semantic-consolidation.js +7 -7
- package/dist/semantic-rule-promotion.js +5 -5
- package/dist/semantic-rule-verifier.js +6 -6
- package/dist/session-namespace-bindings.d.ts +27 -0
- package/dist/session-namespace-bindings.js +243 -0
- package/dist/session-namespace-bindings.js.map +1 -0
- package/dist/storage.js +4 -4
- package/dist/testing/lifecycle-matrix.d.ts +100 -0
- package/dist/testing/lifecycle-matrix.js +149 -0
- package/dist/testing/lifecycle-matrix.js.map +1 -0
- package/dist/transfer/backup.js +4 -4
- package/dist/transfer/capsule-export.js +4 -4
- package/dist/transfer/capsule-import.js +3 -3
- package/dist/verified-recall.js +6 -6
- package/package.json +22 -2
- package/src/access-http-lcm-compaction.ts +116 -0
- package/src/access-http.test.ts +218 -0
- package/src/access-http.ts +8 -8
- package/src/access-schema-pi.test.ts +9 -0
- package/src/access-schema.ts +29 -4
- package/src/access-surface-catalog.test.ts +1 -0
- package/src/meetings/recall-exclusion.test.ts +4 -2
- package/src/orchestration/generic-recall-paths.ts +119 -0
- package/src/orchestration/namespace-read-fanout.ts +0 -12
- package/src/orchestration/orchestrator-helpers.ts +0 -35
- package/src/orchestration/recall-internal.ts +6 -6
- package/src/orchestration/recall-search-pipeline.test.ts +194 -28
- package/src/orchestration/recall-search-pipeline.ts +39 -89
- package/src/orchestrator.ts +5 -17
- package/src/recall-cold-deadline.test.ts +11 -13
- package/src/session-namespace-bindings.test.ts +358 -0
- package/src/session-namespace-bindings.ts +301 -0
- package/src/testing/lifecycle-matrix.ts +17 -7
- package/src/testing/subjects/generic-recall-paths.test.ts +157 -0
- package/dist/chunk-32XTXKIA.js.map +0 -1
- package/dist/chunk-FR75B4UU.js.map +0 -1
- package/dist/chunk-P25IPRXP.js.map +0 -1
- package/src/recall/archive-scoring.ts +0 -544
- /package/dist/{capsule-crypto-7FJQINUR.js.map → access-http-lcm-compaction.js.map} +0 -0
- /package/dist/{auto-sync-5OE5QOZB.js.map → auto-sync-UPTUVE4X.js.map} +0 -0
- /package/dist/{chunk-JBPKEARU.js.map → chunk-2QSZNTDO.js.map} +0 -0
- /package/dist/{chunk-BL4JHZSO.js.map → chunk-2XP3PWL3.js.map} +0 -0
- /package/dist/{chunk-AQ2ZQYDI.js.map → chunk-4KKV7W62.js.map} +0 -0
- /package/dist/{chunk-2PHX3I5C.js.map → chunk-6NNNURLK.js.map} +0 -0
- /package/dist/{chunk-BLAKS3CP.js.map → chunk-77CNV7DY.js.map} +0 -0
- /package/dist/{chunk-TVVEYCNW.js.map → chunk-7K5Q6COX.js.map} +0 -0
- /package/dist/{chunk-227UFSEQ.js.map → chunk-7RZJWZ46.js.map} +0 -0
- /package/dist/{chunk-WIGBTWXA.js.map → chunk-7XXCIO6X.js.map} +0 -0
- /package/dist/{chunk-7CPOZH6Y.js.map → chunk-AJ7QCSIS.js.map} +0 -0
- /package/dist/{chunk-2NLLXCJG.js.map → chunk-BXLOS5AJ.js.map} +0 -0
- /package/dist/{chunk-X7Y7WX73.js.map → chunk-DQEMWVMT.js.map} +0 -0
- /package/dist/{chunk-UMFLAACL.js.map → chunk-E6EAB4J6.js.map} +0 -0
- /package/dist/{chunk-WSEA52RI.js.map → chunk-EXM2Q555.js.map} +0 -0
- /package/dist/{chunk-G6MGAE36.js.map → chunk-F6LSNPPB.js.map} +0 -0
- /package/dist/{chunk-7KXQ44PA.js.map → chunk-FE5FKTCI.js.map} +0 -0
- /package/dist/{chunk-5J7HJ7RT.js.map → chunk-H2PBM562.js.map} +0 -0
- /package/dist/{chunk-OIE242WM.js.map → chunk-HBVOJNV4.js.map} +0 -0
- /package/dist/{chunk-PS5OG75R.js.map → chunk-IHMXPG4P.js.map} +0 -0
- /package/dist/{chunk-3UOSVTFR.js.map → chunk-JCVQV6QX.js.map} +0 -0
- /package/dist/{chunk-NY6W6CJW.js.map → chunk-KPWLZBXZ.js.map} +0 -0
- /package/dist/{chunk-W33DEUKR.js.map → chunk-LIIQPDFN.js.map} +0 -0
- /package/dist/{chunk-D7MAWMMK.js.map → chunk-M2KGXXTL.js.map} +0 -0
- /package/dist/{chunk-UOURUMBN.js.map → chunk-MIFF37NN.js.map} +0 -0
- /package/dist/{chunk-37VPMR4E.js.map → chunk-NKDVXEFU.js.map} +0 -0
- /package/dist/{chunk-ZPUFQGUF.js.map → chunk-PQPSZICJ.js.map} +0 -0
- /package/dist/{chunk-ES36HTSC.js.map → chunk-PTN6UAUA.js.map} +0 -0
- /package/dist/{chunk-S2BBISHU.js.map → chunk-RYHWZ52A.js.map} +0 -0
- /package/dist/{chunk-ZN4UZWMT.js.map → chunk-SK3EYPKC.js.map} +0 -0
- /package/dist/{chunk-TPYZ727G.js.map → chunk-SN3ZESWQ.js.map} +0 -0
- /package/dist/{chunk-BUK2FXOL.js.map → chunk-TKBDJTXG.js.map} +0 -0
- /package/dist/{chunk-4B77XSG5.js.map → chunk-UV72DEZD.js.map} +0 -0
- /package/dist/{chunk-K4DP5KZA.js.map → chunk-UZK7VVPD.js.map} +0 -0
- /package/dist/{chunk-KQAFEZQX.js.map → chunk-VDX2J7OX.js.map} +0 -0
- /package/dist/{chunk-SSYBI43M.js.map → chunk-VGEH3STZ.js.map} +0 -0
- /package/dist/{chunk-NRV4Y3PM.js.map → chunk-W4FBGYGK.js.map} +0 -0
- /package/dist/{chunk-IR4LGIH6.js.map → chunk-WMDRLTNA.js.map} +0 -0
- /package/dist/{chunk-MU7ZHSZE.js.map → chunk-WZFAPHIK.js.map} +0 -0
- /package/dist/{chunk-JA4HEXV7.js.map → chunk-XAAIUXJO.js.map} +0 -0
- /package/dist/{chunk-VPSAVXHM.js.map → chunk-XNUBF6RF.js.map} +0 -0
- /package/dist/{chunk-3CHQZVAL.js.map → chunk-Y6E37WRG.js.map} +0 -0
- /package/dist/{chunk-E5A6C4A5.js.map → chunk-YN4AUGNF.js.map} +0 -0
- /package/dist/{chunk-EZ25VE3G.js.map → chunk-YNDLCWXS.js.map} +0 -0
- /package/dist/{chunk-7IGWPXVC.js.map → chunk-ZHP4SUPB.js.map} +0 -0
- /package/dist/{first-start-migration-BKNGOFED.js.map → first-start-migration-LEXL3XYL.js.map} +0 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { rename } from 'node:fs/promises';
|
|
2
|
+
|
|
3
|
+
interface SessionNamespaceBindingStore {
|
|
4
|
+
namespacesFor(sessionKey: string): Promise<string[]>;
|
|
5
|
+
remember(sessionKey: string, namespace: string): Promise<void>;
|
|
6
|
+
replace?(sessionKey: string, namespaces: string[]): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
declare const SESSION_NAMESPACE_BINDING_MAX_ENTRIES = 1000;
|
|
9
|
+
declare const SESSION_NAMESPACE_BINDING_MAX_NAMESPACES = 64;
|
|
10
|
+
declare const SESSION_NAMESPACE_BINDING_MAX_NAMESPACE_LENGTH = 256;
|
|
11
|
+
declare const SESSION_NAMESPACE_BINDING_MAX_AGE_MS: number;
|
|
12
|
+
interface NamespaceBindingEntry {
|
|
13
|
+
namespaces: string[];
|
|
14
|
+
updatedAt: string;
|
|
15
|
+
}
|
|
16
|
+
interface NamespaceBindingFile {
|
|
17
|
+
version: 1;
|
|
18
|
+
entries: Record<string, NamespaceBindingEntry>;
|
|
19
|
+
}
|
|
20
|
+
type BindingFileWriter = (filePath: string, bindings: NamespaceBindingFile) => Promise<void>;
|
|
21
|
+
declare function createInMemorySessionNamespaceBindingStore(): SessionNamespaceBindingStore;
|
|
22
|
+
declare function createFileSessionNamespaceBindingStore(filePath: string, options?: {
|
|
23
|
+
writeBindingFile?: BindingFileWriter;
|
|
24
|
+
renameBindingFile?: typeof rename;
|
|
25
|
+
}): SessionNamespaceBindingStore;
|
|
26
|
+
|
|
27
|
+
export { SESSION_NAMESPACE_BINDING_MAX_AGE_MS, SESSION_NAMESPACE_BINDING_MAX_ENTRIES, SESSION_NAMESPACE_BINDING_MAX_NAMESPACES, SESSION_NAMESPACE_BINDING_MAX_NAMESPACE_LENGTH, type SessionNamespaceBindingStore, createFileSessionNamespaceBindingStore, createInMemorySessionNamespaceBindingStore };
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import "./chunk-PZ5AY32C.js";
|
|
2
|
+
|
|
3
|
+
// src/session-namespace-bindings.ts
|
|
4
|
+
import { randomUUID } from "crypto";
|
|
5
|
+
import { mkdir, readFile, rename, rm, writeFile } from "fs/promises";
|
|
6
|
+
import path from "path";
|
|
7
|
+
var SESSION_NAMESPACE_BINDING_MAX_ENTRIES = 1e3;
|
|
8
|
+
var SESSION_NAMESPACE_BINDING_MAX_NAMESPACES = 64;
|
|
9
|
+
var SESSION_NAMESPACE_BINDING_MAX_NAMESPACE_LENGTH = 256;
|
|
10
|
+
var SESSION_NAMESPACE_BINDING_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
11
|
+
var fileWriteChains = /* @__PURE__ */ new Map();
|
|
12
|
+
var volatileRefreshes = /* @__PURE__ */ new Map();
|
|
13
|
+
function retainVolatileRefresh(filePath, sessionKey, updatedAt) {
|
|
14
|
+
const refreshes = volatileRefreshes.get(filePath) ?? /* @__PURE__ */ new Map();
|
|
15
|
+
refreshes.set(sessionKey, updatedAt);
|
|
16
|
+
volatileRefreshes.set(filePath, refreshes);
|
|
17
|
+
}
|
|
18
|
+
function clearVolatileRefresh(filePath, sessionKey) {
|
|
19
|
+
const refreshes = volatileRefreshes.get(filePath);
|
|
20
|
+
if (refreshes === void 0) return;
|
|
21
|
+
refreshes.delete(sessionKey);
|
|
22
|
+
if (refreshes.size === 0) volatileRefreshes.delete(filePath);
|
|
23
|
+
}
|
|
24
|
+
function applyVolatileRefreshes(filePath, entries) {
|
|
25
|
+
const refreshes = volatileRefreshes.get(filePath);
|
|
26
|
+
if (refreshes === void 0) return;
|
|
27
|
+
for (const [key, updatedAt] of refreshes) {
|
|
28
|
+
const entry = entries[key];
|
|
29
|
+
if (entry === void 0) {
|
|
30
|
+
refreshes.delete(key);
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const persistedAt = Date.parse(entry.updatedAt);
|
|
34
|
+
const refreshedAt = Date.parse(updatedAt);
|
|
35
|
+
if (Number.isFinite(refreshedAt) && (!Number.isFinite(persistedAt) || refreshedAt > persistedAt)) {
|
|
36
|
+
entry.updatedAt = updatedAt;
|
|
37
|
+
} else {
|
|
38
|
+
refreshes.delete(key);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (refreshes.size === 0) volatileRefreshes.delete(filePath);
|
|
42
|
+
}
|
|
43
|
+
function emptyBindingFile() {
|
|
44
|
+
return { version: 1, entries: /* @__PURE__ */ Object.create(null) };
|
|
45
|
+
}
|
|
46
|
+
function capNamespaceHistory(namespaces) {
|
|
47
|
+
return namespaces.length > SESSION_NAMESPACE_BINDING_MAX_NAMESPACES ? namespaces.slice(-SESSION_NAMESPACE_BINDING_MAX_NAMESPACES) : namespaces;
|
|
48
|
+
}
|
|
49
|
+
function normalizeNamespaces(value) {
|
|
50
|
+
if (!Array.isArray(value)) return [];
|
|
51
|
+
const namespaces = [];
|
|
52
|
+
for (const entry of value) {
|
|
53
|
+
if (typeof entry !== "string") continue;
|
|
54
|
+
const namespace = entry.trim();
|
|
55
|
+
const existing = namespaces.indexOf(namespace);
|
|
56
|
+
if (existing >= 0) namespaces.splice(existing, 1);
|
|
57
|
+
namespaces.push(namespace);
|
|
58
|
+
}
|
|
59
|
+
return capNamespaceHistory(namespaces);
|
|
60
|
+
}
|
|
61
|
+
function addNamespace(namespaces, namespace) {
|
|
62
|
+
const normalized = namespace.trim();
|
|
63
|
+
return capNamespaceHistory([
|
|
64
|
+
...namespaces.filter((candidate) => candidate !== normalized),
|
|
65
|
+
normalized
|
|
66
|
+
]);
|
|
67
|
+
}
|
|
68
|
+
function pruneBindingEntries(entries, protectedKey) {
|
|
69
|
+
const cutoff = Date.now() - SESSION_NAMESPACE_BINDING_MAX_AGE_MS;
|
|
70
|
+
const retained = Object.entries(entries).filter(([_key, entry]) => {
|
|
71
|
+
const updatedAt = Date.parse(entry.updatedAt);
|
|
72
|
+
return Number.isFinite(updatedAt) && updatedAt >= cutoff;
|
|
73
|
+
}).sort(([leftKey, left], [rightKey, right]) => {
|
|
74
|
+
if (leftKey === rightKey) return 0;
|
|
75
|
+
if (leftKey === protectedKey) return -1;
|
|
76
|
+
if (rightKey === protectedKey) return 1;
|
|
77
|
+
const leftAt = Date.parse(left.updatedAt);
|
|
78
|
+
const rightAt = Date.parse(right.updatedAt);
|
|
79
|
+
if (leftAt !== rightAt) return rightAt > leftAt ? 1 : -1;
|
|
80
|
+
return leftKey < rightKey ? -1 : 1;
|
|
81
|
+
}).slice(0, SESSION_NAMESPACE_BINDING_MAX_ENTRIES);
|
|
82
|
+
const pruned = /* @__PURE__ */ Object.create(null);
|
|
83
|
+
for (const [key, entry] of retained) pruned[key] = entry;
|
|
84
|
+
return pruned;
|
|
85
|
+
}
|
|
86
|
+
function encodeSessionKey(sessionKey) {
|
|
87
|
+
return encodeURIComponent(sessionKey);
|
|
88
|
+
}
|
|
89
|
+
async function readBindingFile(filePath) {
|
|
90
|
+
let raw;
|
|
91
|
+
try {
|
|
92
|
+
raw = await readFile(filePath, "utf8");
|
|
93
|
+
} catch (err) {
|
|
94
|
+
if (err?.code === "ENOENT") {
|
|
95
|
+
volatileRefreshes.delete(filePath);
|
|
96
|
+
return emptyBindingFile();
|
|
97
|
+
}
|
|
98
|
+
throw err;
|
|
99
|
+
}
|
|
100
|
+
const parsed = JSON.parse(raw);
|
|
101
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) || parsed.version !== 1 || !parsed.entries || typeof parsed.entries !== "object" || Array.isArray(parsed.entries)) {
|
|
102
|
+
throw new Error("session namespace binding file has invalid structure");
|
|
103
|
+
}
|
|
104
|
+
const entries = /* @__PURE__ */ Object.create(null);
|
|
105
|
+
for (const [key, value] of Object.entries(parsed.entries)) {
|
|
106
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
107
|
+
throw new Error("session namespace binding entry has invalid structure");
|
|
108
|
+
}
|
|
109
|
+
const entry = value;
|
|
110
|
+
if (typeof entry.updatedAt !== "string" || !Number.isFinite(Date.parse(entry.updatedAt)) || !Array.isArray(entry.namespaces) || entry.namespaces.length === 0 || entry.namespaces.some(
|
|
111
|
+
(namespace) => typeof namespace !== "string" || namespace.trim().length > SESSION_NAMESPACE_BINDING_MAX_NAMESPACE_LENGTH
|
|
112
|
+
)) {
|
|
113
|
+
throw new Error("session namespace binding entry has invalid structure");
|
|
114
|
+
}
|
|
115
|
+
const namespaces = normalizeNamespaces(entry.namespaces);
|
|
116
|
+
if (namespaces.length === 0) {
|
|
117
|
+
throw new Error("session namespace binding entry has invalid structure");
|
|
118
|
+
}
|
|
119
|
+
entries[key] = { namespaces, updatedAt: entry.updatedAt };
|
|
120
|
+
}
|
|
121
|
+
applyVolatileRefreshes(filePath, entries);
|
|
122
|
+
return { version: 1, entries: pruneBindingEntries(entries) };
|
|
123
|
+
}
|
|
124
|
+
async function writeBindingFile(filePath, bindings, renameFile = rename) {
|
|
125
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
126
|
+
const temporaryPath = `${filePath}.${randomUUID()}.tmp`;
|
|
127
|
+
try {
|
|
128
|
+
await writeFile(temporaryPath, JSON.stringify(bindings, null, 2), "utf8");
|
|
129
|
+
await renameFile(temporaryPath, filePath);
|
|
130
|
+
} catch (err) {
|
|
131
|
+
await rm(temporaryPath, { force: true }).catch(() => void 0);
|
|
132
|
+
throw err;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async function queueFileWrite(filePath, operation) {
|
|
136
|
+
const prior = fileWriteChains.get(filePath) ?? Promise.resolve();
|
|
137
|
+
const run = prior.catch(() => void 0).then(operation);
|
|
138
|
+
fileWriteChains.set(
|
|
139
|
+
filePath,
|
|
140
|
+
run.then(
|
|
141
|
+
() => void 0,
|
|
142
|
+
() => void 0
|
|
143
|
+
)
|
|
144
|
+
);
|
|
145
|
+
return run;
|
|
146
|
+
}
|
|
147
|
+
function createInMemorySessionNamespaceBindingStore() {
|
|
148
|
+
const entries = /* @__PURE__ */ new Map();
|
|
149
|
+
return {
|
|
150
|
+
async namespacesFor(sessionKey) {
|
|
151
|
+
const namespaces = entries.get(sessionKey);
|
|
152
|
+
if (namespaces === void 0) return [];
|
|
153
|
+
entries.delete(sessionKey);
|
|
154
|
+
entries.set(sessionKey, namespaces);
|
|
155
|
+
return [...namespaces];
|
|
156
|
+
},
|
|
157
|
+
async remember(sessionKey, namespace) {
|
|
158
|
+
const existing = entries.get(sessionKey) ?? [];
|
|
159
|
+
entries.delete(sessionKey);
|
|
160
|
+
entries.set(sessionKey, addNamespace(existing, namespace));
|
|
161
|
+
while (entries.size > SESSION_NAMESPACE_BINDING_MAX_ENTRIES) {
|
|
162
|
+
const oldest = entries.keys().next().value;
|
|
163
|
+
if (typeof oldest !== "string") break;
|
|
164
|
+
entries.delete(oldest);
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
async replace(sessionKey, namespaces) {
|
|
168
|
+
entries.delete(sessionKey);
|
|
169
|
+
if (namespaces.length > 0) {
|
|
170
|
+
entries.set(sessionKey, capNamespaceHistory([...namespaces]));
|
|
171
|
+
}
|
|
172
|
+
while (entries.size > SESSION_NAMESPACE_BINDING_MAX_ENTRIES) {
|
|
173
|
+
const oldest = entries.keys().next().value;
|
|
174
|
+
if (typeof oldest !== "string") break;
|
|
175
|
+
entries.delete(oldest);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function createFileSessionNamespaceBindingStore(filePath, options = {}) {
|
|
181
|
+
const write = options.writeBindingFile ?? ((targetPath, bindings) => writeBindingFile(targetPath, bindings, options.renameBindingFile));
|
|
182
|
+
return {
|
|
183
|
+
async namespacesFor(sessionKey) {
|
|
184
|
+
const key = encodeSessionKey(sessionKey);
|
|
185
|
+
return queueFileWrite(filePath, async () => {
|
|
186
|
+
const bindings = await readBindingFile(filePath);
|
|
187
|
+
const entry = bindings.entries[key];
|
|
188
|
+
if (entry === void 0) return [];
|
|
189
|
+
const namespaces = [...entry.namespaces];
|
|
190
|
+
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
191
|
+
entry.updatedAt = updatedAt;
|
|
192
|
+
bindings.entries = pruneBindingEntries(bindings.entries, key);
|
|
193
|
+
try {
|
|
194
|
+
await write(filePath, bindings);
|
|
195
|
+
clearVolatileRefresh(filePath, key);
|
|
196
|
+
} catch {
|
|
197
|
+
retainVolatileRefresh(filePath, key, updatedAt);
|
|
198
|
+
return namespaces;
|
|
199
|
+
}
|
|
200
|
+
return namespaces;
|
|
201
|
+
});
|
|
202
|
+
},
|
|
203
|
+
async remember(sessionKey, namespace) {
|
|
204
|
+
const key = encodeSessionKey(sessionKey);
|
|
205
|
+
await queueFileWrite(filePath, async () => {
|
|
206
|
+
const bindings = await readBindingFile(filePath);
|
|
207
|
+
const existing = bindings.entries[key]?.namespaces ?? [];
|
|
208
|
+
bindings.entries[key] = {
|
|
209
|
+
namespaces: addNamespace(existing, namespace),
|
|
210
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
211
|
+
};
|
|
212
|
+
bindings.entries = pruneBindingEntries(bindings.entries, key);
|
|
213
|
+
await write(filePath, bindings);
|
|
214
|
+
});
|
|
215
|
+
},
|
|
216
|
+
async replace(sessionKey, namespaces) {
|
|
217
|
+
const key = encodeSessionKey(sessionKey);
|
|
218
|
+
await queueFileWrite(filePath, async () => {
|
|
219
|
+
const bindings = await readBindingFile(filePath);
|
|
220
|
+
if (namespaces.length === 0) {
|
|
221
|
+
delete bindings.entries[key];
|
|
222
|
+
} else {
|
|
223
|
+
bindings.entries[key] = {
|
|
224
|
+
namespaces: capNamespaceHistory([...namespaces]),
|
|
225
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
bindings.entries = pruneBindingEntries(bindings.entries, key);
|
|
229
|
+
await write(filePath, bindings);
|
|
230
|
+
clearVolatileRefresh(filePath, key);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
export {
|
|
236
|
+
SESSION_NAMESPACE_BINDING_MAX_AGE_MS,
|
|
237
|
+
SESSION_NAMESPACE_BINDING_MAX_ENTRIES,
|
|
238
|
+
SESSION_NAMESPACE_BINDING_MAX_NAMESPACES,
|
|
239
|
+
SESSION_NAMESPACE_BINDING_MAX_NAMESPACE_LENGTH,
|
|
240
|
+
createFileSessionNamespaceBindingStore,
|
|
241
|
+
createInMemorySessionNamespaceBindingStore
|
|
242
|
+
};
|
|
243
|
+
//# sourceMappingURL=session-namespace-bindings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/session-namespace-bindings.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { mkdir, readFile, rename, rm, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nexport interface SessionNamespaceBindingStore {\n namespacesFor(sessionKey: string): Promise<string[]>;\n remember(sessionKey: string, namespace: string): Promise<void>;\n replace?(sessionKey: string, namespaces: string[]): Promise<void>;\n}\n\nexport const SESSION_NAMESPACE_BINDING_MAX_ENTRIES = 1_000;\nexport const SESSION_NAMESPACE_BINDING_MAX_NAMESPACES = 64;\nexport const SESSION_NAMESPACE_BINDING_MAX_NAMESPACE_LENGTH = 256;\nexport const SESSION_NAMESPACE_BINDING_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1_000;\n\ninterface NamespaceBindingEntry {\n namespaces: string[];\n updatedAt: string;\n}\n\ninterface NamespaceBindingFile {\n version: 1;\n entries: Record<string, NamespaceBindingEntry>;\n}\ntype BindingFileWriter = (filePath: string, bindings: NamespaceBindingFile) => Promise<void>;\n\nconst fileWriteChains = new Map<string, Promise<void>>();\nconst volatileRefreshes = new Map<string, Map<string, string>>();\n\nfunction retainVolatileRefresh(filePath: string, sessionKey: string, updatedAt: string): void {\n const refreshes = volatileRefreshes.get(filePath) ?? new Map<string, string>();\n refreshes.set(sessionKey, updatedAt);\n volatileRefreshes.set(filePath, refreshes);\n}\n\nfunction clearVolatileRefresh(filePath: string, sessionKey: string): void {\n const refreshes = volatileRefreshes.get(filePath);\n if (refreshes === undefined) return;\n refreshes.delete(sessionKey);\n if (refreshes.size === 0) volatileRefreshes.delete(filePath);\n}\n\nfunction applyVolatileRefreshes(filePath: string, entries: Record<string, NamespaceBindingEntry>): void {\n const refreshes = volatileRefreshes.get(filePath);\n if (refreshes === undefined) return;\n for (const [key, updatedAt] of refreshes) {\n const entry = entries[key];\n if (entry === undefined) {\n refreshes.delete(key);\n continue;\n }\n const persistedAt = Date.parse(entry.updatedAt);\n const refreshedAt = Date.parse(updatedAt);\n if (Number.isFinite(refreshedAt) && (!Number.isFinite(persistedAt) || refreshedAt > persistedAt)) {\n entry.updatedAt = updatedAt;\n } else {\n refreshes.delete(key);\n }\n }\n if (refreshes.size === 0) volatileRefreshes.delete(filePath);\n}\n\nfunction emptyBindingFile(): NamespaceBindingFile {\n return { version: 1, entries: Object.create(null) as Record<string, NamespaceBindingEntry> };\n}\n\nfunction capNamespaceHistory(namespaces: string[]): string[] {\n return namespaces.length > SESSION_NAMESPACE_BINDING_MAX_NAMESPACES\n ? namespaces.slice(-SESSION_NAMESPACE_BINDING_MAX_NAMESPACES)\n : namespaces;\n}\n\nfunction normalizeNamespaces(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n const namespaces: string[] = [];\n for (const entry of value) {\n if (typeof entry !== \"string\") continue;\n const namespace = entry.trim();\n const existing = namespaces.indexOf(namespace);\n if (existing >= 0) namespaces.splice(existing, 1);\n namespaces.push(namespace);\n }\n return capNamespaceHistory(namespaces);\n}\n\nfunction addNamespace(namespaces: string[], namespace: string): string[] {\n const normalized = namespace.trim();\n return capNamespaceHistory([\n ...namespaces.filter((candidate) => candidate !== normalized),\n normalized,\n ]);\n}\n\nfunction pruneBindingEntries(\n entries: Record<string, NamespaceBindingEntry>,\n protectedKey?: string\n): Record<string, NamespaceBindingEntry> {\n const cutoff = Date.now() - SESSION_NAMESPACE_BINDING_MAX_AGE_MS;\n const retained = Object.entries(entries)\n .filter(([_key, entry]) => {\n const updatedAt = Date.parse(entry.updatedAt);\n return Number.isFinite(updatedAt) && updatedAt >= cutoff;\n })\n .sort(([leftKey, left], [rightKey, right]) => {\n if (leftKey === rightKey) return 0;\n if (leftKey === protectedKey) return -1;\n if (rightKey === protectedKey) return 1;\n const leftAt = Date.parse(left.updatedAt);\n const rightAt = Date.parse(right.updatedAt);\n if (leftAt !== rightAt) return rightAt > leftAt ? 1 : -1;\n return leftKey < rightKey ? -1 : 1;\n })\n .slice(0, SESSION_NAMESPACE_BINDING_MAX_ENTRIES);\n const pruned = Object.create(null) as Record<string, NamespaceBindingEntry>;\n for (const [key, entry] of retained) pruned[key] = entry;\n return pruned;\n}\n\nfunction encodeSessionKey(sessionKey: string): string {\n return encodeURIComponent(sessionKey);\n}\n\nasync function readBindingFile(filePath: string): Promise<NamespaceBindingFile> {\n let raw: string;\n try {\n raw = await readFile(filePath, \"utf8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException)?.code === \"ENOENT\") {\n volatileRefreshes.delete(filePath);\n return emptyBindingFile();\n }\n throw err;\n }\n const parsed = JSON.parse(raw) as Partial<NamespaceBindingFile>;\n if (\n !parsed ||\n typeof parsed !== \"object\" ||\n Array.isArray(parsed) ||\n parsed.version !== 1 ||\n !parsed.entries ||\n typeof parsed.entries !== \"object\" ||\n Array.isArray(parsed.entries)\n ) {\n throw new Error(\"session namespace binding file has invalid structure\");\n }\n const entries = Object.create(null) as Record<string, NamespaceBindingEntry>;\n for (const [key, value] of Object.entries(parsed.entries)) {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n throw new Error(\"session namespace binding entry has invalid structure\");\n }\n const entry = value as Partial<NamespaceBindingEntry>;\n if (\n typeof entry.updatedAt !== \"string\" ||\n !Number.isFinite(Date.parse(entry.updatedAt)) ||\n !Array.isArray(entry.namespaces) ||\n entry.namespaces.length === 0 ||\n entry.namespaces.some(\n (namespace) =>\n typeof namespace !== \"string\" ||\n namespace.trim().length > SESSION_NAMESPACE_BINDING_MAX_NAMESPACE_LENGTH,\n )\n ) {\n throw new Error(\"session namespace binding entry has invalid structure\");\n }\n const namespaces = normalizeNamespaces(entry.namespaces);\n if (namespaces.length === 0) {\n throw new Error(\"session namespace binding entry has invalid structure\");\n }\n entries[key] = { namespaces, updatedAt: entry.updatedAt };\n }\n applyVolatileRefreshes(filePath, entries);\n return { version: 1, entries: pruneBindingEntries(entries) };\n}\n\nasync function writeBindingFile(\n filePath: string,\n bindings: NamespaceBindingFile,\n renameFile: typeof rename = rename,\n): Promise<void> {\n await mkdir(path.dirname(filePath), { recursive: true });\n const temporaryPath = `${filePath}.${randomUUID()}.tmp`;\n try {\n await writeFile(temporaryPath, JSON.stringify(bindings, null, 2), \"utf8\");\n await renameFile(temporaryPath, filePath);\n } catch (err) {\n await rm(temporaryPath, { force: true }).catch(() => undefined);\n throw err;\n }\n}\n\nasync function queueFileWrite<TResult>(\n filePath: string,\n operation: () => Promise<TResult>,\n): Promise<TResult> {\n const prior = fileWriteChains.get(filePath) ?? Promise.resolve();\n const run = prior.catch(() => undefined).then(operation);\n fileWriteChains.set(\n filePath,\n run.then(\n () => undefined,\n () => undefined,\n ),\n );\n return run;\n}\n\nexport function createInMemorySessionNamespaceBindingStore(): SessionNamespaceBindingStore {\n const entries = new Map<string, string[]>();\n return {\n async namespacesFor(sessionKey: string): Promise<string[]> {\n const namespaces = entries.get(sessionKey);\n if (namespaces === undefined) return [];\n entries.delete(sessionKey);\n entries.set(sessionKey, namespaces);\n return [...namespaces];\n },\n async remember(sessionKey: string, namespace: string): Promise<void> {\n const existing = entries.get(sessionKey) ?? [];\n entries.delete(sessionKey);\n entries.set(sessionKey, addNamespace(existing, namespace));\n while (entries.size > SESSION_NAMESPACE_BINDING_MAX_ENTRIES) {\n const oldest = entries.keys().next().value;\n if (typeof oldest !== \"string\") break;\n entries.delete(oldest);\n }\n },\n async replace(sessionKey: string, namespaces: string[]): Promise<void> {\n entries.delete(sessionKey);\n if (namespaces.length > 0) {\n entries.set(sessionKey, capNamespaceHistory([...namespaces]));\n }\n while (entries.size > SESSION_NAMESPACE_BINDING_MAX_ENTRIES) {\n const oldest = entries.keys().next().value;\n if (typeof oldest !== \"string\") break;\n entries.delete(oldest);\n }\n },\n };\n}\n\nexport function createFileSessionNamespaceBindingStore(\n filePath: string,\n options: { writeBindingFile?: BindingFileWriter; renameBindingFile?: typeof rename } = {},\n): SessionNamespaceBindingStore {\n const write =\n options.writeBindingFile ??\n ((targetPath: string, bindings: NamespaceBindingFile) =>\n writeBindingFile(targetPath, bindings, options.renameBindingFile));\n return {\n async namespacesFor(sessionKey: string): Promise<string[]> {\n const key = encodeSessionKey(sessionKey);\n return queueFileWrite(filePath, async () => {\n const bindings = await readBindingFile(filePath);\n const entry = bindings.entries[key];\n if (entry === undefined) return [];\n const namespaces = [...entry.namespaces];\n const updatedAt = new Date().toISOString();\n entry.updatedAt = updatedAt;\n bindings.entries = pruneBindingEntries(bindings.entries, key);\n try {\n await write(filePath, bindings);\n clearVolatileRefresh(filePath, key);\n } catch {\n retainVolatileRefresh(filePath, key, updatedAt);\n return namespaces;\n }\n return namespaces;\n });\n },\n async remember(sessionKey: string, namespace: string): Promise<void> {\n const key = encodeSessionKey(sessionKey);\n await queueFileWrite(filePath, async () => {\n const bindings = await readBindingFile(filePath);\n const existing = bindings.entries[key]?.namespaces ?? [];\n bindings.entries[key] = {\n namespaces: addNamespace(existing, namespace),\n updatedAt: new Date().toISOString(),\n };\n bindings.entries = pruneBindingEntries(bindings.entries, key);\n await write(filePath, bindings);\n });\n },\n async replace(sessionKey: string, namespaces: string[]): Promise<void> {\n const key = encodeSessionKey(sessionKey);\n await queueFileWrite(filePath, async () => {\n const bindings = await readBindingFile(filePath);\n if (namespaces.length === 0) {\n delete bindings.entries[key];\n } else {\n bindings.entries[key] = {\n namespaces: capNamespaceHistory([...namespaces]),\n updatedAt: new Date().toISOString(),\n };\n }\n bindings.entries = pruneBindingEntries(bindings.entries, key);\n await write(filePath, bindings);\n clearVolatileRefresh(filePath, key);\n });\n },\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/storage.js
CHANGED
|
@@ -16,14 +16,14 @@ import {
|
|
|
16
16
|
parseFrontmatter,
|
|
17
17
|
parseFrontmatterStringValue,
|
|
18
18
|
serializeEntityFile
|
|
19
|
-
} from "./chunk-
|
|
20
|
-
import "./chunk-2QMKMVHH.js";
|
|
19
|
+
} from "./chunk-UZK7VVPD.js";
|
|
21
20
|
import "./chunk-UHVXE24Y.js";
|
|
21
|
+
import "./chunk-2QMKMVHH.js";
|
|
22
22
|
import "./chunk-DEBHXHJR.js";
|
|
23
23
|
import "./chunk-DNHVPZOD.js";
|
|
24
24
|
import "./chunk-6CIAGZ6X.js";
|
|
25
|
-
import "./chunk-J6A3CX5N.js";
|
|
26
25
|
import "./chunk-HKF5VASQ.js";
|
|
26
|
+
import "./chunk-J6A3CX5N.js";
|
|
27
27
|
import "./chunk-V25OWXAC.js";
|
|
28
28
|
import "./chunk-XKGPIWGI.js";
|
|
29
29
|
import "./chunk-EGGE52UU.js";
|
|
@@ -43,12 +43,12 @@ import {
|
|
|
43
43
|
import "./chunk-BT7NVCML.js";
|
|
44
44
|
import "./chunk-JYIJNNN3.js";
|
|
45
45
|
import "./chunk-AAD2ZS6P.js";
|
|
46
|
-
import "./chunk-W5H6ZRPT.js";
|
|
47
46
|
import "./chunk-5VQJBH2G.js";
|
|
48
47
|
import "./chunk-AITCS2RC.js";
|
|
49
48
|
import "./chunk-XMMESCPD.js";
|
|
50
49
|
import "./chunk-42JKGUFJ.js";
|
|
51
50
|
import "./chunk-PDJTHRAW.js";
|
|
51
|
+
import "./chunk-W5H6ZRPT.js";
|
|
52
52
|
import "./chunk-G7D6GZ5J.js";
|
|
53
53
|
import "./chunk-C23BYTJV.js";
|
|
54
54
|
import "./chunk-QIEUU4UK.js";
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lifecycle scenario-matrix harness (issue #1993, umbrella #1988 phase 5).
|
|
3
|
+
*
|
|
4
|
+
* AGENTS.md mandates a scenario matrix for stateful subsystem changes
|
|
5
|
+
* (session/retrieval/cache work): the nine canonical rows a reviewer will
|
|
6
|
+
* otherwise probe by hand — provider identity, sparse metadata ± remembered
|
|
7
|
+
* binding, provider rebinding, restart/reload recovery, compaction flush,
|
|
8
|
+
* `before_reset`, `session_end`, and dedupe/replay. Historically that matrix
|
|
9
|
+
* lived only as prose (see the "Why Stateful PRs Churn" section) and nobody
|
|
10
|
+
* instantiated it, so PR #1923 burned 53 review threads on exactly those
|
|
11
|
+
* adjacent invariants.
|
|
12
|
+
*
|
|
13
|
+
* This module makes the matrix EXECUTABLE. A subsystem adapter implements
|
|
14
|
+
* `LifecycleSubject<S>`; `runLifecycleMatrix` registers one named test per
|
|
15
|
+
* `MATRIX_ROWS` entry. Adding a row here is a one-file change that
|
|
16
|
+
* automatically extends every adopter — the whole point of the harness.
|
|
17
|
+
*
|
|
18
|
+
* Test-support only: this file lives under `src/testing/` and is exported solely
|
|
19
|
+
* so adapter test subjects can share the canonical matrix across packages.
|
|
20
|
+
* The registrar loads `node:test` through `createRequire` so the built artifact
|
|
21
|
+
* keeps the builtin specifier, and runtime code must never import this module.
|
|
22
|
+
*/
|
|
23
|
+
/** The nine canonical AGENTS.md session/retrieval/cache matrix rows. */
|
|
24
|
+
type MatrixRowId = "explicit-provider-identity" | "sparse-metadata-with-binding" | "sparse-metadata-without-binding" | "provider-rebinding" | "restart-reload-recovery" | "compaction-flush" | "before-reset" | "session-end" | "dedupe-replay";
|
|
25
|
+
/**
|
|
26
|
+
* Typed scenario axes a subject can branch on instead of re-parsing the row
|
|
27
|
+
* id. These encode the real dimensions the AGENTS.md matrix probes.
|
|
28
|
+
*/
|
|
29
|
+
interface MatrixRowDimensions {
|
|
30
|
+
/** How the session's provider/identity binding is established. */
|
|
31
|
+
readonly providerIdentity: "explicit" | "sparse" | "rebound" | "none";
|
|
32
|
+
/** Whether a previously remembered binding is available to reuse. */
|
|
33
|
+
readonly rememberedBinding: boolean;
|
|
34
|
+
/** Whether the scenario exercises a fresh instance over persisted state. */
|
|
35
|
+
readonly restart: boolean;
|
|
36
|
+
/** The forced-flush lifecycle transition under test, if any. */
|
|
37
|
+
readonly flush: "before_reset" | "session_end" | "compaction" | "none";
|
|
38
|
+
/** Whether the scenario replays/duplicates content through the dedupe path. */
|
|
39
|
+
readonly dedupeOrReplay: boolean;
|
|
40
|
+
}
|
|
41
|
+
interface MatrixRow {
|
|
42
|
+
readonly id: MatrixRowId;
|
|
43
|
+
/** Human-readable row title (matches the AGENTS.md bullet). */
|
|
44
|
+
readonly title: string;
|
|
45
|
+
readonly dimensions: MatrixRowDimensions;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The canonical matrix. Order and membership mirror AGENTS.md
|
|
49
|
+
* "Minimum scenario matrix for session/retrieval/cache work". Frozen so an
|
|
50
|
+
* adopter cannot mutate the shared set.
|
|
51
|
+
*/
|
|
52
|
+
declare const MATRIX_ROWS: readonly MatrixRow[];
|
|
53
|
+
/**
|
|
54
|
+
* A subsystem adapter. Each row runs `setup → exercise → invariants`. `setup`
|
|
55
|
+
* is transactional — it cleans up its own partial state if it throws — and once
|
|
56
|
+
* it returns a fixture, `teardown` always runs (even when `exercise` or
|
|
57
|
+
* `invariants` throws), so a leaked temp dir or orchestrator cannot poison a
|
|
58
|
+
* sibling row.
|
|
59
|
+
*
|
|
60
|
+
* A row a subject cannot honestly realize must NOT be faked: return a skip
|
|
61
|
+
* reason from `appliesTo` and the harness registers it as an explicit skipped
|
|
62
|
+
* test (a mock-only pass is a finding about the code, not the harness — #1993).
|
|
63
|
+
*/
|
|
64
|
+
interface LifecycleSubject<S> {
|
|
65
|
+
/** Build the row-specific fixture; MUST clean up its own partial state if it throws. */
|
|
66
|
+
setup(row: MatrixRow): Promise<S>;
|
|
67
|
+
/** Drive the behavior under test. */
|
|
68
|
+
exercise(subject: S, row: MatrixRow): Promise<void>;
|
|
69
|
+
/** Row-specific assertions against observable effects. */
|
|
70
|
+
invariants(subject: S, row: MatrixRow): Promise<void>;
|
|
71
|
+
/** Always-run cleanup. */
|
|
72
|
+
teardown(subject: S, row: MatrixRow): Promise<void>;
|
|
73
|
+
/**
|
|
74
|
+
* Optional: return a non-empty string to SKIP a row with that reason, or
|
|
75
|
+
* `false`. Omitted / `true` means the row applies. A skip is honest coverage
|
|
76
|
+
* documentation, not a silent pass.
|
|
77
|
+
*/
|
|
78
|
+
appliesTo?(row: MatrixRow): boolean | string;
|
|
79
|
+
}
|
|
80
|
+
/** Minimal test registrar seam (defaults to `node:test`), injectable for self-test. */
|
|
81
|
+
type MatrixTestRegistrar = (name: string, fn: () => void | Promise<void>) => void;
|
|
82
|
+
type MatrixSkipRegistrar = (name: string, reason: string) => void;
|
|
83
|
+
interface RunLifecycleMatrixOptions {
|
|
84
|
+
/** Override the test registrar (self-test injects a capturing fake). */
|
|
85
|
+
readonly register?: MatrixTestRegistrar;
|
|
86
|
+
/** Override the skip registrar. */
|
|
87
|
+
readonly registerSkipped?: MatrixSkipRegistrar;
|
|
88
|
+
/** Override the row set (self-test only; production adopters use MATRIX_ROWS). */
|
|
89
|
+
readonly rows?: readonly MatrixRow[];
|
|
90
|
+
}
|
|
91
|
+
/** Deterministic, greppable test name — the coverage manifest and CI depend on it. */
|
|
92
|
+
declare function lifecycleTestName(subjectName: string, row: MatrixRow): string;
|
|
93
|
+
/**
|
|
94
|
+
* Register one test per matrix row for `subject`, named with the row identity
|
|
95
|
+
* so a failing row is unambiguous in CI output. Runs `setup → exercise →
|
|
96
|
+
* invariants`; `teardown` runs whenever `setup` produced a fixture.
|
|
97
|
+
*/
|
|
98
|
+
declare function runLifecycleMatrix<S>(subjectName: string, subject: LifecycleSubject<S>, options?: RunLifecycleMatrixOptions): void;
|
|
99
|
+
|
|
100
|
+
export { type LifecycleSubject, MATRIX_ROWS, type MatrixRow, type MatrixRowDimensions, type MatrixRowId, type MatrixSkipRegistrar, type MatrixTestRegistrar, type RunLifecycleMatrixOptions, lifecycleTestName, runLifecycleMatrix };
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import "../chunk-PZ5AY32C.js";
|
|
2
|
+
|
|
3
|
+
// src/testing/lifecycle-matrix.ts
|
|
4
|
+
import { createRequire } from "module";
|
|
5
|
+
var MATRIX_ROWS = Object.freeze([
|
|
6
|
+
{
|
|
7
|
+
id: "explicit-provider-identity",
|
|
8
|
+
title: "explicit provider identity",
|
|
9
|
+
dimensions: {
|
|
10
|
+
providerIdentity: "explicit",
|
|
11
|
+
rememberedBinding: false,
|
|
12
|
+
restart: false,
|
|
13
|
+
flush: "none",
|
|
14
|
+
dedupeOrReplay: false
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: "sparse-metadata-with-binding",
|
|
19
|
+
title: "sparse metadata with remembered binding",
|
|
20
|
+
dimensions: {
|
|
21
|
+
providerIdentity: "sparse",
|
|
22
|
+
rememberedBinding: true,
|
|
23
|
+
restart: false,
|
|
24
|
+
flush: "none",
|
|
25
|
+
dedupeOrReplay: false
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: "sparse-metadata-without-binding",
|
|
30
|
+
title: "sparse metadata without remembered binding",
|
|
31
|
+
dimensions: {
|
|
32
|
+
providerIdentity: "sparse",
|
|
33
|
+
rememberedBinding: false,
|
|
34
|
+
restart: false,
|
|
35
|
+
flush: "none",
|
|
36
|
+
dedupeOrReplay: false
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: "provider-rebinding",
|
|
41
|
+
title: "provider rebinding",
|
|
42
|
+
dimensions: {
|
|
43
|
+
providerIdentity: "rebound",
|
|
44
|
+
rememberedBinding: true,
|
|
45
|
+
restart: false,
|
|
46
|
+
flush: "none",
|
|
47
|
+
dedupeOrReplay: false
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: "restart-reload-recovery",
|
|
52
|
+
title: "restart/reload recovery",
|
|
53
|
+
dimensions: {
|
|
54
|
+
providerIdentity: "explicit",
|
|
55
|
+
rememberedBinding: true,
|
|
56
|
+
restart: true,
|
|
57
|
+
flush: "none",
|
|
58
|
+
dedupeOrReplay: false
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: "compaction-flush",
|
|
63
|
+
title: "compaction flush",
|
|
64
|
+
dimensions: {
|
|
65
|
+
providerIdentity: "explicit",
|
|
66
|
+
rememberedBinding: false,
|
|
67
|
+
restart: false,
|
|
68
|
+
flush: "compaction",
|
|
69
|
+
dedupeOrReplay: false
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: "before-reset",
|
|
74
|
+
title: "before_reset",
|
|
75
|
+
dimensions: {
|
|
76
|
+
providerIdentity: "explicit",
|
|
77
|
+
rememberedBinding: false,
|
|
78
|
+
restart: false,
|
|
79
|
+
flush: "before_reset",
|
|
80
|
+
dedupeOrReplay: false
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: "session-end",
|
|
85
|
+
title: "session_end",
|
|
86
|
+
dimensions: {
|
|
87
|
+
providerIdentity: "explicit",
|
|
88
|
+
rememberedBinding: false,
|
|
89
|
+
restart: false,
|
|
90
|
+
flush: "session_end",
|
|
91
|
+
dedupeOrReplay: false
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: "dedupe-replay",
|
|
96
|
+
title: "dedupe/replay behavior",
|
|
97
|
+
dimensions: {
|
|
98
|
+
providerIdentity: "explicit",
|
|
99
|
+
rememberedBinding: false,
|
|
100
|
+
restart: false,
|
|
101
|
+
flush: "none",
|
|
102
|
+
dedupeOrReplay: true
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
]);
|
|
106
|
+
function lifecycleTestName(subjectName, row) {
|
|
107
|
+
return `lifecycle-matrix[${subjectName}] :: ${row.id} \u2014 ${row.title}`;
|
|
108
|
+
}
|
|
109
|
+
var requiredNodeTest = createRequire(import.meta.url)("node:test");
|
|
110
|
+
var nodeTest = typeof requiredNodeTest === "function" ? requiredNodeTest : requiredNodeTest.test;
|
|
111
|
+
var defaultRegister = (name, fn) => {
|
|
112
|
+
nodeTest(name, fn);
|
|
113
|
+
};
|
|
114
|
+
var defaultRegisterSkipped = (name, reason) => {
|
|
115
|
+
nodeTest(name, { skip: reason }, () => {
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
function runLifecycleMatrix(subjectName, subject, options = {}) {
|
|
119
|
+
const register = options.register ?? defaultRegister;
|
|
120
|
+
const registerSkipped = options.registerSkipped ?? defaultRegisterSkipped;
|
|
121
|
+
const rows = options.rows ?? MATRIX_ROWS;
|
|
122
|
+
for (const row of rows) {
|
|
123
|
+
const name = lifecycleTestName(subjectName, row);
|
|
124
|
+
const applicability = subject.appliesTo ? subject.appliesTo(row) : true;
|
|
125
|
+
if (applicability !== true) {
|
|
126
|
+
const reason = typeof applicability === "string" && applicability.length > 0 ? applicability : "not applicable to this subsystem";
|
|
127
|
+
registerSkipped(name, reason);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
register(name, async () => {
|
|
131
|
+
let built;
|
|
132
|
+
let setupDone = false;
|
|
133
|
+
try {
|
|
134
|
+
built = await subject.setup(row);
|
|
135
|
+
setupDone = true;
|
|
136
|
+
await subject.exercise(built, row);
|
|
137
|
+
await subject.invariants(built, row);
|
|
138
|
+
} finally {
|
|
139
|
+
if (setupDone) await subject.teardown(built, row);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
export {
|
|
145
|
+
MATRIX_ROWS,
|
|
146
|
+
lifecycleTestName,
|
|
147
|
+
runLifecycleMatrix
|
|
148
|
+
};
|
|
149
|
+
//# sourceMappingURL=lifecycle-matrix.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/testing/lifecycle-matrix.ts"],"sourcesContent":["/**\n * Lifecycle scenario-matrix harness (issue #1993, umbrella #1988 phase 5).\n *\n * AGENTS.md mandates a scenario matrix for stateful subsystem changes\n * (session/retrieval/cache work): the nine canonical rows a reviewer will\n * otherwise probe by hand — provider identity, sparse metadata ± remembered\n * binding, provider rebinding, restart/reload recovery, compaction flush,\n * `before_reset`, `session_end`, and dedupe/replay. Historically that matrix\n * lived only as prose (see the \"Why Stateful PRs Churn\" section) and nobody\n * instantiated it, so PR #1923 burned 53 review threads on exactly those\n * adjacent invariants.\n *\n * This module makes the matrix EXECUTABLE. A subsystem adapter implements\n * `LifecycleSubject<S>`; `runLifecycleMatrix` registers one named test per\n * `MATRIX_ROWS` entry. Adding a row here is a one-file change that\n * automatically extends every adopter — the whole point of the harness.\n *\n * Test-support only: this file lives under `src/testing/` and is exported solely\n * so adapter test subjects can share the canonical matrix across packages.\n * The registrar loads `node:test` through `createRequire` so the built artifact\n * keeps the builtin specifier, and runtime code must never import this module.\n */\n\nimport { createRequire } from \"node:module\";\n\n/** The nine canonical AGENTS.md session/retrieval/cache matrix rows. */\nexport type MatrixRowId =\n | \"explicit-provider-identity\"\n | \"sparse-metadata-with-binding\"\n | \"sparse-metadata-without-binding\"\n | \"provider-rebinding\"\n | \"restart-reload-recovery\"\n | \"compaction-flush\"\n | \"before-reset\"\n | \"session-end\"\n | \"dedupe-replay\";\n\n/**\n * Typed scenario axes a subject can branch on instead of re-parsing the row\n * id. These encode the real dimensions the AGENTS.md matrix probes.\n */\nexport interface MatrixRowDimensions {\n /** How the session's provider/identity binding is established. */\n readonly providerIdentity: \"explicit\" | \"sparse\" | \"rebound\" | \"none\";\n /** Whether a previously remembered binding is available to reuse. */\n readonly rememberedBinding: boolean;\n /** Whether the scenario exercises a fresh instance over persisted state. */\n readonly restart: boolean;\n /** The forced-flush lifecycle transition under test, if any. */\n readonly flush: \"before_reset\" | \"session_end\" | \"compaction\" | \"none\";\n /** Whether the scenario replays/duplicates content through the dedupe path. */\n readonly dedupeOrReplay: boolean;\n}\n\nexport interface MatrixRow {\n readonly id: MatrixRowId;\n /** Human-readable row title (matches the AGENTS.md bullet). */\n readonly title: string;\n readonly dimensions: MatrixRowDimensions;\n}\n\n/**\n * The canonical matrix. Order and membership mirror AGENTS.md\n * \"Minimum scenario matrix for session/retrieval/cache work\". Frozen so an\n * adopter cannot mutate the shared set.\n */\nexport const MATRIX_ROWS: readonly MatrixRow[] = Object.freeze([\n {\n id: \"explicit-provider-identity\",\n title: \"explicit provider identity\",\n dimensions: {\n providerIdentity: \"explicit\",\n rememberedBinding: false,\n restart: false,\n flush: \"none\",\n dedupeOrReplay: false,\n },\n },\n {\n id: \"sparse-metadata-with-binding\",\n title: \"sparse metadata with remembered binding\",\n dimensions: {\n providerIdentity: \"sparse\",\n rememberedBinding: true,\n restart: false,\n flush: \"none\",\n dedupeOrReplay: false,\n },\n },\n {\n id: \"sparse-metadata-without-binding\",\n title: \"sparse metadata without remembered binding\",\n dimensions: {\n providerIdentity: \"sparse\",\n rememberedBinding: false,\n restart: false,\n flush: \"none\",\n dedupeOrReplay: false,\n },\n },\n {\n id: \"provider-rebinding\",\n title: \"provider rebinding\",\n dimensions: {\n providerIdentity: \"rebound\",\n rememberedBinding: true,\n restart: false,\n flush: \"none\",\n dedupeOrReplay: false,\n },\n },\n {\n id: \"restart-reload-recovery\",\n title: \"restart/reload recovery\",\n dimensions: {\n providerIdentity: \"explicit\",\n rememberedBinding: true,\n restart: true,\n flush: \"none\",\n dedupeOrReplay: false,\n },\n },\n {\n id: \"compaction-flush\",\n title: \"compaction flush\",\n dimensions: {\n providerIdentity: \"explicit\",\n rememberedBinding: false,\n restart: false,\n flush: \"compaction\",\n dedupeOrReplay: false,\n },\n },\n {\n id: \"before-reset\",\n title: \"before_reset\",\n dimensions: {\n providerIdentity: \"explicit\",\n rememberedBinding: false,\n restart: false,\n flush: \"before_reset\",\n dedupeOrReplay: false,\n },\n },\n {\n id: \"session-end\",\n title: \"session_end\",\n dimensions: {\n providerIdentity: \"explicit\",\n rememberedBinding: false,\n restart: false,\n flush: \"session_end\",\n dedupeOrReplay: false,\n },\n },\n {\n id: \"dedupe-replay\",\n title: \"dedupe/replay behavior\",\n dimensions: {\n providerIdentity: \"explicit\",\n rememberedBinding: false,\n restart: false,\n flush: \"none\",\n dedupeOrReplay: true,\n },\n },\n] as const);\n\n/**\n * A subsystem adapter. Each row runs `setup → exercise → invariants`. `setup`\n * is transactional — it cleans up its own partial state if it throws — and once\n * it returns a fixture, `teardown` always runs (even when `exercise` or\n * `invariants` throws), so a leaked temp dir or orchestrator cannot poison a\n * sibling row.\n *\n * A row a subject cannot honestly realize must NOT be faked: return a skip\n * reason from `appliesTo` and the harness registers it as an explicit skipped\n * test (a mock-only pass is a finding about the code, not the harness — #1993).\n */\nexport interface LifecycleSubject<S> {\n /** Build the row-specific fixture; MUST clean up its own partial state if it throws. */\n setup(row: MatrixRow): Promise<S>;\n /** Drive the behavior under test. */\n exercise(subject: S, row: MatrixRow): Promise<void>;\n /** Row-specific assertions against observable effects. */\n invariants(subject: S, row: MatrixRow): Promise<void>;\n /** Always-run cleanup. */\n teardown(subject: S, row: MatrixRow): Promise<void>;\n /**\n * Optional: return a non-empty string to SKIP a row with that reason, or\n * `false`. Omitted / `true` means the row applies. A skip is honest coverage\n * documentation, not a silent pass.\n */\n appliesTo?(row: MatrixRow): boolean | string;\n}\n\n/** Minimal test registrar seam (defaults to `node:test`), injectable for self-test. */\nexport type MatrixTestRegistrar = (\n name: string,\n fn: () => void | Promise<void>,\n) => void;\n\nexport type MatrixSkipRegistrar = (name: string, reason: string) => void;\n\nexport interface RunLifecycleMatrixOptions {\n /** Override the test registrar (self-test injects a capturing fake). */\n readonly register?: MatrixTestRegistrar;\n /** Override the skip registrar. */\n readonly registerSkipped?: MatrixSkipRegistrar;\n /** Override the row set (self-test only; production adopters use MATRIX_ROWS). */\n readonly rows?: readonly MatrixRow[];\n}\n\n/** Deterministic, greppable test name — the coverage manifest and CI depend on it. */\nexport function lifecycleTestName(subjectName: string, row: MatrixRow): string {\n return `lifecycle-matrix[${subjectName}] :: ${row.id} — ${row.title}`;\n}\ntype NodeTestRegistrar = {\n (name: string, fn: () => void | Promise<void>): void;\n (name: string, options: { skip: string }, fn: () => void): void;\n};\n\nconst requiredNodeTest = createRequire(import.meta.url)(\"node:test\") as\n | NodeTestRegistrar\n | { test: NodeTestRegistrar };\nconst nodeTest: NodeTestRegistrar =\n typeof requiredNodeTest === \"function\" ? requiredNodeTest : requiredNodeTest.test;\n\nconst defaultRegister: MatrixTestRegistrar = (name, fn) => {\n nodeTest(name, fn);\n};\n\nconst defaultRegisterSkipped: MatrixSkipRegistrar = (name, reason) => {\n nodeTest(name, { skip: reason }, () => {});\n};\n\n/**\n * Register one test per matrix row for `subject`, named with the row identity\n * so a failing row is unambiguous in CI output. Runs `setup → exercise →\n * invariants`; `teardown` runs whenever `setup` produced a fixture.\n */\nexport function runLifecycleMatrix<S>(\n subjectName: string,\n subject: LifecycleSubject<S>,\n options: RunLifecycleMatrixOptions = {},\n): void {\n const register = options.register ?? defaultRegister;\n const registerSkipped = options.registerSkipped ?? defaultRegisterSkipped;\n const rows = options.rows ?? MATRIX_ROWS;\n\n for (const row of rows) {\n const name = lifecycleTestName(subjectName, row);\n const applicability = subject.appliesTo ? subject.appliesTo(row) : true;\n if (applicability !== true) {\n const reason =\n typeof applicability === \"string\" && applicability.length > 0\n ? applicability\n : \"not applicable to this subsystem\";\n registerSkipped(name, reason);\n continue;\n }\n\n register(name, async () => {\n let built: S | undefined;\n let setupDone = false;\n try {\n built = await subject.setup(row);\n setupDone = true;\n await subject.exercise(built, row);\n await subject.invariants(built, row);\n } finally {\n // Teardown runs whenever setup COMPLETED — even when a subject's fixture\n // type is `void`/undefined — and is skipped only when setup itself threw\n // (a transactional setup cleans up its own partial state on throw). The\n // sentinel is a completion flag, NOT the fixture value, so a stateless or\n // void subject that did setup work is still torn down.\n if (setupDone) await subject.teardown(built as S, row);\n }\n });\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/transfer/backup.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
backupMemoryDir
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-BXLOS5AJ.js";
|
|
4
4
|
import "../chunk-765K3SAT.js";
|
|
5
|
-
import "../chunk-
|
|
5
|
+
import "../chunk-DQEMWVMT.js";
|
|
6
6
|
import "../chunk-J4EB7DNW.js";
|
|
7
|
-
import "../chunk-UI3NYK34.js";
|
|
8
|
-
import "../chunk-GCGJW34D.js";
|
|
9
7
|
import "../chunk-BJMBJZ2Y.js";
|
|
10
8
|
import "../chunk-UKJAGEXH.js";
|
|
11
9
|
import "../chunk-FP2373TW.js";
|
|
10
|
+
import "../chunk-UI3NYK34.js";
|
|
11
|
+
import "../chunk-GCGJW34D.js";
|
|
12
12
|
import "../chunk-A6XUJE5D.js";
|
|
13
13
|
import "../chunk-PZ5AY32C.js";
|
|
14
14
|
export {
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
exportCapsule,
|
|
3
3
|
isValidCapsuleSince
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-VDX2J7OX.js";
|
|
5
5
|
import "../chunk-WEHSQBFR.js";
|
|
6
|
-
import "../chunk-
|
|
6
|
+
import "../chunk-DQEMWVMT.js";
|
|
7
7
|
import "../chunk-J4EB7DNW.js";
|
|
8
|
-
import "../chunk-UI3NYK34.js";
|
|
9
|
-
import "../chunk-GCGJW34D.js";
|
|
10
8
|
import "../chunk-BJMBJZ2Y.js";
|
|
11
9
|
import "../chunk-UKJAGEXH.js";
|
|
12
10
|
import "../chunk-FP2373TW.js";
|
|
11
|
+
import "../chunk-UI3NYK34.js";
|
|
12
|
+
import "../chunk-GCGJW34D.js";
|
|
13
13
|
import "../chunk-A6XUJE5D.js";
|
|
14
14
|
import "../chunk-PZ5AY32C.js";
|
|
15
15
|
export {
|