tinybase 9.7.0-beta.0 → 9.7.0-beta.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.
@@ -43,6 +43,7 @@ export * from '../persisters/persister-react-native-mmkv/index.d.ts';
43
43
  export * from '../persisters/persister-react-native-sqlite/index.d.ts';
44
44
  export * from '../persisters/persister-remote/index.d.ts';
45
45
  export * from '../persisters/persister-sqlite-bun/index.d.ts';
46
+ export * from '../persisters/persister-sqlite-node/index.d.ts';
46
47
  export * from '../persisters/persister-sqlite-wasm/index.d.ts';
47
48
  export * from '../persisters/persister-sqlite3/index.d.ts';
48
49
  export * from '../persisters/persister-supabase/index.d.ts';
@@ -42,6 +42,7 @@ export * from '../../persisters/persister-react-native-mmkv/with-schemas/index.d
42
42
  export * from '../../persisters/persister-react-native-sqlite/with-schemas/index.d.ts';
43
43
  export * from '../../persisters/persister-remote/with-schemas/index.d.ts';
44
44
  export * from '../../persisters/persister-sqlite-bun/with-schemas/index.d.ts';
45
+ export * from '../../persisters/persister-sqlite-node/with-schemas/index.d.ts';
45
46
  export * from '../../persisters/persister-sqlite-wasm/with-schemas/index.d.ts';
46
47
  export * from '../../persisters/persister-sqlite3/with-schemas/index.d.ts';
47
48
  export * from '../../persisters/persister-supabase/with-schemas/index.d.ts';
