@peerbit/indexer-sqlite3 0.0.1-cccc078
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +111 -0
- package/dist/benchmark/index.d.ts +2 -0
- package/dist/benchmark/index.d.ts.map +1 -0
- package/dist/benchmark/index.js +6 -0
- package/dist/benchmark/index.js.map +1 -0
- package/dist/peerbit/sqlite3-bundler-friendly.mjs +14481 -0
- package/dist/peerbit/sqlite3-node.mjs +12561 -0
- package/dist/peerbit/sqlite3-opfs-async-proxy.js +826 -0
- package/dist/peerbit/sqlite3-worker1-bundler-friendly.mjs +35 -0
- package/dist/peerbit/sqlite3-worker1-promiser.js +193 -0
- package/dist/peerbit/sqlite3-worker1-promiser.mjs +187 -0
- package/dist/peerbit/sqlite3-worker1.js +46 -0
- package/dist/peerbit/sqlite3.js +14520 -0
- package/dist/peerbit/sqlite3.min.js +21695 -0
- package/dist/peerbit/sqlite3.mjs +14483 -0
- package/dist/peerbit/sqlite3.wasm +0 -0
- package/dist/peerbit/sqlite3.worker.min.js +17995 -0
- package/dist/src/engine.d.ts +90 -0
- package/dist/src/engine.d.ts.map +1 -0
- package/dist/src/engine.js +414 -0
- package/dist/src/engine.js.map +1 -0
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +15 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/schema.d.ts +73 -0
- package/dist/src/schema.d.ts.map +1 -0
- package/dist/src/schema.js +1075 -0
- package/dist/src/schema.js.map +1 -0
- package/dist/src/sqlite3-messages.worker.d.ts +86 -0
- package/dist/src/sqlite3-messages.worker.d.ts.map +1 -0
- package/dist/src/sqlite3-messages.worker.js +9 -0
- package/dist/src/sqlite3-messages.worker.js.map +1 -0
- package/dist/src/sqlite3.browser.d.ts +4 -0
- package/dist/src/sqlite3.browser.d.ts.map +1 -0
- package/dist/src/sqlite3.browser.js +181 -0
- package/dist/src/sqlite3.browser.js.map +1 -0
- package/dist/src/sqlite3.d.ts +4 -0
- package/dist/src/sqlite3.d.ts.map +1 -0
- package/dist/src/sqlite3.js +51 -0
- package/dist/src/sqlite3.js.map +1 -0
- package/dist/src/sqlite3.wasm.d.ts +30 -0
- package/dist/src/sqlite3.wasm.d.ts.map +1 -0
- package/dist/src/sqlite3.wasm.js +180 -0
- package/dist/src/sqlite3.wasm.js.map +1 -0
- package/dist/src/sqlite3.worker.d.ts +2 -0
- package/dist/src/sqlite3.worker.d.ts.map +1 -0
- package/dist/src/sqlite3.worker.js +105 -0
- package/dist/src/sqlite3.worker.js.map +1 -0
- package/dist/src/types.d.ts +23 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/package.json +80 -0
- package/src/engine.ts +639 -0
- package/src/index.ts +16 -0
- package/src/schema.ts +1607 -0
- package/src/sqlite3-messages.worker.ts +123 -0
- package/src/sqlite3.browser.ts +245 -0
- package/src/sqlite3.ts +56 -0
- package/src/sqlite3.wasm.ts +211 -0
- package/src/sqlite3.worker.ts +109 -0
- package/src/types.ts +39 -0
package/src/engine.ts
ADDED
|
@@ -0,0 +1,639 @@
|
|
|
1
|
+
import { type AbstractType, type Constructor, getSchema } from "@dao-xyz/borsh";
|
|
2
|
+
import type {
|
|
3
|
+
CloseIteratorRequest,
|
|
4
|
+
CollectNextRequest,
|
|
5
|
+
Index,
|
|
6
|
+
IndexEngineInitProperties,
|
|
7
|
+
IndexedResult,
|
|
8
|
+
IndexedResults,
|
|
9
|
+
SearchRequest,
|
|
10
|
+
Shape,
|
|
11
|
+
} from "@peerbit/indexer-interface";
|
|
12
|
+
import * as types from "@peerbit/indexer-interface";
|
|
13
|
+
import { v4 as uuid } from "uuid";
|
|
14
|
+
import {
|
|
15
|
+
type Table,
|
|
16
|
+
buildJoin,
|
|
17
|
+
convertCountRequestToQuery,
|
|
18
|
+
convertDeleteRequestToQuery,
|
|
19
|
+
convertSearchRequestToQuery,
|
|
20
|
+
/* getTableName, */
|
|
21
|
+
convertSumRequestToQuery,
|
|
22
|
+
escapeColumnName,
|
|
23
|
+
getInlineTableFieldName,
|
|
24
|
+
getSQLTable,
|
|
25
|
+
getTablePrefixedField,
|
|
26
|
+
insert,
|
|
27
|
+
resolveInstanceFromValue,
|
|
28
|
+
resolveTable,
|
|
29
|
+
selectAllFields,
|
|
30
|
+
selectChildren,
|
|
31
|
+
} from "./schema.js";
|
|
32
|
+
import type { Database, Statement } from "./types.js";
|
|
33
|
+
|
|
34
|
+
const escapePathToSQLName = (path: string[]) => {
|
|
35
|
+
return path.map((x) => x.replace(/[^a-zA-Z0-9]/g, "_"));
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export class SQLLiteIndex<T extends Record<string, any>>
|
|
39
|
+
implements Index<T, any>
|
|
40
|
+
{
|
|
41
|
+
primaryKeyArr: string[];
|
|
42
|
+
primaryKeyString: string;
|
|
43
|
+
putStatement: Map<string, Statement>;
|
|
44
|
+
replaceStatement: Map<string, Statement>;
|
|
45
|
+
resolveChildrenStatement: Map<string, Statement>;
|
|
46
|
+
private scopeString?: string;
|
|
47
|
+
private _rootTables: Table[];
|
|
48
|
+
private _tables: Map<string, Table>;
|
|
49
|
+
private _cursor: Map<
|
|
50
|
+
string,
|
|
51
|
+
{
|
|
52
|
+
kept: number;
|
|
53
|
+
fetch: (
|
|
54
|
+
amount: number,
|
|
55
|
+
) => Promise<{ results: IndexedResult[]; kept: number }>;
|
|
56
|
+
fetchStatement: Statement;
|
|
57
|
+
/* countStatement: Statement; */
|
|
58
|
+
timeout: ReturnType<typeof setTimeout>;
|
|
59
|
+
}
|
|
60
|
+
>; // TODO choose limit better
|
|
61
|
+
|
|
62
|
+
iteratorTimeout: number;
|
|
63
|
+
closed: boolean = true;
|
|
64
|
+
|
|
65
|
+
id: string;
|
|
66
|
+
constructor(
|
|
67
|
+
readonly properties: {
|
|
68
|
+
scope: string[];
|
|
69
|
+
db: Database;
|
|
70
|
+
schema: AbstractType<any>;
|
|
71
|
+
start?: () => Promise<void> | void;
|
|
72
|
+
stop?: () => Promise<void> | void;
|
|
73
|
+
},
|
|
74
|
+
options?: { iteratorTimeout?: number },
|
|
75
|
+
) {
|
|
76
|
+
this.closed = true;
|
|
77
|
+
this.id = uuid();
|
|
78
|
+
this.scopeString =
|
|
79
|
+
properties.scope.length > 0
|
|
80
|
+
? "_" + escapePathToSQLName(properties.scope).join("_")
|
|
81
|
+
: undefined;
|
|
82
|
+
this.iteratorTimeout = options?.iteratorTimeout || 60e3;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
get tables() {
|
|
86
|
+
if (this.closed) {
|
|
87
|
+
throw new Error("Not started");
|
|
88
|
+
}
|
|
89
|
+
return this._tables;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
get rootTables() {
|
|
93
|
+
if (this.closed) {
|
|
94
|
+
throw new Error("Not started");
|
|
95
|
+
}
|
|
96
|
+
return this._rootTables;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
get cursor() {
|
|
100
|
+
if (this.closed) {
|
|
101
|
+
throw new Error("Not started");
|
|
102
|
+
}
|
|
103
|
+
return this._cursor;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
init(properties: IndexEngineInitProperties<T, any>) {
|
|
107
|
+
if (properties.indexBy) {
|
|
108
|
+
this.primaryKeyArr = Array.isArray(properties.indexBy)
|
|
109
|
+
? properties.indexBy
|
|
110
|
+
: [properties.indexBy];
|
|
111
|
+
} else {
|
|
112
|
+
const indexBy = types.getIdProperty(properties.schema);
|
|
113
|
+
|
|
114
|
+
if (!indexBy) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
"No indexBy property defined nor schema has a property decorated with `id()`",
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
this.primaryKeyArr = indexBy;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!this.properties.schema) {
|
|
124
|
+
throw new Error("Missing schema");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
this.primaryKeyString = getInlineTableFieldName(
|
|
128
|
+
this.primaryKeyArr.slice(0, this.primaryKeyArr.length - 1),
|
|
129
|
+
this.primaryKeyArr[this.primaryKeyArr.length - 1],
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
return this;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async start(): Promise<void> {
|
|
136
|
+
if (this.closed === false) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (this.primaryKeyArr == null || this.primaryKeyArr.length === 0) {
|
|
141
|
+
throw new Error("Not initialized");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
await this.properties.start?.();
|
|
145
|
+
|
|
146
|
+
this.putStatement = new Map();
|
|
147
|
+
this.replaceStatement = new Map();
|
|
148
|
+
this.resolveChildrenStatement = new Map();
|
|
149
|
+
this._tables = new Map();
|
|
150
|
+
this._cursor = new Map();
|
|
151
|
+
|
|
152
|
+
const tables = getSQLTable(
|
|
153
|
+
this.properties.schema!,
|
|
154
|
+
this.scopeString ? [this.scopeString] : [],
|
|
155
|
+
getInlineTableFieldName(
|
|
156
|
+
this.primaryKeyArr.slice(0, -1),
|
|
157
|
+
this.primaryKeyArr[this.primaryKeyArr.length - 1],
|
|
158
|
+
), // TODO fix this, should be array
|
|
159
|
+
false,
|
|
160
|
+
undefined,
|
|
161
|
+
false,
|
|
162
|
+
/* getTableName(this.scopeString, this.properties.schema!) */
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
this._rootTables = tables.filter((x) => x.parent == null);
|
|
166
|
+
|
|
167
|
+
if (this._rootTables.length > 1) {
|
|
168
|
+
throw new Error("Multiple root tables not supported (yet)");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const allTables = tables;
|
|
172
|
+
|
|
173
|
+
for (const table of allTables) {
|
|
174
|
+
this._tables.set(table.name, table);
|
|
175
|
+
|
|
176
|
+
for (const child of table.children) {
|
|
177
|
+
allTables.push(child);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (table.inline) {
|
|
181
|
+
// this table does not 'really' exist as a separate table
|
|
182
|
+
// but its fields are in the root table
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const sqlCreateTable = `create table if not exists ${table.name} (${[...table.fields, ...table.constraints].map((s) => s.definition).join(", ")}) strict`;
|
|
187
|
+
const sqlCreateIndex = `create index if not exists ${table.name}_index on ${table.name} (${table.fields.map((field) => escapeColumnName(field.name)).join(", ")})`;
|
|
188
|
+
|
|
189
|
+
this.properties.db.exec(sqlCreateTable);
|
|
190
|
+
this.properties.db.exec(sqlCreateIndex);
|
|
191
|
+
|
|
192
|
+
// put and return the id
|
|
193
|
+
let sqlPut = `insert into ${table.name} (${table.fields.map((field) => escapeColumnName(field.name)).join(", ")}) VALUES (${table.fields.map((_x) => "?").join(", ")}) RETURNING ${table.primary};`;
|
|
194
|
+
|
|
195
|
+
// insert or replace with id already defined
|
|
196
|
+
let sqlReplace = `insert or replace into ${table.name} (${table.fields.map((field) => escapeColumnName(field.name)).join(", ")}) VALUES (${table.fields.map((_x) => "?").join(", ")});`;
|
|
197
|
+
|
|
198
|
+
this.putStatement.set(
|
|
199
|
+
table.name,
|
|
200
|
+
await this.properties.db.prepare(sqlPut),
|
|
201
|
+
);
|
|
202
|
+
this.replaceStatement.set(
|
|
203
|
+
table.name,
|
|
204
|
+
await this.properties.db.prepare(sqlReplace),
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
if (table.parent) {
|
|
208
|
+
this.resolveChildrenStatement.set(
|
|
209
|
+
table.name,
|
|
210
|
+
await this.properties.db.prepare(selectChildren(table)),
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
this.closed = false;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private async clearStatements() {
|
|
219
|
+
if ((await this.properties.db.status()) === "closed") {
|
|
220
|
+
// TODO this should never be true, but if we remove this statement the tests faiL for browser tests?
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
for (const [_k, v] of this.putStatement) {
|
|
225
|
+
await v.finalize?.();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
for (const [_k, v] of this.replaceStatement) {
|
|
229
|
+
await v.finalize?.();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
for (const [_k, v] of this.resolveChildrenStatement) {
|
|
233
|
+
await v.finalize?.();
|
|
234
|
+
}
|
|
235
|
+
this.putStatement.clear();
|
|
236
|
+
this.replaceStatement.clear();
|
|
237
|
+
this.resolveChildrenStatement.clear();
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async stop(): Promise<void> {
|
|
241
|
+
if (this.closed) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
this.closed = true;
|
|
245
|
+
|
|
246
|
+
await this.clearStatements();
|
|
247
|
+
|
|
248
|
+
this._tables.clear();
|
|
249
|
+
|
|
250
|
+
for (const [k, _v] of this._cursor) {
|
|
251
|
+
await this.clearupIterator(k);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async drop(): Promise<void> {
|
|
256
|
+
this.closed = true;
|
|
257
|
+
|
|
258
|
+
await this.clearStatements();
|
|
259
|
+
|
|
260
|
+
// drop root table and cascade
|
|
261
|
+
// drop table faster by dropping constraints first
|
|
262
|
+
for (const table of this._rootTables) {
|
|
263
|
+
await this.properties.db.exec(`drop table if exists ${table.name}`);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
this._tables.clear();
|
|
267
|
+
|
|
268
|
+
for (const [k, _v] of this._cursor) {
|
|
269
|
+
await this.clearupIterator(k);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
private async resolveDependencies(
|
|
274
|
+
parentId: any,
|
|
275
|
+
table: Table,
|
|
276
|
+
): Promise<any[]> {
|
|
277
|
+
const stmt = this.resolveChildrenStatement.get(table.name)!;
|
|
278
|
+
const results = await stmt.all([parentId]);
|
|
279
|
+
await stmt.reset?.();
|
|
280
|
+
return results;
|
|
281
|
+
}
|
|
282
|
+
async get(
|
|
283
|
+
id: types.IdKey,
|
|
284
|
+
options?: { shape: Shape },
|
|
285
|
+
): Promise<IndexedResult<T> | undefined> {
|
|
286
|
+
for (const table of this._rootTables) {
|
|
287
|
+
const { join: joinMap, query } = selectAllFields(table, options?.shape);
|
|
288
|
+
const sql = `${query} ${buildJoin(joinMap, true)} where ${this.primaryKeyString} = ? `;
|
|
289
|
+
const stmt = await this.properties.db.prepare(sql);
|
|
290
|
+
const rows = await stmt.get([id.key]);
|
|
291
|
+
await stmt.finalize?.();
|
|
292
|
+
if (!rows) {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
return {
|
|
296
|
+
value: (await resolveInstanceFromValue(
|
|
297
|
+
rows,
|
|
298
|
+
this.tables,
|
|
299
|
+
table,
|
|
300
|
+
this.resolveDependencies.bind(this),
|
|
301
|
+
true,
|
|
302
|
+
options?.shape,
|
|
303
|
+
)) as unknown as T,
|
|
304
|
+
id,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
return undefined;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async put(value: T, _id?: any): Promise<void> {
|
|
311
|
+
const classOfValue = value.constructor as Constructor<T>;
|
|
312
|
+
return insert(
|
|
313
|
+
async (values, table) => {
|
|
314
|
+
const preId = values[table.primaryIndex];
|
|
315
|
+
|
|
316
|
+
if (preId != null) {
|
|
317
|
+
const statement = this.replaceStatement.get(table.name)!;
|
|
318
|
+
await statement.run(
|
|
319
|
+
values.map((x) => (typeof x === "boolean" ? (x ? 1 : 0) : x)),
|
|
320
|
+
);
|
|
321
|
+
await statement.reset?.();
|
|
322
|
+
return preId;
|
|
323
|
+
} else {
|
|
324
|
+
const statement = this.putStatement.get(table.name)!;
|
|
325
|
+
const out = await statement.get(
|
|
326
|
+
values.map((x) => (typeof x === "boolean" ? (x ? 1 : 0) : x)),
|
|
327
|
+
);
|
|
328
|
+
await statement.reset?.();
|
|
329
|
+
|
|
330
|
+
// TODO types
|
|
331
|
+
return out[table.primary as string];
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
value,
|
|
335
|
+
this.tables,
|
|
336
|
+
resolveTable(
|
|
337
|
+
this.scopeString ? [this.scopeString] : [],
|
|
338
|
+
this.tables,
|
|
339
|
+
classOfValue,
|
|
340
|
+
true,
|
|
341
|
+
),
|
|
342
|
+
getSchema(classOfValue).fields,
|
|
343
|
+
(_fn) => {
|
|
344
|
+
throw new Error("Unexpected");
|
|
345
|
+
},
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async query(
|
|
350
|
+
request: SearchRequest,
|
|
351
|
+
options?: { shape: Shape },
|
|
352
|
+
): Promise<IndexedResults<T>> {
|
|
353
|
+
// create a sql statement where the offset and the limit id dynamic and can be updated
|
|
354
|
+
// TODO don't use offset but sort and limit 'next' calls by the last value of the sort
|
|
355
|
+
let sqlFetch = convertSearchRequestToQuery(
|
|
356
|
+
request,
|
|
357
|
+
this.tables,
|
|
358
|
+
this._rootTables,
|
|
359
|
+
options?.shape,
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
const stmt = await this.properties.db.prepare(sqlFetch);
|
|
363
|
+
/* const totalCountKey = "count"; */
|
|
364
|
+
/* const sqlTotalCount = convertCountRequestToQuery(new types.CountRequest({ query: request.query }), this.tables, this.tables.get(this.rootTableName)!)
|
|
365
|
+
const countStmt = await this.properties.db.prepare(sqlTotalCount); */
|
|
366
|
+
|
|
367
|
+
let offset = 0;
|
|
368
|
+
let first = false;
|
|
369
|
+
|
|
370
|
+
const fetch = async (amount: number) => {
|
|
371
|
+
if (!first) {
|
|
372
|
+
stmt.reset?.();
|
|
373
|
+
/* countStmt.reset?.(); */
|
|
374
|
+
|
|
375
|
+
// Bump timeout timer
|
|
376
|
+
clearTimeout(iterator.timeout);
|
|
377
|
+
iterator.timeout = setTimeout(
|
|
378
|
+
() => this.clearupIterator(request.idString),
|
|
379
|
+
this.iteratorTimeout,
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
first = true;
|
|
384
|
+
const offsetStart = offset;
|
|
385
|
+
const allResults: Record<string, any>[] = await stmt.all([
|
|
386
|
+
amount,
|
|
387
|
+
offsetStart,
|
|
388
|
+
]);
|
|
389
|
+
|
|
390
|
+
let results: IndexedResult<T>[] = await Promise.all(
|
|
391
|
+
allResults.map(async (row: any) => {
|
|
392
|
+
let selectedTable = this._rootTables.find(
|
|
393
|
+
(table) =>
|
|
394
|
+
row[getTablePrefixedField(table, this.primaryKeyString)] != null,
|
|
395
|
+
)!;
|
|
396
|
+
const value = await resolveInstanceFromValue<T>(
|
|
397
|
+
row,
|
|
398
|
+
this.tables,
|
|
399
|
+
selectedTable,
|
|
400
|
+
this.resolveDependencies.bind(this),
|
|
401
|
+
true,
|
|
402
|
+
options?.shape,
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
return {
|
|
406
|
+
value,
|
|
407
|
+
id: types.toId(
|
|
408
|
+
row[getTablePrefixedField(selectedTable, this.primaryKeyString)],
|
|
409
|
+
),
|
|
410
|
+
};
|
|
411
|
+
}),
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
offset += amount;
|
|
415
|
+
|
|
416
|
+
if (results.length > 0) {
|
|
417
|
+
const totalCount = await this.count(
|
|
418
|
+
new types.CountRequest({ query: request.query }),
|
|
419
|
+
); /* (await countStmt.get())[totalCountKey] as number; */
|
|
420
|
+
iterator.kept = totalCount - results.length - offsetStart;
|
|
421
|
+
} else {
|
|
422
|
+
iterator.kept = 0;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (iterator.kept === 0) {
|
|
426
|
+
await this.clearupIterator(request.idString);
|
|
427
|
+
clearTimeout(iterator.timeout);
|
|
428
|
+
}
|
|
429
|
+
return { results, kept: iterator.kept };
|
|
430
|
+
};
|
|
431
|
+
const iterator = {
|
|
432
|
+
kept: 0,
|
|
433
|
+
fetch,
|
|
434
|
+
fetchStatement: stmt,
|
|
435
|
+
/* countStatement: countStmt, */
|
|
436
|
+
timeout: setTimeout(
|
|
437
|
+
() => this.clearupIterator(request.idString),
|
|
438
|
+
this.iteratorTimeout,
|
|
439
|
+
),
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
this.cursor.set(request.idString, iterator);
|
|
443
|
+
return fetch(request.fetch);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
next(query: CollectNextRequest): Promise<IndexedResults<T>> {
|
|
447
|
+
const cache = this.cursor.get(query.idString);
|
|
448
|
+
if (!cache) {
|
|
449
|
+
throw new Error("No cursor found with id: " + query.idString);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// reuse statement
|
|
453
|
+
return cache.fetch(query.amount) as Promise<IndexedResults<T>>;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
close(query: CloseIteratorRequest): void | Promise<void> {
|
|
457
|
+
return this.clearupIterator(query.idString);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
private async clearupIterator(id: string) {
|
|
461
|
+
const cache = this._cursor.get(id);
|
|
462
|
+
if (!cache) {
|
|
463
|
+
return; // already cleared
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
clearTimeout(cache.timeout);
|
|
467
|
+
/* cache.countStatement.finalize?.(); */
|
|
468
|
+
await cache.fetchStatement.finalize?.();
|
|
469
|
+
this._cursor.delete(id);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
async getSize(): Promise<number> {
|
|
473
|
+
if (this.tables.size === 0) {
|
|
474
|
+
return 0;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/* const stmt = await this.properties.db.prepare(`select count(*) as total from ${this.rootTableName}`);
|
|
478
|
+
const result = await stmt.get()
|
|
479
|
+
stmt.finalize?.();
|
|
480
|
+
return result.total as number */
|
|
481
|
+
return this.count(new types.CountRequest({ query: {} }));
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
async del(query: types.DeleteRequest): Promise<types.IdKey[]> {
|
|
485
|
+
let ret: types.IdKey[] = [];
|
|
486
|
+
for (const table of this._rootTables) {
|
|
487
|
+
const stmt = await this.properties.db.prepare(
|
|
488
|
+
convertDeleteRequestToQuery(query, this.tables, table),
|
|
489
|
+
);
|
|
490
|
+
const results: any[] = await stmt.all([]);
|
|
491
|
+
await stmt.finalize?.();
|
|
492
|
+
// TODO types
|
|
493
|
+
for (const result of results) {
|
|
494
|
+
ret.push(types.toId(result[table.primary as string]));
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return ret;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
async sum(query: types.SumRequest): Promise<number | bigint> {
|
|
501
|
+
let ret: number | bigint | undefined = undefined;
|
|
502
|
+
for (const table of this._rootTables) {
|
|
503
|
+
const stmt = await this.properties.db.prepare(
|
|
504
|
+
convertSumRequestToQuery(query, this.tables, table),
|
|
505
|
+
);
|
|
506
|
+
const result = await stmt.get();
|
|
507
|
+
await stmt.finalize?.();
|
|
508
|
+
if (ret == null) {
|
|
509
|
+
(ret as any) = result.sum as number;
|
|
510
|
+
} else {
|
|
511
|
+
(ret as any) += result.sum as number;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
return ret != null ? ret : 0;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
async count(request: types.CountRequest): Promise<number> {
|
|
518
|
+
let ret: number = 0;
|
|
519
|
+
for (const table of this._rootTables) {
|
|
520
|
+
const stmt = await this.properties.db.prepare(
|
|
521
|
+
convertCountRequestToQuery(request, this.tables, table),
|
|
522
|
+
);
|
|
523
|
+
const result = await stmt.get();
|
|
524
|
+
await stmt.finalize?.();
|
|
525
|
+
ret += Number(result.count);
|
|
526
|
+
}
|
|
527
|
+
return ret;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
getPending(cursorId: string): number | undefined {
|
|
531
|
+
const cursor = this.cursor.get(cursorId);
|
|
532
|
+
if (!cursor) {
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
return cursor.kept;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
get cursorCount(): number {
|
|
539
|
+
return this.cursor.size;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
export class SQLiteIndices implements types.Indices {
|
|
544
|
+
private _scope: string[];
|
|
545
|
+
private scopes: Map<string, SQLiteIndices>;
|
|
546
|
+
private indices: { schema: any; index: Index<any, any> }[];
|
|
547
|
+
private closed = true;
|
|
548
|
+
|
|
549
|
+
constructor(
|
|
550
|
+
readonly properties: {
|
|
551
|
+
scope?: string[];
|
|
552
|
+
db: Database;
|
|
553
|
+
parent?: SQLiteIndices;
|
|
554
|
+
},
|
|
555
|
+
) {
|
|
556
|
+
this._scope = properties.scope || [];
|
|
557
|
+
this.scopes = new Map();
|
|
558
|
+
this.indices = [];
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
async init<T extends Record<string, any>, NestedType>(
|
|
562
|
+
properties: IndexEngineInitProperties<T, NestedType>,
|
|
563
|
+
): Promise<Index<T, NestedType>> {
|
|
564
|
+
const existing = this.indices.find((x) => x.schema === properties.schema);
|
|
565
|
+
if (existing) {
|
|
566
|
+
return existing.index;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
const index: types.Index<T, any> = new SQLLiteIndex({
|
|
570
|
+
db: this.properties.db,
|
|
571
|
+
schema: properties.schema,
|
|
572
|
+
scope: this._scope,
|
|
573
|
+
});
|
|
574
|
+
await index.init(properties);
|
|
575
|
+
this.indices.push({ schema: properties.schema, index });
|
|
576
|
+
|
|
577
|
+
if (!this.closed) {
|
|
578
|
+
await index.start();
|
|
579
|
+
}
|
|
580
|
+
return index;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
async scope(name: string): Promise<types.Indices> {
|
|
584
|
+
if (!this.scopes.has(name)) {
|
|
585
|
+
const scope = new SQLiteIndices({
|
|
586
|
+
scope: [...this._scope, name],
|
|
587
|
+
db: this.properties.db,
|
|
588
|
+
parent: this,
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
if (!this.closed) {
|
|
592
|
+
await scope.start();
|
|
593
|
+
}
|
|
594
|
+
this.scopes.set(name, scope);
|
|
595
|
+
}
|
|
596
|
+
return this.scopes.get(name)!;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
async start(): Promise<void> {
|
|
600
|
+
this.closed = false;
|
|
601
|
+
|
|
602
|
+
await this.properties.db.open(); // TODO only open if parent is not defined ? or this method will not be the opposite of close
|
|
603
|
+
|
|
604
|
+
for (const scope of this.scopes.values()) {
|
|
605
|
+
await scope.start();
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
for (const index of this.indices) {
|
|
609
|
+
await index.index.start();
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
async stop(): Promise<void> {
|
|
614
|
+
this.closed = true;
|
|
615
|
+
for (const scope of this.scopes.values()) {
|
|
616
|
+
await scope.stop();
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
for (const index of this.indices) {
|
|
620
|
+
await index.index.stop();
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
if (!this.properties.parent) {
|
|
624
|
+
await this.properties.db.close();
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
async drop(): Promise<void> {
|
|
629
|
+
for (const scope of this.scopes.values()) {
|
|
630
|
+
await scope.drop();
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
for (const index of this.indices) {
|
|
634
|
+
await index.index.drop();
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
this.scopes.clear();
|
|
638
|
+
}
|
|
639
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { BinaryWriter } from "@dao-xyz/borsh";
|
|
2
|
+
import { sha256Sync, toBase58 } from "@peerbit/crypto";
|
|
3
|
+
import { SQLLiteIndex, SQLiteIndices } from "./engine.js";
|
|
4
|
+
import { create as sqlite3 } from "./sqlite3.js";
|
|
5
|
+
|
|
6
|
+
export const encodeName = (name: string): string => {
|
|
7
|
+
const writer = new BinaryWriter();
|
|
8
|
+
writer.string(name);
|
|
9
|
+
return toBase58(sha256Sync(writer.finalize()));
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const create = async (directory?: string): Promise<SQLiteIndices> => {
|
|
13
|
+
const db = await sqlite3(directory);
|
|
14
|
+
return new SQLiteIndices({ db });
|
|
15
|
+
};
|
|
16
|
+
export { create, SQLiteIndices, SQLLiteIndex };
|