@peerbit/indexer-sqlite3 2.1.2 → 3.0.0-b712c6b
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.min.js +80 -48
- package/dist/index.min.js.map +2 -2
- package/dist/src/engine.d.ts +2 -0
- package/dist/src/engine.d.ts.map +1 -1
- package/dist/src/engine.js +81 -50
- package/dist/src/engine.js.map +1 -1
- package/dist/src/schema.d.ts.map +1 -1
- package/dist/src/schema.js.map +1 -1
- package/package.json +8 -8
- package/src/engine.ts +113 -77
- package/src/schema.ts +14 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peerbit/indexer-sqlite3",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0-b712c6b",
|
|
4
4
|
"description": "SQLite index for document store",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -61,20 +61,20 @@
|
|
|
61
61
|
"dependencies": {
|
|
62
62
|
"better-sqlite3": "^12.5.0",
|
|
63
63
|
"@dao-xyz/borsh": "^6.0.0",
|
|
64
|
+
"@peerbit/indexer-interface": "3.0.0-b712c6b",
|
|
65
|
+
"@peerbit/crypto": "3.0.0-b712c6b",
|
|
66
|
+
"@peerbit/time": "3.0.0-b712c6b",
|
|
64
67
|
"@sqlite.org/sqlite-wasm": "^3.51.1-build2",
|
|
65
68
|
"p-defer": "^4.0.0",
|
|
66
69
|
"uint8arrays": "^5.1.0",
|
|
67
70
|
"uuid": "^10.0.0",
|
|
68
|
-
"libsodium-wrappers": "0.7.15"
|
|
69
|
-
"@peerbit/indexer-interface": "2.1.1",
|
|
70
|
-
"@peerbit/crypto": "2.4.1",
|
|
71
|
-
"@peerbit/time": "2.3.0"
|
|
71
|
+
"libsodium-wrappers": "0.7.15"
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|
|
74
74
|
"@types/better-sqlite3": "^7.6.13",
|
|
75
|
-
"
|
|
76
|
-
"@peerbit/
|
|
77
|
-
"
|
|
75
|
+
"@peerbit/indexer-tests": "3.0.0-b712c6b",
|
|
76
|
+
"@peerbit/build-assets": "1.1.0-b712c6b",
|
|
77
|
+
"esbuild": "0.27.0"
|
|
78
78
|
},
|
|
79
79
|
"scripts": {
|
|
80
80
|
"clean": "aegir clean",
|
package/src/engine.ts
CHANGED
|
@@ -87,6 +87,38 @@ async function getIgnoreFK(stmt: Statement, values: any[]) {
|
|
|
87
87
|
export class SQLLiteIndex<T extends Record<string, any>>
|
|
88
88
|
implements Index<T, any>
|
|
89
89
|
{
|
|
90
|
+
// SQLite writes are inherently serialized per connection.
|
|
91
|
+
// We still need an explicit async barrier because our API is async and
|
|
92
|
+
// awaits between statements (root insert -> many child inserts). Without
|
|
93
|
+
// a barrier, concurrent `put()` and `del()` can interleave mid-insert and
|
|
94
|
+
// create large volumes of FK constraint noise (and occasional timeouts in
|
|
95
|
+
// browser/webworker runners).
|
|
96
|
+
// TODO(perf): This is intentionally coarse-grained for correctness.
|
|
97
|
+
// Possible optimizations:
|
|
98
|
+
// 1) wrap nested writes in explicit transactions to reduce lock time;
|
|
99
|
+
// 2) use table/key-scoped write queues when overlap detection is available.
|
|
100
|
+
// Any relaxation must keep concurrent put/del stability across all runners.
|
|
101
|
+
private _writeBarrier: Promise<void> = Promise.resolve();
|
|
102
|
+
|
|
103
|
+
private async withWriteBarrier<R>(fn: () => Promise<R>): Promise<R> {
|
|
104
|
+
const prev = this._writeBarrier;
|
|
105
|
+
let release!: () => void;
|
|
106
|
+
const next = new Promise<void>((r) => (release = r));
|
|
107
|
+
// Keep the chain alive even if `prev` rejected.
|
|
108
|
+
this._writeBarrier = prev.then(
|
|
109
|
+
() => next,
|
|
110
|
+
() => next,
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
// Wait for previous writer without propagating its error.
|
|
114
|
+
await prev.catch(() => undefined);
|
|
115
|
+
try {
|
|
116
|
+
return await fn();
|
|
117
|
+
} finally {
|
|
118
|
+
release();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
90
122
|
primaryKeyArr!: string[];
|
|
91
123
|
primaryKeyString!: string;
|
|
92
124
|
planner: QueryPlanner;
|
|
@@ -397,52 +429,54 @@ export class SQLLiteIndex<T extends Record<string, any>>
|
|
|
397
429
|
}
|
|
398
430
|
|
|
399
431
|
async put(value: T, _id?: any): Promise<void> {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
? await runIgnoreFK(statement, values)
|
|
412
|
-
: await statement.run(values);
|
|
413
|
-
return preId;
|
|
414
|
-
} else {
|
|
415
|
-
statement = this.properties.db.statements.get(
|
|
416
|
-
putStatementKey(table),
|
|
417
|
-
)!;
|
|
418
|
-
const out =
|
|
432
|
+
return this.withWriteBarrier(async () => {
|
|
433
|
+
const classOfValue = value.constructor as Constructor<T>;
|
|
434
|
+
return insert(
|
|
435
|
+
async (values, table) => {
|
|
436
|
+
let preId = values[table.primaryIndex];
|
|
437
|
+
let statement: Statement | undefined = undefined;
|
|
438
|
+
try {
|
|
439
|
+
if (preId != null) {
|
|
440
|
+
statement = this.properties.db.statements.get(
|
|
441
|
+
replaceStatementKey(table),
|
|
442
|
+
)!;
|
|
419
443
|
this.fkMode === "race-tolerant"
|
|
420
|
-
? await
|
|
421
|
-
: await statement.
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
444
|
+
? await runIgnoreFK(statement, values)
|
|
445
|
+
: await statement.run(values);
|
|
446
|
+
return preId;
|
|
447
|
+
} else {
|
|
448
|
+
statement = this.properties.db.statements.get(
|
|
449
|
+
putStatementKey(table),
|
|
450
|
+
)!;
|
|
451
|
+
const out =
|
|
452
|
+
this.fkMode === "race-tolerant"
|
|
453
|
+
? await getIgnoreFK(statement, values)
|
|
454
|
+
: await statement.get(values);
|
|
455
|
+
|
|
456
|
+
// TODO types
|
|
457
|
+
if (out == null) {
|
|
458
|
+
return undefined;
|
|
459
|
+
}
|
|
460
|
+
return out[table.primary as string];
|
|
426
461
|
}
|
|
427
|
-
|
|
462
|
+
} finally {
|
|
463
|
+
await statement?.reset?.();
|
|
428
464
|
}
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
}
|
|
432
|
-
},
|
|
433
|
-
value,
|
|
434
|
-
this.tables,
|
|
435
|
-
resolveTable(
|
|
436
|
-
this.scopeString ? [this.scopeString] : [],
|
|
465
|
+
},
|
|
466
|
+
value,
|
|
437
467
|
this.tables,
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
468
|
+
resolveTable(
|
|
469
|
+
this.scopeString ? [this.scopeString] : [],
|
|
470
|
+
this.tables,
|
|
471
|
+
classOfValue,
|
|
472
|
+
true,
|
|
473
|
+
),
|
|
474
|
+
getSchema(classOfValue).fields,
|
|
475
|
+
(_fn) => {
|
|
476
|
+
throw new Error("Unexpected");
|
|
477
|
+
},
|
|
478
|
+
);
|
|
479
|
+
});
|
|
446
480
|
}
|
|
447
481
|
|
|
448
482
|
iterate<S extends Shape | undefined>(
|
|
@@ -629,46 +663,48 @@ export class SQLLiteIndex<T extends Record<string, any>>
|
|
|
629
663
|
}
|
|
630
664
|
|
|
631
665
|
async del(query: types.DeleteOptions): Promise<types.IdKey[]> {
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
const stmt = await this.properties.db.prepare(sql, sql);
|
|
643
|
-
const results: any[] = await stmt.all(bindable);
|
|
644
|
-
|
|
645
|
-
// TODO types
|
|
646
|
-
for (const result of results) {
|
|
647
|
-
ret.push(
|
|
648
|
-
types.toId(
|
|
649
|
-
convertFromSQLType(
|
|
650
|
-
result[table.primary as string],
|
|
651
|
-
table.primaryField!.from!.type,
|
|
652
|
-
),
|
|
653
|
-
),
|
|
666
|
+
return this.withWriteBarrier(async () => {
|
|
667
|
+
let ret: types.IdKey[] = [];
|
|
668
|
+
let once = false;
|
|
669
|
+
let lastError: Error | undefined = undefined;
|
|
670
|
+
for (const table of this._rootTables) {
|
|
671
|
+
try {
|
|
672
|
+
const { sql, bindable } = convertDeleteRequestToQuery(
|
|
673
|
+
query,
|
|
674
|
+
this.tables,
|
|
675
|
+
table,
|
|
654
676
|
);
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
} catch (error) {
|
|
658
|
-
if (error instanceof MissingFieldError) {
|
|
659
|
-
lastError = error;
|
|
660
|
-
continue;
|
|
661
|
-
}
|
|
677
|
+
const stmt = await this.properties.db.prepare(sql, sql);
|
|
678
|
+
const results: any[] = await stmt.all(bindable);
|
|
662
679
|
|
|
663
|
-
|
|
680
|
+
// TODO types
|
|
681
|
+
for (const result of results) {
|
|
682
|
+
ret.push(
|
|
683
|
+
types.toId(
|
|
684
|
+
convertFromSQLType(
|
|
685
|
+
result[table.primary as string],
|
|
686
|
+
table.primaryField!.from!.type,
|
|
687
|
+
),
|
|
688
|
+
),
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
once = true;
|
|
692
|
+
} catch (error) {
|
|
693
|
+
if (error instanceof MissingFieldError) {
|
|
694
|
+
lastError = error;
|
|
695
|
+
continue;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
throw error;
|
|
699
|
+
}
|
|
664
700
|
}
|
|
665
|
-
}
|
|
666
701
|
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
702
|
+
if (!once) {
|
|
703
|
+
throw lastError!;
|
|
704
|
+
}
|
|
670
705
|
|
|
671
|
-
|
|
706
|
+
return ret;
|
|
707
|
+
});
|
|
672
708
|
}
|
|
673
709
|
|
|
674
710
|
async sum(query: types.SumOptions): Promise<number | bigint> {
|
package/src/schema.ts
CHANGED
|
@@ -222,7 +222,13 @@ const resolvePrimaryFieldInfoFromSchema = (
|
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
if (typeof fieldType === "string" || isUint8ArrayType(fieldType)) {
|
|
225
|
-
const sqlField = createScalarSQLField(
|
|
225
|
+
const sqlField = createScalarSQLField(
|
|
226
|
+
path,
|
|
227
|
+
field,
|
|
228
|
+
fieldType,
|
|
229
|
+
primary,
|
|
230
|
+
true,
|
|
231
|
+
);
|
|
226
232
|
if (sqlField.isPrimary) {
|
|
227
233
|
return {
|
|
228
234
|
name: sqlField.name,
|
|
@@ -545,7 +551,13 @@ export const getSQLFields = (
|
|
|
545
551
|
type: FieldType,
|
|
546
552
|
isOptional: boolean,
|
|
547
553
|
) => {
|
|
548
|
-
const sqlField = createScalarSQLField(
|
|
554
|
+
const sqlField = createScalarSQLField(
|
|
555
|
+
path,
|
|
556
|
+
field,
|
|
557
|
+
type,
|
|
558
|
+
primary,
|
|
559
|
+
isOptional,
|
|
560
|
+
);
|
|
549
561
|
foundPrimary = foundPrimary || sqlField.isPrimary;
|
|
550
562
|
sqlFields.push(sqlField);
|
|
551
563
|
};
|