@@ -0,0 +1,184 @@
1
+ /**
2
+ * The persister-sqlite-node module of the TinyBase project lets you save and
3
+ * load Store data to and from a local SQLite database, via the synchronous
4
+ * [`node:sqlite`](https://nodejs.org/api/sqlite.html) module built into
5
+ * Node.js.
6
+ * @see Database Persistence guide
7
+ * @packageDocumentation
8
+ * @module persister-sqlite-node
9
+ * @since 9.7.0
10
+ */
11
+ import type {DatabaseSync} from 'node:sqlite';
12
+ import type {MergeableStore} from '../../mergeable-store/index.d.ts';
13
+ import type {Store} from '../../store/index.d.ts';
14
+ import type {
15
+ DatabasePersisterConfig,
16
+ DpcJson,
17
+ Persister,
18
+ Persists,
19
+ } from '../index.d.ts';
20
+
21
+ /**
22
+ * The SqliteNodePersister interface represents a Persister that lets you save
23
+ * and load Store data to and from a local SQLite database.
24
+ *
25
+ * You should use the createSqliteNodePersister function to create a
26
+ * SqliteNodePersister object.
27
+ *
28
+ * It is a minor extension to the Persister interface and simply provides an
29
+ * extra getDb method for accessing a reference to the database the Store is
30
+ * being persisted to.
31
+ * @category Persister
32
+ * @since 9.7.0
33
+ */
34
+ export interface SqliteNodePersister extends Persister<Persists.StoreOrMergeableStore> {
35
+ /**
36
+ * The getDb method returns a reference to the database the Store is being
37
+ * persisted to.
38
+ * @returns A reference to the database.
39
+ * @example
40
+ * This example creates a Persister object against a newly-created Store and
41
+ * then gets the database back out again.
42
+ *
43
+ * ```js
44
+ * import {DatabaseSync} from 'node:sqlite';
45
+ * import {createStore} from 'tinybase';
46
+ * import {createSqliteNodePersister} from 'tinybase/persisters/persister-sqlite-node';
47
+ *
48
+ * const db = new DatabaseSync(':memory:');
49
+ * const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
50
+ * const persister = createSqliteNodePersister(store, db, 'my_tinybase');
51
+ *
52
+ * console.log(persister.getDb() == db);
53
+ * // -> true
54
+ *
55
+ * await persister.destroy();
56
+ * db.close();
57
+ * ```
58
+ * @category Getter
59
+ * @since 9.7.0
60
+ */
61
+ getDb(): DatabaseSync;
62
+ }
63
+
64
+ /**
65
+ * The createSqliteNodePersister function creates a SqliteNodePersister object
66
+ * that can persist a Store to a local SQLite database.
67
+ *
68
+ * A SqliteNodePersister supports regular Store objects, and can also be used to
69
+ * persist the metadata of a MergeableStore when using the JSON serialization
70
+ * mode, as described below.
71
+ *
72
+ * As well as providing a reference to the Store to persist, you must provide a
73
+ * `db` parameter which is the database returned from `new DatabaseSync(...)`.
74
+ *
75
+ * Since the `node:sqlite` module is built into Node.js, this is the only
76
+ * SQLite-based Persister that requires no additional dependency at all. Note
77
+ * that the module is only available in newer versions of Node.js, and that it
78
+ * is still marked as experimental, so its API may change.
79
+ *
80
+ * A database Persister uses one of two modes: either a JSON serialization of
81
+ * the whole Store stored in a single row of a table (the default), or a tabular
82
+ * mapping of Table Ids to database table names and vice-versa).
83
+ *
84
+ * The third argument is a DatabasePersisterConfig object that configures which
85
+ * of those modes to use, and settings for each. If the third argument is simply
86
+ * a string, it is used as the `storeTableName` property of the JSON
87
+ * serialization.
88
+ *
89
+ * See the documentation for the DpcJson and DpcTabular types for more
90
+ * information on how both of those modes can be configured.
91
+ *
92
+ * Note that `node:sqlite` does not signal when the database changes, so if you
93
+ * enable automatic loading with the startAutoLoad method, it polls the database
94
+ * for changes. The Sqlite3Persister, which uses the asynchronous `sqlite3`
95
+ * module, is notified of changes as they happen, and may suit you better if
96
+ * that matters.
97
+ * @param store The Store or MergeableStore to persist.
98
+ * @param db The database that was returned from `new DatabaseSync(...)`.
99
+ * @param configOrStoreTableName A DatabasePersisterConfig to configure the
100
+ * persistence mode (or a string to set the `storeTableName` property of the
101
+ * JSON serialization).
102
+ * @param onSqlCommand An optional handler called every time the Persister
103
+ * executes a SQL command or query. This is suitable for logging persistence
104
+ * behavior in a development environment.
105
+ * @param onIgnoredError An optional handler for the errors that the Persister
106
+ * would otherwise ignore when trying to save or load data. This is suitable for
107
+ * debugging persistence issues in a development environment.
108
+ * @returns A reference to the new SqliteNodePersister object.
109
+ * @example
110
+ * This example creates a SqliteNodePersister object and persists the Store to a
111
+ * local SQLite database as a JSON serialization into the `my_tinybase` table.
112
+ * It makes a change to the database directly and then reloads it back into the
113
+ * Store.
114
+ *
115
+ * ```js
116
+ * import {DatabaseSync} from 'node:sqlite';
117
+ * import {createStore} from 'tinybase';
118
+ * import {createSqliteNodePersister} from 'tinybase/persisters/persister-sqlite-node';
119
+ *
120
+ * const db = new DatabaseSync(':memory:');
121
+ * const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
122
+ * const persister = createSqliteNodePersister(store, db, 'my_tinybase');
123
+ *
124
+ * await persister.save();
125
+ * // Store will be saved to the database.
126
+ *
127
+ * console.log(db.prepare('SELECT * FROM my_tinybase;').all());
128
+ * // -> [{_id: '_', store: '[{"pets":{"fido":{"species":"dog"}}},{}]'}]
129
+ *
130
+ * db.prepare('UPDATE my_tinybase SET store = ? WHERE _id = ?;').run(
131
+ * '[{"pets":{"felix":{"species":"cat"}}},{}]',
132
+ * '_',
133
+ * );
134
+ * await persister.load();
135
+ * console.log(store.getTables());
136
+ * // -> {pets: {felix: {species: 'cat'}}}
137
+ *
138
+ * await persister.destroy();
139
+ * db.close();
140
+ * ```
141
+ * @example
142
+ * This example creates a SqliteNodePersister object and persists the Store to a
143
+ * local SQLite database with tabular mapping.
144
+ *
145
+ * ```js
146
+ * import {DatabaseSync} from 'node:sqlite';
147
+ * import {createStore} from 'tinybase';
148
+ * import {createSqliteNodePersister} from 'tinybase/persisters/persister-sqlite-node';
149
+ *
150
+ * const db = new DatabaseSync(':memory:');
151
+ * const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
152
+ * const persister = createSqliteNodePersister(store, db, {
153
+ * mode: 'tabular',
154
+ * tables: {load: {pets: 'pets'}, save: {pets: 'pets'}},
155
+ * });
156
+ *
157
+ * await persister.save();
158
+ * console.log(db.prepare('SELECT * FROM pets;').all());
159
+ * // -> [{_id: 'fido', species: 'dog'}]
160
+ *
161
+ * db.prepare(
162
+ * `INSERT INTO pets (_id, species) VALUES ('felix', 'cat')`,
163
+ * ).run();
164
+ * await persister.load();
165
+ * console.log(store.getTables());
166
+ * // -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
167
+ *
168
+ * await persister.destroy();
169
+ * db.close();
170
+ * ```
171
+ * @category Creation
172
+ * @since 9.7.0
173
+ */
174
+ export function createSqliteNodePersister<StoreType extends Store>(
175
+ store: StoreType,
176
+ db: DatabaseSync,
177
+ configOrStoreTableName?:
178
+ | (NoInfer<StoreType> extends MergeableStore
179
+ ? DpcJson
180
+ : DatabasePersisterConfig)
181
+ | string,
182
+ onSqlCommand?: (sql: string, params?: any[]) => void,
183
+ onIgnoredError?: (error: any) => void,
184
+ ): SqliteNodePersister;
@@ -0,0 +1,208 @@
1
+ /**
2
+ * The persister-sqlite-node module of the TinyBase project lets you save and
3
+ * load Store data to and from a local SQLite database, via the synchronous
4
+ * [`node:sqlite`](https://nodejs.org/api/sqlite.html) module built into
5
+ * Node.js.
6
+ * @see Database Persistence guide
7
+ * @packageDocumentation
8
+ * @module persister-sqlite-node
9
+ * @since 9.7.0
10
+ */
11
+ import type {DatabaseSync} from 'node:sqlite';
12
+ import type {MergeableStore} from '../../../mergeable-store/with-schemas/index.d.ts';
13
+ import type {
14
+ OptionalSchemas,
15
+ Store,
16
+ } from '../../../store/with-schemas/index.d.ts';
17
+ import type {
18
+ DatabasePersisterConfig,
19
+ DpcJson,
20
+ Persister,
21
+ Persists,
22
+ } from '../../with-schemas/index.d.ts';
23
+
24
+ /**
25
+ * The SqliteNodePersister interface represents a Persister that lets you save
26
+ * and load Store data to and from a local SQLite database.
27
+ *
28
+ * You should use the createSqliteNodePersister function to create a
29
+ * SqliteNodePersister object.
30
+ *
31
+ * It is a minor extension to the Persister interface and simply provides an
32
+ * extra getDb method for accessing a reference to the database the Store is
33
+ * being persisted to.
34
+ * @category Persister
35
+ * @since 9.7.0
36
+ */
37
+ export interface SqliteNodePersister<
38
+ Schemas extends OptionalSchemas,
39
+ > extends Persister<Schemas, Persists.StoreOrMergeableStore> {
40
+ /**
41
+ * The getDb method returns a reference to the database the Store is being
42
+ * persisted to.
43
+ * @returns A reference to the database.
44
+ * @example
45
+ * This example creates a Persister object against a newly-created Store and
46
+ * then gets the database back out again.
47
+ *
48
+ * ```js
49
+ * import {DatabaseSync} from 'node:sqlite';
50
+ * import {createStore} from 'tinybase';
51
+ * import {createSqliteNodePersister} from 'tinybase/persisters/persister-sqlite-node';
52
+ *
53
+ * const db = new DatabaseSync(':memory:');
54
+ * const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
55
+ * const persister = createSqliteNodePersister(store, db, 'my_tinybase');
56
+ *
57
+ * console.log(persister.getDb() == db);
58
+ * // -> true
59
+ *
60
+ * await persister.destroy();
61
+ * db.close();
62
+ * ```
63
+ * @category Getter
64
+ * @since 9.7.0
65
+ */
66
+ getDb(): DatabaseSync;
67
+ }
68
+
69
+ /**
70
+ * The createSqliteNodePersister function creates a SqliteNodePersister object
71
+ * that can persist a Store to a local SQLite database.
72
+ *
73
+ * This has schema-based typing. The following is a simplified representation:
74
+ *
75
+ * ```ts override
76
+ * createSqliteNodePersister<StoreType extends Store>(
77
+ * store: StoreType,
78
+ * db: DatabaseSync,
79
+ * configOrStoreTableName?:
80
+ * | (NoInfer<StoreType> extends MergeableStore
81
+ * ? DpcJson
82
+ * : DatabasePersisterConfig)
83
+ * | string,
84
+ * onSqlCommand?: (sql: string, params?: any[]) => void,
85
+ * onIgnoredError?: (error: any) => void,
86
+ * ): SqliteNodePersister;
87
+ * ```
88
+ *
89
+ * A SqliteNodePersister supports regular Store objects, and can also be used to
90
+ * persist the metadata of a MergeableStore when using the JSON serialization
91
+ * mode, as described below.
92
+ *
93
+ * As well as providing a reference to the Store to persist, you must provide a
94
+ * `db` parameter which is the database returned from `new DatabaseSync(...)`.
95
+ *
96
+ * Since the `node:sqlite` module is built into Node.js, this is the only
97
+ * SQLite-based Persister that requires no additional dependency at all. Note
98
+ * that the module is only available in newer versions of Node.js, and that it
99
+ * is still marked as experimental, so its API may change.
100
+ *
101
+ * A database Persister uses one of two modes: either a JSON serialization of
102
+ * the whole Store stored in a single row of a table (the default), or a tabular
103
+ * mapping of Table Ids to database table names and vice-versa).
104
+ *
105
+ * The third argument is a DatabasePersisterConfig object that configures which
106
+ * of those modes to use, and settings for each. If the third argument is simply
107
+ * a string, it is used as the `storeTableName` property of the JSON
108
+ * serialization.
109
+ *
110
+ * See the documentation for the DpcJson and DpcTabular types for more
111
+ * information on how both of those modes can be configured.
112
+ *
113
+ * Note that `node:sqlite` does not signal when the database changes, so if you
114
+ * enable automatic loading with the startAutoLoad method, it polls the database
115
+ * for changes. The Sqlite3Persister, which uses the asynchronous `sqlite3`
116
+ * module, is notified of changes as they happen, and may suit you better if
117
+ * that matters.
118
+ * @param store The Store or MergeableStore to persist.
119
+ * @param db The database that was returned from `new DatabaseSync(...)`.
120
+ * @param configOrStoreTableName A DatabasePersisterConfig to configure the
121
+ * persistence mode (or a string to set the `storeTableName` property of the
122
+ * JSON serialization).
123
+ * @param onSqlCommand An optional handler called every time the Persister
124
+ * executes a SQL command or query. This is suitable for logging persistence
125
+ * behavior in a development environment.
126
+ * @param onIgnoredError An optional handler for the errors that the Persister
127
+ * would otherwise ignore when trying to save or load data. This is suitable for
128
+ * debugging persistence issues in a development environment.
129
+ * @returns A reference to the new SqliteNodePersister object.
130
+ * @example
131
+ * This example creates a SqliteNodePersister object and persists the Store to a
132
+ * local SQLite database as a JSON serialization into the `my_tinybase` table.
133
+ * It makes a change to the database directly and then reloads it back into the
134
+ * Store.
135
+ *
136
+ * ```js
137
+ * import {DatabaseSync} from 'node:sqlite';
138
+ * import {createStore} from 'tinybase';
139
+ * import {createSqliteNodePersister} from 'tinybase/persisters/persister-sqlite-node';
140
+ *
141
+ * const db = new DatabaseSync(':memory:');
142
+ * const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
143
+ * const persister = createSqliteNodePersister(store, db, 'my_tinybase');
144
+ *
145
+ * await persister.save();
146
+ * // Store will be saved to the database.
147
+ *
148
+ * console.log(db.prepare('SELECT * FROM my_tinybase;').all());
149
+ * // -> [{_id: '_', store: '[{"pets":{"fido":{"species":"dog"}}},{}]'}]
150
+ *
151
+ * db.prepare('UPDATE my_tinybase SET store = ? WHERE _id = ?;').run(
152
+ * '[{"pets":{"felix":{"species":"cat"}}},{}]',
153
+ * '_',
154
+ * );
155
+ * await persister.load();
156
+ * console.log(store.getTables());
157
+ * // -> {pets: {felix: {species: 'cat'}}}
158
+ *
159
+ * await persister.destroy();
160
+ * db.close();
161
+ * ```
162
+ * @example
163
+ * This example creates a SqliteNodePersister object and persists the Store to a
164
+ * local SQLite database with tabular mapping.
165
+ *
166
+ * ```js
167
+ * import {DatabaseSync} from 'node:sqlite';
168
+ * import {createStore} from 'tinybase';
169
+ * import {createSqliteNodePersister} from 'tinybase/persisters/persister-sqlite-node';
170
+ *
171
+ * const db = new DatabaseSync(':memory:');
172
+ * const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
173
+ * const persister = createSqliteNodePersister(store, db, {
174
+ * mode: 'tabular',
175
+ * tables: {load: {pets: 'pets'}, save: {pets: 'pets'}},
176
+ * });
177
+ *
178
+ * await persister.save();
179
+ * console.log(db.prepare('SELECT * FROM pets;').all());
180
+ * // -> [{_id: 'fido', species: 'dog'}]
181
+ *
182
+ * db.prepare(
183
+ * `INSERT INTO pets (_id, species) VALUES ('felix', 'cat')`,
184
+ * ).run();
185
+ * await persister.load();
186
+ * console.log(store.getTables());
187
+ * // -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}
188
+ *
189
+ * await persister.destroy();
190
+ * db.close();
191
+ * ```
192
+ * @category Creation
193
+ * @since 9.7.0
194
+ */
195
+ export function createSqliteNodePersister<Schemas extends OptionalSchemas>(
196
+ store: MergeableStore<Schemas>,
197
+ db: DatabaseSync,
198
+ configOrStoreTableName?: DpcJson | string,
199
+ onSqlCommand?: (sql: string, params?: any[]) => void,
200
+ onIgnoredError?: (error: any) => void,
201
+ ): SqliteNodePersister<Schemas>;
202
+ export function createSqliteNodePersister<Schemas extends OptionalSchemas>(
203
+ store: Store<Schemas> & {getMergeableContent?: never},
204
+ db: DatabaseSync,
205
+ configOrStoreTableName?: DatabasePersisterConfig<Schemas> | string,
206
+ onSqlCommand?: (sql: string, params?: any[]) => void,
207
+ onIgnoredError?: (error: any) => void,
208
+ ): SqliteNodePersister<Schemas>;
package/agents.md CHANGED
@@ -234,6 +234,32 @@ npm run serveDocs # Preview documentation locally
234
234
  - **Types**: Unit, performance, end-to-end, production
