bun-sqlite-for-rxdb 1.1.3 → 1.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.
package/dist/index.js CHANGED
@@ -10341,80 +10341,6 @@ var require_dist = __commonJS((exports) => {
10341
10341
  exports.default = PQueue;
10342
10342
  });
10343
10343
 
10344
- // node_modules/fast-stable-stringify/index.js
10345
- var require_fast_stable_stringify = __commonJS((exports, module) => {
10346
- var objToString = Object.prototype.toString;
10347
- var objKeys = Object.keys || function(obj) {
10348
- var keys = [];
10349
- for (var name in obj) {
10350
- keys.push(name);
10351
- }
10352
- return keys;
10353
- };
10354
- function stringify(val, isArrayProp) {
10355
- var i, max, str, keys, key, propVal, toStr;
10356
- if (val === true) {
10357
- return "true";
10358
- }
10359
- if (val === false) {
10360
- return "false";
10361
- }
10362
- switch (typeof val) {
10363
- case "object":
10364
- if (val === null) {
10365
- return null;
10366
- } else if (val.toJSON && typeof val.toJSON === "function") {
10367
- return stringify(val.toJSON(), isArrayProp);
10368
- } else {
10369
- toStr = objToString.call(val);
10370
- if (toStr === "[object Array]") {
10371
- str = "[";
10372
- max = val.length - 1;
10373
- for (i = 0;i < max; i++) {
10374
- str += stringify(val[i], true) + ",";
10375
- }
10376
- if (max > -1) {
10377
- str += stringify(val[i], true);
10378
- }
10379
- return str + "]";
10380
- } else if (toStr === "[object Object]") {
10381
- keys = objKeys(val).sort();
10382
- max = keys.length;
10383
- str = "";
10384
- i = 0;
10385
- while (i < max) {
10386
- key = keys[i];
10387
- propVal = stringify(val[key], false);
10388
- if (propVal !== undefined) {
10389
- if (str) {
10390
- str += ",";
10391
- }
10392
- str += JSON.stringify(key) + ":" + propVal;
10393
- }
10394
- i++;
10395
- }
10396
- return "{" + str + "}";
10397
- } else {
10398
- return JSON.stringify(val);
10399
- }
10400
- }
10401
- case "function":
10402
- case "undefined":
10403
- return isArrayProp ? null : undefined;
10404
- case "string":
10405
- return JSON.stringify(val);
10406
- default:
10407
- return isFinite(val) ? val : null;
10408
- }
10409
- }
10410
- module.exports = function(val) {
10411
- var returnVal = stringify(val, false);
10412
- if (returnVal !== undefined) {
10413
- return "" + returnVal;
10414
- }
10415
- };
10416
- });
10417
-
10418
10344
  // node_modules/rxdb/dist/esm/rx-storage-multiinstance.js
10419
10345
  var import_rxjs = __toESM(require_cjs(), 1);
10420
10346
  var import_operators = __toESM(require_operators(), 1);
