@peerbit/indexer-sqlite3 1.2.26 → 1.2.27-a4bcfcf

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/src/engine.ts CHANGED
@@ -30,6 +30,7 @@ import {
30
30
  selectChildren,
31
31
  } from "./schema.js";
32
32
  import type { Database, Statement } from "./types.js";
33
+ import { isFKError } from "./utils.js";
33
34
 
34
35
  const escapePathToSQLName = (path: string[]) => {
35
36
  return path.map((x) => x.replace(/[^a-zA-Z0-9]/g, "_"));
@@ -40,6 +41,49 @@ const replaceStatementKey = (table: Table) => table.name + "_replicate";
40
41
  const resolveChildrenStatement = (table: Table) =>
41
42
  table.name + "_resolve_children";
42
43
 
44
+ type FKMode = "strict" | "race-tolerant";
45
+
46
+ async function safeReset(stmt?: Statement) {
47
+ if (!stmt || !stmt.reset) return;
48
+ try {
49
+ await stmt.reset();
50
+ } catch (e) {
51
+ if (isFKError(e)) return; // swallow FK-reset noise
52
+ throw e;
53
+ }
54
+ }
55
+
56
+ async function runIgnoreFK(stmt: Statement, values: any[]) {
57
+ try {
58
+ await stmt.run(values);
59
+ await safeReset(stmt); // success path: reset safely
60
+ return;
61
+ } catch (e) {
62
+ if (isFKError(e)) {
63
+ await safeReset(stmt); // swallow FK + swallow reset error
64
+ return; // pretend no-op
65
+ }
66
+ // real error
67
+ await safeReset(stmt); // best effort
68
+ throw e;
69
+ }
70
+ }
71
+
72
+ async function getIgnoreFK(stmt: Statement, values: any[]) {
73
+ try {
74
+ const out = await stmt.get(values);
75
+ await safeReset(stmt); // success path
76
+ return out;
77
+ } catch (e) {
78
+ if (isFKError(e)) {
79
+ await safeReset(stmt); // swallow FK + reset error
80
+ return undefined;
81
+ }
82
+ await safeReset(stmt);
83
+ throw e;
84
+ }
85
+ }
86
+
43
87
  export class SQLLiteIndex<T extends Record<string, any>>
44
88
  implements Index<T, any>
45
89
  {
@@ -61,6 +105,7 @@ export class SQLLiteIndex<T extends Record<string, any>>
61
105
 
62
106
  iteratorTimeout: number;
63
107
  closed: boolean = true;
108
+ private fkMode: FKMode;
64
109
 
65
110
  id: string;
66
111
  constructor(
@@ -71,8 +116,9 @@ export class SQLLiteIndex<T extends Record<string, any>>
71
116
  start?: () => Promise<void> | void;
72
117
  stop?: () => Promise<void> | void;
73
118
  },
74
- options?: { iteratorTimeout?: number },
119
+ options?: { iteratorTimeout?: number; fkMode?: FKMode },
75
120
  ) {
121
+ this.fkMode = options?.fkMode || "race-tolerant";
76
122
  this.closed = true;
77
123
  this.id = uuid();
78
124
  this.scopeString =
@@ -347,26 +393,33 @@ export class SQLLiteIndex<T extends Record<string, any>>
347
393
  return insert(
348
394
  async (values, table) => {
349
395
  let preId = values[table.primaryIndex];
350
-
351
- if (preId != null) {
352
- const statement = this.properties.db.statements.get(
353
- replaceStatementKey(table),
354
- )!;
355
- await statement.run(values);
356
- await statement.reset?.();
357
- return preId;
358
- } else {
359
- const statement = this.properties.db.statements.get(
360
- putStatementKey(table),
361
- )!;
362
- const out = await statement.get(values);
363
- await statement.reset?.();
364
-
365
- // TODO types
366
- if (out == null) {
367
- return undefined;
396
+ let statement: Statement | undefined = undefined;
397
+ try {
398
+ if (preId != null) {
399
+ statement = this.properties.db.statements.get(
400
+ replaceStatementKey(table),
401
+ )!;
402
+ this.fkMode === "race-tolerant"
403
+ ? await runIgnoreFK(statement, values)
404
+ : await statement.run(values);
405
+ return preId;
406
+ } else {
407
+ statement = this.properties.db.statements.get(
408
+ putStatementKey(table),
409
+ )!;
410
+ const out =
411
+ this.fkMode === "race-tolerant"
412
+ ? await getIgnoreFK(statement, values)
413
+ : await statement.get(values);
414
+
415
+ // TODO types
416
+ if (out == null) {
417
+ return undefined;
418
+ }
419
+ return out[table.primary as string];
368
420
  }
369
- return out[table.primary as string];
421
+ } finally {
422
+ await statement?.reset?.();
370
423
  }
371
424
  },
372
425
  value,
@@ -441,8 +494,6 @@ export class SQLLiteIndex<T extends Record<string, any>>
441
494
 
442
495
  once = true;
443
496
 
444
- /* console.log("----------------------")
445
- console.log(sqlFetch); */
446
497
  const allResults = await planningScope.perform(async () => {
447
498
  const allResults: Record<string, any>[] = await stmt.all([
448
499
  ...bindable,
@@ -585,7 +636,14 @@ export class SQLLiteIndex<T extends Record<string, any>>
585
636
 
586
637
  // TODO types
587
638
  for (const result of results) {
588
- ret.push(types.toId(result[table.primary as string]));
639
+ ret.push(
640
+ types.toId(
641
+ convertFromSQLType(
642
+ result[table.primary as string],
643
+ table.primaryField!.from!.type,
644
+ ),
645
+ ),
646
+ );
589
647
  }
590
648
  once = true;
591
649
  } catch (error) {
package/src/schema.ts CHANGED
@@ -96,7 +96,7 @@ export class MissingFieldError extends Error {
96
96
 
97
97
  export const convertFromSQLType = (
98
98
  value: boolean | bigint | string | number | Uint8Array,
99
- type?: FieldType,
99
+ type: FieldType | undefined,
100
100
  ) => {
101
101
  if (type === "bool") {
102
102
  if (
@@ -341,7 +341,7 @@ export const getSQLFields = (
341
341
  ? sqlFields.find((field) => field.name === primary)
342
342
  : undefined;
343
343
  const parentPrimaryFieldName =
344
- parentPrimaryField?.key || CHILD_TABLE_ID;
344
+ parentPrimaryField?.name || CHILD_TABLE_ID;
345
345
  const parentPrimaryFieldType = parentPrimaryField
346
346
  ? parentPrimaryField.type
347
347
  : "INTEGER";
@@ -707,7 +707,9 @@ export const insert = async (
707
707
  tables: Map<string, Table>,
708
708
  table: Table,
709
709
  fields: Field[],
710
- handleNestedCallback?: (cb: (parentId: any) => Promise<void>) => void,
710
+ handleNestedCallback?: (
711
+ cb: (parentId: any) => Promise<void>,
712
+ ) => Promise<void> | void | number,
711
713
  parentId: any = undefined,
712
714
  index?: number,
713
715
  ): Promise<void> => {
@@ -843,7 +845,7 @@ export const insert = async (
843
845
  // we need to know the id of the parent document to insert the foreign key correctly
844
846
  for (const nested of nestedFields) {
845
847
  const isOptional = nested.type instanceof OptionKind;
846
- handleNestedCallback!((id) => handleNested(nested, isOptional, id));
848
+ await handleNestedCallback!((id) => handleNested(nested, isOptional, id));
847
849
  }
848
850
 
849
851
  const thisId = await insertFn(bindableValues, table);
@@ -236,7 +236,7 @@ const init = async (): Promise<DatabaseCreator> => {
236
236
 
237
237
  const resolver = resolvers[message.id];
238
238
  if (message.type === "error") {
239
- resolver.reject(message.message);
239
+ resolver.reject(new Error(message.message));
240
240
  } else if (message.type === "response") {
241
241
  resolver.resolve(message.result);
242
242
  }
@@ -66,7 +66,29 @@ class Statement implements IStatement {
66
66
  }
67
67
 
68
68
  async reset() {
69
- await this.statement.reset();
69
+ try {
70
+ await this.statement.reset(); // this returns the last rc
71
+ } catch (e: any) {
72
+ // sqlite3_reset() can surface the prior step/exec rc.
73
+ // The statement *is* reset; ignore benign codes.
74
+ const msg = e?.message || "";
75
+ const rc = e?.rc;
76
+ const code = e?.code;
77
+
78
+ const isFk =
79
+ code === "SQLITE_CONSTRAINT_FOREIGNKEY" ||
80
+ rc === 787 ||
81
+ msg.includes("SQLITE_CONSTRAINT_FOREIGNKEY") ||
82
+ msg.includes("FOREIGN KEY constraint failed");
83
+
84
+ const isBusy = code === "SQLITE_BUSY" || rc === 5; // optional
85
+
86
+ if (isFk || isBusy) {
87
+ return this as IStatement; // swallow; stmt is reset
88
+ }
89
+ // (keep throwing for other unexpected errors)
90
+ throw e;
91
+ }
70
92
  return this as IStatement;
71
93
  }
72
94
 
package/src/utils.ts ADDED
@@ -0,0 +1,9 @@
1
+ export const isFKError = (e: any) => {
2
+ return (
3
+ e?.code === "SQLITE_CONSTRAINT_FOREIGNKEY" ||
4
+ e?.rc === 787 ||
5
+ (e?.message &&
6
+ (e.message.includes("SQLITE_CONSTRAINT_FOREIGNKEY") ||
7
+ e.message.includes("FOREIGN KEY constraint failed")))
8
+ );
9
+ };