@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.
- package/README.md +308 -1
- package/dist/helper-CiacMqje.d.ts +14 -0
- package/dist/helper-CiacMqje.d.ts.map +1 -0
- package/dist/helper-zdJT5FUc.js +58 -0
- package/dist/helper-zdJT5FUc.js.map +1 -0
- package/dist/index-CTyXKcq_.d.ts +1736 -0
- package/dist/index-CTyXKcq_.d.ts.map +1 -0
- package/dist/mysql-C8SN443m.d.ts +104 -0
- package/dist/mysql-C8SN443m.d.ts.map +1 -0
- package/dist/mysql-DELSRrpY.js +569 -0
- package/dist/mysql-DELSRrpY.js.map +1 -0
- package/dist/overlap-checker-CVkbbuEQ.js +187 -0
- package/dist/overlap-checker-CVkbbuEQ.js.map +1 -0
- package/dist/postgres-DE-XyNAQ.js +823 -0
- package/dist/postgres-DE-XyNAQ.js.map +1 -0
- package/dist/postgres-D_gwCHx-.d.ts +108 -0
- package/dist/postgres-D_gwCHx-.d.ts.map +1 -0
- package/dist/query-CbQLjeME.js +713 -0
- package/dist/query-CbQLjeME.js.map +1 -0
- package/dist/realtime_db-D9p2X2PH.js +246 -0
- package/dist/realtime_db-D9p2X2PH.js.map +1 -0
- package/dist/router-DlTYWpop.js +81 -0
- package/dist/router-DlTYWpop.js.map +1 -0
- package/dist/schema-SKcIWHfo.js +167 -0
- package/dist/schema-SKcIWHfo.js.map +1 -0
- package/dist/src/client/index.d.ts +2 -17
- package/dist/src/client/index.js +1151 -18
- package/dist/src/client/index.js.map +1 -1
- package/dist/src/exports/mysql.d.ts +2 -2
- package/dist/src/exports/mysql.js +2 -2
- package/dist/src/exports/postgres.d.ts +2 -2
- package/dist/src/exports/postgres.js +2 -2
- package/dist/src/exports/procedure.d.ts +3 -5
- package/dist/src/exports/procedure.js +5 -5
- package/dist/src/exports/server.d.ts +368 -2
- package/dist/src/exports/server.d.ts.map +1 -1
- package/dist/src/exports/server.js +1346 -2
- package/dist/src/exports/server.js.map +1 -1
- package/dist/src/exports/subscription-manager-worker-entry.d.ts +1 -0
- package/dist/src/exports/subscription-manager-worker-entry.js +115 -0
- package/dist/src/exports/subscription-manager-worker-entry.js.map +1 -0
- package/dist/src/exports/types.d.ts +4 -6
- package/dist/src/exports/types.js +5 -6
- package/dist/subscription-worker-BurdPO8d.js +511 -0
- package/dist/subscription-worker-BurdPO8d.js.map +1 -0
- package/dist/types_client_rpc-CIsjQQGG.js +105 -0
- package/dist/types_client_rpc-CIsjQQGG.js.map +1 -0
- package/dist/types_db-Ctf-ksoy.js +168 -0
- package/dist/types_db-Ctf-ksoy.js.map +1 -0
- package/dist/types_server-Ch56y8L3.d.ts +216 -0
- package/dist/types_server-Ch56y8L3.d.ts.map +1 -0
- package/dist/types_server-Dk6o_B7N.js +24 -0
- package/dist/types_server-Dk6o_B7N.js.map +1 -0
- package/package.json +3 -3
- package/dist/src/client/caller_client.d.ts +0 -42
- package/dist/src/client/caller_client.d.ts.map +0 -1
- package/dist/src/client/caller_client.js +0 -77
- package/dist/src/client/caller_client.js.map +0 -1
- package/dist/src/client/client.d.ts +0 -149
- package/dist/src/client/client.d.ts.map +0 -1
- package/dist/src/client/client.js +0 -308
- package/dist/src/client/client.js.map +0 -1
- package/dist/src/client/index.d.ts.map +0 -1
- package/dist/src/client/sub_key.d.ts +0 -20
- package/dist/src/client/sub_key.d.ts.map +0 -1
- package/dist/src/client/sub_key.js +0 -37
- package/dist/src/client/sub_key.js.map +0 -1
- package/dist/src/client/ws_client_manager.d.ts +0 -268
- package/dist/src/client/ws_client_manager.d.ts.map +0 -1
- package/dist/src/client/ws_client_manager.js +0 -1013
- package/dist/src/client/ws_client_manager.js.map +0 -1
- package/dist/src/config.d.ts +0 -117
- package/dist/src/config.d.ts.map +0 -1
- package/dist/src/config.js +0 -2
- package/dist/src/config.js.map +0 -1
- package/dist/src/db/bytes.d.ts +0 -4
- package/dist/src/db/bytes.d.ts.map +0 -1
- package/dist/src/db/bytes.js +0 -24
- package/dist/src/db/bytes.js.map +0 -1
- package/dist/src/db/cache.d.ts +0 -30
- package/dist/src/db/cache.d.ts.map +0 -1
- package/dist/src/db/cache.js +0 -153
- package/dist/src/db/cache.js.map +0 -1
- package/dist/src/db/codec.d.ts +0 -10
- package/dist/src/db/codec.d.ts.map +0 -1
- package/dist/src/db/codec.js +0 -73
- package/dist/src/db/codec.js.map +0 -1
- package/dist/src/db/context.d.ts +0 -28
- package/dist/src/db/context.d.ts.map +0 -1
- package/dist/src/db/context.js +0 -147
- package/dist/src/db/context.js.map +0 -1
- package/dist/src/db/db.d.ts +0 -154
- package/dist/src/db/db.d.ts.map +0 -1
- package/dist/src/db/db.js +0 -23
- package/dist/src/db/db.js.map +0 -1
- package/dist/src/db/mysql.d.ts +0 -120
- package/dist/src/db/mysql.d.ts.map +0 -1
- package/dist/src/db/mysql.js +0 -708
- package/dist/src/db/mysql.js.map +0 -1
- package/dist/src/db/postgres.d.ts +0 -116
- package/dist/src/db/postgres.d.ts.map +0 -1
- package/dist/src/db/postgres.js +0 -998
- package/dist/src/db/postgres.js.map +0 -1
- package/dist/src/db/query.d.ts +0 -174
- package/dist/src/db/query.d.ts.map +0 -1
- package/dist/src/db/query.js +0 -790
- package/dist/src/db/query.js.map +0 -1
- package/dist/src/db/query_definition.d.ts +0 -54
- package/dist/src/db/query_definition.d.ts.map +0 -1
- package/dist/src/db/query_definition.js +0 -47
- package/dist/src/db/query_definition.js.map +0 -1
- package/dist/src/db/realtime_db.d.ts +0 -19
- package/dist/src/db/realtime_db.d.ts.map +0 -1
- package/dist/src/db/realtime_db.js +0 -98
- package/dist/src/db/realtime_db.js.map +0 -1
- package/dist/src/db/schema.d.ts +0 -110
- package/dist/src/db/schema.d.ts.map +0 -1
- package/dist/src/db/schema.js +0 -47
- package/dist/src/db/schema.js.map +0 -1
- package/dist/src/db/types_client_rpc.d.ts +0 -157
- package/dist/src/db/types_client_rpc.d.ts.map +0 -1
- package/dist/src/db/types_client_rpc.js +0 -106
- package/dist/src/db/types_client_rpc.js.map +0 -1
- package/dist/src/db/types_db.d.ts +0 -236
- package/dist/src/db/types_db.d.ts.map +0 -1
- package/dist/src/db/types_db.js +0 -188
- package/dist/src/db/types_db.js.map +0 -1
- package/dist/src/exports/mysql.d.ts.map +0 -1
- package/dist/src/exports/mysql.js.map +0 -1
- package/dist/src/exports/postgres.d.ts.map +0 -1
- package/dist/src/exports/postgres.js.map +0 -1
- package/dist/src/exports/procedure.d.ts.map +0 -1
- package/dist/src/exports/procedure.js.map +0 -1
- package/dist/src/exports/types.d.ts.map +0 -1
- package/dist/src/exports/types.js.map +0 -1
- package/dist/src/helper.d.ts +0 -11
- package/dist/src/helper.d.ts.map +0 -1
- package/dist/src/helper.js +0 -45
- package/dist/src/helper.js.map +0 -1
- package/dist/src/logger.d.ts +0 -7
- package/dist/src/logger.d.ts.map +0 -1
- package/dist/src/logger.js +0 -45
- package/dist/src/logger.js.map +0 -1
- package/dist/src/router/index.d.ts +0 -4
- package/dist/src/router/index.d.ts.map +0 -1
- package/dist/src/router/index.js +0 -4
- package/dist/src/router/index.js.map +0 -1
- package/dist/src/router/procedure.d.ts +0 -204
- package/dist/src/router/procedure.d.ts.map +0 -1
- package/dist/src/router/procedure.js +0 -122
- package/dist/src/router/procedure.js.map +0 -1
- package/dist/src/router/router.d.ts +0 -88
- package/dist/src/router/router.d.ts.map +0 -1
- package/dist/src/router/router.js +0 -88
- package/dist/src/router/router.js.map +0 -1
- package/dist/src/server/hash.d.ts +0 -12
- package/dist/src/server/hash.d.ts.map +0 -1
- package/dist/src/server/hash.js +0 -20
- package/dist/src/server/hash.js.map +0 -1
- package/dist/src/server/hash.test.d.ts +0 -2
- package/dist/src/server/hash.test.d.ts.map +0 -1
- package/dist/src/server/hash.test.js +0 -43
- package/dist/src/server/hash.test.js.map +0 -1
- package/dist/src/server/index.d.ts +0 -8
- package/dist/src/server/index.d.ts.map +0 -1
- package/dist/src/server/index.js +0 -7
- package/dist/src/server/index.js.map +0 -1
- package/dist/src/server/overlap-checker.d.ts +0 -18
- package/dist/src/server/overlap-checker.d.ts.map +0 -1
- package/dist/src/server/overlap-checker.js +0 -247
- package/dist/src/server/overlap-checker.js.map +0 -1
- package/dist/src/server/sub_hash.d.ts +0 -23
- package/dist/src/server/sub_hash.d.ts.map +0 -1
- package/dist/src/server/sub_hash.js +0 -38
- package/dist/src/server/sub_hash.js.map +0 -1
- package/dist/src/server/subscription-manager-client.d.ts +0 -120
- package/dist/src/server/subscription-manager-client.d.ts.map +0 -1
- package/dist/src/server/subscription-manager-client.js +0 -207
- package/dist/src/server/subscription-manager-client.js.map +0 -1
- package/dist/src/server/subscription-manager-types.d.ts +0 -168
- package/dist/src/server/subscription-manager-types.d.ts.map +0 -1
- package/dist/src/server/subscription-manager-types.js +0 -94
- package/dist/src/server/subscription-manager-types.js.map +0 -1
- package/dist/src/server/subscription-manager-worker-entry.d.ts +0 -2
- package/dist/src/server/subscription-manager-worker-entry.d.ts.map +0 -1
- package/dist/src/server/subscription-manager-worker-entry.js +0 -127
- package/dist/src/server/subscription-manager-worker-entry.js.map +0 -1
- package/dist/src/server/subscription-manager-worker-protocol.d.ts +0 -40
- package/dist/src/server/subscription-manager-worker-protocol.d.ts.map +0 -1
- package/dist/src/server/subscription-manager-worker-protocol.js +0 -20
- package/dist/src/server/subscription-manager-worker-protocol.js.map +0 -1
- package/dist/src/server/subscription-manager.d.ts +0 -32
- package/dist/src/server/subscription-manager.d.ts.map +0 -1
- package/dist/src/server/subscription-manager.js +0 -249
- package/dist/src/server/subscription-manager.js.map +0 -1
- package/dist/src/server/subscription-manager.test.d.ts +0 -2
- package/dist/src/server/subscription-manager.test.d.ts.map +0 -1
- package/dist/src/server/subscription-manager.test.js +0 -610
- package/dist/src/server/subscription-manager.test.js.map +0 -1
- package/dist/src/server/subscription-worker.d.ts +0 -32
- package/dist/src/server/subscription-worker.d.ts.map +0 -1
- package/dist/src/server/subscription-worker.js +0 -190
- package/dist/src/server/subscription-worker.js.map +0 -1
- package/dist/src/server/supalive-server.d.ts +0 -158
- package/dist/src/server/supalive-server.d.ts.map +0 -1
- package/dist/src/server/supalive-server.js +0 -1048
- package/dist/src/server/supalive-server.js.map +0 -1
- package/dist/src/server/types_server.d.ts +0 -96
- package/dist/src/server/types_server.d.ts.map +0 -1
- package/dist/src/server/types_server.js +0 -36
- package/dist/src/server/types_server.js.map +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
package/dist/src/db/query.js
DELETED
|
@@ -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
|