@noy-db/hub 0.1.0-pre.3

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.
Files changed (195) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +197 -0
  3. package/dist/aggregate/index.cjs +476 -0
  4. package/dist/aggregate/index.cjs.map +1 -0
  5. package/dist/aggregate/index.d.cts +38 -0
  6. package/dist/aggregate/index.d.ts +38 -0
  7. package/dist/aggregate/index.js +53 -0
  8. package/dist/aggregate/index.js.map +1 -0
  9. package/dist/blobs/index.cjs +1480 -0
  10. package/dist/blobs/index.cjs.map +1 -0
  11. package/dist/blobs/index.d.cts +45 -0
  12. package/dist/blobs/index.d.ts +45 -0
  13. package/dist/blobs/index.js +48 -0
  14. package/dist/blobs/index.js.map +1 -0
  15. package/dist/bundle/index.cjs +436 -0
  16. package/dist/bundle/index.cjs.map +1 -0
  17. package/dist/bundle/index.d.cts +7 -0
  18. package/dist/bundle/index.d.ts +7 -0
  19. package/dist/bundle/index.js +40 -0
  20. package/dist/bundle/index.js.map +1 -0
  21. package/dist/chunk-2QR2PQTT.js +217 -0
  22. package/dist/chunk-2QR2PQTT.js.map +1 -0
  23. package/dist/chunk-4OWFYIDQ.js +79 -0
  24. package/dist/chunk-4OWFYIDQ.js.map +1 -0
  25. package/dist/chunk-5AATM2M2.js +90 -0
  26. package/dist/chunk-5AATM2M2.js.map +1 -0
  27. package/dist/chunk-ACLDOTNQ.js +543 -0
  28. package/dist/chunk-ACLDOTNQ.js.map +1 -0
  29. package/dist/chunk-BTDCBVJW.js +160 -0
  30. package/dist/chunk-BTDCBVJW.js.map +1 -0
  31. package/dist/chunk-CIMZBAZB.js +72 -0
  32. package/dist/chunk-CIMZBAZB.js.map +1 -0
  33. package/dist/chunk-E445ICYI.js +365 -0
  34. package/dist/chunk-E445ICYI.js.map +1 -0
  35. package/dist/chunk-EXQRC2L4.js +722 -0
  36. package/dist/chunk-EXQRC2L4.js.map +1 -0
  37. package/dist/chunk-FZU343FL.js +32 -0
  38. package/dist/chunk-FZU343FL.js.map +1 -0
  39. package/dist/chunk-GJILMRPO.js +354 -0
  40. package/dist/chunk-GJILMRPO.js.map +1 -0
  41. package/dist/chunk-GOUT6DND.js +1285 -0
  42. package/dist/chunk-GOUT6DND.js.map +1 -0
  43. package/dist/chunk-J66GRPNH.js +111 -0
  44. package/dist/chunk-J66GRPNH.js.map +1 -0
  45. package/dist/chunk-M2F2JAWB.js +464 -0
  46. package/dist/chunk-M2F2JAWB.js.map +1 -0
  47. package/dist/chunk-M5INGEFC.js +84 -0
  48. package/dist/chunk-M5INGEFC.js.map +1 -0
  49. package/dist/chunk-M62XNWRA.js +72 -0
  50. package/dist/chunk-M62XNWRA.js.map +1 -0
  51. package/dist/chunk-MR4424N3.js +275 -0
  52. package/dist/chunk-MR4424N3.js.map +1 -0
  53. package/dist/chunk-NPC4LFV5.js +132 -0
  54. package/dist/chunk-NPC4LFV5.js.map +1 -0
  55. package/dist/chunk-NXFEYLVG.js +311 -0
  56. package/dist/chunk-NXFEYLVG.js.map +1 -0
  57. package/dist/chunk-R36SIKES.js +79 -0
  58. package/dist/chunk-R36SIKES.js.map +1 -0
  59. package/dist/chunk-TDR6T5CJ.js +381 -0
  60. package/dist/chunk-TDR6T5CJ.js.map +1 -0
  61. package/dist/chunk-UF3BUNQZ.js +1 -0
  62. package/dist/chunk-UF3BUNQZ.js.map +1 -0
  63. package/dist/chunk-UQFSPSWG.js +1109 -0
  64. package/dist/chunk-UQFSPSWG.js.map +1 -0
  65. package/dist/chunk-USKYUS74.js +793 -0
  66. package/dist/chunk-USKYUS74.js.map +1 -0
  67. package/dist/chunk-XCL3WP6J.js +121 -0
  68. package/dist/chunk-XCL3WP6J.js.map +1 -0
  69. package/dist/chunk-XHFOENR2.js +680 -0
  70. package/dist/chunk-XHFOENR2.js.map +1 -0
  71. package/dist/chunk-ZFKD4QMV.js +430 -0
  72. package/dist/chunk-ZFKD4QMV.js.map +1 -0
  73. package/dist/chunk-ZLMV3TUA.js +490 -0
  74. package/dist/chunk-ZLMV3TUA.js.map +1 -0
  75. package/dist/chunk-ZRG4V3F5.js +17 -0
  76. package/dist/chunk-ZRG4V3F5.js.map +1 -0
  77. package/dist/consent/index.cjs +204 -0
  78. package/dist/consent/index.cjs.map +1 -0
  79. package/dist/consent/index.d.cts +24 -0
  80. package/dist/consent/index.d.ts +24 -0
  81. package/dist/consent/index.js +23 -0
  82. package/dist/consent/index.js.map +1 -0
  83. package/dist/crdt/index.cjs +152 -0
  84. package/dist/crdt/index.cjs.map +1 -0
  85. package/dist/crdt/index.d.cts +30 -0
  86. package/dist/crdt/index.d.ts +30 -0
  87. package/dist/crdt/index.js +24 -0
  88. package/dist/crdt/index.js.map +1 -0
  89. package/dist/crypto-IVKU7YTT.js +44 -0
  90. package/dist/crypto-IVKU7YTT.js.map +1 -0
  91. package/dist/delegation-XDJCBTI2.js +16 -0
  92. package/dist/delegation-XDJCBTI2.js.map +1 -0
  93. package/dist/dev-unlock-CeXic1xC.d.cts +263 -0
  94. package/dist/dev-unlock-KrKkcqD3.d.ts +263 -0
  95. package/dist/hash-9KO1BGxh.d.cts +63 -0
  96. package/dist/hash-ChfJjRjQ.d.ts +63 -0
  97. package/dist/history/index.cjs +1215 -0
  98. package/dist/history/index.cjs.map +1 -0
  99. package/dist/history/index.d.cts +62 -0
  100. package/dist/history/index.d.ts +62 -0
  101. package/dist/history/index.js +79 -0
  102. package/dist/history/index.js.map +1 -0
  103. package/dist/i18n/index.cjs +746 -0
  104. package/dist/i18n/index.cjs.map +1 -0
  105. package/dist/i18n/index.d.cts +38 -0
  106. package/dist/i18n/index.d.ts +38 -0
  107. package/dist/i18n/index.js +55 -0
  108. package/dist/i18n/index.js.map +1 -0
  109. package/dist/index-BRHBCmLt.d.ts +1940 -0
  110. package/dist/index-C8kQtmOk.d.ts +380 -0
  111. package/dist/index-DN-J-5wT.d.cts +1940 -0
  112. package/dist/index-DhjMjz7L.d.cts +380 -0
  113. package/dist/index.cjs +14756 -0
  114. package/dist/index.cjs.map +1 -0
  115. package/dist/index.d.cts +269 -0
  116. package/dist/index.d.ts +269 -0
  117. package/dist/index.js +6085 -0
  118. package/dist/index.js.map +1 -0
  119. package/dist/indexing/index.cjs +736 -0
  120. package/dist/indexing/index.cjs.map +1 -0
  121. package/dist/indexing/index.d.cts +36 -0
  122. package/dist/indexing/index.d.ts +36 -0
  123. package/dist/indexing/index.js +77 -0
  124. package/dist/indexing/index.js.map +1 -0
  125. package/dist/lazy-builder-BwEoBQZ9.d.ts +304 -0
  126. package/dist/lazy-builder-CZVLKh0Z.d.cts +304 -0
  127. package/dist/ledger-2NX4L7PN.js +33 -0
  128. package/dist/ledger-2NX4L7PN.js.map +1 -0
  129. package/dist/mime-magic-CBBSOkjm.d.cts +50 -0
  130. package/dist/mime-magic-CBBSOkjm.d.ts +50 -0
  131. package/dist/periods/index.cjs +1035 -0
  132. package/dist/periods/index.cjs.map +1 -0
  133. package/dist/periods/index.d.cts +21 -0
  134. package/dist/periods/index.d.ts +21 -0
  135. package/dist/periods/index.js +25 -0
  136. package/dist/periods/index.js.map +1 -0
  137. package/dist/predicate-SBHmi6D0.d.cts +161 -0
  138. package/dist/predicate-SBHmi6D0.d.ts +161 -0
  139. package/dist/query/index.cjs +1957 -0
  140. package/dist/query/index.cjs.map +1 -0
  141. package/dist/query/index.d.cts +3 -0
  142. package/dist/query/index.d.ts +3 -0
  143. package/dist/query/index.js +62 -0
  144. package/dist/query/index.js.map +1 -0
  145. package/dist/session/index.cjs +487 -0
  146. package/dist/session/index.cjs.map +1 -0
  147. package/dist/session/index.d.cts +45 -0
  148. package/dist/session/index.d.ts +45 -0
  149. package/dist/session/index.js +44 -0
  150. package/dist/session/index.js.map +1 -0
  151. package/dist/shadow/index.cjs +133 -0
  152. package/dist/shadow/index.cjs.map +1 -0
  153. package/dist/shadow/index.d.cts +16 -0
  154. package/dist/shadow/index.d.ts +16 -0
  155. package/dist/shadow/index.js +20 -0
  156. package/dist/shadow/index.js.map +1 -0
  157. package/dist/store/index.cjs +1069 -0
  158. package/dist/store/index.cjs.map +1 -0
  159. package/dist/store/index.d.cts +491 -0
  160. package/dist/store/index.d.ts +491 -0
  161. package/dist/store/index.js +34 -0
  162. package/dist/store/index.js.map +1 -0
  163. package/dist/strategy-BSxFXGzb.d.cts +110 -0
  164. package/dist/strategy-BSxFXGzb.d.ts +110 -0
  165. package/dist/strategy-D-SrOLCl.d.cts +548 -0
  166. package/dist/strategy-D-SrOLCl.d.ts +548 -0
  167. package/dist/sync/index.cjs +1062 -0
  168. package/dist/sync/index.cjs.map +1 -0
  169. package/dist/sync/index.d.cts +42 -0
  170. package/dist/sync/index.d.ts +42 -0
  171. package/dist/sync/index.js +28 -0
  172. package/dist/sync/index.js.map +1 -0
  173. package/dist/team/index.cjs +1233 -0
  174. package/dist/team/index.cjs.map +1 -0
  175. package/dist/team/index.d.cts +117 -0
  176. package/dist/team/index.d.ts +117 -0
  177. package/dist/team/index.js +39 -0
  178. package/dist/team/index.js.map +1 -0
  179. package/dist/tx/index.cjs +212 -0
  180. package/dist/tx/index.cjs.map +1 -0
  181. package/dist/tx/index.d.cts +20 -0
  182. package/dist/tx/index.d.ts +20 -0
  183. package/dist/tx/index.js +20 -0
  184. package/dist/tx/index.js.map +1 -0
  185. package/dist/types-BZpCZB8N.d.ts +7526 -0
  186. package/dist/types-Bfs0qr5F.d.cts +7526 -0
  187. package/dist/ulid-COREQ2RQ.js +9 -0
  188. package/dist/ulid-COREQ2RQ.js.map +1 -0
  189. package/dist/util/index.cjs +230 -0
  190. package/dist/util/index.cjs.map +1 -0
  191. package/dist/util/index.d.cts +77 -0
  192. package/dist/util/index.d.ts +77 -0
  193. package/dist/util/index.js +190 -0
  194. package/dist/util/index.js.map +1 -0
  195. package/package.json +244 -0
