@romedb/node 0.2.13 → 0.2.14

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 CHANGED
@@ -23,7 +23,7 @@ version, 0.2.12, differs from its asset only in `package.json` and this README.
23
23
  project can also depend on the asset of a GitHub release by URL:
24
24
 
25
25
  ```sh
26
- pnpm add https://github.com/romedb/romedb/releases/download/romedb-v0.2.13/romedb-node-0.2.13.tgz
26
+ pnpm add https://github.com/romedb/romedb/releases/download/romedb-v0.2.14/romedb-node-0.2.14.tgz
27
27
  ```
28
28
 
29
29
  ## A first database
@@ -125,7 +125,7 @@ own connection.
125
125
  | `boolean`, `null`, `string` | The same type |
126
126
  | Array, plain object | Array, object |
127
127
  | `Buffer`, `Uint8Array` | Binary |
128
- | `Record(table, key)` | A record ID. The key is a string or a `bigint`. |
128
+ | `Record(table, key)` | A record ID. The key is a string or a `bigint`. `String(record)` gives `table:key`, and `record.equals(other)` compares the table and the key. |
129
129
  | `Vector` | A vector of validated native-format bytes. See [typed vectors](#typed-vectors). |
130
130
 
131
131
  RomeDB never makes a reference from a string. Unsupported JavaScript types,
@@ -702,8 +702,30 @@ it. Exports have no deadline unless you pass `timeoutMs`. `signal`, `timeoutMs`
702
702
  `close()` cancel a running export. It then rejects with `RDB_CANCELLED`, unless it
703
703
  finished first.
704
704
 
705
- The URL client has the streaming form, `rql.exportFile({ signal, timeoutMs })`, for
706
- `romedb serve` and `serve --directory`. Cloud URLs do not support it yet. See
705
+ #### Leaving data out of a copy
706
+
707
+ A whole copy holds everything, including sessions and tokens. Use `emptyTables` when
708
+ someone else gets the file:
709
+
710
+ ```js
711
+ await rql.exportFile('site.romedb', { emptyTables: ['cms_auth_sessions', 'cms_cache'] });
712
+ ```
713
+
714
+ - The named tables keep their schema and indexes, but no rows.
715
+ - Their rows are never read, so they cannot reach the file. They are not in freed
716
+ pages, a log or a receipt.
717
+ - A name that is not a table fails with `RDB_VALIDATION`.
718
+ - An edge table must be emptied together with the tables it connects.
719
+ - Rows in other tables keep their references to the emptied rows.
720
+ - The copy also gets a new identity, because a receipt can hold row data.
721
+
722
+ Use `newIdentity: true` to copy every row into a new database, for example to
723
+ duplicate a site. The copy leaves out the database ID, the sequence and the receipts.
724
+ Its sequence starts at 0, and it gets a new ID the first time it opens.
725
+
726
+ The URL client has the streaming form, `rql.exportFile({ signal, timeoutMs, emptyTables,
727
+ newIdentity })`, for `romedb serve`, `serve --directory` and Cloud. On Cloud, set
728
+ `timeoutMs` high enough for the size of the database: a job builds the file first. See
707
729
  [database files](https://github.com/romedb/romedb/blob/main/romedb/docs/service-protocol.md#database-files) for how the copy is
708
730
  made.
709
731
 
@@ -4,7 +4,7 @@ Generated from the pinned Cargo dependency inventory and checksummed source arch
4
4
 
5
5
  This is a partial notice collection. It supplements LICENSE.md and THIRD_PARTY_NOTICES.md. Inline attributions, unconventional filenames and other bundled components require further review before a complete distribution-notice claim.
6
6
 
7
- Target: `x86_64-unknown-linux-gnu`. Cargo.lock SHA-256: `092e973bbc52cf5defe48c0c1c1cf5bc8755b603f5a6ef47927c31bfa5276aa3`.
7
+ Target: `x86_64-unknown-linux-gnu`. Cargo.lock SHA-256: `1bceceb035554db77bd940502707e4fea905a961d66b28cdbb2ca9b7206c5964`.
8
8
 
9
9
  ## Packages without collected archive or supplemental texts
10
10
 
@@ -703,13 +703,13 @@ Target: `x86_64-unknown-linux-gnu`. Cargo.lock SHA-256: `092e973bbc52cf5defe48c0
703
703
  - `LICENSE-APACHE`: [source text a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2](#text-a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2)
704
704
  - `LICENSE-MIT`: [source text 42d3bf7e7d4d49d72c0555d14ed99c3ee7ce9ce3cbffbc38bbafe8c103f50969](#text-42d3bf7e7d4d49d72c0555d14ed99c3ee7ce9ce3cbffbc38bbafe8c103f50969)
705
705
 
706
- ### romedb-node 0.2.13
706
+ ### romedb-node 0.2.14
707
707
  - Workspace `LICENSE.md`: [source text b646f9ee8bcaf87e8de75153b9df7a2861c7ac445c87e741768b3c2bccf47bc5](#text-b646f9ee8bcaf87e8de75153b9df7a2861c7ac445c87e741768b3c2bccf47bc5)
708
708
 
709
- ### romedb 0.2.13
709
+ ### romedb 0.2.14
710
710
  - Workspace `LICENSE.md`: [source text b646f9ee8bcaf87e8de75153b9df7a2861c7ac445c87e741768b3c2bccf47bc5](#text-b646f9ee8bcaf87e8de75153b9df7a2861c7ac445c87e741768b3c2bccf47bc5)
711
711
 
712
- ### romeql-parser 0.2.13
712
+ ### romeql-parser 0.2.14
713
713
  - Workspace `LICENSE.md`: [source text b646f9ee8bcaf87e8de75153b9df7a2861c7ac445c87e741768b3c2bccf47bc5](#text-b646f9ee8bcaf87e8de75153b9df7a2861c7ac445c87e741768b3c2bccf47bc5)
714
714
 
715
715
  ### rquickjs-core 0.13.0
package/client.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
  // Web APIs only: this file also runs in Workers and browsers through client.mjs.
3
- const { Float64, Record, Vector, encodeValue, decodeValue, plainObject, text } = require('./values.cjs');
3
+ const { Float64, Record, Vector, encodeValue, decodeValue, plainObject, text, checkCopyOptions } = require('./values.cjs');
4
4
 
5
5
  const MAX_REQUEST = 65_536;
6
6
  const MAX_CLOUD_REQUEST = 1_048_576;
@@ -161,8 +161,8 @@ class RemoteDatabase {
161
161
  async exportFile(options = {}) {
162
162
  this.#assertOpen();
163
163
  if (this.#transport) throw new ClientError('RDB_UNSUPPORTED', 'Export embedded databases through EmbeddedDatabase.exportFile', 'not-sent');
164
- if (this.#cloud) throw new ClientError('RDB_UNSUPPORTED', 'Cloud databases do not export database files yet', 'not-sent');
165
- if (!plainObject(options) || Object.keys(options).some(key => !['signal', 'timeoutMs'].includes(key))) throw new TypeError('export options take signal and timeoutMs');
164
+ if (!plainObject(options) || Object.keys(options).some(key => !['signal', 'timeoutMs', 'emptyTables', 'newIdentity'].includes(key))) throw new TypeError('export options take signal, timeoutMs, emptyTables and newIdentity');
165
+ checkCopyOptions(options);
166
166
  const duration = timeout(options.timeoutMs ?? this.#timeout);
167
167
  if (options.signal !== undefined && !(options.signal instanceof AbortSignal)) throw new TypeError('signal must be an AbortSignal');
168
168
  if (options.signal?.aborted) throw new ClientError('RDB_CANCELLED', 'Request cancelled before dispatch', 'not-sent');
@@ -183,7 +183,7 @@ class RemoteDatabase {
183
183
  const credential = this.#token;
184
184
  let status;
185
185
  try {
186
- const response = await this.#fetch(this.#url + '/file', {
186
+ const response = await this.#fetch(this.#url + '/file' + fileQuery(options), {
187
187
  method: 'GET',
188
188
  headers: { authorization: `Bearer ${credential}`, accept: `${DATABASE_FILE_TYPE}, application/json`, ...(this.#id === undefined ? {} : { 'x-romedb-database-id': this.#id }) },
189
189
  redirect: 'manual', signal: controller.signal,
@@ -473,6 +473,12 @@ function pause(ms, signal) {
473
473
  signal.addEventListener('abort', stop, { once: true });
474
474
  });
475
475
  }
476
+ function fileQuery({ emptyTables, newIdentity }) {
477
+ const parts = [];
478
+ if (emptyTables?.length) parts.push(`empty=${emptyTables.map(encodeURIComponent).join(',')}`);
479
+ if (newIdentity) parts.push('identity=new');
480
+ return parts.length ? `?${parts.join('&')}` : '';
481
+ }
476
482
  function httpCode(status) { return status === 401 ? 'RDB_AUTH' : status === 403 ? 'RDB_FORBIDDEN' : status === 409 ? 'RDB_CONFLICT' : status === 429 || status === 413 ? 'RDB_LIMIT' : 'RDB_REMOTE'; }
477
483
  function redactMessage(message, token) { return (token ? message.replaceAll(token, '[redacted]') : message).slice(0, 2048); }
478
484
  function safeMessage(reply, token) { return redactMessage(typeof reply?.error === 'string' ? reply.error : typeof reply?.error?.message === 'string' ? reply.error.message : 'Database request was rejected', token); }
package/client.d.ts CHANGED
@@ -2,6 +2,18 @@ import type { Value } from './values';
2
2
  export { Float64, Record, Vector, Value, PortableValue, encodeValue, decodeValue } from './values';
3
3
  export interface Statement { sql: string; params?: { [key: string]: Value }; }
4
4
  export interface RequestOptions { signal?: AbortSignal; timeoutMs?: number; }
5
+ /** What a copy of the database leaves out. */
6
+ export interface CopyOptions {
7
+ /**
8
+ * These tables keep their schema and indexes but no rows. Their rows are never read, so
9
+ * they cannot reach the file. Unknown names fail with RDB_VALIDATION. The copy also gets
10
+ * a new identity, because a receipt can hold row data.
11
+ */
12
+ emptyTables?: readonly string[];
13
+ /** Leave out the database ID, the sequence and the receipts: the copy is a new database. */
14
+ newIdentity?: boolean;
15
+ }
16
+ export interface FileExportOptions extends RequestOptions, CopyOptions {}
5
17
  export interface WorkLimits { rowsRead?: number; rowMutations?: number; vmSteps?: number; }
6
18
  export interface ReadOptions extends RequestOptions { afterSequence?: number; workLimits?: WorkLimits; }
7
19
  export interface WriteOptions extends ReadOptions { requestId?: string; ifSequence?: number; }
@@ -73,7 +85,7 @@ export class RemoteDatabase implements Client {
73
85
  * Download the database as a .romedb file (romedb serve and serve --directory; not Cloud yet).
74
86
  * timeoutMs covers building the copy, until the file starts arriving; signal also stops the download.
75
87
  */
76
- exportFile(options?: RequestOptions): Promise<DatabaseFile>;
88
+ exportFile(options?: FileExportOptions): Promise<DatabaseFile>;
77
89
  readonly sequence: number;
78
90
  readonly databaseId: string;
79
91
  info(options?: RequestOptions): Promise<DatabaseInfo>;
package/client.mjs CHANGED
@@ -5,7 +5,7 @@ const commonjs = { exports: {} };
5
5
  (function (module, exports, require) {
6
6
  'use strict';
7
7
  // Web APIs only: this file also runs in Workers and browsers through client.mjs.
8
- const { Float64, Record, Vector, encodeValue, decodeValue, plainObject, text } = require('./values.cjs');
8
+ const { Float64, Record, Vector, encodeValue, decodeValue, plainObject, text, checkCopyOptions } = require('./values.cjs');
9
9
 
10
10
  const MAX_REQUEST = 65_536;
11
11
  const MAX_CLOUD_REQUEST = 1_048_576;
@@ -166,8 +166,8 @@ class RemoteDatabase {
166
166
  async exportFile(options = {}) {
167
167
  this.#assertOpen();
168
168
  if (this.#transport) throw new ClientError('RDB_UNSUPPORTED', 'Export embedded databases through EmbeddedDatabase.exportFile', 'not-sent');
169
- if (this.#cloud) throw new ClientError('RDB_UNSUPPORTED', 'Cloud databases do not export database files yet', 'not-sent');
170
- if (!plainObject(options) || Object.keys(options).some(key => !['signal', 'timeoutMs'].includes(key))) throw new TypeError('export options take signal and timeoutMs');
169
+ if (!plainObject(options) || Object.keys(options).some(key => !['signal', 'timeoutMs', 'emptyTables', 'newIdentity'].includes(key))) throw new TypeError('export options take signal, timeoutMs, emptyTables and newIdentity');
170
+ checkCopyOptions(options);
171
171
  const duration = timeout(options.timeoutMs ?? this.#timeout);
172
172
  if (options.signal !== undefined && !(options.signal instanceof AbortSignal)) throw new TypeError('signal must be an AbortSignal');
173
173
  if (options.signal?.aborted) throw new ClientError('RDB_CANCELLED', 'Request cancelled before dispatch', 'not-sent');
@@ -188,7 +188,7 @@ class RemoteDatabase {
188
188
  const credential = this.#token;
189
189
  let status;
190
190
  try {
191
- const response = await this.#fetch(this.#url + '/file', {
191
+ const response = await this.#fetch(this.#url + '/file' + fileQuery(options), {
192
192
  method: 'GET',
193
193
  headers: { authorization: `Bearer ${credential}`, accept: `${DATABASE_FILE_TYPE}, application/json`, ...(this.#id === undefined ? {} : { 'x-romedb-database-id': this.#id }) },
194
194
  redirect: 'manual', signal: controller.signal,
@@ -478,6 +478,12 @@ function pause(ms, signal) {
478
478
  signal.addEventListener('abort', stop, { once: true });
479
479
  });
480
480
  }
481
+ function fileQuery({ emptyTables, newIdentity }) {
482
+ const parts = [];
483
+ if (emptyTables?.length) parts.push(`empty=${emptyTables.map(encodeURIComponent).join(',')}`);
484
+ if (newIdentity) parts.push('identity=new');
485
+ return parts.length ? `?${parts.join('&')}` : '';
486
+ }
481
487
  function httpCode(status) { return status === 401 ? 'RDB_AUTH' : status === 403 ? 'RDB_FORBIDDEN' : status === 409 ? 'RDB_CONFLICT' : status === 429 || status === 413 ? 'RDB_LIMIT' : 'RDB_REMOTE'; }
482
488
  function redactMessage(message, token) { return (token ? message.replaceAll(token, '[redacted]') : message).slice(0, 2048); }
483
489
  function safeMessage(reply, token) { return redactMessage(typeof reply?.error === 'string' ? reply.error : typeof reply?.error?.message === 'string' ? reply.error.message : 'Database request was rejected', token); }
@@ -7,7 +7,7 @@ const path = require('node:path');
7
7
  const { pathToFileURL } = require('node:url');
8
8
  const common = require('./client.cjs');
9
9
  const { connect, RemoteDatabase, ClientError, Float64, Record, Vector, encodeValue, decodeValue, normalizeUrl, validatePrepared, saveRequest, loadRequest } = common;
10
- const { plainObject } = require('./values.cjs');
10
+ const { plainObject, checkCopyOptions } = require('./values.cjs');
11
11
  const { createEmbeddedClient } = require('./client-internal.cjs');
12
12
  const native = require('./native.cjs');
13
13
  const sessions = new Map();
@@ -73,7 +73,8 @@ async function openEmbeddedFile(options) {
73
73
  }
74
74
 
75
75
  function checkExportOptions(options) {
76
- if (!plainObject(options) || Object.keys(options).some(key => !['signal', 'timeoutMs'].includes(key))) throw new TypeError('export options take signal and timeoutMs');
76
+ if (!plainObject(options) || Object.keys(options).some(key => !['signal', 'timeoutMs', 'emptyTables', 'newIdentity'].includes(key))) throw new TypeError('export options take signal, timeoutMs, emptyTables and newIdentity');
77
+ checkCopyOptions(options);
77
78
  if (options.signal !== undefined && !(options.signal instanceof AbortSignal)) throw new TypeError('signal must be an AbortSignal');
78
79
  if (options.timeoutMs !== undefined && (!Number.isInteger(options.timeoutMs) || options.timeoutMs < 1 || options.timeoutMs > 4294967295)) throw new RangeError('timeoutMs must be 1..4294967295');
79
80
  }
@@ -131,7 +132,7 @@ class EmbeddedDatabase {
131
132
  async #exportTo(destination, options) {
132
133
  checkExportOptions(options);
133
134
  if (!destination || destination.length > 4096 || destination.includes('\0')) throw new TypeError('exportFile requires a bounded destination path');
134
- const { sequence, bytes } = await this.#session.request('export', JSON.stringify({ destination: path.resolve(destination) }), options.signal, options.timeoutMs);
135
+ const { sequence, bytes } = await this.#session.request('export', JSON.stringify({ destination: path.resolve(destination), emptyTables: options.emptyTables, newIdentity: options.newIdentity }), options.signal, options.timeoutMs);
135
136
  return { sequence, bytes };
136
137
  }
137
138
  async #exportStream(options) {
@@ -1,7 +1,7 @@
1
1
  export * from './client';
2
- import type { Client, DatabaseInfo, Statement, RequestOptions, ReadOptions, WriteOptions, BatchResult, PreparedWrite, DatabaseFile } from './client';
2
+ import type { Client, DatabaseInfo, Statement, RequestOptions, ReadOptions, WriteOptions, BatchResult, PreparedWrite, DatabaseFile, FileExportOptions } from './client';
3
3
  /** timeoutMs has no default for exports: without it, an export runs until it finishes or signal aborts it. */
4
- export interface ExportOptions { signal?: AbortSignal; timeoutMs?: number; }
4
+ export interface ExportOptions extends FileExportOptions {}
5
5
  export interface ExportedFile { sequence: number; bytes: number; }
6
6
  export interface EmbeddedOptions { path: string; create?: boolean; timeoutMs?: number; maxOpen?: number; }
7
7
  export class EmbeddedDatabase implements Client {
@@ -35,7 +35,8 @@ if (database) parentPort.on('message', ({ id, operation, body, cancellationKey }
35
35
  if (fenced) fail('RDB_STORAGE', 'Embedded outcome is uncertain; close and reconcile the database', 'unknown');
36
36
  if (operation === 'export') {
37
37
  // Writes run on this thread, so none commits between the start of the copy's snapshot and reading the sequence.
38
- const pending = database.startFileExport(JSON.parse(body).destination, cancellationKey);
38
+ const copy = JSON.parse(body);
39
+ const pending = database.startFileExport(copy.destination, cancellationKey, copy.emptyTables, copy.newIdentity);
39
40
  const snapshot = sequence;
40
41
  const finished = pending.finish().then(
41
42
  bytes => parentPort.postMessage({ id, result: { sequence: snapshot, bytes: Number(bytes) } }),
package/index.cjs CHANGED
@@ -1,4 +1,5 @@
1
1
  'use strict';
2
+ const { checkCopyOptions } = require('./values.cjs');
2
3
  const { NativeDatabase, interruptConnection, createCancellationToken, cancelOperation, releaseCancellationToken, vectorFromComponents, vectorFromSparseEntries } = require('./native.cjs');
3
4
  class Record {
4
5
  constructor(table, key) {
@@ -6,6 +7,8 @@ class Record {
6
7
  this.table = table; this.key = key;
7
8
  Object.freeze(this);
8
9
  }
10
+ toString() { return `${this.table}:${this.key}`; }
11
+ equals(other) { return other instanceof Record && other.table === this.table && other.key === this.key; }
9
12
  }
10
13
  class Vector {
11
14
  static float32(values) { return constructVector('float32', values); }
@@ -414,7 +417,9 @@ class AsyncDatabase {
414
417
  }
415
418
  async exportFile(destination, options = {}) {
416
419
  if (typeof destination !== 'string' || !destination || destination.length > 4096 || destination.includes('\0')) throw new TypeError('exportFile requires a bounded destination path');
417
- try { return await this.#request('exportFile', [require('node:path').resolve(destination)], false, options.signal, options.timeoutMs); }
420
+ checkCopyOptions(options);
421
+ const copy = JSON.stringify({ emptyTables: options.emptyTables, newIdentity: options.newIdentity });
422
+ try { return await this.#request('exportFile', [require('node:path').resolve(destination), copy], false, options.signal, options.timeoutMs); }
418
423
  catch (error) {
419
424
  const coded = /^(RDB_[A-Z0-9_]+): (.*)$/s.exec(error.message);
420
425
  if (!coded) throw error;
package/index.d.ts CHANGED
@@ -67,6 +67,12 @@ export class Database {
67
67
 
68
68
  export interface ExecuteOptions { signal?: AbortSignal; timeoutMs?: number; }
69
69
  export interface FileExport { bytes: number; }
70
+ export interface FileExportOptions extends ExecuteOptions {
71
+ /** These tables keep their schema and indexes but no rows, and the copy gets a new identity. */
72
+ emptyTables?: readonly string[];
73
+ /** Leave out the database ID, the sequence and the receipts: the copy is a new database. */
74
+ newIdentity?: boolean;
75
+ }
70
76
  export class AsyncDatabase {
71
77
  collection<T extends object = Document>(name: string): AsyncCollection<T>;
72
78
  private constructor();
@@ -77,7 +83,7 @@ export class AsyncDatabase {
77
83
  exportDocuments(table: string, format?: TransferFormat, options?: ExecuteOptions): Promise<string>;
78
84
  importDocuments(table: string, input: string, format?: TransferFormat, options?: ExecuteOptions): Promise<ImportReport>;
79
85
  /** Copy an MVCC database into a new private file while queries continue; the destination and its log files must not exist. */
80
- exportFile(destination: string, options?: ExecuteOptions): Promise<FileExport>;
86
+ exportFile(destination: string, options?: FileExportOptions): Promise<FileExport>;
81
87
  execute(sql: string, parameters?: Parameters, options?: ExecuteOptions): Promise<QueryResult>;
82
88
  profileSelect(sql: string, parameters?: Parameters, options?: ExecuteOptions): Promise<ProfiledQuery>;
83
89
  /** Checks write results atomically; does not bound engine or logical materialization memory. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@romedb/node",
3
- "version": "0.2.13",
3
+ "version": "0.2.14",
4
4
  "description": "RomeDB for Node.js: the embedded database (Linux x64 native addon) and a Web-API client for romedb serve and RomeDB Cloud",
5
5
  "main": "index.cjs",
6
6
  "types": "index.d.ts",
package/romedb.node CHANGED
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/romedb.node
3
- size: 41428704 bytes
4
- sha256: 3baeea0d26a9eb94d1cad57563330062c82226e3ecd7de6ce1394f96c6f893a8
3
+ size: 41464424 bytes
4
+ sha256: d98971746cedc29b00b8333f763433c47db42f65306b933966e636e0221aac3c
package/values.cjs CHANGED
@@ -18,6 +18,8 @@ class Record {
18
18
  this.key = key;
19
19
  Object.freeze(this);
20
20
  }
21
+ toString() { return `${this.table}:${this.key}`; }
22
+ equals(other) { return other instanceof Record && other.table === this.table && other.key === this.key; }
21
23
  }
22
24
 
23
25
  class Vector {
@@ -204,8 +206,16 @@ function checkDepth(depth) {
204
206
  if (depth > 64) throw new RangeError('value nesting exceeds 64');
205
207
  }
206
208
 
209
+ function checkCopyOptions(options) {
210
+ const { emptyTables, newIdentity } = options;
211
+ if (emptyTables !== undefined && (!Array.isArray(emptyTables) || emptyTables.length > 256 || emptyTables.some(name => typeof name !== 'string' || !name || name.length > 255 || /[,\0]/.test(name)))) {
212
+ throw new TypeError('emptyTables takes up to 256 table names without commas');
213
+ }
214
+ if (newIdentity !== undefined && typeof newIdentity !== 'boolean') throw new TypeError('newIdentity must be a boolean');
215
+ }
216
+
207
217
  function plainObject(value) {
208
218
  return value !== null && typeof value === 'object' && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
209
219
  }
210
220
 
211
- module.exports = { Float64, Record, Vector, encodeValue, decodeValue, plainObject, text };
221
+ module.exports = { Float64, Record, Vector, encodeValue, decodeValue, plainObject, text, checkCopyOptions };
package/values.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export class Float64 { constructor(value: number); readonly value: number; }
2
- export class Record { constructor(table: string, key: string | bigint); readonly table: string; readonly key: string | bigint; }
2
+ export class Record { constructor(table: string, key: string | bigint); readonly table: string; readonly key: string | bigint; /** `table:key`. */ toString(): string; /** The same table and key, including the key's type. */ equals(other: unknown): boolean; }
3
3
  export class Vector {
4
4
  constructor(bytes: Uint8Array);
5
5
  /** A copy of the encoded bytes. */
package/values.mjs CHANGED
@@ -22,6 +22,8 @@ class Record {
22
22
  this.key = key;
23
23
  Object.freeze(this);
24
24
  }
25
+ toString() { return `${this.table}:${this.key}`; }
26
+ equals(other) { return other instanceof Record && other.table === this.table && other.key === this.key; }
25
27
  }
26
28
 
27
29
  class Vector {
@@ -208,13 +210,21 @@ function checkDepth(depth) {
208
210
  if (depth > 64) throw new RangeError('value nesting exceeds 64');
209
211
  }
210
212
 
213
+ function checkCopyOptions(options) {
214
+ const { emptyTables, newIdentity } = options;
215
+ if (emptyTables !== undefined && (!Array.isArray(emptyTables) || emptyTables.length > 256 || emptyTables.some(name => typeof name !== 'string' || !name || name.length > 255 || /[,\0]/.test(name)))) {
216
+ throw new TypeError('emptyTables takes up to 256 table names without commas');
217
+ }
218
+ if (newIdentity !== undefined && typeof newIdentity !== 'boolean') throw new TypeError('newIdentity must be a boolean');
219
+ }
220
+
211
221
  function plainObject(value) {
212
222
  return value !== null && typeof value === 'object' && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
213
223
  }
214
224
 
215
- module.exports = { Float64, Record, Vector, encodeValue, decodeValue, plainObject, text };
225
+ module.exports = { Float64, Record, Vector, encodeValue, decodeValue, plainObject, text, checkCopyOptions };
216
226
  })(commonjs, commonjs.exports, specifier => {
217
227
  throw new TypeError(`Cannot load ${specifier}`);
218
228
  });
219
229
 
220
- export const { Float64, Record, Vector, encodeValue, decodeValue, plainObject, text } = commonjs.exports;
230
+ export const { Float64, Record, Vector, encodeValue, decodeValue, plainObject, text, checkCopyOptions } = commonjs.exports;
package/worker.cjs CHANGED
@@ -10,7 +10,7 @@ parentPort.on('message', ({ id, method, args, cancellationKey }) => {
10
10
  if (method === 'exportFile') {
11
11
  const key = cancellationKey ?? createCancellationToken();
12
12
  const finished = Promise.resolve()
13
- .then(() => db.startFileExport(args[0], key).finish())
13
+ .then(() => { const copy = JSON.parse(args[1]); return db.startFileExport(args[0], key, copy.emptyTables, copy.newIdentity).finish(); })
14
14
  .then(bytes => parentPort.postMessage({ id, result: { bytes: Number(bytes) } }), error => parentPort.postMessage({ id, error: { message: error.message } }))
15
15
  .finally(() => { copies.delete(key); if (cancellationKey === undefined) releaseCancellationToken(key); });
16
16
  copies.set(key, finished);