@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,190 @@
1
+ import {
2
+ FilenameSanitizationError
3
+ } from "../chunk-ACLDOTNQ.js";
4
+
5
+ // src/util/sanitize-filename.ts
6
+ var REPLACEMENT = "_";
7
+ var BIDI_OVERRIDES = /[‪-‮⁦-⁩]/g;
8
+ var WINDOWS_RESERVED_CHARS = /[<>:"/\\|?*]/g;
9
+ var WINDOWS_RESERVED_NAMES = /* @__PURE__ */ new Set([
10
+ "CON",
11
+ "PRN",
12
+ "AUX",
13
+ "NUL",
14
+ "COM1",
15
+ "COM2",
16
+ "COM3",
17
+ "COM4",
18
+ "COM5",
19
+ "COM6",
20
+ "COM7",
21
+ "COM8",
22
+ "COM9",
23
+ "LPT1",
24
+ "LPT2",
25
+ "LPT3",
26
+ "LPT4",
27
+ "LPT5",
28
+ "LPT6",
29
+ "LPT7",
30
+ "LPT8",
31
+ "LPT9"
32
+ ]);
33
+ var MAC_HIDDEN_NOISE = /^(?:\.DS_Store|\.localized|\.fseventsd|\._.+)$/i;
34
+ var URL_UNRESERVED = /[A-Za-z0-9\-._~]/;
35
+ var utf8 = new TextEncoder();
36
+ function isControlCode(cp) {
37
+ return cp >= 0 && cp <= 31 || cp === 127;
38
+ }
39
+ function stripControlChars(s) {
40
+ let out = "";
41
+ for (let i = 0; i < s.length; i++) {
42
+ out += isControlCode(s.charCodeAt(i)) ? REPLACEMENT : s[i];
43
+ }
44
+ return out;
45
+ }
46
+ function trimWhitespaceAndControls(s) {
47
+ let start = 0;
48
+ let end = s.length;
49
+ while (start < end) {
50
+ const ch = s[start];
51
+ if (!isControlCode(s.charCodeAt(start)) && ch.trim() !== "") break;
52
+ start++;
53
+ }
54
+ while (end > start) {
55
+ const ch = s[end - 1];
56
+ if (!isControlCode(s.charCodeAt(end - 1)) && ch.trim() !== "") break;
57
+ end--;
58
+ }
59
+ return s.slice(start, end);
60
+ }
61
+ function sanitizeFilename(name, opts) {
62
+ if (typeof name !== "string") {
63
+ throw new FilenameSanitizationError("input must be a string");
64
+ }
65
+ if (name.includes("\0")) {
66
+ throw new FilenameSanitizationError("NUL byte in filename");
67
+ }
68
+ let s = name.normalize("NFC").replace(BIDI_OVERRIDES, "");
69
+ if (opts.profile === "opaque") {
70
+ return applyOpaque(s, opts);
71
+ }
72
+ s = trimWhitespaceAndControls(s);
73
+ switch (opts.profile) {
74
+ case "posix":
75
+ return cap(applyPosix(s), opts.maxBytes ?? 255, "utf8");
76
+ case "windows":
77
+ return cap(applyWindows(s), opts.maxBytes ?? 255, "utf16");
78
+ case "macos-smb":
79
+ return cap(applyMacosSmb(s), opts.maxBytes ?? 240, "utf8");
80
+ case "zip":
81
+ return cap(applyZip(s), opts.maxBytes ?? 255, "utf8");
82
+ case "url-path":
83
+ return cap(applyUrlPath(s), opts.maxBytes ?? 1024, "bytes-pre-encode");
84
+ case "s3-key":
85
+ return cap(applyS3Key(s), opts.maxBytes ?? 1024, "utf8");
86
+ }
87
+ }
88
+ function applyPosix(s) {
89
+ const cleaned = s.replace(/\//g, REPLACEMENT);
90
+ return rejectDotSegments(cleaned);
91
+ }
92
+ function applyWindows(s) {
93
+ let cleaned = s.replace(WINDOWS_RESERVED_CHARS, REPLACEMENT);
94
+ cleaned = stripControlChars(cleaned);
95
+ cleaned = cleaned.replace(/[. ]+$/g, "");
96
+ cleaned = rejectDotSegments(cleaned);
97
+ return avoidWindowsReservedName(cleaned);
98
+ }
99
+ function applyMacosSmb(s) {
100
+ let cleaned = applyWindows(s);
101
+ if (MAC_HIDDEN_NOISE.test(cleaned)) {
102
+ cleaned = REPLACEMENT + cleaned;
103
+ }
104
+ return cleaned;
105
+ }
106
+ function applyZip(s) {
107
+ let cleaned = s.replace(/^\/+/, "");
108
+ cleaned = rejectDotSegments(cleaned);
109
+ return stripControlChars(cleaned);
110
+ }
111
+ function applyUrlPath(s) {
112
+ let out = "";
113
+ for (const ch of s) {
114
+ if (URL_UNRESERVED.test(ch)) {
115
+ out += ch;
116
+ continue;
117
+ }
118
+ const bytes = utf8.encode(ch);
119
+ for (const b of bytes) {
120
+ out += "%" + b.toString(16).toUpperCase().padStart(2, "0");
121
+ }
122
+ }
123
+ return out;
124
+ }
125
+ function applyS3Key(s) {
126
+ return applyUrlPath(s.replace(/^\/+/, ""));
127
+ }
128
+ function applyOpaque(s, opts) {
129
+ if (!opts.opaqueId) {
130
+ throw new FilenameSanitizationError("opaque profile requires opaqueId");
131
+ }
132
+ const dot = s.lastIndexOf(".");
133
+ if (dot > 0 && dot < s.length - 1) {
134
+ const ext = s.slice(dot + 1);
135
+ if (/^[A-Za-z0-9]{1,16}$/.test(ext)) {
136
+ return `${opts.opaqueId}.${ext.toLowerCase()}`;
137
+ }
138
+ }
139
+ return opts.opaqueId;
140
+ }
141
+ function rejectDotSegments(s) {
142
+ if (s === "." || s === ".." || s.split(/[/\\]/).some((seg) => seg === "..")) {
143
+ throw new FilenameSanitizationError("path traversal segment in filename");
144
+ }
145
+ if (s.length === 0) {
146
+ throw new FilenameSanitizationError("empty filename after sanitization");
147
+ }
148
+ return s;
149
+ }
150
+ function avoidWindowsReservedName(s) {
151
+ const dot = s.indexOf(".");
152
+ const base = dot === -1 ? s : s.slice(0, dot);
153
+ if (WINDOWS_RESERVED_NAMES.has(base.toUpperCase())) {
154
+ return REPLACEMENT + s;
155
+ }
156
+ return s;
157
+ }
158
+ function cap(s, max, unit) {
159
+ if (max <= 0) {
160
+ throw new FilenameSanitizationError("maxBytes must be positive");
161
+ }
162
+ if (unit === "utf16") {
163
+ if (s.length <= max) return s;
164
+ return s.slice(0, max);
165
+ }
166
+ if (unit === "bytes-pre-encode") {
167
+ if (utf8.encode(s).byteLength <= max) return s;
168
+ return truncateToByteCap(s, max);
169
+ }
170
+ if (utf8.encode(s).byteLength <= max) return s;
171
+ return truncateToByteCap(s, max);
172
+ }
173
+ function truncateToByteCap(s, max) {
174
+ let out = "";
175
+ let used = 0;
176
+ for (const cp of s) {
177
+ const n = utf8.encode(cp).byteLength;
178
+ if (used + n > max) break;
179
+ out += cp;
180
+ used += n;
181
+ }
182
+ if (out.length === 0) {
183
+ throw new FilenameSanitizationError("maxBytes too small for a single code point");
184
+ }
185
+ return out;
186
+ }
187
+ export {
188
+ sanitizeFilename
189
+ };
190
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/util/sanitize-filename.ts"],"sourcesContent":["/**\n * Target-profile aware filename sanitizer.\n *\n * Pure string in / string out. No filesystem access, no I/O. Use this\n * at the boundary where a user-supplied filename meets a storage\n * destination — local FS, ZIP archive, S3 key, URL path, SMB share —\n * to defuse the canonical 14-class footgun before it reaches that\n * destination.\n *\n * ## Threat model\n *\n * 1. Path injection — `..`, NUL bytes, `\\` on POSIX, absolute paths.\n * 2. Windows reserved names — `CON`, `PRN`, `AUX`, `NUL`,\n * `COM1-9`, `LPT1-9`, with or without an extension.\n * 3. Windows reserved chars — `< > : \" / \\ | ? *` plus ASCII 0-31.\n * 4. Trailing `.` and ` ` on Windows / SMB.\n * 5. Unicode normalization drift — same display, different bytes →\n * \"two files with the same name\" sync ghosts.\n * 6. Bidi override spoofing — U+202E reversing `harmless.exe.txt`\n * into `harmless.txt.exe`.\n * 7. URL `+` ambiguity in S3 presigned URLs.\n * 8. ZIP general-purpose-flag bit 11 (UTF-8 filename) optional in\n * pre-2006 readers.\n * 9-14. Length caps, leading/trailing whitespace + controls,\n * `.DS_Store`-style hidden noise, etc.\n *\n * ## Always-on transforms (every profile)\n *\n * - NFC normalize (`String.prototype.normalize('NFC')`).\n * - Reject NUL — hard fail, not strip. Silent strip enables a\n * classic truncation bypass (`safe.txt\\0.exe` → `safe.txt`).\n * - Strip bidi overrides (`U+202A..U+202E`, `U+2066..U+2069`).\n * - Trim leading/trailing whitespace and ASCII control chars.\n *\n * ## Non-goals (i18n boundary policy, )\n *\n * - No transliteration.\n * - No locale-aware slugging.\n * - No script-specific segmentation.\n *\n * @module\n */\n\nimport { FilenameSanitizationError } from '../errors.js'\n\n/**\n * One of seven storage destinations the sanitizer knows how to defang\n * for. Pick the most restrictive that covers your write site:\n * `'macos-smb'` is the safe default for \"I don't know where these\n * files end up but they're going to a real filesystem somewhere.\"\n */\nexport type FilenameProfile =\n | 'posix'\n | 'windows'\n | 'macos-smb'\n | 'zip'\n | 'url-path'\n | 's3-key'\n | 'opaque'\n\nexport interface SanitizeFilenameOptions {\n /** Target-destination profile. */\n readonly profile: FilenameProfile\n /**\n * Override the per-profile length cap. Useful when leaving headroom\n * for a collision suffix — e.g. `maxBytes: 240` on a `posix` write\n * site that wants 15 bytes of slack for `-1`, `-2`, …\n */\n readonly maxBytes?: number\n /**\n * Required when `profile === 'opaque'`. The opaque profile replaces\n * the entire input with `${opaqueId}.${ext}` (extension preserved\n * from the input when present).\n */\n readonly opaqueId?: string\n}\n\nconst REPLACEMENT = '_'\n\n// Bidi overrides: U+202A LRE, U+202B RLE, U+202C PDF, U+202D LRO,\n// U+202E RLO, U+2066 LRI, U+2067 RLI, U+2068 FSI, U+2069 PDI.\nconst BIDI_OVERRIDES = /[‪-‮⁦-⁩]/g\n\n// Reserved by Windows / NTFS / FAT / SMB.\nconst WINDOWS_RESERVED_CHARS = /[<>:\"/\\\\|?*]/g\n\nconst WINDOWS_RESERVED_NAMES = new Set([\n 'CON', 'PRN', 'AUX', 'NUL',\n 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9',\n 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9',\n])\n\n// macOS legacy hidden-noise files. Match the full name.\nconst MAC_HIDDEN_NOISE = /^(?:\\.DS_Store|\\.localized|\\.fseventsd|\\._.+)$/i\n\n// RFC 3986 unreserved set.\nconst URL_UNRESERVED = /[A-Za-z0-9\\-._~]/\n\nconst utf8 = new TextEncoder()\n\nfunction isControlCode(cp: number): boolean {\n return (cp >= 0 && cp <= 0x1f) || cp === 0x7f\n}\n\nfunction stripControlChars(s: string): string {\n let out = ''\n for (let i = 0; i < s.length; i++) {\n out += isControlCode(s.charCodeAt(i)) ? REPLACEMENT : s[i]\n }\n return out\n}\n\nfunction trimWhitespaceAndControls(s: string): string {\n let start = 0\n let end = s.length\n while (start < end) {\n const ch = s[start]!\n if (!isControlCode(s.charCodeAt(start)) && ch.trim() !== '') break\n start++\n }\n while (end > start) {\n const ch = s[end - 1]!\n if (!isControlCode(s.charCodeAt(end - 1)) && ch.trim() !== '') break\n end--\n }\n return s.slice(start, end)\n}\n\n/**\n * Sanitize a filename for a target-destination profile. Pure: same\n * input + options always returns the same output, no I/O.\n *\n * Returns the sanitized name. Throws {@link FilenameSanitizationError}\n * when the input cannot be made safe at all (NUL byte, empty after\n * normalization, missing `opaqueId` for the opaque profile,\n * `..` segment that would fall out of any reasonable target).\n */\nexport function sanitizeFilename(name: string, opts: SanitizeFilenameOptions): string {\n if (typeof name !== 'string') {\n throw new FilenameSanitizationError('input must be a string')\n }\n if (name.includes('\\0')) {\n throw new FilenameSanitizationError('NUL byte in filename')\n }\n\n let s = name.normalize('NFC').replace(BIDI_OVERRIDES, '')\n\n if (opts.profile === 'opaque') {\n return applyOpaque(s, opts)\n }\n\n s = trimWhitespaceAndControls(s)\n\n switch (opts.profile) {\n case 'posix': return cap(applyPosix(s), opts.maxBytes ?? 255, 'utf8')\n case 'windows': return cap(applyWindows(s), opts.maxBytes ?? 255, 'utf16')\n case 'macos-smb': return cap(applyMacosSmb(s), opts.maxBytes ?? 240, 'utf8')\n case 'zip': return cap(applyZip(s), opts.maxBytes ?? 255, 'utf8')\n case 'url-path': return cap(applyUrlPath(s), opts.maxBytes ?? 1024, 'bytes-pre-encode')\n case 's3-key': return cap(applyS3Key(s), opts.maxBytes ?? 1024, 'utf8')\n }\n}\n\n// ─── Profile implementations ────────────────────────────────────────────\n\nfunction applyPosix(s: string): string {\n // POSIX is permissive; only `/` and NUL are off-limits.\n const cleaned = s.replace(/\\//g, REPLACEMENT)\n return rejectDotSegments(cleaned)\n}\n\nfunction applyWindows(s: string): string {\n let cleaned = s.replace(WINDOWS_RESERVED_CHARS, REPLACEMENT)\n cleaned = stripControlChars(cleaned)\n // Trailing space/dot are stripped by Win32 path resolution.\n cleaned = cleaned.replace(/[. ]+$/g, '')\n cleaned = rejectDotSegments(cleaned)\n return avoidWindowsReservedName(cleaned)\n}\n\nfunction applyMacosSmb(s: string): string {\n let cleaned = applyWindows(s)\n if (MAC_HIDDEN_NOISE.test(cleaned)) {\n cleaned = REPLACEMENT + cleaned\n }\n return cleaned\n}\n\nfunction applyZip(s: string): string {\n let cleaned = s.replace(/^\\/+/, '')\n cleaned = rejectDotSegments(cleaned)\n return stripControlChars(cleaned)\n}\n\nfunction applyUrlPath(s: string): string {\n // RFC 3986 percent-encoding for path segment. Keep `unreserved`,\n // encode everything else. `+` is encoded as `%2B` because S3 and\n // legacy servers treat raw `+` as space ambiguously.\n let out = ''\n for (const ch of s) {\n if (URL_UNRESERVED.test(ch)) { out += ch; continue }\n const bytes = utf8.encode(ch)\n for (const b of bytes) {\n out += '%' + b.toString(16).toUpperCase().padStart(2, '0')\n }\n }\n return out\n}\n\nfunction applyS3Key(s: string): string {\n // Strip leading slashes BEFORE percent-encoding — once `/` is\n // encoded to `%2F` the leading-slash regex no longer matches.\n return applyUrlPath(s.replace(/^\\/+/, ''))\n}\n\nfunction applyOpaque(s: string, opts: SanitizeFilenameOptions): string {\n if (!opts.opaqueId) {\n throw new FilenameSanitizationError('opaque profile requires opaqueId')\n }\n // Preserve a \"safe-looking\" extension only — alphanumeric, ≤16 bytes.\n const dot = s.lastIndexOf('.')\n if (dot > 0 && dot < s.length - 1) {\n const ext = s.slice(dot + 1)\n if (/^[A-Za-z0-9]{1,16}$/.test(ext)) {\n return `${opts.opaqueId}.${ext.toLowerCase()}`\n }\n }\n return opts.opaqueId\n}\n\n// ─── Helpers ────────────────────────────────────────────────────────────\n\nfunction rejectDotSegments(s: string): string {\n if (s === '.' || s === '..' || s.split(/[/\\\\]/).some((seg) => seg === '..')) {\n throw new FilenameSanitizationError('path traversal segment in filename')\n }\n if (s.length === 0) {\n throw new FilenameSanitizationError('empty filename after sanitization')\n }\n return s\n}\n\nfunction avoidWindowsReservedName(s: string): string {\n const dot = s.indexOf('.')\n const base = dot === -1 ? s : s.slice(0, dot)\n if (WINDOWS_RESERVED_NAMES.has(base.toUpperCase())) {\n return REPLACEMENT + s\n }\n return s\n}\n\ntype LengthUnit = 'utf8' | 'utf16' | 'bytes-pre-encode'\n\nfunction cap(s: string, max: number, unit: LengthUnit): string {\n if (max <= 0) {\n throw new FilenameSanitizationError('maxBytes must be positive')\n }\n if (unit === 'utf16') {\n if (s.length <= max) return s\n return s.slice(0, max)\n }\n if (unit === 'bytes-pre-encode') {\n if (utf8.encode(s).byteLength <= max) return s\n return truncateToByteCap(s, max)\n }\n if (utf8.encode(s).byteLength <= max) return s\n return truncateToByteCap(s, max)\n}\n\nfunction truncateToByteCap(s: string, max: number): string {\n // Walk by code points so we never split a multi-byte sequence in\n // the middle. Whole-grapheme handling (ZWJ-joined emoji) is a\n // documented non-goal; we only protect against UTF-8 boundary\n // splits here.\n let out = ''\n let used = 0\n for (const cp of s) {\n const n = utf8.encode(cp).byteLength\n if (used + n > max) break\n out += cp\n used += n\n }\n if (out.length === 0) {\n throw new FilenameSanitizationError('maxBytes too small for a single code point')\n }\n return out\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,244 @@
1
+ {
2
+ "name": "@noy-db/hub",
3
+ "version": "0.1.0-pre.3",
4
+ "description": "Zero-knowledge, offline-first, encrypted document store — core library with AES-256-GCM, PBKDF2, multi-user keyring, and sync engine",
5
+ "license": "MIT",
6
+ "author": "vLannaAi <vicio@lanna.ai>",
7
+ "homepage": "https://github.com/vLannaAi/noy-db/tree/main/packages/hub#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/vLannaAi/noy-db.git",
11
+ "directory": "packages/hub"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/vLannaAi/noy-db/issues"
15
+ },
16
+ "type": "module",
17
+ "sideEffects": false,
18
+ "exports": {
19
+ ".": {
20
+ "import": {
21
+ "types": "./dist/index.d.ts",
22
+ "default": "./dist/index.js"
23
+ },
24
+ "require": {
25
+ "types": "./dist/index.d.cts",
26
+ "default": "./dist/index.cjs"
27
+ }
28
+ },
29
+ "./i18n": {
30
+ "import": {
31
+ "types": "./dist/i18n/index.d.ts",
32
+ "default": "./dist/i18n/index.js"
33
+ },
34
+ "require": {
35
+ "types": "./dist/i18n/index.d.cts",
36
+ "default": "./dist/i18n/index.cjs"
37
+ }
38
+ },
39
+ "./store": {
40
+ "import": {
41
+ "types": "./dist/store/index.d.ts",
42
+ "default": "./dist/store/index.js"
43
+ },
44
+ "require": {
45
+ "types": "./dist/store/index.d.cts",
46
+ "default": "./dist/store/index.cjs"
47
+ }
48
+ },
49
+ "./team": {
50
+ "import": {
51
+ "types": "./dist/team/index.d.ts",
52
+ "default": "./dist/team/index.js"
53
+ },
54
+ "require": {
55
+ "types": "./dist/team/index.d.cts",
56
+ "default": "./dist/team/index.cjs"
57
+ }
58
+ },
59
+ "./session": {
60
+ "import": {
61
+ "types": "./dist/session/index.d.ts",
62
+ "default": "./dist/session/index.js"
63
+ },
64
+ "require": {
65
+ "types": "./dist/session/index.d.cts",
66
+ "default": "./dist/session/index.cjs"
67
+ }
68
+ },
69
+ "./history": {
70
+ "import": {
71
+ "types": "./dist/history/index.d.ts",
72
+ "default": "./dist/history/index.js"
73
+ },
74
+ "require": {
75
+ "types": "./dist/history/index.d.cts",
76
+ "default": "./dist/history/index.cjs"
77
+ }
78
+ },
79
+ "./query": {
80
+ "import": {
81
+ "types": "./dist/query/index.d.ts",
82
+ "default": "./dist/query/index.js"
83
+ },
84
+ "require": {
85
+ "types": "./dist/query/index.d.cts",
86
+ "default": "./dist/query/index.cjs"
87
+ }
88
+ },
89
+ "./blobs": {
90
+ "import": {
91
+ "types": "./dist/blobs/index.d.ts",
92
+ "default": "./dist/blobs/index.js"
93
+ },
94
+ "require": {
95
+ "types": "./dist/blobs/index.d.cts",
96
+ "default": "./dist/blobs/index.cjs"
97
+ }
98
+ },
99
+ "./indexing": {
100
+ "import": {
101
+ "types": "./dist/indexing/index.d.ts",
102
+ "default": "./dist/indexing/index.js"
103
+ },
104
+ "require": {
105
+ "types": "./dist/indexing/index.d.cts",
106
+ "default": "./dist/indexing/index.cjs"
107
+ }
108
+ },
109
+ "./aggregate": {
110
+ "import": {
111
+ "types": "./dist/aggregate/index.d.ts",
112
+ "default": "./dist/aggregate/index.js"
113
+ },
114
+ "require": {
115
+ "types": "./dist/aggregate/index.d.cts",
116
+ "default": "./dist/aggregate/index.cjs"
117
+ }
118
+ },
119
+ "./crdt": {
120
+ "import": {
121
+ "types": "./dist/crdt/index.d.ts",
122
+ "default": "./dist/crdt/index.js"
123
+ },
124
+ "require": {
125
+ "types": "./dist/crdt/index.d.cts",
126
+ "default": "./dist/crdt/index.cjs"
127
+ }
128
+ },
129
+ "./bundle": {
130
+ "import": {
131
+ "types": "./dist/bundle/index.d.ts",
132
+ "default": "./dist/bundle/index.js"
133
+ },
134
+ "require": {
135
+ "types": "./dist/bundle/index.d.cts",
136
+ "default": "./dist/bundle/index.cjs"
137
+ }
138
+ },
139
+ "./consent": {
140
+ "import": {
141
+ "types": "./dist/consent/index.d.ts",
142
+ "default": "./dist/consent/index.js"
143
+ },
144
+ "require": {
145
+ "types": "./dist/consent/index.d.cts",
146
+ "default": "./dist/consent/index.cjs"
147
+ }
148
+ },
149
+ "./periods": {
150
+ "import": {
151
+ "types": "./dist/periods/index.d.ts",
152
+ "default": "./dist/periods/index.js"
153
+ },
154
+ "require": {
155
+ "types": "./dist/periods/index.d.cts",
156
+ "default": "./dist/periods/index.cjs"
157
+ }
158
+ },
159
+ "./shadow": {
160
+ "import": {
161
+ "types": "./dist/shadow/index.d.ts",
162
+ "default": "./dist/shadow/index.js"
163
+ },
164
+ "require": {
165
+ "types": "./dist/shadow/index.d.cts",
166
+ "default": "./dist/shadow/index.cjs"
167
+ }
168
+ },
169
+ "./tx": {
170
+ "import": {
171
+ "types": "./dist/tx/index.d.ts",
172
+ "default": "./dist/tx/index.js"
173
+ },
174
+ "require": {
175
+ "types": "./dist/tx/index.d.cts",
176
+ "default": "./dist/tx/index.cjs"
177
+ }
178
+ },
179
+ "./sync": {
180
+ "import": {
181
+ "types": "./dist/sync/index.d.ts",
182
+ "default": "./dist/sync/index.js"
183
+ },
184
+ "require": {
185
+ "types": "./dist/sync/index.d.cts",
186
+ "default": "./dist/sync/index.cjs"
187
+ }
188
+ },
189
+ "./util": {
190
+ "import": {
191
+ "types": "./dist/util/index.d.ts",
192
+ "default": "./dist/util/index.js"
193
+ },
194
+ "require": {
195
+ "types": "./dist/util/index.d.cts",
196
+ "default": "./dist/util/index.cjs"
197
+ }
198
+ }
199
+ },
200
+ "main": "./dist/index.cjs",
201
+ "module": "./dist/index.js",
202
+ "types": "./dist/index.d.ts",
203
+ "files": [
204
+ "dist",
205
+ "README.md",
206
+ "LICENSE"
207
+ ],
208
+ "engines": {
209
+ "node": ">=18.0.0"
210
+ },
211
+ "devDependencies": {
212
+ "@types/node": "^22.0.0",
213
+ "esbuild": "^0.25.0",
214
+ "zod": "^3.23.0"
215
+ },
216
+ "keywords": [
217
+ "noy-db",
218
+ "encryption",
219
+ "zero-knowledge",
220
+ "offline-first",
221
+ "document-store",
222
+ "database",
223
+ "aes-256-gcm",
224
+ "pbkdf2",
225
+ "web-crypto",
226
+ "crypto",
227
+ "e2ee",
228
+ "privacy",
229
+ "multi-user",
230
+ "sync",
231
+ "audit-log"
232
+ ],
233
+ "publishConfig": {
234
+ "access": "public",
235
+ "tag": "latest"
236
+ },
237
+ "scripts": {
238
+ "build": "tsup",
239
+ "test": "vitest run",
240
+ "lint": "eslint src/",
241
+ "typecheck": "tsc --noEmit",
242
+ "bundle-check": "node scripts/check-bundle.mjs"
243
+ }
244
+ }