@peerbit/indexer-sqlite3 3.0.0-e6ea5c0 → 3.0.1

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.
Files changed (47) hide show
  1. package/dist/assets/sqlite3/sqlite3.worker.min.js +168 -52
  2. package/dist/index.min.js +277 -53
  3. package/dist/index.min.js.map +3 -3
  4. package/dist/src/engine.d.ts +9 -3
  5. package/dist/src/engine.d.ts.map +1 -1
  6. package/dist/src/engine.js +75 -9
  7. package/dist/src/engine.js.map +1 -1
  8. package/dist/src/index.d.ts +7 -3
  9. package/dist/src/index.d.ts.map +1 -1
  10. package/dist/src/index.js +5 -4
  11. package/dist/src/index.js.map +1 -1
  12. package/dist/src/schema.d.ts +5 -1
  13. package/dist/src/schema.d.ts.map +1 -1
  14. package/dist/src/schema.js +42 -12
  15. package/dist/src/schema.js.map +1 -1
  16. package/dist/src/sqlite3-messages.worker.d.ts +47 -8
  17. package/dist/src/sqlite3-messages.worker.d.ts.map +1 -1
  18. package/dist/src/sqlite3-messages.worker.js +42 -3
  19. package/dist/src/sqlite3-messages.worker.js.map +1 -1
  20. package/dist/src/sqlite3.browser.d.ts +22 -1
  21. package/dist/src/sqlite3.browser.d.ts.map +1 -1
  22. package/dist/src/sqlite3.browser.js +138 -31
  23. package/dist/src/sqlite3.browser.js.map +1 -1
  24. package/dist/src/sqlite3.d.ts +4 -1
  25. package/dist/src/sqlite3.d.ts.map +1 -1
  26. package/dist/src/sqlite3.js +14 -5
  27. package/dist/src/sqlite3.js.map +1 -1
  28. package/dist/src/sqlite3.wasm.d.ts +4 -1
  29. package/dist/src/sqlite3.wasm.d.ts.map +1 -1
  30. package/dist/src/sqlite3.wasm.js +15 -3
  31. package/dist/src/sqlite3.wasm.js.map +1 -1
  32. package/dist/src/sqlite3.worker.js +146 -53
  33. package/dist/src/sqlite3.worker.js.map +1 -1
  34. package/dist/src/utils.d.ts +1 -0
  35. package/dist/src/utils.d.ts.map +1 -1
  36. package/dist/src/utils.js +11 -0
  37. package/dist/src/utils.js.map +1 -1
  38. package/package.json +10 -9
  39. package/src/engine.ts +99 -11
  40. package/src/index.ts +39 -4
  41. package/src/schema.ts +67 -11
  42. package/src/sqlite3-messages.worker.ts +104 -10
  43. package/src/sqlite3.browser.ts +246 -88
  44. package/src/sqlite3.ts +19 -5
  45. package/src/sqlite3.wasm.ts +25 -3
  46. package/src/sqlite3.worker.ts +170 -49
  47. package/src/utils.ts +14 -0
@@ -9,109 +9,264 @@ import {
9
9
  type StatementGetResult,
10
10
  } from "./types.js";
11
11
 
