@zukhruf/sqlite-federation 0.0.0-stage → 0.3.31

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ezzabuzaid
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,154 @@
1
- # Temporary Holding Version
1
+ # @zukhruf/sqlite-federation
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A host keeps its data in many SQLite databases, for example one database for each data source. A statement must read some of them together, for example to join messages with contacts. One SQLite connection can attach at most 10 databases, and the SQLite in Node cannot raise that limit. This package gives one read-only handle over a primary database and every additional database that the host can locate. The primary database stays open for the lifetime of the handle. Each statement attaches the additional databases it names and detaches them when it ends. This is also known as query federation: one query reads many separate databases.
4
+
5
+ The words in these documents have one meaning each. See the glossary in [CONTEXT.md](./CONTEXT.md).
6
+
7
+ ## Use it
8
+
9
+ This project is an experiment. It uses only `node:sqlite`.
10
+
11
+ ```sh
12
+ npm install @zukhruf/sqlite-federation
13
+ ```
14
+
15
+ ## Read many databases with one handle
16
+
17
+ The host gives `locate`. A statement names each database as a schema.
18
+
19
+ ```ts
20
+ import { mkdtempDisposable } from 'node:fs/promises';
21
+ import { tmpdir } from 'node:os';
22
+ import { join } from 'node:path';
23
+ import { DatabaseSync } from 'node:sqlite';
24
+
25
+ import { Federation } from '@zukhruf/sqlite-federation';
26
+
27
+ await using scratch = await mkdtempDisposable(join(tmpdir(), 'federation-'));
28
+ const messagesPath = join(scratch.path, 'messages.sqlite');
29
+ const contactsPath = join(scratch.path, 'contacts.sqlite');
30
+ {
31
+ using messages = new DatabaseSync(messagesPath);
32
+ messages.exec(`CREATE TABLE messages (handle TEXT, text TEXT);
33
+ INSERT INTO messages VALUES ('+962790000000', 'See you at 6');`);
34
+ using contacts = new DatabaseSync(contactsPath);
35
+ contacts.exec(`CREATE TABLE contacts (phone TEXT, name TEXT);
36
+ INSERT INTO contacts VALUES ('+962790000000', 'Sara');`);
37
+ }
38
+ const paths = new Map([
39
+ ['messages', messagesPath],
40
+ ['contacts', contactsPath],
41
+ ]);
42
+
43
+ using federation = new Federation({
44
+ main: ':memory:',
45
+ locate: (name) => paths.get(name),
46
+ });
47
+ console.log(federation.sql.all`
48
+ SELECT c.name, m.text
49
+ FROM messages.messages m JOIN contacts.contacts c ON c.phone = m.handle`);
50
+ ```
51
+
52
+ Output:
53
+
54
+ ```
55
+ [ [Object: null prototype] { name: 'Sara', text: 'See you at 6' } ]
56
+ ```
57
+
58
+ - `sql` has `all` and `get` of the `SQLTagStore` of `node:sqlite`. A value in `${}` is a bound parameter.
59
+ - `main` is a primary database path or `{ name }` to find that path through `locate`. It is opened read-only for the lifetime of the handle. Use `':memory:'` when there is no primary file. `main` does not count toward the attachment limit.
60
+ - A statement names each additional database as a schema: `messages.messages`. Use unqualified names for the primary database and temporary views; qualify cross-database tables. There is no pre-attached search path for unqualified source tables.
61
+ - `locate(name)` is required. It returns the path of a database, or `undefined` when the host has no database by that name. The constructor calls it once for a named `main`; each statement calls it for additional databases, so it reads them from their new paths after the host moves them.
62
+ - Return `undefined` for a database that the host has not written yet. The handle cannot make a database, so an attach of a path with no file fails.
63
+ - Additional databases are read-only. A statement that writes to one fails with `attempt to write a readonly database`. Connection-local temporary objects remain writable.
64
+ - `using` closes the connection of the handle at the end of its scope.
65
+
66
+ ## Load temporary views and run SQL strings
67
+
68
+ `exec(sql)` executes connection-local setup SQL, including a file containing several `CREATE TEMP VIEW` statements. It uses SQLite's `exec` directly, without replaying the script. The databases a temporary view refers to are resolved when a query reads it.
69
+
70
+ `query(sql)` runs exactly one SQL statement and returns `QueryResult`: `columns`, including headings when there are no rows, and `rows`, whose values are indexed by column position. Duplicate column names therefore keep both values. Trailing comments and empty statements are allowed; a second statement, even malformed, fails before the first executes.
71
+
72
+ ```ts
73
+ federation.exec(`
74
+ CREATE TEMP VIEW conversations AS
75
+ SELECT c.name, m.text
76
+ FROM messages.messages m JOIN contacts.contacts c ON c.phone = m.handle;
77
+ `);
78
+ const result = federation.query('SELECT * FROM conversations');
79
+ ```
80
+
81
+ Qualified tables and PRAGMAs discover additional databases through `locate`. Both `PRAGMA contacts.table_info(contacts)` and `SELECT * FROM pragma_table_info('contacts', 'contacts')` locate Contacts, including when the schema is a bound parameter in a tagged query. Quoted schema names such as `"call-history"` work too. Every attachment made for the statement is detached afterwards, including on failure; `main` and temporary views remain on the connection. The additional databases count toward the attachment limit.
82
+
83
+ The constructor accepts Node's `timeout` option for waiting on a writer's lock, in milliseconds. Omit it to use Node's native behavior. Hosts that need crash recovery perform it before supplying the paths, including inside `locate` for a named primary or additional database; Federation opens those files read-only.
84
+
85
+ ## Read a primary database
86
+
87
+ Supply `main: { name }` to find the primary database through `locate`, or supply its path directly. Tables can be qualified with `main`, and unqualified schema queries and PRAGMAs describe that database. A named primary is opened as `main`, not attached under its name. Presets loaded with `exec` live on the same connection and can be joined with attached databases. Federation closes the connection on disposal.
88
+
89
+ ```ts
90
+ import { Federation } from '@zukhruf/sqlite-federation';
91
+
92
+ using database = new Federation({
93
+ main: { name: 'messages' },
94
+ locate: (name) => paths.get(name),
95
+ timeout: 30_000,
96
+ });
97
+ const result = database.query(
98
+ 'SELECT c.name, m.text FROM main.messages m JOIN contacts.contacts c ON c.phone = m.handle',
99
+ );
100
+ ```
101
+
102
+ ## Each statement attaches what it names
103
+
104
+ - SQLite fails with `no such table: <name>.<table>` for a qualified table or `unknown database <name>` for a PRAGMA when its database is not attached. The handle decodes the name, attaches that database and runs the statement again. It never parses the statement. [ADR 0002](./docs/adr/0002-the-error-of-sqlite-names-the-database.md) tells why.
105
+ - When the statement ends, the handle detaches each database that it attached, also when the statement fails. An attach costs less than 1 ms. [ADR 0001](./docs/adr/0001-each-statement-attaches-what-it-names.md) tells why.
106
+ - Between two statements, the handle holds no lock. A writer in another connection or process can commit.
107
+ - One statement can attach at most 10 databases in addition to `main`. To read more, split the statement and join the rows in code. [ADR 0003](./docs/adr/0003-a-statement-over-the-limit-fails.md) tells why.
108
+
109
+ ## Errors
110
+
111
+ | Error | When |
112
+ | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
113
+ | `TooManyDatabasesError` | A statement names more databases than the limit. `names` has at least one name more than the limit. The message tells you to split the statement. |
114
+ | `Unknown main database <name>` | The constructor cannot locate the named primary database. |
115
+ | `no such table: <name>.<table>` | The error of SQLite, when `locate` returns `undefined` for the name, or when the database does not have the table. |
116
+ | `unable to open database file` | The error of SQLite, when `locate` returns a path with no file. |
117
+ | `attempt to write a readonly database` | The error of SQLite, when a statement writes. |
118
+ | The error of `locate` | The constructor or statement fails with that error. The databases that the statement attached before are detached. |
119
+ | `unknown database <name>` | The error of SQLite, when a PRAGMA names a database that `locate` cannot find. Quoting follows the statement or the table-valued PRAGMA argument. |
120
+ | `Run one SQL statement at a time` | A raw query contains a second statement. |
121
+
122
+ ## The same concepts in other systems
123
+
124
+ | System | What it does |
125
+ | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
126
+ | SQLite `swarmvtab` | A virtual table over one table in many databases. It opens each database on its own connection and closes the least recently used one. It is an extension that Node does not include. |
127
+ | Datasette `--crossdb` | It attaches the first 10 databases and ignores the others. |
128
+ | `q` (harelba/q) | It attaches databases up to the limit, and copies the tables of the others into a temporary database. |
129
+ | PostgreSQL `postgres_fdw` | A foreign table reads a table of another server, and one query can join it with local tables. |
130
+
131
+ ## Documentation
132
+
133
+ - [0001 Each statement attaches what it names](./docs/adr/0001-each-statement-attaches-what-it-names.md)
134
+ - [0002 The error of SQLite names the database](./docs/adr/0002-the-error-of-sqlite-names-the-database.md)
135
+ - [0003 A statement over the limit fails](./docs/adr/0003-a-statement-over-the-limit-fails.md)
136
+
137
+ ## Development
138
+
139
+ This package is part of the [zukhruf](../../../README.md) workspace. Run the commands from the workspace root.
140
+
141
+ ```sh
142
+ npm install
143
+ npx nx run sqlite-federation:test # builds, then runs the tests in src/
144
+ npx nx run sqlite-federation:typecheck # formats, lints, then type checks
145
+ npx nx run sqlite-federation:build # compiles src/ to dist/
146
+ ```
147
+
148
+ ```
149
+ src/
150
+ index.ts the exports
151
+ federation.ts Federation, FederationOptions, Query, QueryResult and Locate
152
+ too-many-databases-error.ts TooManyDatabasesError
153
+ federation.test.ts the tests, on real SQLite files
154
+ ```
@@ -0,0 +1,22 @@
1
+ import { type DatabaseSyncOptions, type SQLOutputValue, type SQLTagStore } from 'node:sqlite';
2
+ export type Query = Pick<SQLTagStore, 'all' | 'get'>;
3
+ export type QueryResult = {
4
+ readonly columns: readonly string[];
5
+ readonly rows: readonly Readonly<Record<number, SQLOutputValue>>[];
6
+ };
7
+ export type Locate = (name: string) => string | undefined;
8
+ export type FederationOptions = Pick<DatabaseSyncOptions, 'timeout'> & {
9
+ readonly main: string | {
10
+ readonly name: string;
11
+ };
12
+ readonly locate: Locate;
13
+ };
14
+ export declare class Federation implements Disposable {
15
+ #private;
16
+ readonly sql: Query;
17
+ constructor({ main, locate, ...options }: FederationOptions);
18
+ exec(sql: string): void;
19
+ query(sql: string): QueryResult;
20
+ [Symbol.dispose](): void;
21
+ }
22
+ //# sourceMappingURL=federation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"federation.d.ts","sourceRoot":"","sources":["../src/federation.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,KAAK,WAAW,EACjB,MAAM,aAAa,CAAC;AAMrB,MAAM,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,CAAC;AAGrD,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,IAAI,EAAE,SAAS,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC;CACpE,CAAC;AAIF,MAAM,MAAM,MAAM,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;AAE1D,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,mBAAmB,EAAE,SAAS,CAAC,GAAG;IACrE,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAClD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,CAAC;AAKF,qBAAa,UAAW,YAAW,UAAU;;IAC3C,QAAQ,CAAC,GAAG,EAAE,KAAK,CAAC;gBAIR,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,EAAE,EAAE,iBAAiB;IAuB3D,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAOvB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW;IAqD/B,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI;CAGzB"}
@@ -0,0 +1,114 @@
1
+ import { DatabaseSync, } from 'node:sqlite';
2
+ import { TooManyDatabasesError } from "./too-many-databases-error.js";
3
+ // One read-only handle over main and every database the host can locate, past
4
+ // the limit on how many one SQLite connection attaches at once. Main stays
5
+ // open; each statement attaches and detaches the additional databases it names.
6
+ export class Federation {
7
+ sql;
8
+ #connection;
9
+ #locate;
10
+ constructor({ main, locate, ...options }) {
11
+ this.#locate = locate;
12
+ if (typeof main !== 'string') {
13
+ const path = locate(main.name);
14
+ if (path === undefined)
15
+ throw new Error(`Unknown main database ${main.name}`);
16
+ main = path;
17
+ }
18
+ this.#connection = new DatabaseSync(main, {
19
+ ...options,
20
+ readOnly: true,
21
+ });
22
+ const store = this.#connection.createTagStore();
23
+ this.sql = {
24
+ all: (strings, ...values) => this.#statement(() => store.all(strings, ...values)),
25
+ get: (strings, ...values) => this.#statement(() => store.get(strings, ...values)),
26
+ };
27
+ }
28
+ // Loads connection-local SQL, such as a file defining temporary views.
29
+ // SQLite resolves the databases those views name when a query reads them.
30
+ exec(sql) {
31
+ this.#connection.exec(sql);
32
+ }
33
+ // Runs one SQL string, discovering additional databases from qualified names.
34
+ // SQLite judges the tail: comments and empty statements are allowed, but a
35
+ // second statement, even malformed, is rejected before the first executes.
36
+ query(sql) {
37
+ return this.#statement(() => {
38
+ const statement = this.#connection.prepare(sql);
39
+ let another;
40
+ try {
41
+ this.#connection.prepare(sql.slice(statement.sourceSQL.length));
42
+ another = true;
43
+ }
44
+ catch (error) {
45
+ another = !(error instanceof Error &&
46
+ 'code' in error &&
47
+ error.code === 'ERR_INVALID_ARG_VALUE');
48
+ }
49
+ if (another)
50
+ throw new Error('Run one SQL statement at a time');
51
+ statement.setReturnArrays(true);
52
+ return {
53
+ columns: statement.columns().map(({ name }) => name),
54
+ rows: statement.all(),
55
+ };
56
+ });
57
+ }
58
+ // SQLite says which database is missing, so the statement is never parsed
59
+ // here. A name the host cannot locate, or a table its database lacks, fails
60
+ // as the statement would. No additional database stays attached, so the next
61
+ // statement asks the host again where each additional database is.
62
+ #statement(run) {
63
+ const attached = [];
64
+ try {
65
+ for (;;) {
66
+ try {
67
+ return run();
68
+ }
69
+ catch (error) {
70
+ const name = missingDatabase(error);
71
+ if (name === undefined || attached.includes(name))
72
+ throw error;
73
+ const path = this.#locate(name);
74
+ if (path === undefined)
75
+ throw error;
76
+ const { attach: limit } = this.#connection.limits;
77
+ if (attached.length === limit)
78
+ throw new TooManyDatabasesError(limit, [...attached, name]);
79
+ this.#connection
80
+ .prepare(`ATTACH DATABASE ? AS ${quoted(name)}`)
81
+ .run(path);
82
+ attached.push(name);
83
+ }
84
+ }
85
+ }
86
+ finally {
87
+ for (const name of attached)
88
+ this.#connection.exec(`DETACH DATABASE ${quoted(name)}`);
89
+ }
90
+ }
91
+ [Symbol.dispose]() {
92
+ this.#connection.close();
93
+ }
94
+ }
95
+ // Table errors name the schema directly. PRAGMA errors carry a SQL token:
96
+ // SQLite preserves identifier quoting, and table-valued PRAGMAs quote it
97
+ // as a string. Decode that token, without parsing the statement itself.
98
+ function missingDatabase(error) {
99
+ if (!(error instanceof Error))
100
+ return undefined;
101
+ const token = /^unknown database (.+)$/s.exec(error.message)?.[1];
102
+ if (token === undefined)
103
+ return /^no such table: ([^.]+)\./.exec(error.message)?.[1];
104
+ const quote = token[0];
105
+ if (quote === '[')
106
+ return token.slice(1, -1);
107
+ if (quote === '"' || quote === "'" || quote === '`')
108
+ return token.slice(1, -1).replaceAll(quote + quote, quote);
109
+ return token;
110
+ }
111
+ function quoted(name) {
112
+ return `"${name.replaceAll('"', '""')}"`;
113
+ }
114
+ //# sourceMappingURL=federation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"federation.js","sourceRoot":"","sources":["../src/federation.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,GAIb,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAqBtE,8EAA8E;AAC9E,2EAA2E;AAC3E,gFAAgF;AAChF,MAAM,OAAO,UAAU;IACZ,GAAG,CAAQ;IACX,WAAW,CAAe;IAC1B,OAAO,CAAS;IAEzB,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,EAAqB;QACzD,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;QACtB,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC7B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC/B,IAAI,IAAI,KAAK,SAAS;gBACpB,MAAM,IAAI,KAAK,CAAC,yBAAyB,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;YACxD,IAAI,GAAG,IAAI,CAAC;QACd,CAAC;QACD,IAAI,CAAC,WAAW,GAAG,IAAI,YAAY,CAAC,IAAI,EAAE;YACxC,GAAG,OAAO;YACV,QAAQ,EAAE,IAAI;SACf,CAAC,CAAC;QACH,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,cAAc,EAAE,CAAC;QAChD,IAAI,CAAC,GAAG,GAAG;YACT,GAAG,EAAE,CAAC,OAAO,EAAE,GAAG,MAAM,EAAE,EAAE,CAC1B,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,MAAM,CAAC,CAAC;YACtD,GAAG,EAAE,CAAC,OAAO,EAAE,GAAG,MAAM,EAAE,EAAE,CAC1B,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,MAAM,CAAC,CAAC;SACvD,CAAC;IACJ,CAAC;IAED,uEAAuE;IACvE,0EAA0E;IAC1E,IAAI,CAAC,GAAW;QACd,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;IAED,8EAA8E;IAC9E,2EAA2E;IAC3E,2EAA2E;IAC3E,KAAK,CAAC,GAAW;QACf,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE;YAC1B,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAChD,IAAI,OAAgB,CAAC;YACrB,IAAI,CAAC;gBACH,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;gBAChE,OAAO,GAAG,IAAI,CAAC;YACjB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,GAAG,CAAC,CACT,KAAK,YAAY,KAAK;oBACtB,MAAM,IAAI,KAAK;oBACf,KAAK,CAAC,IAAI,KAAK,uBAAuB,CACvC,CAAC;YACJ,CAAC;YACD,IAAI,OAAO;gBAAE,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;YAChE,SAAS,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;YAChC,OAAO;gBACL,OAAO,EAAE,SAAS,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC;gBACpD,IAAI,EAAE,SAAS,CAAC,GAAG,EAAE;aACtB,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;IAED,0EAA0E;IAC1E,4EAA4E;IAC5E,6EAA6E;IAC7E,mEAAmE;IACnE,UAAU,CAAS,GAAiB;QAClC,MAAM,QAAQ,GAAa,EAAE,CAAC;QAC9B,IAAI,CAAC;YACH,SAAS,CAAC;gBACR,IAAI,CAAC;oBACH,OAAO,GAAG,EAAE,CAAC;gBACf,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,MAAM,IAAI,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;oBACpC,IAAI,IAAI,KAAK,SAAS,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC;wBAAE,MAAM,KAAK,CAAC;oBAC/D,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;oBAChC,IAAI,IAAI,KAAK,SAAS;wBAAE,MAAM,KAAK,CAAC;oBACpC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;oBAClD,IAAI,QAAQ,CAAC,MAAM,KAAK,KAAK;wBAC3B,MAAM,IAAI,qBAAqB,CAAC,KAAK,EAAE,CAAC,GAAG,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC;oBAC9D,IAAI,CAAC,WAAW;yBACb,OAAO,CAAC,wBAAwB,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;yBAC/C,GAAG,CAAC,IAAI,CAAC,CAAC;oBACb,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACtB,CAAC;YACH,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,KAAK,MAAM,IAAI,IAAI,QAAQ;gBACzB,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,mBAAmB,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;IAED,CAAC,MAAM,CAAC,OAAO,CAAC;QACd,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC;IAC3B,CAAC;CACF;AAED,0EAA0E;AAC1E,yEAAyE;AACzE,wEAAwE;AACxE,SAAS,eAAe,CAAC,KAAc;IACrC,IAAI,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAChD,MAAM,KAAK,GAAG,0BAA0B,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAClE,IAAI,KAAK,KAAK,SAAS;QACrB,OAAO,2BAA2B,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC9D,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IACvB,IAAI,KAAK,KAAK,GAAG;QAAE,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC7C,IAAI,KAAK,KAAK,GAAG,IAAI,KAAK,KAAK,GAAG,IAAI,KAAK,KAAK,GAAG;QACjD,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,GAAG,KAAK,EAAE,KAAK,CAAC,CAAC;IAC7D,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,MAAM,CAAC,IAAY;IAC1B,OAAO,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC;AAC3C,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { Federation, type FederationOptions, type Locate, type Query, type QueryResult, } from './federation.ts';
2
+ export { TooManyDatabasesError } from './too-many-databases-error.ts';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,KAAK,iBAAiB,EACtB,KAAK,MAAM,EACX,KAAK,KAAK,EACV,KAAK,WAAW,GACjB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { Federation, } from "./federation.js";
2
+ export { TooManyDatabasesError } from "./too-many-databases-error.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,GAKX,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC"}
@@ -0,0 +1,5 @@
1
+ export declare class TooManyDatabasesError extends Error {
2
+ readonly names: readonly string[];
3
+ constructor(limit: number, names: readonly string[]);
4
+ }
5
+ //# sourceMappingURL=too-many-databases-error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"too-many-databases-error.d.ts","sourceRoot":"","sources":["../src/too-many-databases-error.ts"],"names":[],"mappings":"AACA,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;gBAEtB,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE;CAOpD"}
@@ -0,0 +1,10 @@
1
+ // One statement names more databases than one SQLite connection can attach.
2
+ export class TooManyDatabasesError extends Error {
3
+ names;
4
+ constructor(limit, names) {
5
+ super(`One statement can read at most ${limit} databases, and this one names at least ${names.length}: ${names.join(', ')}. Split it into statements that each name ${limit} or fewer, and join their rows in code.`);
6
+ this.name = 'TooManyDatabasesError';
7
+ this.names = names;
8
+ }
9
+ }
10
+ //# sourceMappingURL=too-many-databases-error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"too-many-databases-error.js","sourceRoot":"","sources":["../src/too-many-databases-error.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IACrC,KAAK,CAAoB;IAElC,YAAY,KAAa,EAAE,KAAwB;QACjD,KAAK,CACH,kCAAkC,KAAK,2CAA2C,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,6CAA6C,KAAK,yCAAyC,CAC/M,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;QACpC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;CACF"}
package/package.json CHANGED
@@ -1,6 +1,43 @@
1
1
  {
2
2
  "name": "@zukhruf/sqlite-federation",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.3.31",
4
+ "description": "One read-only handle over many SQLite databases. Each statement attaches the databases that it names and detaches them when it ends. Thus a host can keep more databases than the attach limit of SQLite, and one statement can read up to 10 of them in addition to the primary database.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/JanuaryLabs/zukhruf",
9
+ "directory": "packages/sqlite/federation"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/JanuaryLabs/zukhruf/issues"
13
+ },
14
+ "homepage": "https://github.com/JanuaryLabs/zukhruf/tree/main/packages/sqlite/federation#readme",
15
+ "keywords": [
16
+ "sqlite",
17
+ "node-sqlite",
18
+ "attach",
19
+ "federation",
20
+ "federated-query",
21
+ "multi-database",
22
+ "read-only"
23
+ ],
24
+ "type": "module",
25
+ "exports": {
26
+ "./package.json": "./package.json",
27
+ ".": {
28
+ "@zukhruf/source": "./src/index.ts",
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.js",
31
+ "default": "./dist/index.js"
32
+ }
33
+ },
34
+ "files": [
35
+ "dist",
36
+ "!dist/spec/**",
37
+ "!**/*.tsbuildinfo",
38
+ "!**/*.test.*"
39
+ ],
40
+ "publishConfig": {
41
+ "access": "public"
42
+ }
43
+ }