@peerbit/indexer-sqlite3 2.1.1 → 2.1.2-000e3f1
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 +85 -49
- 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 +9 -1
- package/dist/src/schema.js.map +1 -1
- package/package.json +8 -8
- package/src/engine.ts +113 -77
- package/src/schema.ts +22 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peerbit/indexer-sqlite3",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2-000e3f1",
|
|
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": "2.1.1-000e3f1",
|
|
65
|
+
"@peerbit/crypto": "2.4.1-000e3f1",
|
|
66
|
+
"@peerbit/time": "2.3.0-000e3f1",
|
|
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/crypto": "2.4.1",
|
|
70
|
-
"@peerbit/indexer-interface": "2.1.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": "2.0.1-000e3f1",
|
|
76
|
+
"@peerbit/build-assets": "1.1.0-000e3f1",
|
|
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
|
@@ -207,7 +207,14 @@ const resolvePrimaryFieldInfoFromSchema = (
|
|
|
207
207
|
fieldType = fieldType.elementType;
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
|
|
210
|
+
// fixedArray(u8, N) represents bytes and must map to BLOB in SQL. Note that
|
|
211
|
+
// FixedArrayKind is also a WrappedType, so unwrapNestedType() would otherwise
|
|
212
|
+
// turn it into the scalar "u8" and incorrectly treat it as INTEGER.
|
|
213
|
+
if (fieldType instanceof FixedArrayKind && fieldType.elementType === "u8") {
|
|
214
|
+
fieldType = Uint8Array;
|
|
215
|
+
} else {
|
|
216
|
+
fieldType = unwrapNestedType(fieldType);
|
|
217
|
+
}
|
|
211
218
|
|
|
212
219
|
// Arrays are always stored in separate tables.
|
|
213
220
|
if (fieldType instanceof VecKind) {
|
|
@@ -215,7 +222,13 @@ const resolvePrimaryFieldInfoFromSchema = (
|
|
|
215
222
|
}
|
|
216
223
|
|
|
217
224
|
if (typeof fieldType === "string" || isUint8ArrayType(fieldType)) {
|
|
218
|
-
const sqlField = createScalarSQLField(
|
|
225
|
+
const sqlField = createScalarSQLField(
|
|
226
|
+
path,
|
|
227
|
+
field,
|
|
228
|
+
fieldType,
|
|
229
|
+
primary,
|
|
230
|
+
true,
|
|
231
|
+
);
|
|
219
232
|
if (sqlField.isPrimary) {
|
|
220
233
|
return {
|
|
221
234
|
name: sqlField.name,
|
|
@@ -538,7 +551,13 @@ export const getSQLFields = (
|
|
|
538
551
|
type: FieldType,
|
|
539
552
|
isOptional: boolean,
|
|
540
553
|
) => {
|
|
541
|
-
const sqlField = createScalarSQLField(
|
|
554
|
+
const sqlField = createScalarSQLField(
|
|
555
|
+
path,
|
|
556
|
+
field,
|
|
557
|
+
type,
|
|
558
|
+
primary,
|
|
559
|
+
isOptional,
|
|
560
|
+
);
|
|
542
561
|
foundPrimary = foundPrimary || sqlField.isPrimary;
|
|
543
562
|
sqlFields.push(sqlField);
|
|
544
563
|
};
|