12
+ type RequestType =
13
+ | messages.DatabaseMessages["type"]
14
+ | messages.StatementMessages["type"];
15
+
16
+ export type SQLiteBrowserOptions = {
17
+ protocol?: messages.SqliteWorkerProtocol;
18
+ pragmas?: messages.SQLitePragmaOptions;
19
+ profile?: boolean;
20
+ onProfile?: (sample: SQLiteProfileSample) => void;
21
+ };
22
+
23
+ export type SQLiteProfileSample = {
24
+ requestType: RequestType;
25
+ protocol: messages.SqliteWorkerProtocol;
26
+ databaseId: string;
27
+ databaseDirectory?: string;
28
+ sql?: string;
29
+ clientEncodeMs: number;
30
+ clientRoundTripMs: number;
31
+ valueCount: number;
32
+ blobValueCount: number;
33
+ blobBytes: number;
34
+ worker?: messages.WorkerTiming;
35
+ };
36
+
37
+ type SendMetrics = messages.ClientEncodeMetrics & {
38
+ requestType: RequestType;
39
+ sql?: string;
40
+ };
41
+
42
+ type ResponseMessage = Extract<messages.ResponseMessages, { type: "response" }>;
43
+ type ErrorMessage = Extract<messages.ResponseMessages, { type: "error" }>;
44
+
45
+ const DEFAULT_PROTOCOL: messages.SqliteWorkerProtocol = "clone";
46
+ const EMPTY_ENCODE_METRICS: messages.ClientEncodeMetrics = {
47
+ encodeMs: 0,
48
+ valueCount: 0,
49
+ blobValueCount: 0,
50
+ blobBytes: 0,
51
+ };
52
+
53
+ const getProtocol = (
54
+ options?: SQLiteBrowserOptions,
55
+ ): messages.SqliteWorkerProtocol => options?.protocol ?? DEFAULT_PROTOCOL;
56
+
12
57
  class ProxyStatement implements IStatement {
13
58
  id: string;
14
- resolvers: {
15
- [hash in string]: {
16
- resolve: (...args: any) => void;
17
- reject: (...args: any) => void;
18
- };
19
- } = {};
59
+ private needsReset = false;
20
60
 
21
61
  constructor(
22
62
  readonly send: <T>(
23
63
  message: messages.DatabaseMessages | messages.StatementMessages,
64
+ metrics?: SendMetrics,
24
65
  ) => Promise<T>,
25
66
  readonly databaseId: string,
26
67
  readonly statementId: string,
68
+ readonly sql: string,
69
+ readonly options?: SQLiteBrowserOptions,
27
70
  ) {
28
71
  this.id = statementId;
29
72
  }
30
73
 
31
74
  async bind(values: any[]) {
32
- await this.send({
33
- type: "bind",
34
- values: values.map(messages.encodeValue),
35
- id: uuid(),
36
- databaseId: this.databaseId,
37
- statementId: this.statementId,
38
- });
75
+ const encoded = messages.encodeValues(values, getProtocol(this.options));
76
+ await this.send(
77
+ {
78
+ type: "bind",
79
+ values: encoded.values ?? [],
80
+ id: uuid(),
81
+ databaseId: this.databaseId,
82
+ statementId: this.statementId,
83
+ },
84
+ {
85
+ requestType: "bind",
86
+ sql: this.sql,
87
+ ...encoded.metrics,
88
+ },
89
+ );
90
+ this.needsReset = true;
39
91
  return this;
40
92
  }
41
93
 
42
94
  async finalize() {
43
- await this.send({
44
- type: "finalize",
45
- id: uuid(),
46
- databaseId: this.databaseId,
47
- statementId: this.statementId,
48
- });
95
+ await this.send(
96
+ {
97
+ type: "finalize",
98
+ id: uuid(),
99
+ databaseId: this.databaseId,
100
+ statementId: this.statementId,
101
+ },
102
+ {
103
+ requestType: "finalize",
104
+ sql: this.sql,
105
+ ...EMPTY_ENCODE_METRICS,
106
+ },
107
+ );
108
+ this.needsReset = false;
49
109
  }
50
110
 
51
- get(values?: BindableValue[]) {
52
- return this.send<StatementGetResult>({
53
- type: "get",
54
- values: values ? values.map(messages.encodeValue) : undefined,
55
- id: uuid(),
56
- databaseId: this.databaseId,
57
- statementId: this.statementId,
58
- });
111
+ async get(values?: BindableValue[]) {
112
+ const encoded = messages.encodeValues(values, getProtocol(this.options));
113
+ const result = await this.send<StatementGetResult>(
114
+ {
115
+ type: "get",
116
+ values: encoded.values,
117
+ id: uuid(),
118
+ databaseId: this.databaseId,
119
+ statementId: this.statementId,
120
+ },
121
+ {
122
+ requestType: "get",
123
+ sql: this.sql,
124
+ ...encoded.metrics,
125
+ },
126
+ );
127
+ this.needsReset = false;
128
+ return result;
59
129
  }
60
130
 
61
131
  async run(values: BindableValue[]) {
62
- await this.send({
63
- type: "run-statement",
64
- values: values.map(messages.encodeValue),
65
- id: uuid(),
66
- databaseId: this.databaseId,
67
- statementId: this.statementId,
68
- });
132
+ const encoded = messages.encodeValues(values, getProtocol(this.options));
133
+ await this.send(
134
+ {
135
+ type: "run-statement",
136
+ values: encoded.values ?? [],
137
+ id: uuid(),
138
+ databaseId: this.databaseId,
139
+ statementId: this.statementId,
140
+ },
141
+ {
142
+ requestType: "run-statement",
143
+ sql: this.sql,
144
+ ...encoded.metrics,
145
+ },
146
+ );
147
+ this.needsReset = false;
69
148
  }
70
149
 
71
150
  async reset() {
72
- await this.send({
73
- type: "reset",
74
- id: uuid(),
75
- databaseId: this.databaseId,
76
- statementId: this.statementId,
77
- });
151
+ if (!this.needsReset) {
152
+ return this;
153
+ }
154
+ await this.send(
155
+ {
156
+ type: "reset",
157
+ id: uuid(),
158
+ databaseId: this.databaseId,
159
+ statementId: this.statementId,
160
+ },
161
+ {
162
+ requestType: "reset",
163
+ sql: this.sql,
164
+ ...EMPTY_ENCODE_METRICS,
165
+ },
166
+ );
167
+ this.needsReset = false;
78
168
  return this;
79
169
  }
80
170
 
81
171
  async all(values: BindableValue[]) {
82
- let id = uuid();
83
- const results = await this.send({
84
- type: "all",
85
- values: values.map(messages.encodeValue),
86
- id,
87
- databaseId: this.databaseId,
88
- statementId: this.statementId,
89
- });
90
- return results;
172
+ const encoded = messages.encodeValues(values, getProtocol(this.options));
173
+ const result = await this.send(
174
+ {
175
+ type: "all",
176
+ values: encoded.values ?? [],
177
+ id: uuid(),
178
+ databaseId: this.databaseId,
179
+ statementId: this.statementId,
180
+ },
181
+ {
182
+ requestType: "all",
183
+ sql: this.sql,
184
+ ...encoded.metrics,
185
+ },
186
+ );
187
+ this.needsReset = false;
188
+ return result;
91
189
  }
92
190
  }
93
191
 
94
192
  class ProxyDatabase implements IDatabase {
95
193
  statements: Map<string, ProxyStatement> = new Map();
96
-
97
- resolvers: {
98
- [hash in string]: {
99
- resolve: (...args: any) => void;
100
- reject: (...args: any) => void;
101
- };
102
- } = {};
103
194
  databaseId!: string;
195
+ private directory?: string;
196
+
104
197
  constructor(
105
- readonly send: <T>(
198
+ readonly postMessage: (
106
199
  message: messages.DatabaseMessages | messages.StatementMessages,
107
- ) => Promise<T>,
200
+ ) => Promise<ResponseMessage>,
201
+ readonly options?: SQLiteBrowserOptions,
108
202
  ) {}
109
203
 
204
+ private async send<T>(
205
+ message: messages.DatabaseMessages | messages.StatementMessages,
206
+ metrics?: SendMetrics,
207
+ ): Promise<T> {
208
+ const startedAt = performance.now();
209
+ const protocol = getProtocol(this.options);
210
+ const shouldProfile = Boolean(this.options?.profile || this.options?.onProfile);
211
+ const requestType = metrics?.requestType ?? message.type;
212
+
213
+ try {
214
+ const response = await this.postMessage({
215
+ ...message,
216
+ protocol,
217
+ profile: shouldProfile,
218
+ });
219
+ this.options?.onProfile?.({
220
+ requestType,
221
+ protocol,
222
+ databaseId: this.databaseId,
223
+ databaseDirectory: this.directory,
224
+ sql:
225
+ metrics?.sql ??
226
+ ("sql" in message && typeof message.sql === "string"
227
+ ? message.sql
228
+ : undefined),
229
+ clientEncodeMs: metrics?.encodeMs ?? 0,
230
+ clientRoundTripMs: performance.now() - startedAt,
231
+ valueCount: metrics?.valueCount ?? 0,
232
+ blobValueCount: metrics?.blobValueCount ?? 0,
233
+ blobBytes: metrics?.blobBytes ?? 0,
234
+ worker: response.timing,
235
+ });
236
+ return response.result as T;
237
+ } catch (error: any) {
238
+ const responseError = error as ErrorMessage | undefined;
239
+ this.options?.onProfile?.({
240
+ requestType,
241
+ protocol,
242
+ databaseId: this.databaseId,
243
+ databaseDirectory: this.directory,
244
+ sql:
245
+ metrics?.sql ??
246
+ ("sql" in message && typeof message.sql === "string"
247
+ ? message.sql
248
+ : undefined),
249
+ clientEncodeMs: metrics?.encodeMs ?? 0,
250
+ clientRoundTripMs: performance.now() - startedAt,
251
+ valueCount: metrics?.valueCount ?? 0,
252
+ blobValueCount: metrics?.blobValueCount ?? 0,
253
+ blobBytes: metrics?.blobBytes ?? 0,
254
+ worker: responseError?.timing,
255
+ });
256
+ if (responseError?.type === "error") {
257
+ throw new Error(responseError.message);
258
+ }
259
+ throw error;
260
+ }
261
+ }
262
+
110
263
  async init(directory?: string) {
111
264
  this.databaseId = uuid();
265
+ this.directory = directory;
112
266
  return this.send({
113
267
  type: "create",
114
268
  directory,
269
+ pragmas: this.options?.pragmas,
115
270
  databaseId: this.databaseId,
116
271
  id: uuid(),
117
272
  });
@@ -141,9 +296,11 @@ class ProxyDatabase implements IDatabase {
141
296
  databaseId: this.databaseId,
142
297
  });
143
298
  const statement = new ProxyStatement(
144
- this.send,
299
+ this.send.bind(this),
145
300
  this.databaseId,
146
301
  statementId,
302
+ sql,
303
+ this.options,
147
304
  );
148
305
  this.statements.set(statementId, statement);
149
306
 
@@ -181,18 +338,10 @@ class ProxyDatabase implements IDatabase {
181
338
  databaseId: this.databaseId,
182
339
  });
183
340
  }
