@minnowdb/core 0.3.0 → 0.4.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.
Files changed (276) hide show
  1. package/README.md +4 -2
  2. package/dist/block-format/block.d.ts +32 -13
  3. package/dist/block-format/block.js +179 -55
  4. package/dist/block-format/checksum.d.ts +2 -1
  5. package/dist/block-format/checksum.js +5 -2
  6. package/dist/block-format/codecs.d.ts +7 -2
  7. package/dist/block-format/codecs.js +53 -12
  8. package/dist/block-format/column.d.ts +3 -2
  9. package/dist/block-format/column.js +96 -30
  10. package/dist/block-format/index.d.ts +2 -2
  11. package/dist/block-format/index.js +2 -2
  12. package/dist/block-format/physical.d.ts +8 -2
  13. package/dist/block-format/physical.js +14 -6
  14. package/dist/block-format/types.d.ts +7 -3
  15. package/dist/block-format/types.js +0 -1
  16. package/dist/block-format/unicode.d.ts +11 -0
  17. package/dist/block-format/unicode.js +46 -0
  18. package/dist/date-value.d.ts +15 -0
  19. package/dist/date-value.js +64 -0
  20. package/dist/engine/artifact-cache.d.ts +0 -1
  21. package/dist/engine/artifact-cache.js +0 -1
  22. package/dist/engine/batch.d.ts +5 -2
  23. package/dist/engine/batch.js +0 -1
  24. package/dist/engine/buffered-writer.d.ts +2 -1
  25. package/dist/engine/buffered-writer.js +34 -9
  26. package/dist/engine/cache-limits.d.ts +25 -0
  27. package/dist/engine/cache-limits.js +25 -0
  28. package/dist/engine/catalog.d.ts +5 -6
  29. package/dist/engine/catalog.js +5 -5
  30. package/dist/engine/client.d.ts +27 -7
  31. package/dist/engine/client.js +197 -31
  32. package/dist/engine/database.d.ts +161 -38
  33. package/dist/engine/database.js +4320 -1523
  34. package/dist/engine/defaults.d.ts +0 -1
  35. package/dist/engine/defaults.js +2 -4
  36. package/dist/engine/errors.d.ts +19 -1
  37. package/dist/engine/errors.js +32 -2
  38. package/dist/engine/fts.d.ts +0 -6
  39. package/dist/engine/fts.js +39 -14
  40. package/dist/engine/group-index.d.ts +0 -1
  41. package/dist/engine/group-index.js +6 -11
  42. package/dist/engine/index.d.ts +16 -11
  43. package/dist/engine/index.js +12 -10
  44. package/dist/engine/join-index.d.ts +0 -1
  45. package/dist/engine/join-index.js +2 -2
  46. package/dist/engine/keyed-live.d.ts +57 -0
  47. package/dist/engine/keyed-live.js +226 -0
  48. package/dist/engine/live-api.d.ts +4 -0
  49. package/dist/engine/live-api.js +4 -0
  50. package/dist/engine/live.d.ts +43 -26
  51. package/dist/engine/live.js +499 -129
  52. package/dist/engine/memory.d.ts +2 -1
  53. package/dist/engine/memory.js +3 -2
  54. package/dist/engine/optimizer.d.ts +0 -1
  55. package/dist/engine/optimizer.js +4 -4
  56. package/dist/engine/query-api.d.ts +3 -0
  57. package/dist/engine/query-api.js +3 -0
  58. package/dist/engine/query-cache.d.ts +0 -1
  59. package/dist/engine/query-cache.js +4 -4
  60. package/dist/engine/query.d.ts +4 -7
  61. package/dist/engine/query.js +348 -155
  62. package/dist/engine/result-wire.d.ts +0 -1
  63. package/dist/engine/result-wire.js +2 -2
  64. package/dist/engine/schema-wire.d.ts +1 -2
  65. package/dist/engine/schema-wire.js +0 -1
  66. package/dist/engine/schema.d.ts +3 -8
  67. package/dist/engine/schema.js +41 -24
  68. package/dist/engine/sort-keys.d.ts +0 -1
  69. package/dist/engine/sort-keys.js +2 -2
  70. package/dist/engine/sql-domains.d.ts +6 -1
  71. package/dist/engine/sql-domains.js +209 -45
  72. package/dist/engine/sql-driver.d.ts +2 -2
  73. package/dist/engine/sql-driver.js +0 -1
  74. package/dist/engine/sql-json.d.ts +0 -1
  75. package/dist/engine/sql-json.js +53 -7
  76. package/dist/engine/sql-semantics.d.ts +8 -5
  77. package/dist/engine/sql-semantics.js +427 -65
  78. package/dist/engine/typed-live.d.ts +67 -0
  79. package/dist/engine/typed-live.js +349 -0
  80. package/dist/engine/vector.d.ts +7 -1
  81. package/dist/engine/vector.js +202 -58
  82. package/dist/engine/worker-host.d.ts +8 -2
  83. package/dist/engine/worker-host.js +616 -152
  84. package/dist/engine/worker.d.ts +0 -1
  85. package/dist/engine/worker.js +3 -3
  86. package/dist/engine/write-block-planner.d.ts +18 -0
  87. package/dist/engine/write-block-planner.js +112 -0
  88. package/dist/index.d.ts +0 -1
  89. package/dist/index.js +0 -1
  90. package/dist/plan/index.d.ts +0 -1
  91. package/dist/plan/index.js +0 -1
  92. package/dist/storage/index.d.ts +1 -1
  93. package/dist/storage/index.js +1 -1
  94. package/dist/storage/indexeddb.d.ts +74 -65
  95. package/dist/storage/indexeddb.js +12301 -2076
  96. package/dist/storage/memory.d.ts +56 -66
  97. package/dist/storage/memory.js +1004 -139
  98. package/dist/storage/opfs/files.d.ts +18 -7
  99. package/dist/storage/opfs/files.js +115 -28
  100. package/dist/storage/opfs/index.d.ts +1 -1
  101. package/dist/storage/opfs/index.js +1 -1
  102. package/dist/storage/opfs/leader.d.ts +403 -91
  103. package/dist/storage/opfs/leader.js +4306 -602
  104. package/dist/storage/opfs/rpc.d.ts +15 -3
  105. package/dist/storage/opfs/rpc.js +223 -2
  106. package/dist/storage/opfs/snapshot-ledger.d.ts +40 -0
  107. package/dist/storage/opfs/snapshot-ledger.js +281 -0
  108. package/dist/storage/opfs/store.d.ts +26 -121
  109. package/dist/storage/opfs/store.js +436 -388
  110. package/dist/storage/persistence.d.ts +37 -0
  111. package/dist/storage/persistence.js +78 -0
  112. package/dist/storage/snapshot-stream.d.ts +24 -0
  113. package/dist/storage/snapshot-stream.js +897 -0
  114. package/dist/storage/snapshot.d.ts +9 -99
  115. package/dist/storage/snapshot.js +33 -309
  116. package/dist/storage/toolkit/extents.d.ts +41 -7
  117. package/dist/storage/toolkit/extents.js +402 -39
  118. package/dist/storage/toolkit/index.d.ts +3 -4
  119. package/dist/storage/toolkit/index.js +27 -3
  120. package/dist/storage/toolkit/record-core.d.ts +174 -61
  121. package/dist/storage/toolkit/record-core.js +5401 -849
  122. package/dist/storage/toolkit/sync-file.d.ts +10 -1
  123. package/dist/storage/toolkit/sync-file.js +50 -2
  124. package/dist/storage/toolkit/wal.d.ts +23 -5
  125. package/dist/storage/toolkit/wal.js +89 -27
  126. package/dist/storage/toolkit/wire.d.ts +4 -6
  127. package/dist/storage/toolkit/wire.js +80 -25
  128. package/dist/storage/types.d.ts +1271 -218
  129. package/dist/storage/types.js +1471 -138
  130. package/dist/testing/block-store-conformance.d.ts +0 -1
  131. package/dist/testing/block-store-conformance.js +877 -138
  132. package/dist/testing/index.d.ts +42 -31
  133. package/dist/testing/index.js +100 -68
  134. package/dist/testing/opfs-shim.d.ts +13 -3
  135. package/dist/testing/opfs-shim.js +40 -6
  136. package/dist/testing/simulator.d.ts +0 -1
  137. package/dist/testing/simulator.js +43 -12
  138. package/dist/testing/sqllogictest.d.ts +0 -1
  139. package/dist/testing/sqllogictest.js +0 -1
  140. package/dist/transactions/index.d.ts +81 -20
  141. package/dist/transactions/index.js +1050 -268
  142. package/dist/worker-protocol/index.d.ts +3 -2
  143. package/dist/worker-protocol/index.js +2 -5
  144. package/package.json +49 -1
  145. package/dist/block-format/block.d.ts.map +0 -1
  146. package/dist/block-format/block.js.map +0 -1
  147. package/dist/block-format/checksum.d.ts.map +0 -1
  148. package/dist/block-format/checksum.js.map +0 -1
  149. package/dist/block-format/codecs.d.ts.map +0 -1
  150. package/dist/block-format/codecs.js.map +0 -1
  151. package/dist/block-format/column.d.ts.map +0 -1
  152. package/dist/block-format/column.js.map +0 -1
  153. package/dist/block-format/index.d.ts.map +0 -1
  154. package/dist/block-format/index.js.map +0 -1
  155. package/dist/block-format/physical.d.ts.map +0 -1
  156. package/dist/block-format/physical.js.map +0 -1
  157. package/dist/block-format/types.d.ts.map +0 -1
  158. package/dist/block-format/types.js.map +0 -1
  159. package/dist/engine/artifact-cache.d.ts.map +0 -1
  160. package/dist/engine/artifact-cache.js.map +0 -1
  161. package/dist/engine/batch.d.ts.map +0 -1
  162. package/dist/engine/batch.js.map +0 -1
  163. package/dist/engine/buffered-writer.d.ts.map +0 -1
  164. package/dist/engine/buffered-writer.js.map +0 -1
  165. package/dist/engine/catalog.d.ts.map +0 -1
  166. package/dist/engine/catalog.js.map +0 -1
  167. package/dist/engine/client.d.ts.map +0 -1
  168. package/dist/engine/client.js.map +0 -1
  169. package/dist/engine/coordinator.d.ts +0 -17
  170. package/dist/engine/coordinator.d.ts.map +0 -1
  171. package/dist/engine/coordinator.js +0 -60
  172. package/dist/engine/coordinator.js.map +0 -1
  173. package/dist/engine/database.d.ts.map +0 -1
  174. package/dist/engine/database.js.map +0 -1
  175. package/dist/engine/defaults.d.ts.map +0 -1
  176. package/dist/engine/defaults.js.map +0 -1
  177. package/dist/engine/errors.d.ts.map +0 -1
  178. package/dist/engine/errors.js.map +0 -1
  179. package/dist/engine/fts.d.ts.map +0 -1
  180. package/dist/engine/fts.js.map +0 -1
  181. package/dist/engine/group-index.d.ts.map +0 -1
  182. package/dist/engine/group-index.js.map +0 -1
  183. package/dist/engine/index.d.ts.map +0 -1
  184. package/dist/engine/index.js.map +0 -1
  185. package/dist/engine/join-index.d.ts.map +0 -1
  186. package/dist/engine/join-index.js.map +0 -1
  187. package/dist/engine/live.d.ts.map +0 -1
  188. package/dist/engine/live.js.map +0 -1
  189. package/dist/engine/memory.d.ts.map +0 -1
  190. package/dist/engine/memory.js.map +0 -1
  191. package/dist/engine/optimizer.d.ts.map +0 -1
  192. package/dist/engine/optimizer.js.map +0 -1
  193. package/dist/engine/query-cache.d.ts.map +0 -1
  194. package/dist/engine/query-cache.js.map +0 -1
  195. package/dist/engine/query.d.ts.map +0 -1
  196. package/dist/engine/query.js.map +0 -1
  197. package/dist/engine/result-wire.d.ts.map +0 -1
  198. package/dist/engine/result-wire.js.map +0 -1
  199. package/dist/engine/schema-wire.d.ts.map +0 -1
  200. package/dist/engine/schema-wire.js.map +0 -1
  201. package/dist/engine/schema.d.ts.map +0 -1
  202. package/dist/engine/schema.js.map +0 -1
  203. package/dist/engine/sort-keys.d.ts.map +0 -1
  204. package/dist/engine/sort-keys.js.map +0 -1
  205. package/dist/engine/sql-domains.d.ts.map +0 -1
  206. package/dist/engine/sql-domains.js.map +0 -1
  207. package/dist/engine/sql-driver.d.ts.map +0 -1
  208. package/dist/engine/sql-driver.js.map +0 -1
  209. package/dist/engine/sql-json.d.ts.map +0 -1
  210. package/dist/engine/sql-json.js.map +0 -1
  211. package/dist/engine/sql-semantics.d.ts.map +0 -1
  212. package/dist/engine/sql-semantics.js.map +0 -1
  213. package/dist/engine/vector.d.ts.map +0 -1
  214. package/dist/engine/vector.js.map +0 -1
  215. package/dist/engine/worker-host.d.ts.map +0 -1
  216. package/dist/engine/worker-host.js.map +0 -1
  217. package/dist/engine/worker.d.ts.map +0 -1
  218. package/dist/engine/worker.js.map +0 -1
  219. package/dist/index.d.ts.map +0 -1
  220. package/dist/index.js.map +0 -1
  221. package/dist/plan/index.d.ts.map +0 -1
  222. package/dist/plan/index.js.map +0 -1
  223. package/dist/storage/fixture-shape.d.ts +0 -42
  224. package/dist/storage/fixture-shape.d.ts.map +0 -1
  225. package/dist/storage/fixture-shape.js +0 -146
  226. package/dist/storage/fixture-shape.js.map +0 -1
  227. package/dist/storage/index.d.ts.map +0 -1
  228. package/dist/storage/index.js.map +0 -1
  229. package/dist/storage/indexeddb.d.ts.map +0 -1
  230. package/dist/storage/indexeddb.js.map +0 -1
  231. package/dist/storage/memory.d.ts.map +0 -1
  232. package/dist/storage/memory.js.map +0 -1
  233. package/dist/storage/opfs/files.d.ts.map +0 -1
  234. package/dist/storage/opfs/files.js.map +0 -1
  235. package/dist/storage/opfs/index.d.ts.map +0 -1
  236. package/dist/storage/opfs/index.js.map +0 -1
  237. package/dist/storage/opfs/leader.d.ts.map +0 -1
  238. package/dist/storage/opfs/leader.js.map +0 -1
  239. package/dist/storage/opfs/rpc.d.ts.map +0 -1
  240. package/dist/storage/opfs/rpc.js.map +0 -1
  241. package/dist/storage/opfs/store.d.ts.map +0 -1
  242. package/dist/storage/opfs/store.js.map +0 -1
  243. package/dist/storage/snapshot.d.ts.map +0 -1
  244. package/dist/storage/snapshot.js.map +0 -1
  245. package/dist/storage/toolkit/extents.d.ts.map +0 -1
  246. package/dist/storage/toolkit/extents.js.map +0 -1
  247. package/dist/storage/toolkit/index.d.ts.map +0 -1
  248. package/dist/storage/toolkit/index.js.map +0 -1
  249. package/dist/storage/toolkit/record-core.d.ts.map +0 -1
  250. package/dist/storage/toolkit/record-core.js.map +0 -1
  251. package/dist/storage/toolkit/sync-file.d.ts.map +0 -1
  252. package/dist/storage/toolkit/sync-file.js.map +0 -1
  253. package/dist/storage/toolkit/wal.d.ts.map +0 -1
  254. package/dist/storage/toolkit/wal.js.map +0 -1
  255. package/dist/storage/toolkit/wire.d.ts.map +0 -1
  256. package/dist/storage/toolkit/wire.js.map +0 -1
  257. package/dist/storage/types.d.ts.map +0 -1
  258. package/dist/storage/types.js.map +0 -1
  259. package/dist/testing/block-store-conformance.d.ts.map +0 -1
  260. package/dist/testing/block-store-conformance.js.map +0 -1
  261. package/dist/testing/index.d.ts.map +0 -1
  262. package/dist/testing/index.js.map +0 -1
  263. package/dist/testing/opfs-shim.d.ts.map +0 -1
  264. package/dist/testing/opfs-shim.js.map +0 -1
  265. package/dist/testing/seeds.d.ts +0 -11
  266. package/dist/testing/seeds.d.ts.map +0 -1
  267. package/dist/testing/seeds.js +0 -50
  268. package/dist/testing/seeds.js.map +0 -1
  269. package/dist/testing/simulator.d.ts.map +0 -1
  270. package/dist/testing/simulator.js.map +0 -1
  271. package/dist/testing/sqllogictest.d.ts.map +0 -1
  272. package/dist/testing/sqllogictest.js.map +0 -1
  273. package/dist/transactions/index.d.ts.map +0 -1
  274. package/dist/transactions/index.js.map +0 -1
  275. package/dist/worker-protocol/index.d.ts.map +0 -1
  276. package/dist/worker-protocol/index.js.map +0 -1
