@dothift/amarok-lite 1.5.0

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/Cargo.toml ADDED
@@ -0,0 +1,18 @@
1
+ [package]
2
+ name = "amarok_lite_node"
3
+ version = "1.5.0"
4
+ edition = "2021"
5
+ description = "napi-rs bindings for dotHIFT://Amarok Lite"
6
+ license = "MIT"
7
+
8
+ [lib]
9
+ crate-type = ["cdylib"]
10
+
11
+ [dependencies]
12
+ napi = { version = "2", features = ["napi8", "serde-json"] }
13
+ napi-derive = "2"
14
+ amarok-core = { path = "../../crates/amarok-core" }
15
+ serde_json = "1"
16
+
17
+ [build-dependencies]
18
+ napi-build = "2"
package/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # dotHIFT://Amarok — Amarok-Lite Node.js SDK
2
+
3
+ `@dothift/amarok-lite` is an embeddable TypeScript/Node.js database engine for
4
+ [dotHIFT://Amarok](https://gitea.local/dotHIFT/amarok). It wraps
5
+ [better-sqlite3](https://github.com/WiseLibs/better-sqlite3) in WAL mode and
6
+ exposes the Amarok API surface so that code written for Amarok-Lite is
7
+ compatible with the full Amarok server SDK.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install @dothift/amarok-lite
13
+ ```
14
+
15
+ ## Quick start — synchronous
16
+
17
+ ```typescript
18
+ import { Store } from '@dothift/amarok-lite'
19
+
20
+ const db = Store.open('./myapp.db')
21
+
22
+ db.exec('CREATE TABLE IF NOT EXISTS tickets (id TEXT PRIMARY KEY, data TEXT)')
23
+ db.exec('INSERT INTO tickets VALUES ($1, $2)', ['t-001', { title: 'First ticket' }])
24
+
25
+ const rows = db.query('SELECT * FROM tickets WHERE id = $1', ['t-001'])
26
+ console.log(rows.fetchall())
27
+
28
+ db.close()
29
+ ```
30
+
31
+ ## Quick start — async
32
+
33
+ ```typescript
34
+ import { Store } from '@dothift/amarok-lite'
35
+
36
+ const db = Store.open('./myapp.db')
37
+
38
+ await db.execAsync('CREATE TABLE IF NOT EXISTS tickets (id TEXT PRIMARY KEY, data TEXT)')
39
+ await db.execAsync('INSERT INTO tickets VALUES ($1, $2)', ['t-002', { title: 'Second' }])
40
+
41
+ const rows = await db.queryAsync('SELECT * FROM tickets')
42
+ console.log(rows.fetchall())
43
+
44
+ db.close()
45
+ ```
46
+
47
+ ## Transactions
48
+
49
+ ```typescript
50
+ import { Store } from '@dothift/amarok-lite'
51
+
52
+ const db = Store.open('./bank.db')
53
+ db.exec('CREATE TABLE IF NOT EXISTS accounts (id TEXT PRIMARY KEY, balance INTEGER)')
54
+ db.exec('INSERT INTO accounts VALUES ($1, $2)', ['alice', 1000])
55
+ db.exec('INSERT INTO accounts VALUES ($1, $2)', ['bob', 500])
56
+
57
+ db.transaction((tx) => {
58
+ tx.exec('UPDATE accounts SET balance = balance - 100 WHERE id = $1', ['alice'])
59
+ tx.exec('UPDATE accounts SET balance = balance + 100 WHERE id = $1', ['bob'])
60
+ })
61
+
62
+ db.close()
63
+ ```
64
+
65
+ ## JSONB operators
66
+
67
+ Queries using Amarok's PostgreSQL-style JSONB operators are automatically
68
+ rewritten to SQLite3 JSON functions:
69
+
70
+ | Amarok operator | SQLite equivalent |
71
+ |------------------------|----------------------------------------------|
72
+ | `col->>'key'` | `json_extract(col, '$.key')` |
73
+ | `col->'key'` | `json_extract(col, '$.key')` |
74
+ | `col ? 'key'` | `(json_type(col, '$.key') IS NOT NULL)` |
75
+ | `col @> '{"k":"v"}'` | `(json_patch(col, '{"k":"v"}') = col)` |
76
+
77
+ ## Options
78
+
79
+ | Option | Default | Description |
80
+ |--------------------|---------|------------------------------------------|
81
+ | `wal` | `true` | Enable WAL journal mode |
82
+ | `cacheKB` | `8000` | Page cache size in KB (~8 MB) |
83
+ | `walAutocheckpoint`| `1000` | WAL auto-checkpoint threshold (pages) |
84
+ | `timeoutMs` | `30000` | Busy-wait timeout in milliseconds |
85
+ | `enableEventLog` | `true` | Create Phase 2 event log stub tables |
86
+
87
+ ## Part of dotHIFT
88
+
89
+ See the [.platform](https://gitea.local/dotHIFT/.platform) repo for
90
+ cross-product architecture decisions and the dependency manifest.
Binary file
@@ -0,0 +1,315 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+ /* prettier-ignore */
4
+
5
+ /* auto-generated by NAPI-RS */
6
+
7
+ const { existsSync, readFileSync } = require('fs')
8
+ const { join } = require('path')
9
+
10
+ const { platform, arch } = process
11
+
12
+ let nativeBinding = null
13
+ let localFileExisted = false
14
+ let loadError = null
15
+
16
+ function isMusl() {
17
+ // For Node 10
18
+ if (!process.report || typeof process.report.getReport !== 'function') {
19
+ try {
20
+ const lddPath = require('child_process').execSync('which ldd').toString().trim()
21
+ return readFileSync(lddPath, 'utf8').includes('musl')
22
+ } catch (e) {
23
+ return true
24
+ }
25
+ } else {
26
+ const { glibcVersionRuntime } = process.report.getReport().header
27
+ return !glibcVersionRuntime
28
+ }
29
+ }
30
+
31
+ switch (platform) {
32
+ case 'android':
33
+ switch (arch) {
34
+ case 'arm64':
35
+ localFileExisted = existsSync(join(__dirname, 'amarok-lite.android-arm64.node'))
36
+ try {
37
+ if (localFileExisted) {
38
+ nativeBinding = require('./amarok-lite.android-arm64.node')
39
+ } else {
40
+ nativeBinding = require('@dothift/amarok-lite-android-arm64')
41
+ }
42
+ } catch (e) {
43
+ loadError = e
44
+ }
45
+ break
46
+ case 'arm':
47
+ localFileExisted = existsSync(join(__dirname, 'amarok-lite.android-arm-eabi.node'))
48
+ try {
49
+ if (localFileExisted) {
50
+ nativeBinding = require('./amarok-lite.android-arm-eabi.node')
51
+ } else {
52
+ nativeBinding = require('@dothift/amarok-lite-android-arm-eabi')
53
+ }
54
+ } catch (e) {
55
+ loadError = e
56
+ }
57
+ break
58
+ default:
59
+ throw new Error(`Unsupported architecture on Android ${arch}`)
60
+ }
61
+ break
62
+ case 'win32':
63
+ switch (arch) {
64
+ case 'x64':
65
+ localFileExisted = existsSync(
66
+ join(__dirname, 'amarok-lite.win32-x64-msvc.node')
67
+ )
68
+ try {
69
+ if (localFileExisted) {
70
+ nativeBinding = require('./amarok-lite.win32-x64-msvc.node')
71
+ } else {
72
+ nativeBinding = require('@dothift/amarok-lite-win32-x64-msvc')
73
+ }
74
+ } catch (e) {
75
+ loadError = e
76
+ }
77
+ break
78
+ case 'ia32':
79
+ localFileExisted = existsSync(
80
+ join(__dirname, 'amarok-lite.win32-ia32-msvc.node')
81
+ )
82
+ try {
83
+ if (localFileExisted) {
84
+ nativeBinding = require('./amarok-lite.win32-ia32-msvc.node')
85
+ } else {
86
+ nativeBinding = require('@dothift/amarok-lite-win32-ia32-msvc')
87
+ }
88
+ } catch (e) {
89
+ loadError = e
90
+ }
91
+ break
92
+ case 'arm64':
93
+ localFileExisted = existsSync(
94
+ join(__dirname, 'amarok-lite.win32-arm64-msvc.node')
95
+ )
96
+ try {
97
+ if (localFileExisted) {
98
+ nativeBinding = require('./amarok-lite.win32-arm64-msvc.node')
99
+ } else {
100
+ nativeBinding = require('@dothift/amarok-lite-win32-arm64-msvc')
101
+ }
102
+ } catch (e) {
103
+ loadError = e
104
+ }
105
+ break
106
+ default:
107
+ throw new Error(`Unsupported architecture on Windows: ${arch}`)
108
+ }
109
+ break
110
+ case 'darwin':
111
+ localFileExisted = existsSync(join(__dirname, 'amarok-lite.darwin-universal.node'))
112
+ try {
113
+ if (localFileExisted) {
114
+ nativeBinding = require('./amarok-lite.darwin-universal.node')
115
+ } else {
116
+ nativeBinding = require('@dothift/amarok-lite-darwin-universal')
117
+ }
118
+ break
119
+ } catch {}
120
+ switch (arch) {
121
+ case 'x64':
122
+ localFileExisted = existsSync(join(__dirname, 'amarok-lite.darwin-x64.node'))
123
+ try {
124
+ if (localFileExisted) {
125
+ nativeBinding = require('./amarok-lite.darwin-x64.node')
126
+ } else {
127
+ nativeBinding = require('@dothift/amarok-lite-darwin-x64')
128
+ }
129
+ } catch (e) {
130
+ loadError = e
131
+ }
132
+ break
133
+ case 'arm64':
134
+ localFileExisted = existsSync(
135
+ join(__dirname, 'amarok-lite.darwin-arm64.node')
136
+ )
137
+ try {
138
+ if (localFileExisted) {
139
+ nativeBinding = require('./amarok-lite.darwin-arm64.node')
140
+ } else {
141
+ nativeBinding = require('@dothift/amarok-lite-darwin-arm64')
142
+ }
143
+ } catch (e) {
144
+ loadError = e
145
+ }
146
+ break
147
+ default:
148
+ throw new Error(`Unsupported architecture on macOS: ${arch}`)
149
+ }
150
+ break
151
+ case 'freebsd':
152
+ if (arch !== 'x64') {
153
+ throw new Error(`Unsupported architecture on FreeBSD: ${arch}`)
154
+ }
155
+ localFileExisted = existsSync(join(__dirname, 'amarok-lite.freebsd-x64.node'))
156
+ try {
157
+ if (localFileExisted) {
158
+ nativeBinding = require('./amarok-lite.freebsd-x64.node')
159
+ } else {
160
+ nativeBinding = require('@dothift/amarok-lite-freebsd-x64')
161
+ }
162
+ } catch (e) {
163
+ loadError = e
164
+ }
165
+ break
166
+ case 'linux':
167
+ switch (arch) {
168
+ case 'x64':
169
+ if (isMusl()) {
170
+ localFileExisted = existsSync(
171
+ join(__dirname, 'amarok-lite.linux-x64-musl.node')
172
+ )
173
+ try {
174
+ if (localFileExisted) {
175
+ nativeBinding = require('./amarok-lite.linux-x64-musl.node')
176
+ } else {
177
+ nativeBinding = require('@dothift/amarok-lite-linux-x64-musl')
178
+ }
179
+ } catch (e) {
180
+ loadError = e
181
+ }
182
+ } else {
183
+ localFileExisted = existsSync(
184
+ join(__dirname, 'amarok-lite.linux-x64-gnu.node')
185
+ )
186
+ try {
187
+ if (localFileExisted) {
188
+ nativeBinding = require('./amarok-lite.linux-x64-gnu.node')
189
+ } else {
190
+ nativeBinding = require('@dothift/amarok-lite-linux-x64-gnu')
191
+ }
192
+ } catch (e) {
193
+ loadError = e
194
+ }
195
+ }
196
+ break
197
+ case 'arm64':
198
+ if (isMusl()) {
199
+ localFileExisted = existsSync(
200
+ join(__dirname, 'amarok-lite.linux-arm64-musl.node')
201
+ )
202
+ try {
203
+ if (localFileExisted) {
204
+ nativeBinding = require('./amarok-lite.linux-arm64-musl.node')
205
+ } else {
206
+ nativeBinding = require('@dothift/amarok-lite-linux-arm64-musl')
207
+ }
208
+ } catch (e) {
209
+ loadError = e
210
+ }
211
+ } else {
212
+ localFileExisted = existsSync(
213
+ join(__dirname, 'amarok-lite.linux-arm64-gnu.node')
214
+ )
215
+ try {
216
+ if (localFileExisted) {
217
+ nativeBinding = require('./amarok-lite.linux-arm64-gnu.node')
218
+ } else {
219
+ nativeBinding = require('@dothift/amarok-lite-linux-arm64-gnu')
220
+ }
221
+ } catch (e) {
222
+ loadError = e
223
+ }
224
+ }
225
+ break
226
+ case 'arm':
227
+ if (isMusl()) {
228
+ localFileExisted = existsSync(
229
+ join(__dirname, 'amarok-lite.linux-arm-musleabihf.node')
230
+ )
231
+ try {
232
+ if (localFileExisted) {
233
+ nativeBinding = require('./amarok-lite.linux-arm-musleabihf.node')
234
+ } else {
235
+ nativeBinding = require('@dothift/amarok-lite-linux-arm-musleabihf')
236
+ }
237
+ } catch (e) {
238
+ loadError = e
239
+ }
240
+ } else {
241
+ localFileExisted = existsSync(
242
+ join(__dirname, 'amarok-lite.linux-arm-gnueabihf.node')
243
+ )
244
+ try {
245
+ if (localFileExisted) {
246
+ nativeBinding = require('./amarok-lite.linux-arm-gnueabihf.node')
247
+ } else {
248
+ nativeBinding = require('@dothift/amarok-lite-linux-arm-gnueabihf')
249
+ }
250
+ } catch (e) {
251
+ loadError = e
252
+ }
253
+ }
254
+ break
255
+ case 'riscv64':
256
+ if (isMusl()) {
257
+ localFileExisted = existsSync(
258
+ join(__dirname, 'amarok-lite.linux-riscv64-musl.node')
259
+ )
260
+ try {
261
+ if (localFileExisted) {
262
+ nativeBinding = require('./amarok-lite.linux-riscv64-musl.node')
263
+ } else {
264
+ nativeBinding = require('@dothift/amarok-lite-linux-riscv64-musl')
265
+ }
266
+ } catch (e) {
267
+ loadError = e
268
+ }
269
+ } else {
270
+ localFileExisted = existsSync(
271
+ join(__dirname, 'amarok-lite.linux-riscv64-gnu.node')
272
+ )
273
+ try {
274
+ if (localFileExisted) {
275
+ nativeBinding = require('./amarok-lite.linux-riscv64-gnu.node')
276
+ } else {
277
+ nativeBinding = require('@dothift/amarok-lite-linux-riscv64-gnu')
278
+ }
279
+ } catch (e) {
280
+ loadError = e
281
+ }
282
+ }
283
+ break
284
+ case 's390x':
285
+ localFileExisted = existsSync(
286
+ join(__dirname, 'amarok-lite.linux-s390x-gnu.node')
287
+ )
288
+ try {
289
+ if (localFileExisted) {
290
+ nativeBinding = require('./amarok-lite.linux-s390x-gnu.node')
291
+ } else {
292
+ nativeBinding = require('@dothift/amarok-lite-linux-s390x-gnu')
293
+ }
294
+ } catch (e) {
295
+ loadError = e
296
+ }
297
+ break
298
+ default:
299
+ throw new Error(`Unsupported architecture on Linux: ${arch}`)
300
+ }
301
+ break
302
+ default:
303
+ throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
304
+ }
305
+
306
+ if (!nativeBinding) {
307
+ if (loadError) {
308
+ throw loadError
309
+ }
310
+ throw new Error(`Failed to load native binding`)
311
+ }
312
+
313
+ const { AmarokStore } = nativeBinding
314
+
315
+ module.exports.AmarokStore = AmarokStore
@@ -0,0 +1,16 @@
1
+ /** Base class for all Amarok-Lite errors. */
2
+ export declare class AmarokError extends Error {
3
+ constructor(message: string);
4
+ }
5
+ /** Raised for operational errors (I/O, locking, WAL). */
6
+ export declare class AmarokOperationalError extends AmarokError {
7
+ constructor(message: string);
8
+ }
9
+ /** Raised for constraint violations (UNIQUE, NOT NULL, FK). */
10
+ export declare class AmarokIntegrityError extends AmarokError {
11
+ constructor(message: string);
12
+ }
13
+ /** Raised for programming errors (bad SQL, wrong param count). */
14
+ export declare class AmarokProgrammingError extends AmarokError {
15
+ constructor(message: string);
16
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,28 @@
1
+ /** Base class for all Amarok-Lite errors. */
2
+ export class AmarokError extends Error {
3
+ constructor(message) {
4
+ super(message);
5
+ this.name = 'AmarokError';
6
+ }
7
+ }
8
+ /** Raised for operational errors (I/O, locking, WAL). */
9
+ export class AmarokOperationalError extends AmarokError {
10
+ constructor(message) {
11
+ super(message);
12
+ this.name = 'AmarokOperationalError';
13
+ }
14
+ }
15
+ /** Raised for constraint violations (UNIQUE, NOT NULL, FK). */
16
+ export class AmarokIntegrityError extends AmarokError {
17
+ constructor(message) {
18
+ super(message);
19
+ this.name = 'AmarokIntegrityError';
20
+ }
21
+ }
22
+ /** Raised for programming errors (bad SQL, wrong param count). */
23
+ export class AmarokProgrammingError extends AmarokError {
24
+ constructor(message) {
25
+ super(message);
26
+ this.name = 'AmarokProgrammingError';
27
+ }
28
+ }
@@ -0,0 +1,3 @@
1
+ import type { Store } from './store.js';
2
+ /** Create Phase 2 event log stub tables if they do not exist. */
3
+ export declare function ensureEventLog(store: Store): void;
@@ -0,0 +1,31 @@
1
+ const CREATE_EVENTS_TABLE = `
2
+ CREATE TABLE IF NOT EXISTS _amarok_events (
3
+ event_id TEXT NOT NULL,
4
+ sequence INTEGER NOT NULL,
5
+ region TEXT NOT NULL DEFAULT 'local',
6
+ node_id TEXT NOT NULL DEFAULT 'lite',
7
+ ts TEXT NOT NULL,
8
+ db_name TEXT NOT NULL DEFAULT 'default',
9
+ collection TEXT NOT NULL,
10
+ entity_id TEXT NOT NULL,
11
+ operation TEXT NOT NULL CHECK(operation IN ('insert','update','delete','schema')),
12
+ actor_type TEXT NOT NULL DEFAULT 'system',
13
+ actor_id TEXT NOT NULL DEFAULT 'lite',
14
+ correlation_id TEXT,
15
+ before_state TEXT,
16
+ after_state TEXT,
17
+ metadata TEXT,
18
+ PRIMARY KEY (sequence, node_id)
19
+ )`;
20
+ const CREATE_ENTITY_IDX = `
21
+ CREATE INDEX IF NOT EXISTS _amarok_events_entity_idx
22
+ ON _amarok_events(collection, entity_id, sequence)`;
23
+ const CREATE_SEQ_IDX = `
24
+ CREATE INDEX IF NOT EXISTS _amarok_events_seq_idx
25
+ ON _amarok_events(sequence)`;
26
+ /** Create Phase 2 event log stub tables if they do not exist. */
27
+ export function ensureEventLog(store) {
28
+ store.exec(CREATE_EVENTS_TABLE);
29
+ store.exec(CREATE_ENTITY_IDX);
30
+ store.exec(CREATE_SEQ_IDX);
31
+ }
@@ -0,0 +1,6 @@
1
+ export { Store } from './store.js';
2
+ export type { Options } from './store.js';
3
+ export { Rows } from './rows.js';
4
+ export type { Row } from './rows.js';
5
+ export { Tx } from './transaction.js';
6
+ export { AmarokError, AmarokOperationalError, AmarokIntegrityError, AmarokProgrammingError, } from './errors.js';
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { Store } from './store.js';
2
+ export { Rows } from './rows.js';
3
+ export { Tx } from './transaction.js';
4
+ export { AmarokError, AmarokOperationalError, AmarokIntegrityError, AmarokProgrammingError, } from './errors.js';
@@ -0,0 +1,16 @@
1
+ /**
2
+ * JSONB operator passthrough for Amarok-Lite.
3
+ *
4
+ * `amarok-core` natively understands PostgreSQL-style JSONB operators
5
+ * (`->`, `->>`, `@>`, `?`), so no SQL rewriting is required.
6
+ *
7
+ * The `rewrite`, `rewritePlaceholders`, and `normaliseParams` functions are
8
+ * kept as no-ops / identity functions for API compatibility with callers that
9
+ * were written against the SQLite-backed version of the SDK.
10
+ */
11
+ /** Pass SQL through unchanged. amarok-core handles all JSONB operators natively. */
12
+ export declare function rewrite(sql: string): string;
13
+ /** Pass SQL through unchanged. The native engine uses $1/$2 syntax natively. */
14
+ export declare function rewritePlaceholders(sql: string): string;
15
+ /** Pass params through unchanged. */
16
+ export declare function normaliseParams(params: unknown[] | undefined): unknown[];
package/dist/jsonb.js ADDED
@@ -0,0 +1,24 @@
1
+ /**
2
+ * JSONB operator passthrough for Amarok-Lite.
3
+ *
4
+ * `amarok-core` natively understands PostgreSQL-style JSONB operators
5
+ * (`->`, `->>`, `@>`, `?`), so no SQL rewriting is required.
6
+ *
7
+ * The `rewrite`, `rewritePlaceholders`, and `normaliseParams` functions are
8
+ * kept as no-ops / identity functions for API compatibility with callers that
9
+ * were written against the SQLite-backed version of the SDK.
10
+ */
11
+ /** Pass SQL through unchanged. amarok-core handles all JSONB operators natively. */
12
+ export function rewrite(sql) {
13
+ return sql;
14
+ }
15
+ /** Pass SQL through unchanged. The native engine uses $1/$2 syntax natively. */
16
+ export function rewritePlaceholders(sql) {
17
+ return sql;
18
+ }
19
+ /** Pass params through unchanged. */
20
+ export function normaliseParams(params) {
21
+ if (!params || params.length === 0)
22
+ return [];
23
+ return params;
24
+ }
package/dist/rows.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ /** Row type — a record of column name → value. */
2
+ export type Row = Record<string, unknown>;
3
+ /** Iterator over query results. */
4
+ export declare class Rows {
5
+ private readonly _columns;
6
+ private readonly _rows;
7
+ private _pos;
8
+ private _closed;
9
+ /** @internal */
10
+ constructor(rawRows: Record<string, unknown>[], columns: string[]);
11
+ /** Column names for the current result set. */
12
+ get columns(): string[];
13
+ /** Return the next row, or undefined if exhausted. */
14
+ fetchone(): Row | undefined;
15
+ /** Return all remaining rows. */
16
+ fetchall(): Row[];
17
+ /** Iterate rows with for…of. */
18
+ [Symbol.iterator](): Iterator<Row>;
19
+ get length(): number;
20
+ close(): void;
21
+ }
package/dist/rows.js ADDED
@@ -0,0 +1,45 @@
1
+ /** Iterator over query results. */
2
+ export class Rows {
3
+ _columns;
4
+ _rows;
5
+ _pos = 0;
6
+ _closed = false;
7
+ /** @internal */
8
+ constructor(rawRows, columns) {
9
+ this._columns = columns;
10
+ this._rows = rawRows;
11
+ }
12
+ /** Column names for the current result set. */
13
+ get columns() {
14
+ return this._columns;
15
+ }
16
+ /** Return the next row, or undefined if exhausted. */
17
+ fetchone() {
18
+ if (this._closed || this._pos >= this._rows.length)
19
+ return undefined;
20
+ return this._rows[this._pos++];
21
+ }
22
+ /** Return all remaining rows. */
23
+ fetchall() {
24
+ const remaining = this._rows.slice(this._pos);
25
+ this._pos = this._rows.length;
26
+ return remaining;
27
+ }
28
+ /** Iterate rows with for…of. */
29
+ [Symbol.iterator]() {
30
+ return {
31
+ next: () => {
32
+ const row = this.fetchone();
33
+ return row !== undefined
34
+ ? { value: row, done: false }
35
+ : { value: undefined, done: true };
36
+ },
37
+ };
38
+ }
39
+ get length() {
40
+ return this._rows.length;
41
+ }
42
+ close() {
43
+ this._closed = true;
44
+ }
45
+ }