235
235
  - **Environment**: happy-dom (unit), puppeteer (e2e)
236
236
 
237
+ Coverage is measured over four bundles only - `dist/index.js` and the three UI
238
+ entry points - so Persister and Synchronizer modules do not appear in that
239
+ figure. Their correctness comes from the database matrix in
240
+ `test/unit/persisters/common/databases.ts` instead, which every new Persister
241
+ should be added to.
242
+
243
+ `npm run testPerf` asserts against absolute microsecond budgets with roughly
244
+ 40% headroom, and swings by more than 2x under scheduling contention. It needs
245
+ a real foreground terminal, and it does not run in CI, so a failure there is
246
+ not evidence of a regression until it has been reproduced that way.
247
+
248
+ Never pipe a long gulp task through `tail`: the pipeline reports `tail`'s exit
249
+ code rather than gulp's, so a failed `preCommit` or `prePublishPackage` looks
250
+ like a success. Redirect to a file and check `$?`.
251
+
252
+ ### Dependencies
253
+
254
+ - Lift the version for **both** the devDependency and the peerDependency
255
+ together whenever a package appears in both.
256
+ - Use `ncu -u --dep=peer,dev`. Plain `ncu` omits peers, which hides peer-only
257
+ entries entirely.
258
+ - `ncu` keeps the two sections in step only when their ranges are written
259
+ identically. Where the forms differ it updates one and silently leaves the
260
+ other, so use carets consistently and diff both sections after a bump rather
261
+ than trusting the `ncu` summary, which prints one line per package.
262
+
237
263
  ### Code Style
238
264
 
239
265
  - **ESLint**: Enforced with strict rules
@@ -243,6 +269,18 @@ npm run serveDocs # Preview documentation locally
243
269
  - **Semicolons**: Required
244
270
  - **Object spacing**: No spaces in braces `{key: value}`
245
271
 
272
+ ### Git Conventions
273
+
274
+ - **Never add attribution trailers to commit messages.** No `Co-Authored-By:`,
275
+ no "Generated with" lines, no tool or model attribution of any kind. The
276
+ history contains no trailers, and it should stay that way.
277
+ - Commit messages are a **single line** with no body, in the form
278
+ `[topic] Sentence case subject`. The topic is the area of work, usually the
279
+ module or dependency being changed, as in `[hygiene] Dependencies` or
280
+ `[sqlite-node] Test across the database matrix`.
281
+ - A new Persister lands as a sequence of commits in this order: dependency (if
282
+ any), boilerplate (implementation, types and registration), tests, then docs.
283
+
246
284
  ## Project Structure
247
285
 
248
286
  ```