@@ -1,3 +1,6 @@
1
+ import { dateMilliseconds } from "../date-value.js";
2
+ import { assertWellFormedString } from "../block-format/unicode.js";
3
+ import { MAX_SQL_NESTING_DEPTH, MAX_SQL_PATTERN_CHARACTERS, MAX_SQL_PATTERN_MATCH_STEPS, } from "./cache-limits.js";
1
4
  import { collatedDomainCompare, enumDomainCompare, exactNumericCompare, externalSqlDomainValue, externalSqlTextValue, } from "./sql-domains.js";
2
5
  /**
3
6
  * SQL value semantics shared by the row and vector executors.
@@ -28,8 +31,8 @@ export function compareSqlValues(left, right) {
28
31
  const exact = exactNumericCompare(left, right);
29
32
  if (exact !== undefined)
30
33
  return exact;
31
- const a = left instanceof Date ? left.getTime() : left;
32
- const b = right instanceof Date ? right.getTime() : right;
34
+ const a = left instanceof Date ? dateMilliseconds(left) : left;
35
+ const b = right instanceof Date ? dateMilliseconds(right) : right;
33
36
  if (a === b)
34
37
  return 0;
35
38
  if (a === null || a === undefined)
@@ -66,7 +69,7 @@ export function encodeSqlEqualityValue(value) {
66
69
  if (typeof value === "string")
67
70
  return [3, externalSqlTextValue(value)];
68
71
  if (value instanceof Date)
69
- return [4, value.getTime()];
72
+ return [4, dateMilliseconds(value)];
70
73
  throw new TypeError("Query produced an unsupported value");
71
74
  }
72
75
  /**
@@ -80,89 +83,449 @@ export function roundSqlNumber(value, precision = 0) {
80
83
  const rounded = Number(value.toFixed(digits));
81
84
  return rounded === 0 ? 0 : rounded;
82
85
  }
86
+ const MAX_PATTERN_CACHE_ENTRIES = 128;
87
+ const MAX_PATTERN_CACHE_RETAINED_SIZE = 64 * 1024;
83
88
  const likeCache = new Map();
84
- /** Compiles SQL LIKE (% = any run, _ = one codepoint) into a bounded cached RegExp. */
85
- export function compileLikePattern(pattern, caseInsensitive = false, escape) {
86
- const key = JSON.stringify([caseInsensitive, escape ?? null, pattern]);
87
- const cached = likeCache.get(key);
88
- if (cached !== undefined)
89
- return cached;
90
- let source = "^";
89
+ const similarCache = new Map();
90
+ let likeCacheRetainedSize = 0;
91
+ let similarCacheRetainedSize = 0;
92
+ function cachedPattern(cache, key) {
93
+ const entry = cache.get(key);
94
+ if (entry === undefined)
95
+ return undefined;
96
+ cache.delete(key);
97
+ cache.set(key, entry);
98
+ return entry[0];
99
+ }
100
+ function retainPattern(cache, key, matcher, retainedSize, setRetainedSize) {
101
+ const entrySize = key.length + matcher.retainedSize;
102
+ if (entrySize > MAX_PATTERN_CACHE_RETAINED_SIZE)
103
+ return;
104
+ while (cache.size >= MAX_PATTERN_CACHE_ENTRIES ||
105
+ retainedSize + entrySize > MAX_PATTERN_CACHE_RETAINED_SIZE) {
106
+ const oldest = cache.keys().next().value;
107
+ if (oldest === undefined)
108
+ break;
109
+ const removed = cache.get(oldest);
110
+ cache.delete(oldest);
111
+ retainedSize -= removed?.[1] ?? 0;
112
+ }
113
+ cache.set(key, [matcher, entrySize]);
114
+ setRetainedSize(retainedSize + entrySize);
115
+ }
116
+ function throwPatternWorkLimit() {
117
+ throw new RangeError(`SQL pattern match exceeds ${String(MAX_SQL_PATTERN_MATCH_STEPS)} deterministic steps`);
118
+ }
119
+ function exactSingleCharacter(value, label) {
120
+ assertBoundedPattern(value, label, 2);
121
+ const characters = value[Symbol.iterator]();
122
+ const first = characters.next();
123
+ if (first.done === true || characters.next().done !== true) {
124
+ throw new TypeError(`${label.replace(" escape", " ESCAPE")} takes one character`);
125
+ }
126
+ return first.value;
127
+ }
128
+ function tokenizeLike(pattern, escape) {
129
+ const tokens = [];
91
130
  let escaped = false;
92
131
  for (const character of pattern) {
93
132
  if (escaped) {
94
- source += escapeRegExp(character);
133
+ tokens.push(character);
95
134
  escaped = false;
96
- continue;
97
135
  }
98
- if (escape !== undefined && character === escape) {
136
+ else if (escape !== undefined && character === escape) {
99
137
  escaped = true;
100
- continue;
101
138
  }
102
- if (character === "%")
103
- source += "[\\s\\S]*";
139
+ else if (character === "%") {
140
+ if (tokens.at(-1) !== 0)
141
+ tokens.push(0);
142
+ }
104
143
  else if (character === "_")
105
- source += "[\\s\\S]";
144
+ tokens.push(1);
106
145
  else
107
- source += escapeRegExp(character);
146
+ tokens.push(character);
108
147
  }
109
148
  if (escaped)
110
149
  throw new TypeError("LIKE pattern ends with a dangling escape character");
111
- source += "$";
112
- const regExp = new RegExp(source, caseInsensitive ? "iu" : "u");
113
- if (likeCache.size >= 128)
114
- likeCache.clear();
115
- likeCache.set(key, regExp);
116
- return regExp;
150
+ return tokens;
117
151
  }
118
- const similarCache = new Map();
119
- /** PostgreSQL SIMILAR TO: SQL wildcards plus the SQL regular-expression operators, whole-string. */
120
- export function compileSimilarPattern(pattern, escape = "\\") {
121
- if (Array.from(escape).length !== 1)
122
- throw new TypeError("SIMILAR TO ESCAPE takes one character");
123
- const key = JSON.stringify([escape, pattern]);
124
- const cached = similarCache.get(key);
152
+ /** Unicode simple-case approximation used consistently by row and vector LIKE execution. */
153
+ function simpleCaseFold(character) {
154
+ const upper = character.toUpperCase();
155
+ const codePoints = upper[Symbol.iterator]();
156
+ return codePoints.next().done !== true && codePoints.next().done === true ? upper : character;
157
+ }
158
+ function likeCharactersEqual(left, right, caseInsensitive) {
159
+ return left === right || (caseInsensitive && simpleCaseFold(left) === simpleCaseFold(right));
160
+ }
161
+ function literalLikeMatcher(tokens, caseInsensitive) {
162
+ if (tokens.includes(1))
163
+ return undefined;
164
+ const leading = tokens[0] === 0;
165
+ const trailing = tokens.at(-1) === 0;
166
+ const begin = leading ? 1 : 0;
167
+ const end = trailing ? tokens.length - 1 : tokens.length;
168
+ if (tokens.slice(begin, end).some((token) => typeof token !== "string"))
169
+ return undefined;
170
+ const body = tokens
171
+ .slice(begin, end)
172
+ .map((token) => (typeof token === "string" ? token : ""))
173
+ .join("");
174
+ if (!caseInsensitive) {
175
+ const test = (value) => {
176
+ if (typeof value !== "string")
177
+ throw new TypeError("LIKE input must be a string");
178
+ if (leading && trailing)
179
+ return value.includes(body);
180
+ if (leading)
181
+ return value.endsWith(body);
182
+ if (trailing)
183
+ return value.startsWith(body);
184
+ return value === body;
185
+ };
186
+ return { test, retainedSize: tokens.length * 16 + body.length * 2 };
187
+ }
188
+ const needle = Array.from(body, simpleCaseFold);
189
+ const prefix = new Uint32Array(needle.length);
190
+ for (let index = 1, matched = 0; index < needle.length; index += 1) {
191
+ while (matched > 0 && needle[index] !== needle[matched])
192
+ matched = prefix[matched - 1] ?? 0;
193
+ if (needle[index] === needle[matched])
194
+ matched += 1;
195
+ prefix[index] = matched;
196
+ }
197
+ const test = (value) => {
198
+ if (typeof value !== "string")
199
+ throw new TypeError("LIKE input must be a string");
200
+ if (needle.length === 0)
201
+ return leading || trailing || value.length === 0;
202
+ let matched = 0;
203
+ let position = 0;
204
+ let matchedAtEnd = false;
205
+ for (const rawCharacter of value) {
206
+ matchedAtEnd = false;
207
+ const character = simpleCaseFold(rawCharacter);
208
+ while (matched > 0 && character !== needle[matched])
209
+ matched = prefix[matched - 1] ?? 0;
210
+ if (character === needle[matched])
211
+ matched += 1;
212
+ position += 1;
213
+ if (matched === needle.length) {
214
+ const startsAt = position - needle.length;
215
+ if (leading && trailing)
216
+ return true;
217
+ if (!leading && startsAt === 0) {
218
+ if (trailing)
219
+ return true;
220
+ matchedAtEnd = true;
221
+ }
222
+ else if (leading && !trailing)
223
+ matchedAtEnd = true;
224
+ matched = prefix[matched - 1] ?? 0;
225
+ }
226
+ }
227
+ return matchedAtEnd;
228
+ };
229
+ return {
230
+ test,
231
+ retainedSize: tokens.length * 16 + body.length * 2 + prefix.byteLength,
232
+ };
233
+ }
234
+ function generalLikeMatcher(tokens, caseInsensitive) {
235
+ const test = (value) => {
236
+ if (typeof value !== "string")
237
+ throw new TypeError("LIKE input must be a string");
238
+ let remaining = MAX_SQL_PATTERN_MATCH_STEPS;
239
+ let tokenIndex = 0;
240
+ let valueIndex = 0;
241
+ let manyIndex = -1;
242
+ let retryValueIndex = 0;
243
+ while (valueIndex < value.length) {
244
+ if (--remaining < 0)
245
+ throwPatternWorkLimit();
246
+ const token = tokens[tokenIndex];
247
+ if (token === 0) {
248
+ manyIndex = tokenIndex;
249
+ tokenIndex += 1;
250
+ retryValueIndex = valueIndex;
251
+ continue;
252
+ }
253
+ const codePoint = value.codePointAt(valueIndex);
254
+ if (codePoint === undefined)
255
+ break;
256
+ const character = String.fromCodePoint(codePoint);
257
+ if (token === 1 ||
258
+ (typeof token === "string" && likeCharactersEqual(token, character, caseInsensitive))) {
259
+ tokenIndex += 1;
260
+ valueIndex += character.length;
261
+ continue;
262
+ }
263
+ if (manyIndex < 0)
264
+ return false;
265
+ const retryCodePoint = value.codePointAt(retryValueIndex);
266
+ if (retryCodePoint === undefined)
267
+ return false;
268
+ retryValueIndex += String.fromCodePoint(retryCodePoint).length;
269
+ valueIndex = retryValueIndex;
270
+ tokenIndex = manyIndex + 1;
271
+ }
272
+ while (tokens[tokenIndex] === 0) {
273
+ if (--remaining < 0)
274
+ throwPatternWorkLimit();
275
+ tokenIndex += 1;
276
+ }
277
+ return tokenIndex === tokens.length;
278
+ };
279
+ return { test, retainedSize: tokens.length * 16 };
280
+ }
281
+ /** Compiles SQL LIKE without exposing input to the host regular-expression engine. */
282
+ export function compileLikePattern(pattern, caseInsensitive = false, escape) {
283
+ assertBoundedPattern(pattern, "LIKE pattern");
284
+ const exactEscape = escape === undefined ? undefined : exactSingleCharacter(escape, "LIKE escape");
285
+ const key = JSON.stringify([caseInsensitive, exactEscape ?? null, pattern]);
286
+ const cached = cachedPattern(likeCache, key);
125
287
  if (cached !== undefined)
126
288
  return cached;
127
- let source = "^(?:";
128
- let escaped = false;
129
- let inClass = false;
130
- for (const character of pattern) {
131
- if (escaped) {
132
- source += character.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
133
- escaped = false;
134
- continue;
289
+ const tokens = tokenizeLike(pattern, exactEscape);
290
+ const matcher = literalLikeMatcher(tokens, caseInsensitive) ?? generalLikeMatcher(tokens, caseInsensitive);
291
+ retainPattern(likeCache, key, matcher, likeCacheRetainedSize, (size) => {
292
+ likeCacheRetainedSize = size;
293
+ });
294
+ return matcher;
295
+ }
296
+ const ANY_CHARACTER = () => true;
297
+ /** Parses SIMILAR TO directly into an NFA, avoiding an allocation-heavy intermediate AST. */
298
+ class SimilarCompiler {
299
+ escape;
300
+ #characters;
301
+ states = [];
302
+ #index = 0;
303
+ constructor(pattern, escape) {
304
+ this.escape = escape;
305
+ this.#characters = Array.from(pattern);
306
+ }
307
+ compile() {
308
+ const fragment = this.#alternative(0);
309
+ if (this.#index !== this.#characters.length) {
310
+ throw new TypeError("SIMILAR TO pattern has an unmatched closing parenthesis");
135
311
  }
136
- if (character === escape) {
137
- escaped = true;
138
- continue;
312
+ return fragment;
313
+ }
314
+ #state() {
315
+ this.states.push([[]]);
316
+ return this.states.length - 1;
317
+ }
318
+ #empty() {
319
+ const start = this.#state();
320
+ const end = this.#state();
321
+ this.states[start]?.[0].push(end);
322
+ return [start, end];
323
+ }
324
+ #consume(matches) {
325
+ const start = this.#state();
326
+ const end = this.#state();
327
+ const state = this.states[start];
328
+ if (state !== undefined)
329
+ state[1] = [end, matches];
330
+ return [start, end];
331
+ }
332
+ #repeat(fragment, plus) {
333
+ const start = this.#state();
334
+ const end = this.#state();
335
+ this.states[start]?.[0].push(fragment[0]);
336
+ if (!plus)
337
+ this.states[start]?.[0].push(end);
338
+ this.states[fragment[1]]?.[0].push(fragment[0], end);
339
+ return [start, end];
340
+ }
341
+ #alternative(depth) {
342
+ const first = this.#sequence(depth);
343
+ if (this.#characters[this.#index] !== "|" || this.#characters[this.#index] === this.escape) {
344
+ return first;
139
345
  }
140
- if (character === "[")
141
- inClass = true;
142
- if (character === "]")
143
- inClass = false;
144
- if (!inClass && character === "%")
145
- source += "[\\s\\S]*";
146
- else if (!inClass && character === "_")
147
- source += "[\\s\\S]";
148
- else if (inClass || "|*+()[]".includes(character))
149
- source += character;
150
- else
151
- source += character.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
346
+ const start = this.#state();
347
+ const end = this.#state();
348
+ this.states[start]?.[0].push(first[0]);
349
+ this.states[first[1]]?.[0].push(end);
350
+ while (this.#characters[this.#index] === "|" && this.#characters[this.#index] !== this.escape) {
351
+ this.#index += 1;
352
+ const next = this.#sequence(depth);
353
+ this.states[start]?.[0].push(next[0]);
354
+ this.states[next[1]]?.[0].push(end);
355
+ }
356
+ return [start, end];
152
357
  }
153
- if (escaped)
154
- throw new TypeError("SIMILAR TO pattern ends with its escape character");
155
- try {
156
- const compiled = new RegExp(`${source})$`, "u");
157
- similarCache.set(key, compiled);
158
- return compiled;
358
+ #sequence(depth) {
359
+ let result;
360
+ for (;;) {
361
+ const character = this.#characters[this.#index];
362
+ if (character === undefined ||
363
+ ((character === "|" || character === ")") && character !== this.escape)) {
364
+ return result ?? this.#empty();
365
+ }
366
+ let fragment = this.#atom(depth);
367
+ const quantifier = this.#characters[this.#index];
368
+ if ((quantifier === "*" || quantifier === "+") && quantifier !== this.escape) {
369
+ this.#index += 1;
370
+ fragment = this.#repeat(fragment, quantifier === "+");
371
+ const repeated = this.#characters[this.#index];
372
+ if ((repeated === "*" || repeated === "+") && repeated !== this.escape) {
373
+ throw new TypeError("SIMILAR TO pattern repeats a quantifier");
374
+ }
375
+ }
376
+ if (result === undefined)
377
+ result = fragment;
378
+ else {
379
+ this.states[result[1]]?.[0].push(fragment[0]);
380
+ result = [result[0], fragment[1]];
381
+ }
382
+ }
383
+ }
384
+ #atom(depth) {
385
+ const character = this.#characters[this.#index];
386
+ if (character === undefined)
387
+ return this.#empty();
388
+ this.#index += 1;
389
+ if (character === this.escape) {
390
+ const literal = this.#characters[this.#index];
391
+ if (literal === undefined) {
392
+ throw new TypeError("SIMILAR TO pattern ends with its escape character");
393
+ }
394
+ this.#index += 1;
395
+ return this.#consume((candidate) => candidate === literal);
396
+ }
397
+ if (character === "*" || character === "+") {
398
+ throw new TypeError("SIMILAR TO quantifier has no preceding expression");
399
+ }
400
+ if (character === "(") {
401
+ if (depth >= MAX_SQL_NESTING_DEPTH) {
402
+ throw new RangeError(`SIMILAR TO nesting exceeds ${String(MAX_SQL_NESTING_DEPTH)} levels`);
403
+ }
404
+ const child = this.#alternative(depth + 1);
405
+ if (this.#characters[this.#index] !== ")") {
406
+ throw new TypeError("SIMILAR TO pattern has an unmatched opening parenthesis");
407
+ }
408
+ this.#index += 1;
409
+ return child;
410
+ }
411
+ if (character === "[")
412
+ return this.#consume(this.#characterClass());
413
+ if (character === "%")
414
+ return this.#repeat(this.#consume(ANY_CHARACTER), false);
415
+ if (character === "_")
416
+ return this.#consume(ANY_CHARACTER);
417
+ return this.#consume((candidate) => candidate === character);
159
418
  }
160
- catch {
161
- throw new TypeError(`Invalid SIMILAR TO pattern: ${pattern}`);
419
+ #characterClass() {
420
+ let source = "[";
421
+ let closed = false;
422
+ let hasMember = false;
423
+ while (this.#index < this.#characters.length) {
424
+ const character = this.#characters[this.#index] ?? "";
425
+ this.#index += 1;
426
+ if (character === this.escape) {
427
+ const literal = this.#characters[this.#index];
428
+ if (literal === undefined) {
429
+ throw new TypeError("SIMILAR TO pattern ends with its escape character");
430
+ }
431
+ this.#index += 1;
432
+ source += ["\\", "]", "^", "-"].includes(literal) ? `\\${literal}` : literal;
433
+ hasMember = true;
434
+ continue;
435
+ }
436
+ if (character === "]") {
437
+ closed = true;
438
+ break;
439
+ }
440
+ source += character;
441
+ if (character !== "^" || source.length > 2)
442
+ hasMember = true;
443
+ }
444
+ if (!closed)
445
+ throw new TypeError("SIMILAR TO pattern has an unterminated character class");
446
+ if (!hasMember)
447
+ throw new TypeError("SIMILAR TO pattern has an empty character class");
448
+ source += "]";
449
+ let expression;
450
+ try {
451
+ // The host expression is deliberately limited to one character class and one codepoint.
452
+ // It cannot contain repetition, grouping, or alternation, so backtracking work is constant.
453
+ expression = new RegExp(`^(?:${source})$`, "u");
454
+ }
455
+ catch {
456
+ throw new TypeError("SIMILAR TO pattern has an invalid character class");
457
+ }
458
+ return (candidate) => expression.test(candidate);
162
459
  }
163
460
  }
