@supalive/core 0.1.3 → 0.1.5

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 (212) hide show
  1. package/README.md +308 -1
  2. package/dist/helper-CiacMqje.d.ts +14 -0
  3. package/dist/helper-CiacMqje.d.ts.map +1 -0
  4. package/dist/helper-zdJT5FUc.js +58 -0
  5. package/dist/helper-zdJT5FUc.js.map +1 -0
  6. package/dist/index-CTyXKcq_.d.ts +1736 -0
  7. package/dist/index-CTyXKcq_.d.ts.map +1 -0
  8. package/dist/mysql-C8SN443m.d.ts +104 -0
  9. package/dist/mysql-C8SN443m.d.ts.map +1 -0
  10. package/dist/mysql-DELSRrpY.js +569 -0
  11. package/dist/mysql-DELSRrpY.js.map +1 -0
  12. package/dist/overlap-checker-CVkbbuEQ.js +187 -0
  13. package/dist/overlap-checker-CVkbbuEQ.js.map +1 -0
  14. package/dist/postgres-DE-XyNAQ.js +823 -0
  15. package/dist/postgres-DE-XyNAQ.js.map +1 -0
  16. package/dist/postgres-D_gwCHx-.d.ts +108 -0
  17. package/dist/postgres-D_gwCHx-.d.ts.map +1 -0
  18. package/dist/query-CbQLjeME.js +713 -0
  19. package/dist/query-CbQLjeME.js.map +1 -0
  20. package/dist/realtime_db-D9p2X2PH.js +246 -0
  21. package/dist/realtime_db-D9p2X2PH.js.map +1 -0
  22. package/dist/router-DlTYWpop.js +81 -0
  23. package/dist/router-DlTYWpop.js.map +1 -0
  24. package/dist/schema-SKcIWHfo.js +167 -0
  25. package/dist/schema-SKcIWHfo.js.map +1 -0
  26. package/dist/src/client/index.d.ts +2 -17
  27. package/dist/src/client/index.js +1151 -18
  28. package/dist/src/client/index.js.map +1 -1
  29. package/dist/src/exports/mysql.d.ts +2 -2
  30. package/dist/src/exports/mysql.js +2 -2
  31. package/dist/src/exports/postgres.d.ts +2 -2
  32. package/dist/src/exports/postgres.js +2 -2
  33. package/dist/src/exports/procedure.d.ts +3 -5
  34. package/dist/src/exports/procedure.js +5 -5
  35. package/dist/src/exports/server.d.ts +368 -2
  36. package/dist/src/exports/server.d.ts.map +1 -1
  37. package/dist/src/exports/server.js +1346 -2
  38. package/dist/src/exports/server.js.map +1 -1
  39. package/dist/src/exports/subscription-manager-worker-entry.d.ts +1 -0
  40. package/dist/src/exports/subscription-manager-worker-entry.js +115 -0
  41. package/dist/src/exports/subscription-manager-worker-entry.js.map +1 -0
  42. package/dist/src/exports/types.d.ts +4 -6
  43. package/dist/src/exports/types.js +5 -6
  44. package/dist/subscription-worker-BurdPO8d.js +511 -0
  45. package/dist/subscription-worker-BurdPO8d.js.map +1 -0
  46. package/dist/types_client_rpc-CIsjQQGG.js +105 -0
  47. package/dist/types_client_rpc-CIsjQQGG.js.map +1 -0
  48. package/dist/types_db-Ctf-ksoy.js +168 -0
  49. package/dist/types_db-Ctf-ksoy.js.map +1 -0
  50. package/dist/types_server-Ch56y8L3.d.ts +216 -0
  51. package/dist/types_server-Ch56y8L3.d.ts.map +1 -0
  52. package/dist/types_server-Dk6o_B7N.js +24 -0
  53. package/dist/types_server-Dk6o_B7N.js.map +1 -0
  54. package/package.json +3 -3
  55. package/dist/src/client/caller_client.d.ts +0 -42
  56. package/dist/src/client/caller_client.d.ts.map +0 -1
  57. package/dist/src/client/caller_client.js +0 -77
  58. package/dist/src/client/caller_client.js.map +0 -1
  59. package/dist/src/client/client.d.ts +0 -149
  60. package/dist/src/client/client.d.ts.map +0 -1
  61. package/dist/src/client/client.js +0 -308
  62. package/dist/src/client/client.js.map +0 -1
  63. package/dist/src/client/index.d.ts.map +0 -1
  64. package/dist/src/client/sub_key.d.ts +0 -20
  65. package/dist/src/client/sub_key.d.ts.map +0 -1
  66. package/dist/src/client/sub_key.js +0 -37
  67. package/dist/src/client/sub_key.js.map +0 -1
  68. package/dist/src/client/ws_client_manager.d.ts +0 -268
  69. package/dist/src/client/ws_client_manager.d.ts.map +0 -1
  70. package/dist/src/client/ws_client_manager.js +0 -1013
  71. package/dist/src/client/ws_client_manager.js.map +0 -1
  72. package/dist/src/config.d.ts +0 -117
  73. package/dist/src/config.d.ts.map +0 -1
  74. package/dist/src/config.js +0 -2
  75. package/dist/src/config.js.map +0 -1
  76. package/dist/src/db/bytes.d.ts +0 -4
  77. package/dist/src/db/bytes.d.ts.map +0 -1
  78. package/dist/src/db/bytes.js +0 -24
  79. package/dist/src/db/bytes.js.map +0 -1
  80. package/dist/src/db/cache.d.ts +0 -30
  81. package/dist/src/db/cache.d.ts.map +0 -1
  82. package/dist/src/db/cache.js +0 -153
  83. package/dist/src/db/cache.js.map +0 -1
  84. package/dist/src/db/codec.d.ts +0 -10
  85. package/dist/src/db/codec.d.ts.map +0 -1
  86. package/dist/src/db/codec.js +0 -73
  87. package/dist/src/db/codec.js.map +0 -1
  88. package/dist/src/db/context.d.ts +0 -28
  89. package/dist/src/db/context.d.ts.map +0 -1
  90. package/dist/src/db/context.js +0 -147
  91. package/dist/src/db/context.js.map +0 -1
  92. package/dist/src/db/db.d.ts +0 -154
  93. package/dist/src/db/db.d.ts.map +0 -1
  94. package/dist/src/db/db.js +0 -23
  95. package/dist/src/db/db.js.map +0 -1
  96. package/dist/src/db/mysql.d.ts +0 -120
  97. package/dist/src/db/mysql.d.ts.map +0 -1
  98. package/dist/src/db/mysql.js +0 -708
  99. package/dist/src/db/mysql.js.map +0 -1
  100. package/dist/src/db/postgres.d.ts +0 -116
  101. package/dist/src/db/postgres.d.ts.map +0 -1
  102. package/dist/src/db/postgres.js +0 -998
  103. package/dist/src/db/postgres.js.map +0 -1
  104. package/dist/src/db/query.d.ts +0 -174
  105. package/dist/src/db/query.d.ts.map +0 -1
  106. package/dist/src/db/query.js +0 -790
  107. package/dist/src/db/query.js.map +0 -1
  108. package/dist/src/db/query_definition.d.ts +0 -54
  109. package/dist/src/db/query_definition.d.ts.map +0 -1
  110. package/dist/src/db/query_definition.js +0 -47
  111. package/dist/src/db/query_definition.js.map +0 -1
  112. package/dist/src/db/realtime_db.d.ts +0 -19
  113. package/dist/src/db/realtime_db.d.ts.map +0 -1
  114. package/dist/src/db/realtime_db.js +0 -98
  115. package/dist/src/db/realtime_db.js.map +0 -1
  116. package/dist/src/db/schema.d.ts +0 -110
  117. package/dist/src/db/schema.d.ts.map +0 -1
  118. package/dist/src/db/schema.js +0 -47
  119. package/dist/src/db/schema.js.map +0 -1
  120. package/dist/src/db/types_client_rpc.d.ts +0 -157
  121. package/dist/src/db/types_client_rpc.d.ts.map +0 -1
  122. package/dist/src/db/types_client_rpc.js +0 -106
  123. package/dist/src/db/types_client_rpc.js.map +0 -1
  124. package/dist/src/db/types_db.d.ts +0 -236
  125. package/dist/src/db/types_db.d.ts.map +0 -1
  126. package/dist/src/db/types_db.js +0 -188
  127. package/dist/src/db/types_db.js.map +0 -1
  128. package/dist/src/exports/mysql.d.ts.map +0 -1
  129. package/dist/src/exports/mysql.js.map +0 -1
  130. package/dist/src/exports/postgres.d.ts.map +0 -1
  131. package/dist/src/exports/postgres.js.map +0 -1
  132. package/dist/src/exports/procedure.d.ts.map +0 -1
  133. package/dist/src/exports/procedure.js.map +0 -1
  134. package/dist/src/exports/types.d.ts.map +0 -1
  135. package/dist/src/exports/types.js.map +0 -1
  136. package/dist/src/helper.d.ts +0 -11
  137. package/dist/src/helper.d.ts.map +0 -1
  138. package/dist/src/helper.js +0 -45
  139. package/dist/src/helper.js.map +0 -1
  140. package/dist/src/logger.d.ts +0 -7
  141. package/dist/src/logger.d.ts.map +0 -1
  142. package/dist/src/logger.js +0 -45
  143. package/dist/src/logger.js.map +0 -1
  144. package/dist/src/router/index.d.ts +0 -4
  145. package/dist/src/router/index.d.ts.map +0 -1
  146. package/dist/src/router/index.js +0 -4
  147. package/dist/src/router/index.js.map +0 -1
  148. package/dist/src/router/procedure.d.ts +0 -204
  149. package/dist/src/router/procedure.d.ts.map +0 -1
  150. package/dist/src/router/procedure.js +0 -122
  151. package/dist/src/router/procedure.js.map +0 -1
  152. package/dist/src/router/router.d.ts +0 -88
  153. package/dist/src/router/router.d.ts.map +0 -1
  154. package/dist/src/router/router.js +0 -88
  155. package/dist/src/router/router.js.map +0 -1
  156. package/dist/src/server/hash.d.ts +0 -12
  157. package/dist/src/server/hash.d.ts.map +0 -1
  158. package/dist/src/server/hash.js +0 -20
  159. package/dist/src/server/hash.js.map +0 -1
  160. package/dist/src/server/hash.test.d.ts +0 -2
  161. package/dist/src/server/hash.test.d.ts.map +0 -1
  162. package/dist/src/server/hash.test.js +0 -43
  163. package/dist/src/server/hash.test.js.map +0 -1
  164. package/dist/src/server/index.d.ts +0 -8
  165. package/dist/src/server/index.d.ts.map +0 -1
  166. package/dist/src/server/index.js +0 -7
  167. package/dist/src/server/index.js.map +0 -1
  168. package/dist/src/server/overlap-checker.d.ts +0 -18
  169. package/dist/src/server/overlap-checker.d.ts.map +0 -1
  170. package/dist/src/server/overlap-checker.js +0 -247
  171. package/dist/src/server/overlap-checker.js.map +0 -1
  172. package/dist/src/server/sub_hash.d.ts +0 -23
  173. package/dist/src/server/sub_hash.d.ts.map +0 -1
  174. package/dist/src/server/sub_hash.js +0 -38
  175. package/dist/src/server/sub_hash.js.map +0 -1
  176. package/dist/src/server/subscription-manager-client.d.ts +0 -120
  177. package/dist/src/server/subscription-manager-client.d.ts.map +0 -1
  178. package/dist/src/server/subscription-manager-client.js +0 -207
  179. package/dist/src/server/subscription-manager-client.js.map +0 -1
  180. package/dist/src/server/subscription-manager-types.d.ts +0 -168
  181. package/dist/src/server/subscription-manager-types.d.ts.map +0 -1
  182. package/dist/src/server/subscription-manager-types.js +0 -94
  183. package/dist/src/server/subscription-manager-types.js.map +0 -1
  184. package/dist/src/server/subscription-manager-worker-entry.d.ts +0 -2
  185. package/dist/src/server/subscription-manager-worker-entry.d.ts.map +0 -1
  186. package/dist/src/server/subscription-manager-worker-entry.js +0 -127
  187. package/dist/src/server/subscription-manager-worker-entry.js.map +0 -1
  188. package/dist/src/server/subscription-manager-worker-protocol.d.ts +0 -40
  189. package/dist/src/server/subscription-manager-worker-protocol.d.ts.map +0 -1
  190. package/dist/src/server/subscription-manager-worker-protocol.js +0 -20
  191. package/dist/src/server/subscription-manager-worker-protocol.js.map +0 -1
  192. package/dist/src/server/subscription-manager.d.ts +0 -32
  193. package/dist/src/server/subscription-manager.d.ts.map +0 -1
  194. package/dist/src/server/subscription-manager.js +0 -249
  195. package/dist/src/server/subscription-manager.js.map +0 -1
  196. package/dist/src/server/subscription-manager.test.d.ts +0 -2
  197. package/dist/src/server/subscription-manager.test.d.ts.map +0 -1
  198. package/dist/src/server/subscription-manager.test.js +0 -610
  199. package/dist/src/server/subscription-manager.test.js.map +0 -1
  200. package/dist/src/server/subscription-worker.d.ts +0 -32
  201. package/dist/src/server/subscription-worker.d.ts.map +0 -1
  202. package/dist/src/server/subscription-worker.js +0 -190
  203. package/dist/src/server/subscription-worker.js.map +0 -1
  204. package/dist/src/server/supalive-server.d.ts +0 -158
  205. package/dist/src/server/supalive-server.d.ts.map +0 -1
  206. package/dist/src/server/supalive-server.js +0 -1048
  207. package/dist/src/server/supalive-server.js.map +0 -1
  208. package/dist/src/server/types_server.d.ts +0 -96
  209. package/dist/src/server/types_server.d.ts.map +0 -1
  210. package/dist/src/server/types_server.js +0 -36
  211. package/dist/src/server/types_server.js.map +0 -1
  212. package/dist/tsconfig.tsbuildinfo +0 -1