@@ -0,0 +1,23 @@
1
+ import {
2
+ CONSENT_AUDIT_COLLECTION,
3
+ loadConsentEntries,
4
+ writeConsentEntry
5
+ } from "../chunk-M62XNWRA.js";
6
+ import "../chunk-FZU343FL.js";
7
+ import "../chunk-MR4424N3.js";
8
+ import "../chunk-ACLDOTNQ.js";
9
+
10
+ // src/consent/active.ts
11
+ function withConsent() {
12
+ return {
13
+ write: writeConsentEntry,
14
+ read: loadConsentEntries
15
+ };
16
+ }
17
+ export {
18
+ CONSENT_AUDIT_COLLECTION,
19
+ loadConsentEntries,
20
+ withConsent,
21
+ writeConsentEntry
22
+ };
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/consent/active.ts"],"sourcesContent":["/**\n * Active consent strategy. Calling `withConsent()` returns a\n * `ConsentStrategy` that delegates to the real\n * `writeConsentEntry` / `loadConsentEntries` functions. Only\n * reachable through the `@noy-db/hub/consent` subpath.\n */\n\nimport { writeConsentEntry, loadConsentEntries } from './consent.js'\nimport type { ConsentStrategy } from './strategy.js'\n\n/**\n * Build the default consent strategy. Pass into\n * `createNoydb({ consentStrategy: withConsent() })` to enable\n * per-operation audit writes into the reserved `_consent_audit`\n * collection.\n */\nexport function withConsent(): ConsentStrategy {\n return {\n write: writeConsentEntry,\n read: loadConsentEntries,\n }\n}\n"],"mappings":";;;;;;;;;;AAgBO,SAAS,cAA+B;AAC7C,SAAO;AAAA,IACL,OAAO;AAAA,IACP,MAAM;AAAA,EACR;AACF;","names":[]}
@@ -0,0 +1,152 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/crdt/index.ts
21
+ var crdt_exports = {};
22
+ __export(crdt_exports, {
23
+ buildLwwMapState: () => buildLwwMapState,
24
+ buildRgaState: () => buildRgaState,
25
+ mergeCrdtStates: () => mergeCrdtStates,
26
+ resolveCrdtSnapshot: () => resolveCrdtSnapshot,
27
+ withCrdt: () => withCrdt
28
+ });
29
+ module.exports = __toCommonJS(crdt_exports);
30
+
31
+ // src/crdt/crdt.ts
32
+ function resolveCrdtSnapshot(state) {
33
+ switch (state._crdt) {
34
+ case "lww-map": {
35
+ const result = {};
36
+ for (const [field, reg] of Object.entries(state.fields)) {
37
+ result[field] = reg.v;
38
+ }
39
+ return result;
40
+ }
41
+ case "rga": {
42
+ const dead = new Set(state.tombstones);
43
+ return state.items.filter((i) => !dead.has(i.nid)).map((i) => i.v);
44
+ }
45
+ case "yjs":
46
+ return state.update;
47
+ }
48
+ }
49
+ function mergeCrdtStates(a, b) {
50
+ if (a._crdt !== b._crdt) return a;
51
+ switch (a._crdt) {
52
+ case "lww-map":
53
+ return mergeLwwMap(a, b);
54
+ case "rga":
55
+ return mergeRga(a, b);
56
+ case "yjs":
57
+ return a;
58
+ }
59
+ }
60
+ function mergeLwwMap(a, b) {
61
+ const merged = {};
62
+ const allFields = /* @__PURE__ */ new Set([...Object.keys(a.fields), ...Object.keys(b.fields)]);
63
+ for (const field of allFields) {
64
+ const fa = a.fields[field];
65
+ const fb = b.fields[field];
66
+ if (!fa) {
67
+ merged[field] = fb;
68
+ } else if (!fb) {
69
+ merged[field] = fa;
70
+ } else {
71
+ merged[field] = fa.ts >= fb.ts ? fa : fb;
72
+ }
73
+ }
74
+ return { _crdt: "lww-map", fields: merged };
75
+ }
76
+ function mergeRga(a, b) {
77
+ const allTombstones = /* @__PURE__ */ new Set([...a.tombstones, ...b.tombstones]);
78
+ const seenNids = new Set(a.items.map((i) => i.nid));
79
+ const merged = [
80
+ ...a.items,
81
+ ...b.items.filter((i) => !seenNids.has(i.nid))
82
+ ];
83
+ return { _crdt: "rga", items: merged, tombstones: [...allTombstones] };
84
+ }
85
+ function buildLwwMapState(record, existing, now) {
86
+ const fields = {};
87
+ for (const [field, value] of Object.entries(record)) {
88
+ fields[field] = { v: value, ts: now };
89
+ }
90
+ if (existing) {
91
+ for (const [field, reg] of Object.entries(existing.fields)) {
92
+ if (!(field in fields)) {
93
+ fields[field] = reg;
94
+ }
95
+ }
96
+ }
97
+ return { _crdt: "lww-map", fields };
98
+ }
99
+ function buildRgaState(arr, existing, generateNid) {
100
+ const existingByValue = /* @__PURE__ */ new Map();
101
+ if (existing) {
102
+ for (const item of existing.items) {
103
+ const key = JSON.stringify(item.v);
104
+ if (!existingByValue.has(key)) existingByValue.set(key, item);
105
+ }
106
+ }
107
+ const usedNids = /* @__PURE__ */ new Set();
108
+ const newItems = [];
109
+ for (const el of arr) {
110
+ const key = JSON.stringify(el);
111
+ const match = existingByValue.get(key);
112
+ if (match && !usedNids.has(match.nid)) {
113
+ newItems.push(match);
114
+ usedNids.add(match.nid);
115
+ } else {
116
+ const nid = generateNid();
117
+ newItems.push({ nid, v: el });
118
+ usedNids.add(nid);
119
+ }
120
+ }
121
+ const tombstones = existing ? [...existing.tombstones] : [];
122
+ const extraItems = [];
123
+ if (existing) {
124
+ for (const item of existing.items) {
125
+ if (!usedNids.has(item.nid)) {
126
+ if (!tombstones.includes(item.nid)) tombstones.push(item.nid);
127
+ extraItems.push(item);
128
+ }
129
+ }
130
+ }
131
+ const items = [...newItems, ...extraItems];
132
+ return { _crdt: "rga", items, tombstones };
133
+ }
134
+
135
+ // src/crdt/active.ts
136
+ function withCrdt() {
137
+ return {
138
+ buildLwwMapState,
139
+ buildRgaState,
140
+ mergeCrdtStates,
141
+ resolveCrdtSnapshot
142
+ };
143
+ }
144
+ // Annotate the CommonJS export names for ESM import in node:
145
+ 0 && (module.exports = {
146
+ buildLwwMapState,
147
+ buildRgaState,
148
+ mergeCrdtStates,
149
+ resolveCrdtSnapshot,
150
+ withCrdt
151
+ });
152
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/crdt/index.ts","../../src/crdt/crdt.ts","../../src/crdt/active.ts"],"sourcesContent":["/**\n * @noy-db/hub/crdt — opt-in CRDT (conflict-free replicated data type)\n * subsystem.\n *\n * @category capability\n *\n * Collaborative-editing primitives: LWW-Map, RGA (sequence CRDT), and\n * a Yjs bridge state shape. Enabled per-collection via\n * `collection(name, { crdt: 'lww-map' | 'rga' | 'yjs' })`. Apps that\n * stick to plain record-level last-write-wins can omit this subpath\n * and save ~221 LOC of state / merge / snapshot helpers.\n */\n\nexport { withCrdt } from './active.js'\nexport type { CrdtStrategy } from './strategy.js'\n\nexport {\n resolveCrdtSnapshot,\n mergeCrdtStates,\n buildLwwMapState,\n buildRgaState,\n} from './crdt.js'\nexport type {\n CrdtMode,\n CrdtState,\n LwwMapState,\n RgaState,\n YjsState,\n} from './crdt.js'\n","/**\n * CRDT state types, merge logic, and build helpers.\n * per-collection CRDT mode: 'lww-map' | 'rga' | 'yjs'\n *\n * The encrypted envelope wraps the CRDT state (not the resolved snapshot).\n * Adapters only ever see ciphertext. `collection.get(id)` returns the\n * resolved snapshot; `collection.getRaw(id)` returns the full CRDT state.\n */\n\n// ─── Mode ─────────────────────────────────────────────────────────────\n\n/** Per-collection CRDT mode. */\nexport type CrdtMode = 'lww-map' | 'rga' | 'yjs'\n\n// ─── State shapes ─────────────────────────────────────────────────────\n\n/**\n * Per-field last-write-wins registers.\n * Each field carries its latest value and the ISO timestamp of the last write.\n * Merge: for each field, keep the entry with the lexicographically higher `ts`.\n */\nexport interface LwwMapState {\n readonly _crdt: 'lww-map'\n readonly fields: Record<string, { readonly v: unknown; readonly ts: string }>\n}\n\n/**\n * Simplified Replicated Growable Array.\n * Items are assigned stable NID (noy-db id) strings on first insertion.\n * Deleted items are tracked as tombstones so concurrent removals commute.\n *\n * The resolved snapshot is the ordered list of non-tombstoned `v` values.\n */\nexport interface RgaState {\n readonly _crdt: 'rga'\n readonly items: ReadonlyArray<{ readonly nid: string; readonly v: unknown }>\n readonly tombstones: readonly string[]\n}\n\n/**\n * Yjs binary state marker. `update` is base64(Y.encodeStateAsUpdate()).\n * Core stores and retrieves the blob opaquely. `@noy-db/yjs` is responsible\n * for encoding, decoding, and merging via `Y.mergeUpdates`.\n * Core falls back to last-write-wins (higher `_v`) for conflict resolution.\n */\nexport interface YjsState {\n readonly _crdt: 'yjs'\n /** base64-encoded Y.encodeStateAsUpdate() bytes. */\n readonly update: string\n}\n\nexport type CrdtState = LwwMapState | RgaState | YjsState\n\n// ─── Snapshot resolution ──────────────────────────────────────────────\n\n/**\n * Resolve a CRDT state into the end-user record snapshot.\n *\n * - `lww-map` → `Record<string, unknown>` (field values extracted from registers)\n * - `rga` → `unknown[]` (non-tombstoned items in insertion order)\n * - `yjs` → `string` (base64 update blob; use @noy-db/yjs for a Y.Doc)\n */\nexport function resolveCrdtSnapshot(state: CrdtState): unknown {\n switch (state._crdt) {\n case 'lww-map': {\n const result: Record<string, unknown> = {}\n for (const [field, reg] of Object.entries(state.fields)) {\n result[field] = reg.v\n }\n return result\n }\n case 'rga': {\n const dead = new Set(state.tombstones)\n return state.items.filter(i => !dead.has(i.nid)).map(i => i.v)\n }\n case 'yjs':\n return state.update\n }\n}\n\n// ─── CRDT merge ───────────────────────────────────────────────────────\n\n/**\n * Merge two CRDT states produced by concurrent writes.\n * Called by the collection-level conflict resolver registered with SyncEngine.\n *\n * For `yjs`: core cannot merge Yjs without importing the `yjs` package.\n * The caller must handle that case by falling back to the higher-`_v` envelope.\n */\nexport function mergeCrdtStates(a: CrdtState, b: CrdtState): CrdtState {\n // Mismatched modes shouldn't happen in practice — same collection, same schema.\n if (a._crdt !== b._crdt) return a\n\n switch (a._crdt) {\n case 'lww-map':\n return mergeLwwMap(a, b as LwwMapState)\n case 'rga':\n return mergeRga(a, b as RgaState)\n case 'yjs':\n // Signal to caller that Yjs merge is needed externally\n return a\n }\n}\n\nfunction mergeLwwMap(a: LwwMapState, b: LwwMapState): LwwMapState {\n const merged: Record<string, { v: unknown; ts: string }> = {}\n const allFields = new Set([...Object.keys(a.fields), ...Object.keys(b.fields)])\n for (const field of allFields) {\n const fa = a.fields[field]\n const fb = b.fields[field]\n if (!fa) { merged[field] = fb! }\n else if (!fb) { merged[field] = fa }\n else { merged[field] = fa.ts >= fb.ts ? fa : fb }\n }\n return { _crdt: 'lww-map', fields: merged }\n}\n\nfunction mergeRga(a: RgaState, b: RgaState): RgaState {\n // Union tombstones from both sides\n const allTombstones = new Set([...a.tombstones, ...b.tombstones])\n // Union items by nid: start with a's ordering, append b-only items\n const seenNids = new Set(a.items.map(i => i.nid))\n const merged: Array<{ nid: string; v: unknown }> = [\n ...a.items,\n ...b.items.filter(i => !seenNids.has(i.nid)),\n ]\n return { _crdt: 'rga', items: merged, tombstones: [...allTombstones] }\n}\n\n// ─── Build helpers ────────────────────────────────────────────────────\n\n/**\n * Build (or update) an lww-map state from a new record.\n *\n * All fields in the new record win at timestamp `now`.\n * Fields present in the existing state but absent from the new record\n * are preserved (they were written by another device).\n */\nexport function buildLwwMapState(\n record: Record<string, unknown>,\n existing: LwwMapState | undefined,\n now: string,\n): LwwMapState {\n const fields: Record<string, { v: unknown; ts: string }> = {}\n\n // New record fields all get the current timestamp — this device wins for these\n for (const [field, value] of Object.entries(record)) {\n fields[field] = { v: value, ts: now }\n }\n\n // Preserve fields from the existing state that aren't in the new record\n if (existing) {\n for (const [field, reg] of Object.entries(existing.fields)) {\n if (!(field in fields)) {\n fields[field] = reg\n }\n }\n }\n\n return { _crdt: 'lww-map', fields }\n}\n\n/**\n * Build (or update) an RGA state from a new array.\n *\n * Existing items are matched to new elements by deep-equality of their `v`.\n * Unmatched existing items are tombstoned. New elements that have no existing\n * match get a fresh NID via `generateNid()`.\n */\nexport function buildRgaState(\n arr: unknown[],\n existing: RgaState | undefined,\n generateNid: () => string,\n): RgaState {\n // Build an index from JSON(v) → existing item so we can match by value\n const existingByValue = new Map<string, { nid: string; v: unknown }>()\n if (existing) {\n for (const item of existing.items) {\n // Only add first occurrence per value to avoid double-matching\n const key = JSON.stringify(item.v)\n if (!existingByValue.has(key)) existingByValue.set(key, item)\n }\n }\n\n const usedNids = new Set<string>()\n const newItems: Array<{ nid: string; v: unknown }> = []\n\n for (const el of arr) {\n const key = JSON.stringify(el)\n const match = existingByValue.get(key)\n if (match && !usedNids.has(match.nid)) {\n // Reuse existing NID to preserve cross-device identity\n newItems.push(match)\n usedNids.add(match.nid)\n } else {\n // New element — assign a fresh NID\n const nid = generateNid()\n newItems.push({ nid, v: el })\n usedNids.add(nid)\n }\n }\n\n // Elements in the existing state that aren't in the new array → tombstone.\n // Tombstoned items are kept in the items array to preserve ordering for\n // cross-device merge — the resolved snapshot filters them out.\n const tombstones: string[] = existing ? [...existing.tombstones] : []\n const extraItems: Array<{ nid: string; v: unknown }> = []\n if (existing) {\n for (const item of existing.items) {\n if (!usedNids.has(item.nid)) {\n if (!tombstones.includes(item.nid)) tombstones.push(item.nid)\n extraItems.push(item) // retain in items for ordering\n }\n }\n }\n\n // Final items: live items in new order, then tombstoned extras at the end\n const items = [...newItems, ...extraItems]\n\n return { _crdt: 'rga', items, tombstones }\n}\n","/**\n * Active CRDT strategy factory. Calling `withCrdt()` returns a\n * `CrdtStrategy` whose methods delegate to the real LWW-Map / RGA /\n * merge / snapshot helpers in `./crdt.ts`. Only reachable through\n * the `@noy-db/hub/crdt` subpath.\n */\n\nimport {\n buildLwwMapState,\n buildRgaState,\n mergeCrdtStates,\n resolveCrdtSnapshot,\n} from './crdt.js'\nimport type { CrdtStrategy } from './strategy.js'\n\n/**\n * Build the default CRDT strategy. Pass into\n * `createNoydb({ crdtStrategy: withCrdt() })` to enable collections\n * declared with `crdt: 'lww-map' | 'rga' | 'yjs'`.\n *\n * @example\n * ```ts\n * import { createNoydb } from '@noy-db/hub'\n * import { withCrdt } from '@noy-db/hub/crdt'\n *\n * const db = await createNoydb({\n * store, user, secret,\n * crdtStrategy: withCrdt(),\n * })\n * const notes = vault.collection('notes', { crdt: 'lww-map' })\n * ```\n */\nexport function withCrdt(): CrdtStrategy {\n return {\n buildLwwMapState,\n buildRgaState,\n mergeCrdtStates,\n resolveCrdtSnapshot,\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,30 @@
1
+ import { C as CrdtStrategy } from '../strategy-BSxFXGzb.cjs';
2
+ export { a as CrdtMode, b as CrdtState, L as LwwMapState, R as RgaState, Y as YjsState, c as buildLwwMapState, d as buildRgaState, m as mergeCrdtStates, r as resolveCrdtSnapshot } from '../strategy-BSxFXGzb.cjs';
3
+
4
+ /**
5
+ * Active CRDT strategy factory. Calling `withCrdt()` returns a
6
+ * `CrdtStrategy` whose methods delegate to the real LWW-Map / RGA /
7
+ * merge / snapshot helpers in `./crdt.ts`. Only reachable through
8
+ * the `@noy-db/hub/crdt` subpath.
9
+ */
10
+
11
+ /**
12
+ * Build the default CRDT strategy. Pass into
13
+ * `createNoydb({ crdtStrategy: withCrdt() })` to enable collections
14
+ * declared with `crdt: 'lww-map' | 'rga' | 'yjs'`.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * import { createNoydb } from '@noy-db/hub'
19
+ * import { withCrdt } from '@noy-db/hub/crdt'
20
+ *
21
+ * const db = await createNoydb({
22
+ * store, user, secret,
23
+ * crdtStrategy: withCrdt(),
24
+ * })
25
+ * const notes = vault.collection('notes', { crdt: 'lww-map' })
26
+ * ```
27
+ */
28
+ declare function withCrdt(): CrdtStrategy;
29
+
30
+ export { CrdtStrategy, withCrdt };
@@ -0,0 +1,30 @@
1
+ import { C as CrdtStrategy } from '../strategy-BSxFXGzb.js';
2
+ export { a as CrdtMode, b as CrdtState, L as LwwMapState, R as RgaState, Y as YjsState, c as buildLwwMapState, d as buildRgaState, m as mergeCrdtStates, r as resolveCrdtSnapshot } from '../strategy-BSxFXGzb.js';
3
+
4
+ /**
5
+ * Active CRDT strategy factory. Calling `withCrdt()` returns a
6
+ * `CrdtStrategy` whose methods delegate to the real LWW-Map / RGA /
7
+ * merge / snapshot helpers in `./crdt.ts`. Only reachable through
8
+ * the `@noy-db/hub/crdt` subpath.
9
+ */
10
+
11
+ /**
12
+ * Build the default CRDT strategy. Pass into
13
+ * `createNoydb({ crdtStrategy: withCrdt() })` to enable collections
14
+ * declared with `crdt: 'lww-map' | 'rga' | 'yjs'`.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * import { createNoydb } from '@noy-db/hub'
19
+ * import { withCrdt } from '@noy-db/hub/crdt'
20
+ *
21
+ * const db = await createNoydb({
22
+ * store, user, secret,
23
+ * crdtStrategy: withCrdt(),
24
+ * })
25
+ * const notes = vault.collection('notes', { crdt: 'lww-map' })
26
+ * ```
27
+ */
28
+ declare function withCrdt(): CrdtStrategy;
29
+
30
+ export { CrdtStrategy, withCrdt };
@@ -0,0 +1,24 @@
1
+ import {
2
+ buildLwwMapState,
3
+ buildRgaState,
4
+ mergeCrdtStates,
5
+ resolveCrdtSnapshot
6
+ } from "../chunk-J66GRPNH.js";
7
+
8
+ // src/crdt/active.ts
9
+ function withCrdt() {
10
+ return {
11
+ buildLwwMapState,
12
+ buildRgaState,
13
+ mergeCrdtStates,
14
+ resolveCrdtSnapshot
15
+ };
16
+ }
17
+ export {
18
+ buildLwwMapState,
19
+ buildRgaState,
20
+ mergeCrdtStates,
21
+ resolveCrdtSnapshot,
22
+ withCrdt
23
+ };
24
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/crdt/active.ts"],"sourcesContent":["/**\n * Active CRDT strategy factory. Calling `withCrdt()` returns a\n * `CrdtStrategy` whose methods delegate to the real LWW-Map / RGA /\n * merge / snapshot helpers in `./crdt.ts`. Only reachable through\n * the `@noy-db/hub/crdt` subpath.\n */\n\nimport {\n buildLwwMapState,\n buildRgaState,\n mergeCrdtStates,\n resolveCrdtSnapshot,\n} from './crdt.js'\nimport type { CrdtStrategy } from './strategy.js'\n\n/**\n * Build the default CRDT strategy. Pass into\n * `createNoydb({ crdtStrategy: withCrdt() })` to enable collections\n * declared with `crdt: 'lww-map' | 'rga' | 'yjs'`.\n *\n * @example\n * ```ts\n * import { createNoydb } from '@noy-db/hub'\n * import { withCrdt } from '@noy-db/hub/crdt'\n *\n * const db = await createNoydb({\n * store, user, secret,\n * crdtStrategy: withCrdt(),\n * })\n * const notes = vault.collection('notes', { crdt: 'lww-map' })\n * ```\n */\nexport function withCrdt(): CrdtStrategy {\n return {\n buildLwwMapState,\n buildRgaState,\n mergeCrdtStates,\n resolveCrdtSnapshot,\n }\n}\n"],"mappings":";;;;;;;;AAgCO,SAAS,WAAyB;AACvC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,44 @@
1
+ import {
2
+ base64ToBuffer,
3
+ bufferToBase64,
4
+ decrypt,
5
+ decryptBytes,
6
+ decryptBytesWithAAD,
7
+ decryptDeterministic,
8
+ deriveKey,
9
+ derivePresenceKey,
10
+ encrypt,
11
+ encryptBytes,
12
+ encryptBytesWithAAD,
13
+ encryptDeterministic,
14
+ generateDEK,
15
+ generateIV,
16
+ generateSalt,
17
+ hmacSha256Hex,
18
+ sha256Hex,
19
+ unwrapKey,
20
+ wrapKey
21
+ } from "./chunk-MR4424N3.js";
22
+ import "./chunk-ACLDOTNQ.js";
23
+ export {
24
+ base64ToBuffer,
25
+ bufferToBase64,
26
+ decrypt,
27
+ decryptBytes,
28
+ decryptBytesWithAAD,
29
+ decryptDeterministic,
30
+ deriveKey,
31
+ derivePresenceKey,
32
+ encrypt,
33
+ encryptBytes,
34
+ encryptBytesWithAAD,
35
+ encryptDeterministic,
36
+ generateDEK,
37
+ generateIV,
38
+ generateSalt,
39
+ hmacSha256Hex,
40
+ sha256Hex,
41
+ unwrapKey,
42
+ wrapKey
43
+ };
44
+ //# sourceMappingURL=crypto-IVKU7YTT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,16 @@
1
+ import {
2
+ DELEGATIONS_COLLECTION,
3
+ issueDelegation,
4
+ loadActiveDelegations,
5
+ revokeDelegation
6
+ } from "./chunk-XCL3WP6J.js";
7
+ import "./chunk-FZU343FL.js";
8
+ import "./chunk-MR4424N3.js";
9
+ import "./chunk-ACLDOTNQ.js";
10
+ export {
11
+ DELEGATIONS_COLLECTION,
12
+ issueDelegation,
13
+ loadActiveDelegations,
14
+ revokeDelegation
15
+ };
16
+ //# sourceMappingURL=delegation-XDJCBTI2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,263 @@
1
+ import { aq as Role, ar as UnlockedKeyring } from './types-Bfs0qr5F.cjs';
2
+
3
+ /**
4
+ * Session tokens —
5
+ *
6
+ * After a vault is unlocked (via passphrase, WebAuthn, OIDC, or magic-
7
+ * link), the caller can call `createSession()` to get a session token that
8
+ * allows re-establishing the KEK for the session lifetime without re-running
9
+ * PBKDF2 or any interactive auth challenge.
10
+ *
11
+ * Security model
12
+ * ──────────────
13
+ * A session consists of two pieces that must both be present to recover the
14
+ * KEK:
15
+ *
16
+ * 1. The **session key** — a non-extractable AES-256-GCM CryptoKey that
17
+ * exists only in memory. "Non-extractable" is enforced by the WebCrypto
18
+ * API: the key object cannot be serialized, exported, or sent over
19
+ * postMessage. When the JS context is GC'd (tab close, navigation away,
20
+ * worker termination) the key becomes unrecoverable.
21
+ *
22
+ * 2. The **session token** — a JSON object that carries the KEK wrapped
23
+ * with the session key (AES-256-GCM, fresh IV per session), plus
24
+ * unencrypted session metadata (sessionId, userId, vault, role,
25
+ * expiresAt). The token can be serialized to JSON and stored in
26
+ * sessionStorage or passed across callsites within the same tab, but
27
+ * it is useless without the session key.
28
+ *
29
+ * The session key is kept in a module-level Map indexed by sessionId. Callers
30
+ * that need to re-use a session must hold on to the sessionId returned from
31
+ * `createSession()`; the key is looked up automatically by `resolveSession()`.
32
+ *
33
+ * Revocation: `revokeSession()` removes the entry from the Map. Because the
34
+ * key is non-extractable, removal is sufficient — no one holds a serializable
35
+ * copy of the key.
36
+ *
37
+ * Tab-scoped lifetime: the module-level Map lives only as long as the JS
38
+ * module. Tab close → module unloaded → Map GC'd → all session keys gone.
39
+ * This is the zero-effort logout: closing the tab is always a secure logout.
40
+ *
41
+ * Expiry: `createSession()` accepts a `ttlMs` option. `resolveSession()`
42
+ * checks `expiresAt` and throws `SessionExpiredError` if the token is stale,
43
+ * even if the session key is still in the Map.
44
+ */
45
+
46
+ /** The serializable part of a session token. Safe to store in sessionStorage. */
47
+ interface SessionToken {
48
+ readonly _noydb_session: 1;
49
+ /** Unique session identifier (ULID). Use this as the handle for resolve/revoke. */
50
+ readonly sessionId: string;
51
+ readonly userId: string;
52
+ readonly vault: string;
53
+ readonly role: Role;
54
+ /** ISO timestamp — resolveSession() rejects this token after this time. */
55
+ readonly expiresAt: string;
56
+ /** KEK wrapped with the session key (AES-256-GCM). Base64. */
57
+ readonly wrappedKek: string;
58
+ /** IV used for the wrapping operation. Base64. */
59
+ readonly kekIv: string;
60
+ }
61
+ /** Result returned from `createSession()`. */
62
+ interface CreateSessionResult {
63
+ /** Serializable token — store in sessionStorage or pass to `resolveSession()`. */
64
+ token: SessionToken;
65
+ /** The sessionId — use this handle for `resolveSession()` and `revokeSession()`. */
66
+ sessionId: string;
67
+ }
68
+ /** Options for `createSession()`. */
69
+ interface CreateSessionOptions {
70
+ /**
71
+ * Session lifetime in milliseconds. Defaults to 60 minutes.
72
+ * After this duration, `resolveSession()` throws `SessionExpiredError`.
73
+ */
74
+ ttlMs?: number;
75
+ }
76
+ /**
77
+ * Create a session for an already-unlocked keyring.
78
+ *
79
+ * Call this after any successful unlock (passphrase, WebAuthn, OIDC,
80
+ * magic-link). The returned `sessionId` is the handle for later
81
+ * `resolveSession()` and `revokeSession()` calls.
82
+ *
83
+ * The session key is generated fresh (non-extractable) and stored in the
84
+ * module-level Map. The KEK from `keyring.kek` is exported (it must be
85
+ * extractable — it was derived by `deriveKey()` which sets extractable: false,
86
+ * but it's unwrapped from the keyring which sets extractable: true) and then
87
+ * re-wrapped with the session key.
88
+ *
89
+ * @param keyring - An already-unlocked keyring whose `kek` is available.
90
+ * @param vault - The vault name this session is scoped to.
91
+ * @param options - Optional session configuration.
92
+ */
93
+ declare function createSession(keyring: UnlockedKeyring, vault: string, options?: CreateSessionOptions): Promise<CreateSessionResult>;
94
+ /**
95
+ * Resolve a session token back into an UnlockedKeyring.
96
+ *
97
+ * Looks up the session key by `sessionId`, checks the token is not expired,
98
+ * then decrypts the payload to reconstruct the keyring's DEK set.
99
+ *
100
+ * Throws `SessionExpiredError` if the token's `expiresAt` is in the past.
101
+ * Throws `SessionNotFoundError` if the session key is not in the store
102
+ * (tab was reloaded, session was revoked, or the sessionId is wrong).
103
+ *
104
+ * @param token - The SessionToken from `createSession()`.
105
+ */
106
+ declare function resolveSession(token: SessionToken): Promise<UnlockedKeyring>;
107
+ /**
108
+ * Revoke a session by removing its key from the store.
109
+ *
110
+ * After revocation, `resolveSession()` will throw `SessionNotFoundError`
111
+ * for this sessionId. The session token (if held by the caller) becomes
112
+ * permanently useless. This is the explicit logout path.
113
+ *
114
+ * No-op if the session was already expired or does not exist.
115
+ */
116
+ declare function revokeSession(sessionId: string): void;
117
+ /**
118
+ * Check if a session is still alive (key in store + not expired).
119
+ * Does not decrypt anything — purely a metadata check.
120
+ */
121
+ declare function isSessionAlive(token: SessionToken): boolean;
122
+ /**
123
+ * Revoke all active sessions. Used by `Noydb.close()` to ensure that
124
+ * closing the instance destroys all session state, not just the keyring
125
+ * cache.
126
+ */
127
+ declare function revokeAllSessions(): void;
128
+ /**
129
+ * Return the number of active sessions currently in the store.
130
+ * Useful for diagnostics and tests.
131
+ */
132
+ declare function activeSessionCount(): number;
133
+
134
+ /**
135
+ * Dev-mode persistent unlock —
136
+ *
137
+ * Solves the developer inner-loop friction: hot-reload destroys the session
138
+ * (page navigation semantics), forcing a passphrase re-entry every refresh.
139
+ *
140
+ * This module provides an opt-in, deliberately-named escape hatch that lets
141
+ * developers store the keyring payload in sessionStorage or localStorage so
142
+ * the vault auto-unlocks on every page load — without a passphrase,
143
+ * without a biometric prompt, without any OIDC flow.
144
+ *
145
+ * ⚠️ WARNING — this is a loaded footgun ⚠️
146
+ * ─────────────────────────────────────────
147
+ * The keyring payload stored by this module contains the DEKs. Whoever has
148
+ * access to sessionStorage/localStorage has access to the DEKs. On a shared
149
+ * development machine, a compromised browser extension, or a mis-configured
150
+ * origin, this is a complete key exposure.
151
+ *
152
+ * This module is ONLY safe for local development. It must NEVER be active
153
+ * in production builds.
154
+ *
155
+ * Guardrails (all enforced by the module, not by the caller)
156
+ * ──────────────────────────────────────────────────────────
157
+ * 1. **Production guard:** `enableDevUnlock()` throws immediately if
158
+ * `process.env.NODE_ENV === 'production'` or if `import.meta.env?.PROD === true`
159
+ * (Vite convention). Also throws if the hostname is NOT localhost or 127.0.0.1.
160
+ *
161
+ * 2. **Explicit acknowledgement string:** the caller must pass
162
+ * `acknowledge: 'I-UNDERSTAND-THIS-DISABLES-UNLOCK-SECURITY'` or the call
163
+ * throws. This string appears in every grep for `devUnlock` in the codebase,
164
+ * making it impossible to enable this feature accidentally.
165
+ *
166
+ * 3. **Scope is vault + userId:** the storage key includes both the
167
+ * vault name and the userId, so dev-unlock for vault-A does
168
+ * NOT auto-unlock vault-B.
169
+ *
170
+ * 4. **Storage scope:** default is `sessionStorage` (cleared on tab close).
171
+ * `localStorage` is opt-in and requires an additional
172
+ * `persistAcrossTabs: true` flag in the options.
173
+ *
174
+ * 5. **Clear method:** `clearDevUnlock()` removes the stored payload. Wire
175
+ * this to a dev toolbar button or `Ctrl+Shift+L` so clearing is one action.
176
+ *
177
+ * 6. **Console banner:** on first enable, a highly visible console warning
178
+ * fires. Cannot be suppressed.
179
+ *
180
+ * Usage
181
+ * ─────
182
+ * ```ts
183
+ * // In your dev entry point only (guarded by import.meta.env.DEV):
184
+ * if (import.meta.env.DEV) {
185
+ * const { enableDevUnlock, loadDevUnlock } = await import('@noy-db/hub')
186
+ * enableDevUnlock('my-compartment', 'alice', keyring, {
187
+ * acknowledge: 'I-UNDERSTAND-THIS-DISABLES-UNLOCK-SECURITY',
188
+ * })
189
+ * }
190
+ *
191
+ * // On page load:
192
+ * if (import.meta.env.DEV) {
193
+ * const keyring = await loadDevUnlock('my-compartment', 'alice')
194
+ * if (keyring) {
195
+ * // Skip unlock prompt, use keyring directly
196
+ * }
197
+ * }
198
+ * ```
199
+ */
200
+
201
+ interface DevUnlockOptions {
202
+ /**
203
+ * Required: the exact string 'I-UNDERSTAND-THIS-DISABLES-UNLOCK-SECURITY'.
204
+ * Any other value causes `enableDevUnlock()` to throw.
205
+ */
206
+ acknowledge: string;
207
+ /**
208
+ * If `true`, stores in localStorage (persists across tabs and browser restarts).
209
+ * If `false` (default), stores in sessionStorage (cleared on tab close).
210
+ */
211
+ persistAcrossTabs?: boolean;
212
+ }
213
+ /**
214
+ * Serialize and store a keyring to browser storage for dev-mode auto-unlock.
215
+ *
216
+ * Throws immediately if:
217
+ * - The acknowledge string is wrong.
218
+ * - Running in a production environment (NODE_ENV=production).
219
+ * - Running on a non-localhost hostname.
220
+ *
221
+ * Emits a highly visible console warning that cannot be suppressed.
222
+ *
223
+ * @param vault - The vault name.
224
+ * @param userId - The user ID.
225
+ * @param keyring - The unlocked keyring to persist.
226
+ * @param options - Options including the required acknowledge string.
227
+ */
228
+ declare function enableDevUnlock(vault: string, userId: string, keyring: UnlockedKeyring, options: DevUnlockOptions): Promise<void>;
229
+ /**
230
+ * Load a dev-mode keyring from browser storage.
231
+ *
232
+ * Returns `null` if no dev-unlock state is stored for this vault + user,
233
+ * or if the stored payload is malformed.
234
+ *
235
+ * Does NOT perform the production environment check — it's safe to CALL
236
+ * `loadDevUnlock` in production (it will simply return `null` because no
237
+ * dev-unlock state was ever written). The guard only fires on `enableDevUnlock`.
238
+ *
239
+ * @param vault - The vault name.
240
+ * @param userId - The user ID.
241
+ * @param options - Optional storage override.
242
+ */
243
+ declare function loadDevUnlock(vault: string, userId: string, options?: {
244
+ persistAcrossTabs?: boolean;
245
+ }): Promise<UnlockedKeyring | null>;
246
+ /**
247
+ * Remove dev-unlock state from browser storage.
248
+ *
249
+ * Safe to call in production (no-op if no dev state exists).
250
+ */
251
+ declare function clearDevUnlock(vault: string, userId: string, options?: {
252
+ persistAcrossTabs?: boolean;
253
+ }): void;
254
+ /**
255
+ * Check if dev-unlock state exists for this vault + user.
256
+ *
257
+ * Safe to call in production (returns false if nothing is stored).
258
+ */
259
+ declare function isDevUnlockActive(vault: string, userId: string, options?: {
260
+ persistAcrossTabs?: boolean;
261
+ }): boolean;
262
+
263
+ export { type CreateSessionOptions as C, type DevUnlockOptions as D, type SessionToken as S, type CreateSessionResult as a, activeSessionCount as b, clearDevUnlock as c, createSession as d, enableDevUnlock as e, isSessionAlive as f, revokeAllSessions as g, revokeSession as h, isDevUnlockActive as i, loadDevUnlock as l, resolveSession as r };