164
- function escapeRegExp(value) {
165
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
461
+ function nfaMatcher(pattern, escape) {
462
+ const compiler = new SimilarCompiler(pattern, escape);
463
+ const fragment = compiler.compile();
464
+ const { states } = compiler;
465
+ const test = (value) => {
466
+ if (typeof value !== "string")
467
+ throw new TypeError("SIMILAR TO input must be a string");
468
+ let remaining = MAX_SQL_PATTERN_MATCH_STEPS;
469
+ const marks = new Uint32Array(states.length);
470
+ const stack = [];
471
+ let active = [];
472
+ let next = [];
473
+ let generation = 1;
474
+ const addClosure = (seed, output) => {
475
+ stack.push(seed);
476
+ while (stack.length > 0) {
477
+ if (--remaining < 0)
478
+ throwPatternWorkLimit();
479
+ const stateIndex = stack.pop();
480
+ if (stateIndex === undefined || marks[stateIndex] === generation)
481
+ continue;
482
+ marks[stateIndex] = generation;
483
+ output.push(stateIndex);
484
+ for (const target of states[stateIndex]?.[0] ?? [])
485
+ stack.push(target);
486
+ }
487
+ };
488
+ addClosure(fragment[0], active);
489
+ for (const character of value) {
490
+ generation += 1;
491
+ next.length = 0;
492
+ for (const stateIndex of active) {
493
+ if (--remaining < 0)
494
+ throwPatternWorkLimit();
495
+ const transition = states[stateIndex]?.[1];
496
+ if (transition?.[1](character) === true)
497
+ addClosure(transition[0], next);
498
+ }
499
+ if (next.length === 0)
500
+ return false;
501
+ [active, next] = [next, active];
502
+ }
503
+ return active.includes(fragment[1]);
504
+ };
505
+ // State objects, epsilon arrays, transition objects, and matcher closures all cost materially
506
+ // more than their integer payload. A conservative model keeps large compiled patterns out of
507
+ // the cache instead of pretending their Uint32-sized indexes are the whole retained graph.
508
+ return { test, retainedSize: states.length * 32 };
509
+ }
510
+ /** PostgreSQL SIMILAR TO compiled to a Thompson NFA with bounded deterministic work. */
511
+ export function compileSimilarPattern(pattern, escape = "\\") {
512
+ assertBoundedPattern(pattern, "SIMILAR TO pattern");
513
+ const exactEscape = exactSingleCharacter(escape, "SIMILAR TO escape");
514
+ const key = JSON.stringify([exactEscape, pattern]);
515
+ const cached = cachedPattern(similarCache, key);
516
+ if (cached !== undefined)
517
+ return cached;
518
+ const matcher = nfaMatcher(pattern, exactEscape);
519
+ retainPattern(similarCache, key, matcher, similarCacheRetainedSize, (size) => {
520
+ similarCacheRetainedSize = size;
521
+ });
522
+ return matcher;
523
+ }
524
+ function assertBoundedPattern(value, label, limit = MAX_SQL_PATTERN_CHARACTERS) {
525
+ if (value.length > limit) {
526
+ throw new RangeError(`${label} exceeds ${String(limit)} characters`);
527
+ }
528
+ assertWellFormedString(value, label);
166
529
  }
167
530
  /** Defines an own enumerable result-column property, including the special name `__proto__`. */
168
531
  export function defineSqlResultProperty(target, name, value) {
@@ -184,4 +547,3 @@ export function stringArgument(name, value) {
184
547
  throw new TypeError(`${name} requires a string argument`);
185
548
  return external;
186
549
  }
187
- //# sourceMappingURL=sql-semantics.js.map
@@ -0,0 +1,67 @@
1
+ import type { LiveQueryInput, LiveQueryObserveOptions } from "./live.js";
2
+ /** The structural live-query surface shared by MinnowDatabase and its worker client. */
3
+ export interface LiveQueryBackend {
4
+ observe(query: LiveQueryInput, options: LiveQueryObserveOptions): Promise<LiveQuerySubscriptionLike>;
5
+ refresh(): Promise<void>;
6
+ close(): void | Promise<void>;
7
+ }
8
+ export interface LiveQuerySubscriptionLike {
9
+ readonly dependencyTableIds: readonly string[];
10
+ close(): void | Promise<void>;
11
+ }
12
+ export interface LiveQueryDriver {
13
+ liveQueries(options?: {
14
+ channelName?: string;
15
+ pollIntervalMs?: number;
16
+ }): LiveQueryBackend;
17
+ }
18
+ /**
19
+ * Adapter-neutral query boundary. `query` drives dependency tracking in Minnow; `execute`
20
+ * deliberately stays with the adapter so its result transforms and inferred row type survive.
21
+ */
22
+ export interface LiveQuerySource<out TRow> {
23
+ readonly query: LiveQueryInput;
24
+ execute(signal?: AbortSignal): Promise<readonly TRow[]>;
25
+ }
26
+ export type LiveSnapshot<TRow> = {
27
+ readonly status: "loading";
28
+ readonly rows: readonly TRow[];
29
+ } | {
30
+ readonly status: "ready";
31
+ readonly rows: readonly TRow[];
32
+ readonly version: number | null;
33
+ } | {
34
+ readonly status: "error";
35
+ readonly rows: readonly TRow[];
36
+ readonly error: unknown;
37
+ readonly version: number | null;
38
+ };
39
+ /**
40
+ * One typed live SELECT. It is also a framework-neutral external store: getSnapshot is stable
41
+ * until state changes and subscribe returns synchronous cleanup. Slow consumers coalesce to the
42
+ * newest invalidation; errors retain the last good rows.
43
+ */
44
+ export declare class LiveQuery<out TRow> implements AsyncIterable<LiveSnapshot<TRow>> {
45
+ #private;
46
+ /** Type-only row helper (`typeof query.$inferRow`). */
47
+ readonly $inferRow: TRow;
48
+ constructor(backend: LiveQueryBackend, source: LiveQuerySource<TRow>, onClose?: () => void);
49
+ getSnapshot: () => LiveSnapshot<TRow>;
50
+ subscribe: (listener: () => void) => (() => void);
51
+ refresh(): Promise<void>;
52
+ close(): void;
53
+ [Symbol.asyncIterator](): AsyncIterator<LiveSnapshot<TRow>, undefined>;
54
+ }
55
+ export interface LiveQueryManagerOptions {
56
+ readonly channelName?: string;
57
+ readonly pollIntervalMs?: number;
58
+ }
59
+ /** Owns one shared low-level set and every typed query created from it. */
60
+ export declare class LiveQueryManager {
61
+ #private;
62
+ constructor(driver: LiveQueryDriver, options?: LiveQueryManagerOptions);
63
+ watch<TRow>(source: LiveQuerySource<TRow>): LiveQuery<TRow>;
64
+ refresh(): Promise<void>;
65
+ close(): Promise<void>;
66
+ }
67
+ export declare function createLiveQueryManager(driver: LiveQueryDriver, options?: LiveQueryManagerOptions): LiveQueryManager;