@@ -12284,6 +12210,36 @@ function addRxStorageMultiInstanceSupport(storageName, instanceCreationParams, i
12284
12210
  // src/instance.ts
12285
12211
  var import_rxjs2 = __toESM(require_cjs(), 1);
12286
12212
 
12213
+ // src/query/regex-matcher.ts
12214
+ var REGEX_CACHE = new Map;
12215
+ var MAX_REGEX_CACHE_SIZE = 100;
12216
+ function compileRegex(pattern, options) {
12217
+ const cacheKey = `${pattern}::${options || ""}`;
12218
+ const cached = REGEX_CACHE.get(cacheKey);
12219
+ if (cached) {
12220
+ return cached.regex;
12221
+ }
12222
+ const regex = new RegExp(pattern, options);
12223
+ if (REGEX_CACHE.size >= MAX_REGEX_CACHE_SIZE) {
12224
+ const firstKey = REGEX_CACHE.keys().next().value;
12225
+ if (firstKey) {
12226
+ REGEX_CACHE.delete(firstKey);
12227
+ }
12228
+ }
12229
+ REGEX_CACHE.set(cacheKey, { regex });
12230
+ return regex;
12231
+ }
12232
+ function matchesRegex(value, pattern, options) {
12233
+ const regex = compileRegex(pattern, options);
12234
+ if (typeof value === "string") {
12235
+ return regex.test(value);
12236
+ }
12237
+ if (Array.isArray(value)) {
12238
+ return value.some((v) => typeof v === "string" && regex.test(v));
12239
+ }
12240
+ return false;
12241
+ }
12242
+
12287
12243
  // src/query/schema-mapper.ts
12288
12244
  function getColumnInfo(path2, schema) {
12289
12245
  if (path2 === "_deleted") {
@@ -12298,71 +12254,180 @@ function getColumnInfo(path2, schema) {
12298
12254
  if (path2 === schema.primaryKey) {
12299
12255
  return { column: "id", type: "string" };
12300
12256
  }
12257
+ const properties = schema.properties;
12258
+ const fieldSchema = properties?.[path2];
12259
+ if (fieldSchema && typeof fieldSchema === "object" && "type" in fieldSchema) {
12260
+ if (fieldSchema.type === "array") {
12261
+ return { jsonPath: `$.${path2}`, type: "array" };
12262
+ }
12263
+ }
12301
12264
  return { jsonPath: `$.${path2}`, type: "unknown" };
12302
12265
  }
12303
12266
 
12304
12267
  // src/query/smart-regex.ts
12305
- function smartRegexToLike(field, pattern, options) {
12306
- const caseInsensitive = options?.includes("i");
12268
+ var INDEX_CACHE = new Map;
12269
+ var MAX_INDEX_CACHE_SIZE = 1000;
12270
+ function hasExpressionIndex(fieldName, schema) {
12271
+ const indexKey = schema.indexes ? JSON.stringify(schema.indexes) : "none";
12272
+ const cacheKey = `${schema.version}_${fieldName}_${indexKey}`;
12273
+ const cached = INDEX_CACHE.get(cacheKey);
12274
+ if (cached !== undefined) {
12275
+ INDEX_CACHE.delete(cacheKey);
12276
+ INDEX_CACHE.set(cacheKey, cached);
12277
+ return cached;
12278
+ }
12279
+ if (!schema.indexes) {
12280
+ if (INDEX_CACHE.size >= MAX_INDEX_CACHE_SIZE) {
12281
+ const firstKey = INDEX_CACHE.keys().next().value;
12282
+ if (firstKey)
12283
+ INDEX_CACHE.delete(firstKey);
12284
+ }
12285
+ INDEX_CACHE.set(cacheKey, false);
12286
+ return false;
12287
+ }
12288
+ const hasLowerIndex = schema.indexes.some((idx) => {
12289
+ const fields = Array.isArray(idx) ? idx : [idx];
12290
+ return fields.some((f) => {
12291
+ if (typeof f !== "string")
12292
+ return false;
12293
+ const normalized = f.toLowerCase().replace(/\s/g, "");
12294
+ return normalized === `lower(${fieldName})`;
12295
+ });
12296
+ });
12297
+ if (INDEX_CACHE.size >= MAX_INDEX_CACHE_SIZE) {
12298
+ const firstKey = INDEX_CACHE.keys().next().value;
12299
+ if (firstKey)
12300
+ INDEX_CACHE.delete(firstKey);
12301
+ }
12302
+ INDEX_CACHE.set(cacheKey, hasLowerIndex);
12303
+ return hasLowerIndex;
12304
+ }
12305
+ function isComplexRegex(pattern) {
12306
+ return /[*+?()[\]{}|]/.test(pattern.replace(/\\\./g, ""));
12307
+ }
12308
+ function escapeForLike(str) {
12309
+ return str.replace(/[\\%_]/g, "\\$&");
12310
+ }
12311
+ function smartRegexToLike(field, pattern, options, schema, fieldName) {
12312
+ if (typeof pattern !== "string")
12313
+ return null;
12314
+ const caseInsensitive = options?.includes("i") ?? false;
12315
+ const hasLowerIndex = hasExpressionIndex(fieldName, schema);
12307
12316
  const startsWithAnchor = pattern.startsWith("^");
12308
12317
  const endsWithAnchor = pattern.endsWith("$");
12309
12318
  let cleanPattern = pattern.replace(/^\^/, "").replace(/\$$/, "");
12310
- if (startsWithAnchor && endsWithAnchor && !/[*+?()[\]{}|]/.test(cleanPattern)) {
12311
- const exact = cleanPattern.replace(/\\\./g, ".");
12319
+ if (isComplexRegex(cleanPattern)) {
12320
+ return null;
12321
+ }
12322
+ const unescaped = cleanPattern.replace(/\\\./g, ".");
12323
+ const escaped = escapeForLike(unescaped);
12324
+ if (startsWithAnchor && endsWithAnchor) {
12312
12325
  if (caseInsensitive) {
12313
- const escaped = exact.replace(/%/g, "\\%").replace(/_/g, "\\_");
12314
- return { sql: `${field} LIKE ? COLLATE NOCASE ESCAPE '\\'`, args: [escaped] };
12326
+ return hasLowerIndex ? { sql: `LOWER(${field}) = ?`, args: [unescaped.toLowerCase()] } : { sql: `${field} = ? COLLATE NOCASE`, args: [unescaped] };
12315
12327
  }
12316
- return { sql: `${field} = ?`, args: [exact] };
12328
+ return { sql: `${field} = ?`, args: [unescaped] };
12317
12329
  }
12318
12330
  if (startsWithAnchor) {
12319
- const prefix = cleanPattern.replace(/\\\./g, ".");
12320
- if (!/[*+?()[\]{}|]/.test(prefix)) {
12321
- const escaped = prefix.replace(/%/g, "\\%").replace(/_/g, "\\_");
12322
- return caseInsensitive ? { sql: `${field} LIKE ? COLLATE NOCASE ESCAPE '\\'`, args: [escaped + "%"] } : { sql: `${field} LIKE ? ESCAPE '\\'`, args: [escaped + "%"] };
12331
+ const suffix = caseInsensitive ? escaped.toLowerCase() : escaped;
12332
+ if (caseInsensitive && hasLowerIndex) {
12333
+ return { sql: `LOWER(${field}) LIKE ? ESCAPE '\\'`, args: [suffix + "%"] };
12323
12334
  }
12335
+ return { sql: `${field} LIKE ?${caseInsensitive ? " COLLATE NOCASE" : ""} ESCAPE '\\'`, args: [suffix + "%"] };
12324
12336
  }
12325
12337
  if (endsWithAnchor) {
12326
- const suffix = cleanPattern.replace(/\\\./g, ".");
12327
- if (!/[*+?()[\]{}|]/.test(suffix)) {
12328
- const escaped = suffix.replace(/%/g, "\\%").replace(/_/g, "\\_");
12329
- return caseInsensitive ? { sql: `${field} LIKE ? COLLATE NOCASE ESCAPE '\\'`, args: ["%" + escaped] } : { sql: `${field} LIKE ? ESCAPE '\\'`, args: ["%" + escaped] };
12338
+ const prefix = caseInsensitive ? escaped.toLowerCase() : escaped;
12339
+ if (caseInsensitive && hasLowerIndex) {
12340
+ return { sql: `LOWER(${field}) LIKE ? ESCAPE '\\'`, args: ["%" + prefix] };
12330
12341
  }
12342
+ return { sql: `${field} LIKE ?${caseInsensitive ? " COLLATE NOCASE" : ""} ESCAPE '\\'`, args: ["%" + prefix] };
12331
12343
  }
12332
- cleanPattern = cleanPattern.replace(/\\\./g, ".");
12333
- if (!/[*+?()[\]{}|^$]/.test(cleanPattern)) {
12334
- const escaped = cleanPattern.replace(/%/g, "\\%").replace(/_/g, "\\_");
12335
- return caseInsensitive ? { sql: `${field} LIKE ? COLLATE NOCASE ESCAPE '\\'`, args: ["%" + escaped + "%"] } : { sql: `${field} LIKE ? ESCAPE '\\'`, args: ["%" + escaped + "%"] };
12344
+ const middle = caseInsensitive ? escaped.toLowerCase() : escaped;
12345
+ if (caseInsensitive && hasLowerIndex) {
12346
+ return { sql: `LOWER(${field}) LIKE ? ESCAPE '\\'`, args: ["%" + middle + "%"] };
12336
12347
  }
12337
- return null;
12348
+ return { sql: `${field} LIKE ?${caseInsensitive ? " COLLATE NOCASE" : ""} ESCAPE '\\'`, args: ["%" + middle + "%"] };
12338
12349
  }
12339
12350
 
12340
12351
  // src/query/operators.ts
12341
- function translateEq(field, value) {
12352
+ function translateEq(field, value, schema, actualFieldName) {
12342
12353
  if (value === null) {
12343
12354
  return { sql: `${field} IS NULL`, args: [] };
12344
12355
  }
12356
+ if (schema && actualFieldName) {
12357
+ const columnInfo = getColumnInfo(actualFieldName, schema);
12358
+ if (field !== "value" && columnInfo.type === "array") {
12359
+ return {
12360
+ sql: `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE value = ?)`,
12361
+ args: [value]
12362
+ };
12363
+ }
12364
+ }
12345
12365
  return { sql: `${field} = ?`, args: [value] };
12346
12366
  }
12347
- function translateNe(field, value) {
12367
+ function translateNe(field, value, schema, actualFieldName) {
12348
12368
  if (value === null) {
12349
12369
  return { sql: `${field} IS NOT NULL`, args: [] };
12350
12370
  }
12351
- return { sql: `${field} <> ?`, args: [value] };
12371
+ if (schema && actualFieldName) {
12372
+ const columnInfo = getColumnInfo(actualFieldName, schema);
12373
+ if (field !== "value" && columnInfo.type === "array") {
12374
+ return {
12375
+ sql: `NOT EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE value = ?)`,
12376
+ args: [value]
12377
+ };
12378
+ }
12379
+ }
12380
+ return { sql: `(${field} <> ? OR ${field} IS NULL)`, args: [value] };
12352
12381
  }
12353
- function translateGt(field, value) {
12382
+ function translateGt(field, value, schema, actualFieldName) {
12383
+ if (schema && actualFieldName) {
12384
+ const columnInfo = getColumnInfo(actualFieldName, schema);
12385
+ if (field !== "value" && columnInfo.type === "array") {
12386
+ return {
12387
+ sql: `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE value > ?)`,
12388
+ args: [value]
12389
+ };
12390
+ }
12391
+ }
12354
12392
  return { sql: `${field} > ?`, args: [value] };
12355
12393
  }
12356
- function translateGte(field, value) {
12394
+ function translateGte(field, value, schema, actualFieldName) {
12395
+ if (schema && actualFieldName) {
12396
+ const columnInfo = getColumnInfo(actualFieldName, schema);
12397
+ if (field !== "value" && columnInfo.type === "array") {
12398
+ return {
12399
+ sql: `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE value >= ?)`,
12400
+ args: [value]
12401
+ };
12402
+ }
12403
+ }
12357
12404
  return { sql: `${field} >= ?`, args: [value] };
12358
12405
  }
12359
- function translateLt(field, value) {
12406
+ function translateLt(field, value, schema, actualFieldName) {
12407
+ if (schema && actualFieldName) {
12408
+ const columnInfo = getColumnInfo(actualFieldName, schema);
12409
+ if (field !== "value" && columnInfo.type === "array") {
12410
+ return {
12411
+ sql: `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE value < ?)`,
12412
+ args: [value]
12413
+ };
12414
+ }
12415
+ }
12360
12416
  return { sql: `${field} < ?`, args: [value] };
12361
12417
  }
12362
- function translateLte(field, value) {
12418
+ function translateLte(field, value, schema, actualFieldName) {
12419
+ if (schema && actualFieldName) {
12420
+ const columnInfo = getColumnInfo(actualFieldName, schema);
12421
+ if (field !== "value" && columnInfo.type === "array") {
12422
+ return {
12423
+ sql: `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE value <= ?)`,
12424
+ args: [value]
12425
+ };
12426
+ }
12427
+ }
12363
12428
  return { sql: `${field} <= ?`, args: [value] };
12364
12429
  }
12365
- function translateIn(field, values) {
12430
+ function translateIn(field, values, schema, actualFieldName) {
12366
12431
  if (!Array.isArray(values) || values.length === 0) {
12367
12432
  return { sql: "1=0", args: [] };
12368
12433
  }
@@ -12371,17 +12436,31 @@ function translateIn(field, values) {
12371
12436
  if (nonNullValues.length === 0) {
12372
12437
  return { sql: `${field} IS NULL`, args: [] };
12373
12438
  }
12374
- const placeholders = nonNullValues.map(() => "?").join(", ");
12375
- const inClause = `${field} IN (${placeholders})`;
12439
+ if (schema && actualFieldName) {
12440
+ const columnInfo = getColumnInfo(actualFieldName, schema);
12441
+ if (field !== "value" && columnInfo.type === "array") {
12442
+ const inClause2 = `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE value IN (SELECT value FROM json_each(?)))`;
12443
+ const args2 = [JSON.stringify(nonNullValues)];
12444
+ if (hasNull) {
12445
+ return {
12446
+ sql: `(${inClause2} OR ${field} IS NULL)`,
12447
+ args: args2
12448
+ };
12449
+ }
12450
+ return { sql: inClause2, args: args2 };
12451
+ }
12452
+ }
12453
+ const inClause = `${field} IN (SELECT value FROM json_each(?))`;
12454
+ const args = [JSON.stringify(nonNullValues)];
12376
12455
  if (hasNull) {
12377
12456
  return {
12378
12457
  sql: `(${inClause} OR ${field} IS NULL)`,
12379
- args: nonNullValues
12458
+ args
12380
12459
  };
12381
12460
  }
12382
- return { sql: inClause, args: nonNullValues };
12461
+ return { sql: inClause, args };
12383
12462
  }
12384
- function translateNin(field, values) {
12463
+ function translateNin(field, values, schema, actualFieldName) {
12385
12464
  if (!Array.isArray(values) || values.length === 0) {
12386
12465
  return { sql: "1=1", args: [] };
12387
12466
  }
@@ -12390,15 +12469,29 @@ function translateNin(field, values) {
12390
12469
  if (nonNullValues.length === 0) {
12391
12470
  return { sql: `${field} IS NOT NULL`, args: [] };
12392
12471
  }
12393
- const placeholders = nonNullValues.map(() => "?").join(", ");
12394
- const ninClause = `${field} NOT IN (${placeholders})`;
12472
+ if (schema && actualFieldName) {
12473
+ const columnInfo = getColumnInfo(actualFieldName, schema);
12474
+ if (field !== "value" && columnInfo.type === "array") {
12475
+ const ninClause2 = `NOT EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE value IN (SELECT value FROM json_each(?)))`;
12476
+ const args2 = [JSON.stringify(nonNullValues)];
12477
+ if (hasNull) {
12478
+ return {
12479
+ sql: `(${ninClause2} AND ${field} IS NOT NULL)`,
12480
+ args: args2
12481
+ };
12482
+ }
12483
+ return { sql: ninClause2, args: args2 };
12484
+ }
12485
+ }
12486
+ const ninClause = `${field} NOT IN (SELECT value FROM json_each(?))`;
12487
+ const args = [JSON.stringify(nonNullValues)];
12395
12488
  if (hasNull) {
12396
12489
  return {
12397
12490
  sql: `(${ninClause} AND ${field} IS NOT NULL)`,
12398
- args: nonNullValues
12491
+ args
12399
12492
  };
12400
12493
  }
12401
- return { sql: ninClause, args: nonNullValues };
12494
+ return { sql: ninClause, args };
12402
12495
  }
12403
12496
  function translateExists(field, exists) {
12404
12497
  return {
@@ -12406,80 +12499,213 @@ function translateExists(field, exists) {
12406
12499
  args: []
12407
12500
  };
12408
12501
  }
12409
- function translateRegex(field, pattern, options) {
12410
- const smartResult = smartRegexToLike(field, pattern, options);
12502
+ function translateRegex(field, pattern, options, schema, fieldName) {
12503
+ const smartResult = smartRegexToLike(field, pattern, options, schema, fieldName);
12411
12504
  if (smartResult)
12412
12505
  return smartResult;
12413
12506
  return null;
12414
12507
  }
12415
- function translateElemMatch(field, criteria) {
12416
- return null;
12417
- }
12418
- function translateNot(field, criteria) {
12419
- const inner = processOperatorValue(field, criteria);
12508
+ function buildElemMatchConditions(criteria, schema, baseFieldName) {
12509
+ const conditions = [];
12510
+ const args = [];
12511
+ for (const [key, value] of Object.entries(criteria)) {
12512
+ if (key.startsWith("$")) {
12513
+ const fragment = processOperatorValue("value", { [key]: value }, schema, baseFieldName);
12514
+ conditions.push(fragment.sql);
12515
+ args.push(...fragment.args);
12516
+ } else {
12517
+ const propertyField = `json_extract(value, '$.${key}')`;
12518
+ const nestedFieldName = `${baseFieldName}.${key}`;
12519
+ const fragment = processOperatorValue(propertyField, value, schema, nestedFieldName);
12520
+ conditions.push(fragment.sql);
12521
+ args.push(...fragment.args);
12522
+ }
12523
+ }
12420
12524
  return {
12421
- sql: `NOT(${inner.sql})`,
12422
- args: inner.args
12525
+ sql: conditions.length > 0 ? conditions.join(" AND ") : "1=1",
12526
+ args
12423
12527
  };
12424
12528
  }
12425
- function translateNor(conditions) {
12426
- if (conditions.length === 0) {
12427
- return { sql: "1=1", args: [] };
12529
+ function translateElemMatch(field, criteria, schema, actualFieldName) {
12530
+ if (typeof criteria === "object" && criteria !== null && !Array.isArray(criteria) && Object.keys(criteria).length === 0) {
12531
+ return { sql: "1=0", args: [] };
12428
12532
  }
12429
- const fragments = conditions.map((condition) => {
12430
- const [[field, value]] = Object.entries(condition);
12431
- return processOperatorValue(field, value);
12432
- });
12433
- const sql = fragments.map((f) => `(${f.sql})`).join(" OR ");
12434
- const args = fragments.flatMap((f) => f.args);
12533
+ if (typeof criteria !== "object" || criteria === null) {
12534
+ return {
12535
+ sql: `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE value = ?)`,
12536
+ args: [criteria]
12537
+ };
12538
+ }
12539
+ if (criteria.$and && Array.isArray(criteria.$and)) {
12540
+ const fragments = criteria.$and.map((cond) => buildElemMatchConditions(cond, schema, actualFieldName));
12541
+ const sql = fragments.map((f) => f.sql).join(" AND ");
12542
+ const args = fragments.flatMap((f) => f.args);
12543
+ return {
12544
+ sql: `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE ${sql})`,
12545
+ args
12546
+ };
12547
+ }
12548
+ if (criteria.$or && Array.isArray(criteria.$or)) {
12549
+ const fragments = criteria.$or.map((cond) => buildElemMatchConditions(cond, schema, actualFieldName));
12550
+ const sql = fragments.map((f) => f.sql).join(" OR ");
12551
+ const args = fragments.flatMap((f) => f.args);
12552
+ return {
12553
+ sql: `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE ${sql})`,
12554
+ args
12555
+ };
12556
+ }
12557
+ if (criteria.$nor && Array.isArray(criteria.$nor)) {
12558
+ const fragments = criteria.$nor.map((cond) => buildElemMatchConditions(cond, schema, actualFieldName));
12559
+ const sql = fragments.map((f) => f.sql).join(" OR ");
12560
+ const args = fragments.flatMap((f) => f.args);
12561
+ return {
12562
+ sql: `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE NOT (${sql}))`,
12563
+ args
12564
+ };
12565
+ }
12566
+ const fragment = buildElemMatchConditions(criteria, schema, actualFieldName);
12435
12567
  return {
12436
- sql: `NOT(${sql})`,
12437
- args
12568
+ sql: `EXISTS (SELECT 1 FROM jsonb_each(${field}) WHERE ${fragment.sql})`,
12569
+ args: fragment.args
12438
12570
  };
12439
12571
  }
12440
- function processOperatorValue(field, value) {
12572
+ function processOperatorValue(field, value, schema, actualFieldName) {
12441
12573
  if (typeof value === "object" && value !== null && !Array.isArray(value)) {
12442
12574
  const [[op, opValue]] = Object.entries(value);
12575
+ if (!op.startsWith("$")) {
12576
+ const jsonPath = `json_extract(${field}, '$.${op}')`;
12577
+ const nestedFieldName = `${actualFieldName}.${op}`;
12578
+ return translateEq(jsonPath, opValue, schema, nestedFieldName);
12579
+ }
12443
12580
  switch (op) {
12444
12581
  case "$eq":
12445
- return translateEq(field, opValue);
12582
+ return translateEq(field, opValue, schema, actualFieldName);
12446
12583
  case "$ne":
12447
- return translateNe(field, opValue);
12584
+ return translateNe(field, opValue, schema, actualFieldName);
12448
12585
  case "$gt":
12449
- return translateGt(field, opValue);
12586
+ return translateGt(field, opValue, schema, actualFieldName);
12450
12587
  case "$gte":
12451
- return translateGte(field, opValue);
12588
+ return translateGte(field, opValue, schema, actualFieldName);
12452
12589
  case "$lt":
12453
- return translateLt(field, opValue);
12590
+ return translateLt(field, opValue, schema, actualFieldName);
12454
12591
  case "$lte":
12455
- return translateLte(field, opValue);
12592
+ return translateLte(field, opValue, schema, actualFieldName);
12456
12593
  case "$in":
12457
- return translateIn(field, opValue);
12594
+ return translateIn(field, opValue, schema, actualFieldName);
12458
12595
  case "$nin":
12459
- return translateNin(field, opValue);
12596
+ return translateNin(field, opValue, schema, actualFieldName);
12597
+ case "$exists":
12598
+ return translateExists(field, opValue);
12599
+ case "$size":
12600
+ return translateSize(field, opValue);
12601
+ case "$mod": {
12602
+ const result = translateMod(field, opValue);
12603
+ if (!result)
12604
+ return translateEq(field, opValue, schema, actualFieldName);
12605
+ return result;
12606
+ }
12607
+ case "$regex": {
12608
+ const options = value.$options;
12609
+ const regexFragment = translateRegex(field, opValue, options, schema, actualFieldName);
12610
+ return regexFragment || { sql: "1=0", args: [] };
12611
+ }
12612
+ case "$type": {
12613
+ let jsonCol = "data";
12614
+ let path2 = `$.${actualFieldName}`;
12615
+ let useDirectType = false;
12616
+ if (field === "value") {
12617
+ jsonCol = "value";
12618
+ path2 = "";
12619
+ useDirectType = true;
12620
+ } else if (field.startsWith("json_extract(")) {
12621
+ const match = field.match(/json_extract\(([^,]+),\s*'([^']+)'\)/);
12622
+ if (match) {
12623
+ jsonCol = match[1];
12624
+ path2 = match[2];
12625
+ }
12626
+ }
12627
+ if (useDirectType) {
12628
+ const typeMap = {
12629
+ null: "null",
12630
+ boolean: "true",
12631
+ number: "integer",
12632
+ string: "text",
12633
+ array: "array",
12634
+ object: "object"
12635
+ };
12636
+ const sqlType = typeMap[opValue];
12637
+ if (!sqlType)
12638
+ return { sql: "1=0", args: [] };
12639
+ if (opValue === "boolean") {
12640
+ return { sql: `(type IN ('true', 'false'))`, args: [] };
12641
+ }
12642
+ if (opValue === "number") {
12643
+ return { sql: `(type IN ('integer', 'real'))`, args: [] };
12644
+ }
12645
+ return { sql: `type = '${sqlType}'`, args: [] };
12646
+ }
12647
+ const typeFragment = translateType(jsonCol, path2, opValue, true);
12648
+ return typeFragment || { sql: "1=0", args: [] };
12649
+ }
12650
+ case "$elemMatch": {
12651
+ const elemMatchFragment = translateElemMatch(field, opValue, schema, actualFieldName);
12652
+ return elemMatchFragment || { sql: "1=0", args: [] };
12653
+ }
12654
+ case "$not": {
12655
+ const result = translateNot(field, opValue, schema, actualFieldName);
12656
+ if (!result)
12657
+ return translateEq(field, opValue, schema, actualFieldName);
12658
+ return result;
12659
+ }
12660
+ case "$and": {
12661
+ if (!Array.isArray(opValue))
12662
+ return translateEq(field, opValue, schema, actualFieldName);
12663
+ const fragments = opValue.map((v) => processOperatorValue(field, v, schema, actualFieldName));
12664
+ const sql = fragments.map((f) => f.sql).join(" AND ");
12665
+ const args = fragments.flatMap((f) => f.args);
12666
+ return { sql: `(${sql})`, args };
12667
+ }
12668
+ case "$or": {
12669
+ if (!Array.isArray(opValue))
12670
+ return translateEq(field, opValue, schema, actualFieldName);
12671
+ const fragments = opValue.map((v) => processOperatorValue(field, v, schema, actualFieldName));
12672
+ const sql = fragments.map((f) => f.sql).join(" OR ");
12673
+ const args = fragments.flatMap((f) => f.args);
12674
+ return { sql: `(${sql})`, args };
12675
+ }
12460
12676
  default:
12461
- return translateEq(field, opValue);
12677
+ return translateEq(field, opValue, schema, actualFieldName);
12462
12678
  }
12463
12679
  }
12464
- return translateEq(field, value);
12680
+ return translateEq(field, value, schema, actualFieldName);
12681
+ }
12682
+ function translateNot(field, criteria, schema, actualFieldName) {
12683
+ if (criteria === undefined || criteria === null || typeof criteria !== "object" || Array.isArray(criteria) || Object.keys(criteria).length === 0) {
12684
+ return { sql: "1=0", args: [] };
12685
+ }
12686
+ const inner = processOperatorValue(field, criteria, schema, actualFieldName);
12687
+ return {
12688
+ sql: `NOT (${inner.sql})`,
12689
+ args: inner.args
12690
+ };
12465
12691
  }
12466
- function translateType(field, type6) {
12692
+ function translateType(jsonColumn, fieldName, type6, isDirectPath = false) {
12693
+ const jsonPath = isDirectPath ? fieldName : `$.${fieldName}`;
12467
12694
  switch (type6) {
12468
- case "number":
12469
- return {
12470
- sql: `(typeof(${field}) = 'integer' OR typeof(${field}) = 'real')`,
12471
- args: []
12472
- };
12473
- case "string":
12474
- return { sql: `typeof(${field}) = 'text'`, args: [] };
12475
12695
  case "null":
12476
- return { sql: `typeof(${field}) = 'null'`, args: [] };
12696
+ return { sql: `json_type(${jsonColumn}, '${jsonPath}') = 'null'`, args: [] };
12477
12697
  case "boolean":
12698
+ return { sql: `json_type(${jsonColumn}, '${jsonPath}') IN ('true', 'false')`, args: [] };
12699
+ case "number":
12700
+ return { sql: `json_type(${jsonColumn}, '${jsonPath}') IN ('integer', 'real')`, args: [] };
12701
+ case "string":
12702
+ return { sql: `json_type(${jsonColumn}, '${jsonPath}') = 'text'`, args: [] };
12478
12703
  case "array":
12704
+ return { sql: `json_type(${jsonColumn}, '${jsonPath}') = 'array'`, args: [] };
12479
12705
  case "object":
12480
- case "date":
12706
+ return { sql: `json_type(${jsonColumn}, '${jsonPath}') = 'object'`, args: [] };
12481
12707
  default:
12482
- return null;
12708
+ return { sql: "1=0", args: [] };
12483
12709
  }
12484
12710
  }
12485
12711
  function translateSize(field, size) {
@@ -12488,19 +12714,120 @@ function translateSize(field, size) {
12488
12714
  args: [size]
12489
12715
  };
12490
12716
  }
12491
- function translateMod(field, [divisor, remainder]) {
12717
+ function translateMod(field, value) {
12718
+ if (!Array.isArray(value) || value.length !== 2)
12719
+ return { sql: "1=0", args: [] };
12720
+ const [divisor, remainder] = value;
12492
12721
  return {
12493
- sql: `${field} % ? = ?`,
12494
- args: [divisor, remainder]
12722
+ sql: `(${field} - (CAST(${field} / ? AS INTEGER) * ?)) = ?`,
12723
+ args: [divisor, divisor, remainder]
12495
12724
  };
12496
12725
  }
12497
12726
 
12727
+ // src/utils/stable-stringify.ts
12728
+ var strEscapeRegex = /[\u0000-\u001f\u0022\u005c\ud800-\udfff]/;
12729
+ function strEscape(str) {
12730
+ if (str.length < 5000 && !strEscapeRegex.test(str)) {
12731
+ return `"${str}"`;
12732
+ }
12733
+ return JSON.stringify(str);
12734
+ }
12735
+ function callSafe(method, thisArg) {
12736
+ try {
12737
+ return method.call(thisArg);
12738
+ } catch (error) {
12739
+ return `[Error: ${error instanceof Error ? error.message : String(error)}]`;
12740
+ }
12741
+ }
12742
+ function sortKeys(keys) {
12743
+ if (keys.length > 100) {
12744
+ return keys.sort();
12745
+ }
12746
+ for (let i = 1;i < keys.length; i++) {
12747
+ const current = keys[i];
12748
+ let pos = i;
12749
+ while (pos !== 0 && keys[pos - 1] > current) {
12750
+ keys[pos] = keys[pos - 1];
12751
+ pos--;
12752
+ }
12753
+ keys[pos] = current;
12754
+ }
12755
+ return keys;
12756
+ }
12757
+ function stableStringify(value) {
12758
+ return _stringify(value, []);
12759
+ }
12760
+ function _stringify(value, stack) {
12761
+ if (value === null)
12762
+ return "null";
12763
+ if (value === true)
12764
+ return "true";
12765
+ if (value === false)
12766
+ return "false";
12767
+ const type6 = typeof value;
12768
+ if (type6 === "string")
12769
+ return strEscape(value);
12770
+ if (type6 === "number")
12771
+ return isFinite(value) ? String(value) : "null";
12772
+ if (type6 === "undefined")
12773
+ return "null";
12774
+ if (type6 === "bigint")
12775
+ return String(value);
12776
+ if (Array.isArray(value)) {
12777
+ if (stack.indexOf(value) !== -1)
12778
+ return '"[Circular]"';
12779
+ stack.push(value);
12780
+ if (value.length === 0) {
12781
+ stack.pop();
12782
+ return "[]";
12783
+ }
12784
+ let res = "[" + _stringify(value[0], stack);
12785
+ for (let i = 1;i < value.length; i++) {
12786
+ res += "," + _stringify(value[i], stack);
12787
+ }
12788
+ stack.pop();
12789
+ return res + "]";
12790
+ }
12791
+ if (type6 === "object") {
12792
+ const obj = value;
12793
+ if (stack.indexOf(value) !== -1)
12794
+ return '"[Circular]"';
12795
+ stack.push(value);
12796
+ if ("toJSON" in obj && typeof obj.toJSON === "function") {
12797
+ const result = _stringify(callSafe(obj.toJSON, obj), stack);
12798
+ stack.pop();
12799
+ return result;
12800
+ }
12801
+ const objType = Object.prototype.toString.call(obj);
12802
+ if (objType !== "[object Object]") {
12803
+ const result = JSON.stringify(obj);
12804
+ stack.pop();
12805
+ return result;
12806
+ }
12807
+ const keys = sortKeys(Object.keys(obj));
12808
+ let res = "{";
12809
+ let separator = "";
12810
+ for (let i = 0;i < keys.length; i++) {
12811
+ const key = keys[i];
12812
+ const val = obj[key];
12813
+ if (val === undefined)
12814
+ continue;
12815
+ res += separator + strEscape(key) + ":" + _stringify(val, stack);
12816
+ separator = ",";
12817
+ }
12818
+ stack.pop();
12819
+ return res + "}";
12820
+ }
12821
+ return "null";
12822
+ }
12823
+
12498
12824
  // src/query/builder.ts
12499
- var import_fast_stable_stringify = __toESM(require_fast_stable_stringify(), 1);
12500
12825
  var QUERY_CACHE = new Map;
12501
- var MAX_CACHE_SIZE = 500;
12502
- function buildWhereClause(selector, schema) {
12503
- const cacheKey = `v${schema.version}_${import_fast_stable_stringify.default(selector)}`;
12826
+ var MAX_CACHE_SIZE = 1000;
12827
+ function buildWhereClause(selector, schema, collectionName) {
12828
+ if (!selector || typeof selector !== "object")
12829
+ return null;
12830
+ const cacheKey = `v${schema.version}_${collectionName}_${stableStringify(selector)}`;
12504
12831
  const cached = QUERY_CACHE.get(cacheKey);
12505
12832
  if (cached) {
12506
12833
  QUERY_CACHE.delete(cacheKey);
@@ -12508,6 +12835,8 @@ function buildWhereClause(selector, schema) {
12508
12835
  return cached;
12509
12836
  }
12510
12837
  const result = processSelector(selector, schema, 0);
12838
+ if (!result)
12839
+ return null;
12511
12840
  if (QUERY_CACHE.size >= MAX_CACHE_SIZE) {
12512
12841
  const firstKey = QUERY_CACHE.keys().next().value;
12513
12842
  if (firstKey)
@@ -12516,12 +12845,27 @@ function buildWhereClause(selector, schema) {
12516
12845
  QUERY_CACHE.set(cacheKey, result);
12517
12846
  return result;
12518
12847
  }
12848
+ function buildLogicalOperator(operator, conditions, schema, logicalDepth) {
12849
+ if (conditions.length === 0) {
12850
+ return { sql: operator === "or" ? "1=0" : "1=1", args: [] };
12851
+ }
12852
+ const fragments = conditions.map((subSelector) => processSelector(subSelector, schema, logicalDepth + 1));
12853
+ if (fragments.some((f) => f === null))
12854
+ return null;
12855
+ const sql = fragments.map((f) => `(${f.sql})`).join(" OR ");
12856
+ const args = fragments.flatMap((f) => f.args);
12857
+ return operator === "nor" ? { sql: `NOT(${sql})`, args } : { sql, args };
12858
+ }
12519
12859
  function processSelector(selector, schema, logicalDepth) {
12860
+ if (!selector || typeof selector !== "object")
12861
+ return null;
12520
12862
  const conditions = [];
12521
12863
  const args = [];
12522
12864
  for (const [field, value] of Object.entries(selector)) {
12523
12865
  if (field === "$and" && Array.isArray(value)) {
12524
- const andFragments = value.map((subSelector) => processSelector(subSelector, schema, logicalDepth));
12866
+ const andFragments = value.map((subSelector) => processSelector(subSelector, schema, logicalDepth + 1));
12867
+ if (andFragments.some((f) => f === null))
12868
+ return null;
12525
12869
  const andConditions = andFragments.map((f) => f.sql);
12526
12870
  const needsParens = logicalDepth > 0 && andConditions.length > 1;
12527
12871
  const joined = andConditions.join(" AND ");
@@ -12530,81 +12874,94 @@ function processSelector(selector, schema, logicalDepth) {
12530
12874
  continue;
12531
12875
  }
12532
12876
  if (field === "$or" && Array.isArray(value)) {
12533
- const orFragments = value.map((subSelector) => processSelector(subSelector, schema, logicalDepth + 1));
12534
- const orConditions = orFragments.map((f) => f.sql);
12535
- const needsParens = logicalDepth > 0 && orConditions.length > 1;
12536
- const joined = orConditions.join(" OR ");
12537
- conditions.push(needsParens ? `(${joined})` : joined);
12538
- orFragments.forEach((f) => args.push(...f.args));
12877
+ const orFragment = buildLogicalOperator("or", value, schema, logicalDepth);
12878
+ if (!orFragment)
12879
+ return null;
12880
+ conditions.push(`(${orFragment.sql})`);
12881
+ args.push(...orFragment.args);
12539
12882
  continue;
12540
12883
  }
12541
12884
  if (field === "$nor" && Array.isArray(value)) {
12542
- const norFragment = translateNor(value);
12885
+ const norFragment = buildLogicalOperator("nor", value, schema, logicalDepth);
12886
+ if (!norFragment)
12887
+ return null;
12543
12888
  conditions.push(norFragment.sql);
12544
12889
  args.push(...norFragment.args);
12545
12890
  continue;
12546
12891
  }
12547
12892
  const columnInfo = getColumnInfo(field, schema);
12548
12893
  const fieldName = columnInfo.column || `json_extract(data, '${columnInfo.jsonPath}')`;
12894
+ const actualFieldName = columnInfo.jsonPath?.replace(/^\$\./, "") || columnInfo.column || field;
12549
12895
  if (typeof value === "object" && value !== null && !Array.isArray(value)) {
12896
+ if (Object.keys(value).length === 0) {
12897
+ return { sql: "1=0", args: [] };
12898
+ }
12550
12899
  for (const [op, opValue] of Object.entries(value)) {
12551
12900
  let fragment;
12552
12901
  switch (op) {
12553
12902
  case "$eq":
12554
- fragment = translateEq(fieldName, opValue);
12903
+ fragment = translateEq(fieldName, opValue, schema, actualFieldName);
12555
12904
  break;
12556
12905
  case "$ne":
12557
- fragment = translateNe(fieldName, opValue);
12906
+ fragment = translateNe(fieldName, opValue, schema, actualFieldName);
12558
12907
  break;
12559
12908
  case "$gt":
12560
- fragment = translateGt(fieldName, opValue);
12909
+ fragment = translateGt(fieldName, opValue, schema, actualFieldName);
12561
12910
  break;
12562
12911
  case "$gte":
12563
- fragment = translateGte(fieldName, opValue);
12912
+ fragment = translateGte(fieldName, opValue, schema, actualFieldName);
12564
12913
  break;
12565
12914
  case "$lt":
12566
- fragment = translateLt(fieldName, opValue);
12915
+ fragment = translateLt(fieldName, opValue, schema, actualFieldName);
12567
12916
  break;
12568
12917
  case "$lte":
12569
- fragment = translateLte(fieldName, opValue);
12918
+ fragment = translateLte(fieldName, opValue, schema, actualFieldName);
12570
12919
  break;
12571
12920
  case "$in":
12572
- fragment = translateIn(fieldName, opValue);
12921
+ fragment = translateIn(fieldName, opValue, schema, actualFieldName);
12573
12922
  break;
12574
12923
  case "$nin":
12575
- fragment = translateNin(fieldName, opValue);
12924
+ fragment = translateNin(fieldName, opValue, schema, actualFieldName);
12576
12925
  break;
12577
12926
  case "$exists":
12578
12927
  fragment = translateExists(fieldName, opValue);
12579
12928
  break;
12580
12929
  case "$regex":
12581
12930
  const options = value.$options;
12582
- const regexFragment = translateRegex(fieldName, opValue, options);
12931
+ const regexFragment = translateRegex(fieldName, opValue, options, schema, actualFieldName);
12583
12932
  if (!regexFragment)
12584
- continue;
12933
+ return null;
12585
12934
  fragment = regexFragment;
12586
12935
  break;
12587
12936
  case "$elemMatch":
12588
- const elemMatchFragment = translateElemMatch(fieldName, opValue);
12937
+ const elemMatchFragment = translateElemMatch(fieldName, opValue, schema, actualFieldName);
12589
12938
  if (!elemMatchFragment)
12590
- continue;
12939
+ return null;
12591
12940
  fragment = elemMatchFragment;
12592
12941
  break;
12593
- case "$not":
12594
- fragment = translateNot(fieldName, opValue);
12942
+ case "$not": {
12943
+ const notResult = translateNot(fieldName, opValue, schema, actualFieldName);
12944
+ if (!notResult)
12945
+ return null;
12946
+ fragment = notResult;
12595
12947
  break;
12948
+ }
12596
12949
  case "$type":
12597
- const typeFragment = translateType(fieldName, opValue);
12950
+ const typeFragment = translateType("data", actualFieldName, opValue);
12598
12951
  if (!typeFragment)
12599
- continue;
12952
+ return null;
12600
12953
  fragment = typeFragment;
12601
12954
  break;
12602
12955
  case "$size":
12603
12956
  fragment = translateSize(fieldName, opValue);
12604
12957
  break;
12605
- case "$mod":
12606
- fragment = translateMod(fieldName, opValue);
12958
+ case "$mod": {
12959
+ const modResult = translateMod(fieldName, opValue);
12960
+ if (!modResult)
12961
+ return null;
12962
+ fragment = modResult;
12607
12963
  break;
12964
+ }
12608
12965
  default:
12609
12966
  continue;
12610
12967
  }
@@ -12612,7 +12969,7 @@ function processSelector(selector, schema, logicalDepth) {
12612
12969
  args.push(...fragment.args);
12613
12970
  }
12614
12971
  } else {
12615
- const fragment = translateEq(fieldName, value);
12972
+ const fragment = translateEq(fieldName, value, schema, actualFieldName);
12616
12973
  conditions.push(fragment.sql);
12617
12974
  args.push(...fragment.args);
12618
12975
  }
@@ -12621,6 +12978,100 @@ function processSelector(selector, schema, logicalDepth) {
12621
12978
  return { sql: where, args };
12622
12979
  }
12623
12980
 
12981
+ // src/query/lightweight-matcher.ts
12982
+ var operators = {
12983
+ $eq: (a, b) => a === b,
12984
+ $ne: (a, b) => a !== b,
12985
+ $gt: (a, b) => a > b,
12986
+ $gte: (a, b) => a >= b,
12987
+ $lt: (a, b) => a < b,
12988
+ $lte: (a, b) => a <= b,
12989
+ $in: (a, b) => Array.isArray(b) && b.some((v) => v === a),
12990
+ $nin: (a, b) => Array.isArray(b) && !b.some((v) => v === a),
12991
+ $exists: (a, b) => a !== undefined === b,
12992
+ $type: (a, b) => {
12993
+ const type6 = Array.isArray(a) ? "array" : typeof a;
12994
+ return type6 === b;
12995
+ },
12996
+ $mod: (a, b) => {
12997
+ if (!Array.isArray(b) || b.length !== 2)
12998
+ return false;
12999
+ const [divisor, remainder] = b;
13000
+ return typeof a === "number" && a % divisor === remainder;
13001
+ },
13002
+ $size: (a, b) => Array.isArray(a) && a.length === b
13003
+ };
13004
+ function getNestedValue(obj, path2) {
13005
+ return path2.split(".").reduce((current, key) => current?.[key], obj);
13006
+ }
13007
+ function matchesSelector(doc, selector) {
13008
+ if (!selector || typeof selector !== "object")
13009
+ return true;
13010
+ if (selector.$and) {
13011
+ return Array.isArray(selector.$and) && selector.$and.every((s) => matchesSelector(doc, s));
13012
+ }
13013
+ if (selector.$or) {
13014
+ return Array.isArray(selector.$or) && selector.$or.some((s) => matchesSelector(doc, s));
13015
+ }
13016
+ if (selector.$nor) {
13017
+ return Array.isArray(selector.$nor) && !selector.$nor.some((s) => matchesSelector(doc, s));
13018
+ }
13019
+ for (const [field, condition] of Object.entries(selector)) {
13020
+ const value = getNestedValue(doc, field);
13021
+ if (typeof condition !== "object" || condition === null || Array.isArray(condition)) {
13022
+ if (value !== condition)
13023
+ return false;
13024
+ continue;
13025
+ }
13026
+ for (const [op, opValue] of Object.entries(condition)) {
13027
+ if (op === "$regex") {
13028
+ const options = condition.$options;
13029
+ if (!matchesRegex(value, opValue, options))
13030
+ return false;
13031
+ continue;
13032
+ }
13033
+ if (op === "$not") {
13034
+ if (matchesOperator(value, opValue))
13035
+ return false;
13036
+ continue;
13037
+ }
13038
+ if (op === "$elemMatch") {
13039
+ if (!Array.isArray(value))
13040
+ return false;
13041
+ const hasMatch = value.some((item) => matchesSelector(item, opValue));
13042
+ if (!hasMatch)
13043
+ return false;
13044
+ continue;
13045
+ }
13046
+ if (op === "$options")
13047
+ continue;
13048
+ const operator = operators[op];
13049
+ if (!operator)
13050
+ return false;
13051
+ if (!operator(value, opValue))
13052
+ return false;
13053
+ }
13054
+ }
13055
+ return true;
13056
+ }
13057
+ function matchesOperator(value, operator) {
13058
+ if (typeof operator !== "object" || operator === null) {
13059
+ return value === operator;
13060
+ }
13061
+ for (const [op, opValue] of Object.entries(operator)) {
13062
+ if (op === "$regex") {
13063
+ const options = operator.$options;
13064
+ return matchesRegex(value, opValue, options);
13065
+ }
13066
+ const operatorFn = operators[op];
13067
+ if (!operatorFn)
13068
+ return false;
13069
+ if (operatorFn(value, opValue))
13070
+ return true;
13071
+ }
13072
+ return false;
13073
+ }
13074
+
12624
13075
  // src/rxdb-helpers.ts
12625
13076
  function randomToken2(length) {
12626
13077
  return Math.random().toString(36).substring(2, 2 + length);
@@ -12828,32 +13279,64 @@ function ensureRxStorageInstanceParamsAreCorrect(params) {
12828
13279
  class StatementManager {
12829
13280
  db;
12830
13281
  staticStatements = new Map;
13282
+ static MAX_STATEMENTS = 500;
13283
+ closed = false;
12831
13284
  constructor(db) {
12832
13285
  this.db = db;
12833
13286
  }
13287
+ checkClosed() {
13288
+ if (this.closed) {
13289
+ throw new Error("StatementManager is closed");
13290
+ }
13291
+ }
13292
+ evictOldest() {
13293
+ if (this.staticStatements.size >= StatementManager.MAX_STATEMENTS) {
13294
+ const firstKey = this.staticStatements.keys().next().value;
13295
+ if (firstKey) {
13296
+ const stmt = this.staticStatements.get(firstKey);
13297
+ stmt?.finalize();
13298
+ this.staticStatements.delete(firstKey);
13299
+ }
13300
+ }
13301
+ }
12834
13302
  all(queryWithParams) {
13303
+ this.checkClosed();
12835
13304
  const { query, params } = queryWithParams;
12836
- if (this.isStaticSQL(query)) {
12837
- let stmt = this.staticStatements.get(query);
12838
- if (!stmt) {
12839
- stmt = this.db.query(query);
12840
- this.staticStatements.set(query, stmt);
12841
- }
12842
- return stmt.all(...params);
13305
+ let stmt = this.staticStatements.get(query);
13306
+ if (stmt) {
13307
+ this.staticStatements.delete(query);
13308
+ this.staticStatements.set(query, stmt);
12843
13309
  } else {
12844
- const stmt = this.db.prepare(query);
12845
- try {
12846
- return stmt.all(...params);
12847
- } finally {
12848
- stmt.finalize();
12849
- }
13310
+ this.evictOldest();
13311
+ stmt = this.db.query(query);
13312
+ this.staticStatements.set(query, stmt);
12850
13313
  }
13314
+ return stmt.all(...params);
13315
+ }
13316
+ get(queryWithParams) {
13317
+ this.checkClosed();
13318
+ const { query, params } = queryWithParams;
13319
+ let stmt = this.staticStatements.get(query);
13320
+ if (stmt) {
13321
+ this.staticStatements.delete(query);
13322
+ this.staticStatements.set(query, stmt);
13323
+ } else {
13324
+ this.evictOldest();
13325
+ stmt = this.db.query(query);
13326
+ this.staticStatements.set(query, stmt);
13327
+ }
13328
+ return stmt.get(...params);
12851
13329
  }
12852
13330
  run(queryWithParams) {
13331
+ this.checkClosed();
12853
13332
  const { query, params } = queryWithParams;
12854
13333
  if (this.isStaticSQL(query)) {
12855
13334
  let stmt = this.staticStatements.get(query);
12856
- if (!stmt) {
13335
+ if (stmt) {
13336
+ this.staticStatements.delete(query);
13337
+ this.staticStatements.set(query, stmt);
13338
+ } else {
13339
+ this.evictOldest();
12857
13340
  stmt = this.db.query(query);
12858
13341
  this.staticStatements.set(query, stmt);
12859
13342
  }
@@ -12872,6 +13355,7 @@ class StatementManager {
12872
13355
  stmt.finalize();
12873
13356
  }
12874
13357
  this.staticStatements.clear();
13358
+ this.closed = true;
12875
13359
  }
12876
13360
  isStaticSQL(query) {
12877
13361
  if (query.includes("WHERE (")) {
@@ -12912,6 +13396,28 @@ function releaseDatabase(databaseName) {
12912
13396
  }
12913
13397
  }
12914
13398
 
13399
+ // src/transaction-queue.ts
13400
+ var TX_QUEUE_BY_DATABASE = new WeakMap;
13401
+ async function sqliteTransaction(database, handler) {
13402
+ let queue = TX_QUEUE_BY_DATABASE.get(database);
13403
+ if (!queue) {
13404
+ queue = Promise.resolve();
13405
+ }
13406
+ const result = queue.then(async () => {
13407
+ database.run("BEGIN IMMEDIATE");
13408
+ try {
13409
+ const handlerResult = await handler();
13410
+ database.run("COMMIT");
13411
+ return handlerResult;
13412
+ } catch (error) {
13413
+ database.run("ROLLBACK");
13414
+ throw error;
13415
+ }
13416
+ });
13417
+ TX_QUEUE_BY_DATABASE.set(database, result.then(() => {}).catch(() => {}));
13418
+ return result;
13419
+ }
13420
+
12915
13421
  // src/instance.ts
12916
13422
  class BunSQLiteStorageInstance {
12917
13423
  db;
@@ -12947,6 +13453,15 @@ class BunSQLiteStorageInstance {
12947
13453
  if (filename !== ":memory:") {
12948
13454
  this.db.run("PRAGMA journal_mode = WAL");
12949
13455
  this.db.run("PRAGMA synchronous = NORMAL");
13456
+ this.db.run("PRAGMA wal_autocheckpoint = 1000");
13457
+ this.db.run("PRAGMA cache_size = -32000");
13458
+ this.db.run("PRAGMA analysis_limit = 400");
13459
+ const mmapSize = this.options.mmapSize ?? 268435456;
13460
+ if (mmapSize > 0) {
13461
+ this.db.run(`PRAGMA mmap_size = ${mmapSize}`);
13462
+ }
13463
+ this.db.run("PRAGMA temp_store = MEMORY");
13464
+ this.db.run("PRAGMA locking_mode = NORMAL");
12950
13465
  }
12951
13466
  this.db.run(`
12952
13467
  CREATE TABLE IF NOT EXISTS "${this.tableName}" (
@@ -12962,8 +13477,17 @@ class BunSQLiteStorageInstance {
12962
13477
  if (this.schema.indexes) {
12963
13478
  for (const index of this.schema.indexes) {
12964
13479
  const fields = Array.isArray(index) ? index : [index];
12965
- const indexName = `idx_${this.tableName}_${fields.join("_")}`;
12966
- const columns = fields.map((field) => `json_extract(data, '$.${field}')`).join(", ");
13480
+ const indexName = `idx_${this.tableName}_${fields.join("_").replace(/[()]/g, "_")}`;
13481
+ const columns = fields.map((field) => {
13482
+ if (typeof field !== "string")
13483
+ return `json_extract(data, '$.${field}')`;
13484
+ const funcMatch = field.match(/^(\w+)\((.+)\)$/);
13485
+ if (funcMatch) {
13486
+ const [, func, fieldName] = funcMatch;
13487
+ return `${func}(json_extract(data, '$.${fieldName}'))`;
13488
+ }
13489
+ return `json_extract(data, '$.${field}')`;
13490
+ }).join(", ");
12967
13491
  this.db.run(`CREATE INDEX IF NOT EXISTS "${indexName}" ON "${this.tableName}"(${columns})`);
12968
13492
  }
12969
13493
  }
@@ -12976,69 +13500,85 @@ class BunSQLiteStorageInstance {
12976
13500
  `);
12977
13501
  }
12978
13502
  async bulkWrite(documentWrites, context) {
12979
- if (documentWrites.length === 0) {
12980
- return { error: [] };
12981
- }
12982
- const ids = documentWrites.map((w) => w.document[this.primaryPath]);
12983
- const docsInDb = await this.findDocumentsById(ids, true);
12984
- const docsInDbMap = new Map(docsInDb.map((d) => [d[this.primaryPath], d]));
12985
- const categorized = categorizeBulkWriteRows(this, this.primaryPath, docsInDbMap, documentWrites, context);
12986
- const insertQuery = `INSERT INTO "${this.tableName}" (id, data, deleted, rev, mtime_ms) VALUES (?, jsonb(?), ?, ?, ?)`;
12987
- const updateQuery = `UPDATE "${this.tableName}" SET data = jsonb(?), deleted = ?, rev = ?, mtime_ms = ? WHERE id = ?`;
12988
- for (const row of categorized.bulkInsertDocs) {
12989
- const doc = row.document;
12990
- const id = doc[this.primaryPath];
12991
- try {
12992
- this.stmtManager.run({ query: insertQuery, params: [id, JSON.stringify(doc), doc._deleted ? 1 : 0, doc._rev, doc._meta.lwt] });
12993
- } catch (err) {
12994
- if (err.code === "SQLITE_CONSTRAINT_PRIMARYKEY" || err.code === "SQLITE_CONSTRAINT_UNIQUE") {
12995
- const documentInDb = docsInDbMap.get(id);
12996
- categorized.errors.push({
12997
- isError: true,
12998
- status: 409,
12999
- documentId: id,
13000
- writeRow: row,
13001
- documentInDb: documentInDb || doc
13002
- });
13003
- } else {
13004
- throw err;
13503
+ return sqliteTransaction(this.db, async () => {
13504
+ if (documentWrites.length === 0) {
13505
+ return { error: [] };
13506
+ }
13507
+ const ids = documentWrites.map((w) => w.document[this.primaryPath]);
13508
+ const docsInDb = await this.findDocumentsById(ids, true);
13509
+ const docsInDbMap = new Map(docsInDb.map((d) => [d[this.primaryPath], d]));
13510
+ const categorized = categorizeBulkWriteRows(this, this.primaryPath, docsInDbMap, documentWrites, context);
13511
+ const updateQuery = `UPDATE "${this.tableName}" SET data = jsonb(?), deleted = ?, rev = ?, mtime_ms = ? WHERE id = ?`;
13512
+ const BATCH_SIZE = 100;
13513
+ for (let i = 0;i < categorized.bulkInsertDocs.length; i += BATCH_SIZE) {
13514
+ const batch = categorized.bulkInsertDocs.slice(i, i + BATCH_SIZE);
13515
+ const placeholders = batch.map(() => "(?, jsonb(?), ?, ?, ?)").join(", ");
13516
+ const insertQuery = `INSERT INTO "${this.tableName}" (id, data, deleted, rev, mtime_ms) VALUES ${placeholders}`;
13517
+ const params = [];
13518
+ for (const row of batch) {
13519
+ const doc = row.document;
13520
+ const id = doc[this.primaryPath];
13521
+ params.push(id, JSON.stringify(doc), doc._deleted ? 1 : 0, doc._rev, doc._meta.lwt);
13522
+ }
13523
+ try {
13524
+ this.stmtManager.run({ query: insertQuery, params });
13525
+ } catch (err) {
13526
+ if (err && typeof err === "object" && "code" in err && (err.code === "SQLITE_CONSTRAINT_PRIMARYKEY" || err.code === "SQLITE_CONSTRAINT_UNIQUE")) {
13527
+ for (const row of batch) {
13528
+ const doc = row.document;
13529
+ const id = doc[this.primaryPath];
13530
+ const documentInDb = docsInDbMap.get(id);
13531
+ categorized.errors.push({
13532
+ isError: true,
13533
+ status: 409,
13534
+ documentId: id,
13535
+ writeRow: row,
13536
+ documentInDb: documentInDb || doc
13537
+ });
13538
+ }
13539
+ } else {
13540
+ throw err;
13541
+ }
13005
13542
  }
13006
13543
  }
13007
- }
13008
- for (const row of categorized.bulkUpdateDocs) {
13009
- const doc = row.document;
13010
- const id = doc[this.primaryPath];
13011
- this.stmtManager.run({ query: updateQuery, params: [JSON.stringify(doc), doc._deleted ? 1 : 0, doc._rev, doc._meta.lwt, id] });
13012
- }
13013
- const insertAttQuery = `INSERT OR REPLACE INTO "${this.tableName}_attachments" (id, data, digest) VALUES (?, ?, ?)`;
13014
- const deleteAttQuery = `DELETE FROM "${this.tableName}_attachments" WHERE id = ?`;
13015
- for (const att of [...categorized.attachmentsAdd, ...categorized.attachmentsUpdate]) {
13016
- this.stmtManager.run({
13017
- query: insertAttQuery,
13018
- params: [
13019
- this.attachmentMapKey(att.documentId, att.attachmentId),
13020
- att.attachmentData.data,
13021
- att.digest
13022
- ]
13023
- });
13024
- }
13025
- for (const att of categorized.attachmentsRemove) {
13026
- this.stmtManager.run({
13027
- query: deleteAttQuery,
13028
- params: [this.attachmentMapKey(att.documentId, att.attachmentId)]
13029
- });
13030
- }
13031
- const failedDocIds = new Set(categorized.errors.map((e) => e.documentId));
13032
- categorized.eventBulk.events = categorized.eventBulk.events.filter((event) => !failedDocIds.has(event.documentId));
13033
- if (categorized.eventBulk.events.length > 0 && categorized.newestRow) {
13034
- const lastState = categorized.newestRow.document;
13035
- categorized.eventBulk.checkpoint = {
13036
- id: lastState[this.primaryPath],
13037
- lwt: lastState._meta.lwt
13038
- };
13039
- this.changeStream$.next(categorized.eventBulk);
13040
- }
13041
- return { error: categorized.errors };
13544
+ for (let i = 0;i < categorized.bulkUpdateDocs.length; i += BATCH_SIZE) {
13545
+ const batch = categorized.bulkUpdateDocs.slice(i, i + BATCH_SIZE);
13546
+ for (const row of batch) {
13547
+ const doc = row.document;
13548
+ const id = doc[this.primaryPath];
13549
+ this.stmtManager.run({ query: updateQuery, params: [JSON.stringify(doc), doc._deleted ? 1 : 0, doc._rev, doc._meta.lwt, id] });
13550
+ }
13551
+ }
13552
+ const insertAttQuery = `INSERT OR REPLACE INTO "${this.tableName}_attachments" (id, data, digest) VALUES (?, ?, ?)`;
13553
+ const deleteAttQuery = `DELETE FROM "${this.tableName}_attachments" WHERE id = ?`;
13554
+ for (const att of [...categorized.attachmentsAdd, ...categorized.attachmentsUpdate]) {
13555
+ this.stmtManager.run({
13556
+ query: insertAttQuery,
13557
+ params: [
13558
+ this.attachmentMapKey(att.documentId, att.attachmentId),
13559
+ att.attachmentData.data,
13560
+ att.digest
13561
+ ]
13562
+ });
13563
+ }
13564
+ for (const att of categorized.attachmentsRemove) {
13565
+ this.stmtManager.run({
13566
+ query: deleteAttQuery,
13567
+ params: [this.attachmentMapKey(att.documentId, att.attachmentId)]
13568
+ });
13569
+ }
13570
+ const failedDocIds = new Set(categorized.errors.map((e) => e.documentId));
13571
+ categorized.eventBulk.events = categorized.eventBulk.events.filter((event) => !failedDocIds.has(event.documentId));
13572
+ if (categorized.eventBulk.events.length > 0 && categorized.newestRow) {
13573
+ const lastState = categorized.newestRow.document;
13574
+ categorized.eventBulk.checkpoint = {
13575
+ id: lastState[this.primaryPath],
13576
+ lwt: lastState._meta.lwt
13577
+ };
13578
+ this.changeStream$.next(categorized.eventBulk);
13579
+ }
13580
+ return { error: categorized.errors };
13581
+ });
13042
13582
  }
13043
13583
  async findDocumentsById(ids, withDeleted) {
13044
13584
  if (ids.length === 0)
@@ -13050,72 +13590,42 @@ class BunSQLiteStorageInstance {
13050
13590
  return rows.map((row) => JSON.parse(row.data));
13051
13591
  }
13052
13592
  async query(preparedQuery) {
13053
- try {
13054
- const { sql: whereClause, args } = buildWhereClause(preparedQuery.query.selector, this.schema);
13055
- const sql = `
13056
- SELECT json(data) as data FROM "${this.tableName}"
13057
- WHERE (${whereClause})
13058
- `;
13059
- if (process.env.DEBUG_QUERIES) {
13060
- const explainSql = `EXPLAIN QUERY PLAN ${sql}`;
13061
- const plan = this.stmtManager.all({ query: explainSql, params: args });
13062
- console.log("[DEBUG_QUERIES] Query plan:", JSON.stringify(plan, null, 2));
13063
- console.log("[DEBUG_QUERIES] SQL:", sql);
13064
- console.log("[DEBUG_QUERIES] Args:", args);
13065
- }
13066
- const rows = this.stmtManager.all({ query: sql, params: args });
13067
- let documents = rows.map((row) => JSON.parse(row.data));
13068
- if (preparedQuery.query.sort && preparedQuery.query.sort.length > 0) {
13069
- documents = this.sortDocuments(documents, preparedQuery.query.sort);
13070
- }
13071
- if (preparedQuery.query.skip) {
13072
- documents = documents.slice(preparedQuery.query.skip);
13073
- }
13074
- if (preparedQuery.query.limit) {
13075
- documents = documents.slice(0, preparedQuery.query.limit);
13076
- }
13077
- return { documents };
13078
- } catch (err) {
13079
- const query = `SELECT json(data) as data FROM "${this.tableName}"`;
13080
- const rows = this.stmtManager.all({ query, params: [] });
13081
- let documents = rows.map((row) => JSON.parse(row.data));
13082
- documents = documents.filter((doc) => this.matchesSelector(doc, preparedQuery.query.selector));
13083
- if (preparedQuery.query.sort && preparedQuery.query.sort.length > 0) {
13084
- documents = this.sortDocuments(documents, preparedQuery.query.sort);
13085
- }
13086
- if (preparedQuery.query.skip) {
13087
- documents = documents.slice(preparedQuery.query.skip);
13088
- }
13089
- if (preparedQuery.query.limit) {
13090
- documents = documents.slice(0, preparedQuery.query.limit);
13091
- }
13092
- return { documents };
13093
- }
13094
- }
13095
- matchesSelector(doc, selector) {
13096
- for (const [key, value] of Object.entries(selector)) {
13097
- const docValue = this.getNestedValue(doc, key);
13098
- if (typeof value === "object" && value !== null) {
13099
- for (const [op, opValue] of Object.entries(value)) {
13100
- if (op === "$eq" && docValue !== opValue)
13101
- return false;
13102
- if (op === "$ne" && docValue === opValue)
13103
- return false;
13104
- if (op === "$gt" && !(docValue > opValue))
13105
- return false;
13106
- if (op === "$gte" && !(docValue >= opValue))
13107
- return false;
13108
- if (op === "$lt" && !(docValue < opValue))
13109
- return false;
13110
- if (op === "$lte" && !(docValue <= opValue))
13111
- return false;
13112
- }
13113
- } else {
13114
- if (docValue !== value)
13115
- return false;
13116
- }
13117
- }
13118
- return true;
13593
+ const whereResult = buildWhereClause(preparedQuery.query.selector, this.schema, this.collectionName);
13594
+ if (!whereResult) {
13595
+ return this.queryWithOurMemory(preparedQuery);
13596
+ }
13597
+ const { sql: whereClause, args } = whereResult;
13598
+ let sql = `SELECT json(data) as data FROM "${this.tableName}" WHERE (${whereClause})`;
13599
+ const queryArgs = [...args];
13600
+ if (preparedQuery.query.sort && preparedQuery.query.sort.length > 0) {
13601
+ const orderBy = preparedQuery.query.sort.map((sortField) => {
13602
+ const [field, direction] = Object.entries(sortField)[0];
13603
+ const dir = direction === "asc" ? "ASC" : "DESC";
13604
+ return `json_extract(data, '$.${field}') ${dir}`;
13605
+ }).join(", ");
13606
+ sql += ` ORDER BY ${orderBy}`;
13607
+ }
13608
+ if (preparedQuery.query.limit) {
13609
+ sql += ` LIMIT ?`;
13610
+ queryArgs.push(preparedQuery.query.limit);
13611
+ }
13612
+ if (preparedQuery.query.skip) {
13613
+ if (!preparedQuery.query.limit) {
13614
+ sql += ` LIMIT -1`;
13615
+ }
13616
+ sql += ` OFFSET ?`;
13617
+ queryArgs.push(preparedQuery.query.skip);
13618
+ }
13619
+ if (process.env.DEBUG_QUERIES) {
13620
+ const explainSql = `EXPLAIN QUERY PLAN ${sql}`;
13621
+ const plan = this.stmtManager.all({ query: explainSql, params: queryArgs });
13622
+ console.log("[DEBUG_QUERIES] Query plan:", JSON.stringify(plan, null, 2));
13623
+ console.log("[DEBUG_QUERIES] SQL:", sql);
13624
+ console.log("[DEBUG_QUERIES] Args:", queryArgs);
13625
+ }
13626
+ const rows = this.stmtManager.all({ query: sql, params: queryArgs });
13627
+ const documents = rows.map((row) => JSON.parse(row.data));
13628
+ return { documents };
13119
13629
  }
13120
13630
  sortDocuments(docs, sort) {
13121
13631
  return docs.sort((a, b) => {
@@ -13135,9 +13645,18 @@ class BunSQLiteStorageInstance {
13135
13645
  return path2.split(".").reduce((current, key) => current?.[key], obj);
13136
13646
  }
13137
13647
  async count(preparedQuery) {
13138
- const result = await this.query(preparedQuery);
13648
+ const whereResult = buildWhereClause(preparedQuery.query.selector, this.schema, this.collectionName);
13649
+ if (!whereResult) {
13650
+ const allDocs = await this.queryWithOurMemory(preparedQuery);
13651
+ return {
13652
+ count: allDocs.documents.length,
13653
+ mode: "fast"
13654
+ };
13655
+ }
13656
+ const { sql, args } = whereResult;
13657
+ const result = this.db.query(`SELECT COUNT(*) as count FROM "${this.tableName}" WHERE (${sql})`).get(...args);
13139
13658
  return {
13140
- count: result.documents.length,
13659
+ count: result?.count ?? 0,
13141
13660
  mode: "fast"
13142
13661
  };
13143
13662
  }
@@ -13198,9 +13717,64 @@ class BunSQLiteStorageInstance {
13198
13717
  const rows = this.stmtManager.all({ query: sql, params: [checkpointLwt, checkpointLwt, checkpointId, limit] });
13199
13718
  const documents = rows.map((row) => JSON.parse(row.data));
13200
13719
  const lastDoc = documents[documents.length - 1];
13201
- const newCheckpoint = lastDoc ? { id: lastDoc[this.primaryPath], lwt: lastDoc._meta.lwt } : checkpoint ?? null;
13720
+ const newCheckpoint = lastDoc ? {
13721
+ id: String(lastDoc[this.primaryPath]),
13722
+ lwt: lastDoc._meta.lwt
13723
+ } : checkpoint ?? null;
13202
13724
  return { documents, checkpoint: newCheckpoint };
13203
13725
  }
13726
+ queryWithOurMemory(preparedQuery) {
13727
+ const query = `SELECT json(data) as data FROM "${this.tableName}"`;
13728
+ const selector = preparedQuery.query.selector;
13729
+ const hasSort = preparedQuery.query.sort && preparedQuery.query.sort.length > 0;
13730
+ if (hasSort) {
13731
+ const rows = this.stmtManager.all({ query, params: [] });
13732
+ let documents2 = rows.map((row) => JSON.parse(row.data));
13733
+ documents2 = documents2.filter((doc) => matchesSelector(doc, selector));
13734
+ documents2 = this.sortDocuments(documents2, preparedQuery.query.sort);
13735
+ if (preparedQuery.query.skip) {
13736
+ documents2 = documents2.slice(preparedQuery.query.skip);
13737
+ }
13738
+ if (preparedQuery.query.limit) {
13739
+ documents2 = documents2.slice(0, preparedQuery.query.limit);
13740
+ }
13741
+ return { documents: documents2 };
13742
+ }
13743
+ const stmt = this.db.prepare(query);
13744
+ const documents = [];
13745
+ const skip = preparedQuery.query.skip || 0;
13746
+ const limit = preparedQuery.query.limit;
13747
+ let skipped = 0;
13748
+ for (const row of stmt.iterate()) {
13749
+ const doc = JSON.parse(row.data);
13750
+ if (matchesSelector(doc, selector)) {
13751
+ if (skipped < skip) {
13752
+ skipped++;
13753
+ continue;
13754
+ }
13755
+ documents.push(doc);
13756
+ if (limit && documents.length >= limit) {
13757
+ break;
13758
+ }
13759
+ }
13760
+ }
13761
+ return { documents };
13762
+ }
13763
+ matchesRegexSelector(doc, selector) {
13764
+ for (const [field, value] of Object.entries(selector)) {
13765
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
13766
+ const ops = value;
13767
+ if (ops.$regex) {
13768
+ const fieldValue = this.getNestedValue(doc, field);
13769
+ const pattern = ops.$regex;
13770
+ const options = ops.$options;
13771
+ if (!matchesRegex(fieldValue, pattern, options))
13772
+ return false;
13773
+ }
13774
+ }
13775
+ }
13776
+ return true;
13777
+ }
13204
13778
  }
13205
13779
 
13206
13780
  // src/storage.ts