@@ -1,790 +0,0 @@
1
- import { z } from "zod";
2
- import { normalizeCodec } from "./codec";
3
- import { bytesEqual } from "./bytes";
4
- // Escape and join a key path into a JSON path expression usable by both
5
- // PG (`jsonb_path_exists`) and MySQL (`JSON_CONTAINS_PATH`):
6
- // ["a", "b.c"] → `$."a"."b.c"`
7
- function jsonKeysToPath(keys) {
8
- let out = "$";
9
- for (const k of keys) {
10
- out += `."${k.replace(/"/g, '\\"')}"`;
11
- }
12
- return out;
13
- }
14
- // Resolve the `path` / `pathRaw` option pair down to a single jsonpath
15
- // expression. Returns a string in `$."..."` form for structured `path`,
16
- // or the caller's `pathRaw` verbatim. Returns `undefined` when neither is
17
- // set. Throws when both are supplied.
18
- function resolveJsonPath(opts) {
19
- if (opts?.path !== undefined && opts?.pathRaw !== undefined) {
20
- throw new Error("jsonContains: pass either `path` or `pathRaw`, not both");
21
- }
22
- if (opts?.pathRaw !== undefined)
23
- return opts.pathRaw;
24
- if (opts?.path !== undefined) {
25
- const arr = Array.isArray(opts.path) ? opts.path : [opts.path];
26
- return jsonKeysToPath(arr);
27
- }
28
- return undefined;
29
- }
30
- export class QueryBuilder {
31
- ctx;
32
- model;
33
- constructor(ctx, model) {
34
- this.ctx = ctx;
35
- this.model = model;
36
- }
37
- getColumnName(key) {
38
- return this.model.columns[key] ?? key;
39
- }
40
- select(...columns) {
41
- const selectedProps = (columns.length > 0 ? columns : Object.keys(this.model.columns));
42
- return new SelectQueryBuilder(this.ctx, this.model, [], null, null, null, selectedProps);
43
- }
44
- }
45
- class SelectQueryBuilder {
46
- ctx;
47
- model;
48
- filters = [];
49
- orderByClause = null;
50
- limitVal = null;
51
- offsetVal = null;
52
- selectedProps = [];
53
- computedFields = [];
54
- constructor(ctx, model, filters = [], orderByClause = null, limitVal = null, offsetVal = null, selectedProps = [], computedFields = []) {
55
- this.ctx = ctx;
56
- this.model = model;
57
- this.filters = filters;
58
- this.orderByClause = orderByClause;
59
- this.limitVal = limitVal;
60
- this.offsetVal = offsetVal;
61
- this.selectedProps = selectedProps;
62
- this.computedFields = computedFields;
63
- }
64
- getColumnName(key) {
65
- return this.model.columns[key] ?? key;
66
- }
67
- getPropertyName(column) {
68
- return this.model.reverseColumns[column] ?? this.computedFields.find(f => f.alias === column)?.field ?? column;
69
- }
70
- mapRowToSchema(row) {
71
- const result = {};
72
- for (const [col, value] of Object.entries(row)) {
73
- const prop = this.getPropertyName(col);
74
- const codec = this.model.codecs[prop] ?? this.computedFields.find((f) => f.alias === col)?.codec;
75
- result[prop] = codec?.decode && value !== undefined && value !== null ? codec.decode(value) : value;
76
- }
77
- return result;
78
- }
79
- where(filterFn) {
80
- this.filters.push(filterFn);
81
- return this;
82
- }
83
- orderBy(column, direction = "asc") {
84
- this.orderByClause = `${this.getColumnName(column)} ${direction}`;
85
- return this;
86
- }
87
- limit(n) {
88
- this.limitVal = n;
89
- return this;
90
- }
91
- offset(n) {
92
- this.offsetVal = n;
93
- return this;
94
- }
95
- computed(config) {
96
- const computed = {
97
- field: config.field,
98
- sql: config.sql,
99
- alias: config.alias ?? config.field,
100
- codec: normalizeCodec(config.codec, config.type),
101
- };
102
- this.computedFields.push(computed);
103
- return new SelectQueryBuilder(this.ctx, this.model, this.filters, this.orderByClause, this.limitVal, this.offsetVal, [...this.selectedProps, config.field], this.computedFields);
104
- }
105
- async first() {
106
- const rows = await this.limit(1).get();
107
- return (rows[0] ?? null);
108
- }
109
- async exists() {
110
- return await this.count() > 0;
111
- }
112
- static defaultCountType = z.coerce.number();
113
- async count(parser) {
114
- const predicate = this.buildPredicate() ?? { kind: "leaf", column: "1", operator: "=", value: "1" };
115
- const table = this.model.table;
116
- // Build query spec for read tracking (without totalTs)
117
- const querySpec = {
118
- kind: "range",
119
- table: table,
120
- tableId: this.model.tableId,
121
- predicate: predicate,
122
- columns: ["count"],
123
- orderBy: null,
124
- limit: null,
125
- offset: null,
126
- };
127
- // Execute query
128
- const params = [];
129
- const whereSql = buildPredicateSql(predicate, params, this.ctx.db.sqlBuilder);
130
- const sql = `SELECT COUNT(*) as count FROM ${table} WHERE (${whereSql})`;
131
- const result = await this.ctx.db.query(sql, params);
132
- const raw = result.rows[0]?.count ?? "0";
133
- // Record read with totalTs for OCC validation
134
- this.ctx.internalGetReadSet().push({
135
- ...querySpec,
136
- tableId: this.model.tableId,
137
- });
138
- if (parser) {
139
- return parser.parse(raw);
140
- }
141
- else {
142
- return SelectQueryBuilder.defaultCountType.parse(raw);
143
- }
144
- }
145
- async find(id) {
146
- if (this.filters.length > 0) {
147
- throw new Error(`Cannot use find() after where() - find() is meant for primary key lookups without additional filters`);
148
- }
149
- const table = this.model.table;
150
- const tableId = this.model.tableId;
151
- // Check pending writes first
152
- const pendingWrite = this.ctx.internalGetWriteSet().find(w => w.id === id && bytesEqual(w.tableId, tableId));
153
- if (pendingWrite) {
154
- if (pendingWrite.op === "delete") {
155
- // Don't cache pending writes, just return
156
- return null;
157
- }
158
- const row = this.mapRowToSchema({ ...pendingWrite.data, id, commit_ts: this.ctx.beginTs });
159
- return row;
160
- }
161
- const computedCols = this.computedFields.map(f => `${f.sql} as ${f.alias}`);
162
- const selectPropsWithComputed = this.selectedProps.slice(0, this.selectedProps.length - this.computedFields.length);
163
- const selectCols = selectPropsWithComputed.length > 0
164
- ? ["id", ...selectPropsWithComputed.map(p => this.getColumnName(p)), ...computedCols, "commit_ts"].join(", ")
165
- : "*";
166
- const sql = `SELECT ${selectCols} FROM ${table} WHERE id = ${this.ctx.db.sqlBuilder.placeholder(1)}`;
167
- const result = await this.ctx.db.query(sql, [id]);
168
- const row = result.rows[0] ?? null;
169
- // Capture commit_ts as the per-row OCC fingerprint. `null` means
170
- // "no row at read time" — a later concurrent insert with this id
171
- // becomes a conflict at commit time.
172
- const expectedCommitTs = row ? BigInt(row.commit_ts) : null;
173
- this.ctx.internalGetReadSet().push({
174
- kind: "point",
175
- table,
176
- tableId,
177
- id,
178
- expectedCommitTs,
179
- });
180
- const mappedRow = row ? this.mapRowToSchema(row) : null;
181
- return mappedRow;
182
- }
183
- async get() {
184
- const predicate = this.buildPredicate() ?? { kind: "leaf", column: "1", operator: "=", value: "1" };
185
- const table = this.model.table;
186
- const tableId = this.model.tableId;
187
- // Build query spec for read tracking (without totalTs)
188
- const querySpec = {
189
- kind: "range",
190
- table: table,
191
- tableId: tableId,
192
- predicate: predicate,
193
- columns: this.selectedProps,
194
- orderBy: this.orderByClause,
195
- limit: this.limitVal,
196
- offset: this.offsetVal,
197
- };
198
- // Execute query
199
- const params = [];
200
- const whereSql = buildPredicateSql(predicate, params, this.ctx.db.sqlBuilder);
201
- const computedCols = this.computedFields.map(f => `${f.sql} as ${f.alias}`);
202
- const selectPropsWithComputed = this.selectedProps.slice(0, this.selectedProps.length - this.computedFields.length);
203
- const selectColsNames = selectPropsWithComputed.length > 0
204
- ? ["id", ...selectPropsWithComputed.map(p => this.getColumnName(p)), ...computedCols, "commit_ts"]
205
- : "*";
206
- const selectCols = Array.from(new Set(selectColsNames).values()).join(", ");
207
- let sql = `SELECT ${selectCols} FROM ${table} WHERE (${whereSql})`;
208
- if (this.orderByClause) {
209
- sql += ` ORDER BY ${this.orderByClause}`;
210
- }
211
- if (this.limitVal !== null) {
212
- sql += ` LIMIT ${this.limitVal}`;
213
- }
214
- if (this.offsetVal !== null) {
215
- sql += ` OFFSET ${this.offsetVal}`;
216
- }
217
- const result = await this.ctx.db.query(sql, params);
218
- // Record read with totalTs for OCC validation
219
- this.ctx.internalGetReadSet().push({
220
- ...querySpec,
221
- tableId: tableId,
222
- });
223
- const rows = result.rows.map(row => this.mapRowToSchema(row));
224
- return rows;
225
- }
226
- buildPredicate() {
227
- if (this.filters.length === 0)
228
- return null;
229
- const predicates = [];
230
- const fb = new FilterBuilder(this.model.columns, this.model.codecs);
231
- for (const filterFn of this.filters) {
232
- filterFn(fb);
233
- if (fb.predicates.length > 0) {
234
- const hasOr = fb.predicates.some(p => p.kind === "or");
235
- if (hasOr) {
236
- predicates.push(...fb.predicates);
237
- }
238
- else {
239
- predicates.push({ kind: "and", children: fb.predicates });
240
- }
241
- fb.predicates = [];
242
- }
243
- }
244
- if (predicates.length === 0)
245
- return null;
246
- if (predicates.length === 1)
247
- return predicates[0] ?? null;
248
- return { kind: "and", children: predicates };
249
- }
250
- }
251
- // ─── Filter Builder ──────────────────────────────────────────────────────────────
252
- class FilterBuilder {
253
- columns;
254
- codecs;
255
- predicates = [];
256
- constructor(columns, codecs) {
257
- this.columns = columns;
258
- this.codecs = codecs;
259
- }
260
- add(column, operator, value, value2, raw) {
261
- const leaf = { kind: "leaf", column, operator: operator, value, value2 };
262
- if (raw)
263
- leaf.raw = true;
264
- this.predicates.push(leaf);
265
- }
266
- addJsonContains(operator, key, value, opts) {
267
- const col = this.getColumnName(key);
268
- const leaf = {
269
- kind: "leaf",
270
- column: col,
271
- operator: operator,
272
- value: this.encodeJson(value, opts?.raw),
273
- };
274
- if (opts?.raw)
275
- leaf.raw = true;
276
- const path = resolveJsonPath(opts);
277
- if (path !== undefined)
278
- leaf.path = path;
279
- this.predicates.push(leaf);
280
- }
281
- getColumnName(key) {
282
- return this.columns[key] ?? key;
283
- }
284
- encode(key, value) {
285
- const codec = this.codecs[key];
286
- if (!codec?.encode || value === undefined || value === null)
287
- return value;
288
- return codec.encode(value);
289
- }
290
- encodeJson(value, raw) {
291
- if (value === null || value === undefined)
292
- return value;
293
- // raw=true: caller has already produced the dialect-native form
294
- // (typically a JSON-encoded string or a path expression). Pass through.
295
- if (raw)
296
- return value;
297
- // Otherwise serialize the JS value to a JSON literal so the SQL
298
- // emitter can hand a single text param to the driver.
299
- return JSON.stringify(value);
300
- }
301
- eq(key, value) {
302
- this.add(this.getColumnName(key), "=", this.encode(key, value));
303
- return this;
304
- }
305
- neq(key, value) {
306
- this.add(this.getColumnName(key), "!=", this.encode(key, value));
307
- return this;
308
- }
309
- gt(key, value) {
310
- this.add(this.getColumnName(key), ">", this.encode(key, value));
311
- return this;
312
- }
313
- gte(key, value) {
314
- this.add(this.getColumnName(key), ">=", this.encode(key, value));
315
- return this;
316
- }
317
- lt(key, value) {
318
- this.add(this.getColumnName(key), "<", this.encode(key, value));
319
- return this;
320
- }
321
- lte(key, value) {
322
- this.add(this.getColumnName(key), "<=", this.encode(key, value));
323
- return this;
324
- }
325
- like(key, pattern) {
326
- this.add(this.getColumnName(key), "like", this.encode(key, pattern));
327
- return this;
328
- }
329
- ilike(key, pattern) {
330
- this.add(this.getColumnName(key), "ilike", this.encode(key, pattern));
331
- return this;
332
- }
333
- contains(key, value) {
334
- this.add(this.getColumnName(key), "contains", this.encode(key, value));
335
- return this;
336
- }
337
- icontains(key, value) {
338
- this.add(this.getColumnName(key), "icontains", this.encode(key, value));
339
- return this;
340
- }
341
- /**
342
- * JSON containment. Targets a JSON/JSONB column and asserts the document
343
- * contains the given candidate (object-key subset for objects,
344
- * element-subset for arrays — see dialect-specific docs for edge cases).
345
- * Pass `{ raw: true }` if `value` is already a JSON-encoded string.
346
- * Pass `{ path: ... }` to scope the check to a sub-document of the
347
- * target (jsonb_path_query_first on PG, JSON_CONTAINS path arg on MySQL).
348
- */
349
- jsonContains(key, value, opts) {
350
- this.addJsonContains("jsonContains", key, value, opts);
351
- return this;
352
- }
353
- /** Inverse of {@link jsonContains}: column is contained in the given value. */
354
- jsonContainedBy(key, value, opts) {
355
- this.addJsonContains("jsonContainedBy", key, value, opts);
356
- return this;
357
- }
358
- jsonHasKey(key, value, opts) {
359
- const col = this.getColumnName(key);
360
- // Multi-path mode: array of (string|string[]) entries with explicit mode.
361
- if (opts?.mode && Array.isArray(value)) {
362
- const paths = value.map((p) => opts.raw && typeof p === "string" ? p : jsonKeysToPath(Array.isArray(p) ? p : [p]));
363
- const leaf = {
364
- kind: "leaf",
365
- column: col,
366
- operator: "jsonHasKey",
367
- value: paths,
368
- mode: opts.mode,
369
- };
370
- this.predicates.push(leaf);
371
- return this;
372
- }
373
- // Single-key/path mode.
374
- if (Array.isArray(value)) {
375
- this.add(col, "jsonHasKey", jsonKeysToPath(value), undefined, true);
376
- }
377
- else if (opts?.raw) {
378
- this.add(col, "jsonHasKey", value, undefined, true);
379
- }
380
- else {
381
- this.add(col, "jsonHasKey", value, undefined, false);
382
- }
383
- return this;
384
- }
385
- /**
386
- * Array element containment: target column is a JSON array; the given
387
- * `value` (any JSON-encodable type) must appear as one of its elements.
388
- * Pass `{ raw: true }` if `value` is already a JSON-encoded string.
389
- * Pass `{ path: ... }` to scope the check to a sub-array of the column.
390
- */
391
- jsonArrayContains(key, value, opts) {
392
- this.addJsonContains("jsonArrayContains", key, value, opts);
393
- return this;
394
- }
395
- between(key, lo, hi) {
396
- this.add(this.getColumnName(key), "between", this.encode(key, lo), this.encode(key, hi));
397
- return this;
398
- }
399
- isNull(key) {
400
- this.add(this.getColumnName(key), "isNull", null);
401
- return this;
402
- }
403
- notNull(key) {
404
- this.add(this.getColumnName(key), "isNotNull", null);
405
- return this;
406
- }
407
- /**
408
- * Compose an OR clause. Each callback fills a fresh sub-FilterBuilder
409
- * with predicates that are AND-ed together within that branch; the
410
- * branches themselves are OR-ed. Example:
411
- * f.or([
412
- * b => b.eq("tag", "a"),
413
- * b => b.eq("tag", "b").gt("qty", 3), // (tag=b AND qty>3)
414
- * ])
415
- * produces (tag=a) OR (tag=b AND qty>3).
416
- */
417
- or(branches) {
418
- const children = [];
419
- for (const fn of branches) {
420
- const sub = new FilterBuilder(this.columns, this.codecs);
421
- fn(sub);
422
- if (sub.predicates.length === 1) {
423
- children.push(sub.predicates[0]);
424
- }
425
- else if (sub.predicates.length > 1) {
426
- children.push({ kind: "and", children: sub.predicates });
427
- }
428
- }
429
- if (children.length > 0) {
430
- this.predicates.push({ kind: "or", children });
431
- }
432
- return this;
433
- }
434
- }
435
- export function buildPredicateSql(predicate, params, sql) {
436
- switch (predicate.kind) {
437
- case "and": {
438
- const parts = predicate.children.map((c) => buildPredicateSql(c, params, sql));
439
- return `(${parts.join(" AND ")})`;
440
- }
441
- case "or": {
442
- const parts = predicate.children.map((c) => buildPredicateSql(c, params, sql));
443
- return `(${parts.join(" OR ")})`;
444
- }
445
- case "leaf": {
446
- return buildLeafSql(predicate, params, sql);
447
- }
448
- }
449
- }
450
- function buildLeafSql(leaf, params, sql) {
451
- const col = leaf.column;
452
- switch (leaf.operator) {
453
- case "=":
454
- case "!=":
455
- case ">":
456
- case ">=":
457
- case "<":
458
- case "<=": {
459
- params.push(leaf.value);
460
- return `${col} ${leaf.operator} ${sql.placeholder(params.length)}`;
461
- }
462
- case "between": {
463
- params.push(leaf.value);
464
- const p1 = sql.placeholder(params.length);
465
- params.push(leaf.value2);
466
- const p2 = sql.placeholder(params.length);
467
- return `${col} BETWEEN ${p1} AND ${p2}`;
468
- }
469
- case "like": {
470
- params.push(leaf.value);
471
- return `${col} LIKE ${sql.placeholder(params.length)}`;
472
- }
473
- case "ilike": {
474
- params.push(leaf.value);
475
- return sql.ilike(col, params.length);
476
- }
477
- case "contains": {
478
- params.push(`%${leaf.value}%`);
479
- return `${col} LIKE ${sql.placeholder(params.length)}`;
480
- }
481
- case "icontains": {
482
- params.push(`%${leaf.value}%`);
483
- return sql.icontains(col, params.length);
484
- }
485
- case "jsonContains": {
486
- params.push(leaf.value);
487
- const valIdx = params.length;
488
- let pathIndex;
489
- if (leaf.path !== undefined) {
490
- params.push(leaf.path);
491
- pathIndex = params.length;
492
- }
493
- return sql.jsonContains(col, valIdx, { raw: leaf.raw, pathIndex });
494
- }
495
- case "jsonContainedBy": {
496
- params.push(leaf.value);
497
- const valIdx = params.length;
498
- let pathIndex;
499
- if (leaf.path !== undefined) {
500
- params.push(leaf.path);
501
- pathIndex = params.length;
502
- }
503
- return sql.jsonContainedBy(col, valIdx, { raw: leaf.raw, pathIndex });
504
- }
505
- case "jsonHasKey": {
506
- if (leaf.mode && Array.isArray(leaf.value)) {
507
- const paths = leaf.value;
508
- const startIdx = params.length + 1;
509
- for (const p of paths)
510
- params.push(p);
511
- return sql.jsonHasKey(col, startIdx, {
512
- raw: true,
513
- mode: leaf.mode,
514
- pathCount: paths.length,
515
- });
516
- }
517
- params.push(leaf.value);
518
- return sql.jsonHasKey(col, params.length, { raw: leaf.raw });
519
- }
520
- case "jsonArrayContains": {
521
- params.push(leaf.value);
522
- const valIdx = params.length;
523
- let pathIndex;
524
- if (leaf.path !== undefined) {
525
- params.push(leaf.path);
526
- pathIndex = params.length;
527
- }
528
- return sql.jsonArrayContains(col, valIdx, { raw: leaf.raw, pathIndex });
529
- }
530
- case "isNull": return `${col} IS NULL`;
531
- case "isNotNull": return `${col} IS NOT NULL`;
532
- }
533
- }
534
- export function matchesPredicate(row, predicate) {
535
- switch (predicate.kind) {
536
- case "and": return predicate.children.every((c) => matchesPredicate(row, c));
537
- case "or": return predicate.children.some((c) => matchesPredicate(row, c));
538
- case "leaf": return matchesLeaf(row, predicate);
539
- }
540
- }
541
- function matchesLeaf(row, leaf) {
542
- const val = row[leaf.column];
543
- switch (leaf.operator) {
544
- case "=": return val === leaf.value;
545
- case "!=": return val !== leaf.value;
546
- case ">": return val > leaf.value;
547
- case ">=": return val >= leaf.value;
548
- case "<": return val < leaf.value;
549
- case "<=": return val <= leaf.value;
550
- case "between":
551
- return val >= leaf.value &&
552
- val <= leaf.value2;
553
- case "like":
554
- case "contains":
555
- case "ilike":
556
- case "icontains": {
557
- if (typeof val !== "string" || typeof leaf.value !== "string")
558
- return false;
559
- const haystack = (leaf.operator === "ilike" || leaf.operator === "icontains")
560
- ? val.toLowerCase()
561
- : val;
562
- const raw = leaf.value;
563
- const needle = (leaf.operator === "ilike" || leaf.operator === "icontains")
564
- ? raw.toLowerCase()
565
- : raw;
566
- const pattern = (leaf.operator === "contains" || leaf.operator === "icontains")
567
- ? `%${needle}%`
568
- : needle;
569
- const regex = new RegExp("^" + pattern.replace(/%/g, ".*").replace(/_/g, ".") + "$");
570
- return regex.test(haystack);
571
- }
572
- case "jsonContains": {
573
- const candidate = decodeJsonLeafValue(leaf);
574
- const target = leaf.path !== undefined ? jsonPathExtract(val, leaf.path) : val;
575
- if (target === undefined)
576
- return false;
577
- return jsonContains(target, candidate);
578
- }
579
- case "jsonContainedBy": {
580
- const candidate = decodeJsonLeafValue(leaf);
581
- const target = leaf.path !== undefined ? jsonPathExtract(val, leaf.path) : val;
582
- if (target === undefined)
583
- return false;
584
- return jsonContains(candidate, target);
585
- }
586
- case "jsonHasKey": {
587
- if (leaf.mode && Array.isArray(leaf.value)) {
588
- const paths = leaf.value;
589
- if (paths.length === 0)
590
- return false;
591
- return leaf.mode === "all"
592
- ? paths.every((p) => jsonPathExists(val, p))
593
- : paths.some((p) => jsonPathExists(val, p));
594
- }
595
- if (leaf.raw) {
596
- return jsonPathExists(val, leaf.value);
597
- }
598
- const key = leaf.value;
599
- if (val === null || typeof val !== "object")
600
- return false;
601
- if (Array.isArray(val))
602
- return val.includes(key);
603
- return key in val;
604
- }
605
- case "jsonArrayContains": {
606
- const candidate = decodeJsonLeafValue(leaf);
607
- const target = leaf.path !== undefined ? jsonPathExtract(val, leaf.path) : val;
608
- if (!Array.isArray(target))
609
- return false;
610
- return target.some((el) => jsonContains(el, candidate));
611
- }
612
- case "isNull":
613
- return val === null;
614
- case "isNotNull":
615
- return val !== null;
616
- default:
617
- return false;
618
- }
619
- }
620
- // When raw=true the leaf.value is a JSON-encoded string; decode for in-memory
621
- // comparison. Otherwise the value is the original JS value as passed.
622
- function decodeJsonLeafValue(leaf) {
623
- if (!leaf.raw)
624
- return leaf.value;
625
- if (typeof leaf.value !== "string")
626
- return leaf.value;
627
- try {
628
- return JSON.parse(leaf.value);
629
- }
630
- catch {
631
- return leaf.value;
632
- }
633
- }
634
- // PG `@>` / MySQL `JSON_CONTAINS` semantics. Recursive containment:
635
- // - scalars: strict equality
636
- // - object: every candidate key matches target's value recursively
637
- // - array: every candidate element is contained in some target element
638
- // - scalar candidate vs array target: candidate equals some element
639
- export function jsonContains(target, candidate) {
640
- if (candidate === target)
641
- return true;
642
- if (target === null || candidate === null)
643
- return target === candidate;
644
- if (target === undefined || candidate === undefined)
645
- return false;
646
- const tArr = Array.isArray(target);
647
- const cArr = Array.isArray(candidate);
648
- const tObj = !tArr && typeof target === "object";
649
- const cObj = !cArr && typeof candidate === "object";
650
- // Scalar candidate in array target.
651
- if (!cArr && !cObj && tArr) {
652
- return target.some((el) => jsonContains(el, candidate));
653
- }
654
- // Both arrays: every candidate element matches some target element.
655
- if (cArr && tArr) {
656
- const t = target;
657
- return candidate.every((c) => t.some((el) => jsonContains(el, c)));
658
- }
659
- // Both objects.
660
- if (cObj && tObj) {
661
- const t = target;
662
- const c = candidate;
663
- for (const k of Object.keys(c)) {
664
- if (!(k in t))
665
- return false;
666
- if (!jsonContains(t[k], c[k]))
667
- return false;
668
- }
669
- return true;
670
- }
671
- return false;
672
- }
673
- // Walk the JSON path and return the value at it, or `undefined` if any
674
- // segment is missing or unresolvable. Same syntax subset as
675
- // {@link jsonPathExists}.
676
- export function jsonPathExtract(val, path) {
677
- let p = path;
678
- if (p.startsWith("$"))
679
- p = p.slice(1);
680
- let cur = val;
681
- while (p.length > 0) {
682
- if (p.startsWith('."')) {
683
- let end = 2;
684
- while (end < p.length) {
685
- if (p[end] === "\\" && end + 1 < p.length) {
686
- end += 2;
687
- continue;
688
- }
689
- if (p[end] === '"')
690
- break;
691
- end++;
692
- }
693
- if (end >= p.length)
694
- return undefined;
695
- const key = p.slice(2, end).replace(/\\"/g, '"').replace(/\\\\/g, "\\");
696
- if (cur === null || typeof cur !== "object" || Array.isArray(cur))
697
- return undefined;
698
- if (!(key in cur))
699
- return undefined;
700
- cur = cur[key];
701
- p = p.slice(end + 1);
702
- }
703
- else if (p.startsWith(".")) {
704
- const m = p.slice(1).match(/^([^.[]+)/);
705
- if (!m)
706
- return undefined;
707
- const key = m[1];
708
- if (cur === null || typeof cur !== "object" || Array.isArray(cur))
709
- return undefined;
710
- if (!(key in cur))
711
- return undefined;
712
- cur = cur[key];
713
- p = p.slice(1 + key.length);
714
- }
715
- else if (p.startsWith("[")) {
716
- const end = p.indexOf("]");
717
- if (end === -1)
718
- return undefined;
719
- const idx = parseInt(p.slice(1, end), 10);
720
- if (!Array.isArray(cur) || idx < 0 || idx >= cur.length)
721
- return undefined;
722
- cur = cur[idx];
723
- p = p.slice(end + 1);
724
- }
725
- else {
726
- return undefined;
727
- }
728
- }
729
- return cur;
730
- }
731
- // Best-effort JSON path evaluator for the in-memory predicate match.
732
- // Supports the path syntax this module emits (`$."key"."nested"`) plus the
733
- // common shorthands `$.key` and `$.key[0]`. Anything more exotic (filters,
734
- // wildcards) returns false — DB will still evaluate it correctly server-side.
735
- export function jsonPathExists(val, path) {
736
- let p = path;
737
- if (p.startsWith("$"))
738
- p = p.slice(1);
739
- let cur = val;
740
- while (p.length > 0) {
741
- if (p.startsWith('."')) {
742
- let end = 2;
743
- while (end < p.length) {
744
- if (p[end] === "\\" && end + 1 < p.length) {
745
- end += 2;
746
- continue;
747
- }
748
- if (p[end] === '"')
749
- break;
750
- end++;
751
- }
752
- if (end >= p.length)
753
- return false;
754
- const key = p.slice(2, end).replace(/\\"/g, '"').replace(/\\\\/g, "\\");
755
- if (cur === null || typeof cur !== "object" || Array.isArray(cur))
756
- return false;
757
- if (!(key in cur))
758
- return false;
759
- cur = cur[key];
760
- p = p.slice(end + 1);
761
- }
762
- else if (p.startsWith(".")) {
763
- const m = p.slice(1).match(/^([^.[]+)/);
764
- if (!m)
765
- return false;
766
- const key = m[1];
767
- if (cur === null || typeof cur !== "object" || Array.isArray(cur))
768
- return false;
769
- if (!(key in cur))
770
- return false;
771
- cur = cur[key];
772
- p = p.slice(1 + key.length);
773
- }
774
- else if (p.startsWith("[")) {
775
- const end = p.indexOf("]");
776
- if (end === -1)
777
- return false;
778
- const idx = parseInt(p.slice(1, end), 10);
779
- if (!Array.isArray(cur) || idx < 0 || idx >= cur.length)
780
- return false;
781
- cur = cur[idx];
782
- p = p.slice(end + 1);
783
- }
784
- else {
785
- return false;
786
- }
787
- }
788
- return cur !== undefined;
789
- }
790
- //# sourceMappingURL=query.js.map