184
-
185
- /* async get(sql: string) {
186
- return this.send({ type: 'get', sql, id: uuid() });
187
- }
188
-
189
- async run(sql: string, bind: any[]) {
190
- return this.send({ type: 'run', sql, bind, id: uuid() });
191
- } */
192
341
  }
193
342
 
194
343
  interface DatabaseCreator {
195
- create(directory?: string): Promise<ProxyDatabase>;
344
+ create(directory?: string, options?: SQLiteBrowserOptions): Promise<ProxyDatabase>;
196
345
  close(): Promise<void> | void;
197
346
  }
198
347
 
@@ -202,21 +351,22 @@ const init = async (): Promise<DatabaseCreator> => {
202
351
  return initialized;
203
352
  }
204
353
 
205
- let worker = new Worker(
354
+ const worker = new Worker(
206
355
  new URL("/peerbit/sqlite3/sqlite3.worker.min.js", import.meta.url),
207
356
  { type: "module" },
208
357
  );
209
- let resolvers: {
210
- [hash in string]: {
211
- resolve: (...args: any) => void;
212
- reject: (...args: any) => void;
213
- };
214
- } = {};
215
-
216
- let send = <T>(
358
+ const resolvers: Record<
359
+ string,
360
+ {
361
+ resolve: (message: ResponseMessage) => void;
362
+ reject: (message: ErrorMessage) => void;
363
+ }
364
+ > = {};
365
+
366
+ const postMessage = (
217
367
  message: messages.DatabaseMessages | messages.StatementMessages,
218
368
  ) => {
219
- const promise = new Promise<T>((resolve, reject) => {
369
+ const promise = new Promise<ResponseMessage>((resolve, reject) => {
220
370
  resolvers[message.id] = { resolve, reject };
221
371
  });
222
372
  worker.postMessage(message);
@@ -224,7 +374,7 @@ const init = async (): Promise<DatabaseCreator> => {
224
374
  return promise.finally(() => delete resolvers[message.id]);
225
375
  };
226
376
 
227
- let isReady = pDefer();
377
+ const isReady = pDefer();
228
378
 
229
379
  worker.onmessage = async (ev) => {
230
380
  const message = ev.data as messages.ResponseMessages | messages.IsReady;
@@ -235,15 +385,21 @@ const init = async (): Promise<DatabaseCreator> => {
235
385
  }
236
386
 
237
387
  const resolver = resolvers[message.id];
388
+ if (!resolver) {
389
+ return;
390
+ }
238
391
  if (message.type === "error") {
239
- resolver.reject(new Error(message.message));
240
- } else if (message.type === "response") {
241
- resolver.resolve(message.result);
392
+ resolver.reject(message);
393
+ } else {
394
+ resolver.resolve(message);
242
395
  }
243
396
  };
244
397
 
245
- const create = async (directory?: string) => {
246
- const db = new ProxyDatabase(send);
398
+ const create = async (
399
+ directory?: string,
400
+ options?: SQLiteBrowserOptions,
401
+ ) => {
402
+ const db = new ProxyDatabase(postMessage, options);
247
403
  await isReady.promise;
248
404
  await db.init(directory);
249
405
  await db.open();
@@ -258,12 +414,14 @@ const init = async (): Promise<DatabaseCreator> => {
258
414
  });
259
415
  };
260
416
 
261
- const create = (directory?: string): Promise<IDatabase> => {
417
+ const create = (
418
+ directory?: string,
419
+ options?: SQLiteBrowserOptions,
420
+ ): Promise<IDatabase> => {
262
421
  if (directory) {
263
422
  // persist the database
264
- return init().then((creator) => creator.create(directory));
265
- } else {
266
- return createDatabase();
423
+ return init().then((creator) => creator.create(directory, options));
267
424
  }
425
+ return createDatabase(directory, options);
268
426
  };
269
427
  export { create };
package/src/sqlite3.ts CHANGED
@@ -1,11 +1,28 @@
1
1
  import DB from "better-sqlite3";
2
2
  import fs from "fs";
3
+ import type { SQLitePragmaOptions } from "./sqlite3-messages.worker.js";
3
4
  import type {
4
5
  Database as IDatabase,
5
6
  Statement as IStatement,
6
7
  } from "./types.js";
7
8
 
8
- let create = async (directory?: string) => {
9
+ const applyPragmas = (db: DB.Database, pragmas?: SQLitePragmaOptions) => {
10
+ db.pragma("journal_mode = WAL");
11
+ db.pragma("foreign_keys = on");
12
+ db.pragma(`synchronous = ${(pragmas?.synchronous ?? "FULL").toUpperCase()}`);
13
+ if (pragmas?.lockingMode) {
14
+ db.pragma(`locking_mode = ${pragmas.lockingMode.toUpperCase()}`);
15
+ }
16
+ if (pragmas?.tempStore && pragmas.tempStore !== "DEFAULT") {
17
+ db.pragma(`temp_store = ${pragmas.tempStore.toUpperCase()}`);
18
+ }
19
+ db.defaultSafeIntegers(true);
20
+ };
21
+
22
+ let create = async (
23
+ directory?: string,
24
+ options?: { pragmas?: SQLitePragmaOptions },
25
+ ) => {
9
26
  let db: DB.Database | undefined = undefined;
10
27
  let statements: Map<string, IStatement> = new Map();
11
28
  let dbFileName: string;
@@ -50,10 +67,7 @@ let create = async (directory?: string) => {
50
67
  });
51
68
  }
52
69
 
53
- // TODO this test makes things faster, but for benchmarking it might yield wierd results where some runs are faster than others
54
- db.pragma("journal_mode = WAL");
55
- db.pragma("foreign_keys = on");
56
- db.defaultSafeIntegers(true);
70
+ applyPragmas(db, options?.pragmas);
57
71
  };
58
72
 
59
73
  return {
@@ -12,6 +12,7 @@ import {
12
12
  type Statement as IStatement,
13
13
  type StatementGetResult,
14
14
  } from "./types.js";
15
+ import type { SQLitePragmaOptions } from "./sqlite3-messages.worker.js";
15
16
 
16
17
  export const encodeName = (name: string): string => {
17
18
  // since "/" and perhaps other characters might not be allowed we do encode
@@ -183,7 +184,29 @@ const getSqlite3 = async (): Promise<Sqlite3Module> => {
183
184
  return sqlite3Promise;
184
185
  };
185
186
 
186
- const create = async (directory?: string) => {
187
+ const applyPragmas = (
188
+ db: SQLDatabase | OpfsSAHPoolDatabase,
189
+ pragmas?: SQLitePragmaOptions,
190
+ ) => {
191
+ db.exec("PRAGMA journal_mode = WAL");
192
+ db.exec("PRAGMA foreign_keys = on");
193
+ // Browser SQLite state is a rebuildable materialized view over the log, so
194
+ // NORMAL is a better default tradeoff than FULL for OPFS-backed write latency.
195
+ db.exec(
196
+ `PRAGMA synchronous = ${(pragmas?.synchronous ?? "NORMAL").toUpperCase()}`,
197
+ );
198
+ if (pragmas?.lockingMode) {
199
+ db.exec(`PRAGMA locking_mode = ${pragmas.lockingMode.toUpperCase()}`);
200
+ }
201
+ if (pragmas?.tempStore && pragmas.tempStore !== "DEFAULT") {
202
+ db.exec(`PRAGMA temp_store = ${pragmas.tempStore.toUpperCase()}`);
203
+ }
204
+ };
205
+
206
+ const create = async (
207
+ directory?: string,
208
+ options?: { pragmas?: SQLitePragmaOptions },
209
+ ) => {
187
210
  let statements: Map<string, Statement> = new Map();
188
211
 
189
212
  const sqlite3 = await getSqlite3();
@@ -299,8 +322,7 @@ const create = async (directory?: string) => {
299
322
  if (!sqliteDb) {
300
323
  throw new Error("Failed to open sqlite database");
301
324
  }
302
- sqliteDb.exec("PRAGMA journal_mode = WAL");
303
- sqliteDb.exec("PRAGMA foreign_keys = on");
325
+ applyPragmas(sqliteDb, options?.pragmas);
304
326
  };
305
327
 
306
328
  return {