@hozon/db 0.2.1 → 0.2.2
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/lib/db.d.ts +6 -0
- package/lib/db.js +23 -5
- package/lib/index.d.ts +1 -1
- package/lib/table-prefix.d.ts +5 -1
- package/lib/table-prefix.js +6 -3
- package/package.json +1 -1
package/lib/db.d.ts
CHANGED
|
@@ -12,6 +12,12 @@ export type StoreDefinition<Tables, API> = {
|
|
|
12
12
|
name: string;
|
|
13
13
|
migrations: Record<string, Migration> | ((ctx: MigrationContext) => Record<string, Migration>);
|
|
14
14
|
dependsOn?: Array<string>;
|
|
15
|
+
/**
|
|
16
|
+
* Logical table names this store keeps outside the table prefix, for example
|
|
17
|
+
* dynamic tables whose names would exceed identifier length limits. Matching
|
|
18
|
+
* tables are not namespaced: the store must keep them collision-free.
|
|
19
|
+
*/
|
|
20
|
+
unprefixedTables?: (name: string) => boolean;
|
|
15
21
|
createAPI: (db: Kysely<Tables>, adapter: Adapter) => API;
|
|
16
22
|
};
|
|
17
23
|
export type StoreProvider<Stores extends Record<string, unknown> = Record<string, unknown>> = {
|
package/lib/db.js
CHANGED
|
@@ -7,6 +7,7 @@ import { checkStore } from './preflight.js';
|
|
|
7
7
|
import { TablePrefixPlugin } from './table-prefix.js';
|
|
8
8
|
export class HozonDB {
|
|
9
9
|
#tablePrefix;
|
|
10
|
+
#defaultPlugin;
|
|
10
11
|
#closed = false;
|
|
11
12
|
#closing = null;
|
|
12
13
|
#preflight = null;
|
|
@@ -15,6 +16,7 @@ export class HozonDB {
|
|
|
15
16
|
#adapter;
|
|
16
17
|
#db;
|
|
17
18
|
#storeDB;
|
|
19
|
+
#storePlugins = new Map();
|
|
18
20
|
#logger;
|
|
19
21
|
#migrated = new Map();
|
|
20
22
|
#storeAPIs = new Map();
|
|
@@ -24,6 +26,7 @@ export class HozonDB {
|
|
|
24
26
|
if (!/^[a-z][a-z0-9_]{0,30}$/.test(this.#tablePrefix)) {
|
|
25
27
|
throw new InvalidTablePrefixError(this.#tablePrefix);
|
|
26
28
|
}
|
|
29
|
+
this.#defaultPlugin = new TablePrefixPlugin(this.#tablePrefix);
|
|
27
30
|
this.#adapter = params.adapter;
|
|
28
31
|
this.#db = new Kysely({
|
|
29
32
|
// SQLite supports transactional DDL, but Kysely's SQLite adapters do not report it.
|
|
@@ -32,7 +35,7 @@ export class HozonDB {
|
|
|
32
35
|
new ParseJSONResultsPlugin()
|
|
33
36
|
]
|
|
34
37
|
});
|
|
35
|
-
this.#storeDB = this.#db.withPlugin(
|
|
38
|
+
this.#storeDB = this.#db.withPlugin(this.#defaultPlugin);
|
|
36
39
|
this.#logger = params.logger ?? getLogger([
|
|
37
40
|
'hozon',
|
|
38
41
|
'db'
|
|
@@ -63,7 +66,8 @@ export class HozonDB {
|
|
|
63
66
|
if (store == null) {
|
|
64
67
|
throw new Error(`Store "${name}" is not registered`);
|
|
65
68
|
}
|
|
66
|
-
|
|
69
|
+
const storeDB = store.unprefixedTables == null ? this.#storeDB : this.#db.withPlugin(this.#getStorePlugin(store));
|
|
70
|
+
this.#storeAPIs.set(name, store.createAPI(storeDB, this.#adapter));
|
|
67
71
|
}
|
|
68
72
|
return this.#storeAPIs.get(name);
|
|
69
73
|
}
|
|
@@ -95,8 +99,10 @@ export class HozonDB {
|
|
|
95
99
|
let result;
|
|
96
100
|
let savepointID = 0;
|
|
97
101
|
try {
|
|
98
|
-
|
|
102
|
+
// The transaction runs on the unprefixed base so each store applies its own plugin.
|
|
103
|
+
result = await this.#db.transaction().execute(async (trx)=>{
|
|
99
104
|
const txKysely = trx;
|
|
105
|
+
const txStoreDB = txKysely.withPlugin(this.#defaultPlugin);
|
|
100
106
|
const txStoreAPIs = new Map();
|
|
101
107
|
const createScopedProvider = (scopedCommitHooks, scopedRollbackHooks)=>{
|
|
102
108
|
// Savepoints on one connection form a stack: a scope holds at most
|
|
@@ -110,7 +116,8 @@ export class HozonDB {
|
|
|
110
116
|
if (store == null) {
|
|
111
117
|
throw new Error(`Store "${name}" is not registered`);
|
|
112
118
|
}
|
|
113
|
-
|
|
119
|
+
const storeDB = store.unprefixedTables == null ? txStoreDB : txKysely.withPlugin(this.#getStorePlugin(store));
|
|
120
|
+
txStoreAPIs.set(name, store.createAPI(storeDB, this.#adapter));
|
|
114
121
|
}
|
|
115
122
|
return txStoreAPIs.get(name);
|
|
116
123
|
},
|
|
@@ -217,7 +224,7 @@ export class HozonDB {
|
|
|
217
224
|
types: this.#adapter.types,
|
|
218
225
|
functions: this.#adapter.functions
|
|
219
226
|
}) : store.migrations;
|
|
220
|
-
const plugin =
|
|
227
|
+
const plugin = this.#getStorePlugin(store);
|
|
221
228
|
migrations = Object.fromEntries(Object.entries(storeMigrations).map(([name, migration])=>{
|
|
222
229
|
const down = migration.down;
|
|
223
230
|
return [
|
|
@@ -232,6 +239,17 @@ export class HozonDB {
|
|
|
232
239
|
}
|
|
233
240
|
return migrations;
|
|
234
241
|
}
|
|
242
|
+
#getStorePlugin(store) {
|
|
243
|
+
if (store.unprefixedTables == null) return this.#defaultPlugin;
|
|
244
|
+
let plugin = this.#storePlugins.get(store.name);
|
|
245
|
+
if (plugin == null) {
|
|
246
|
+
plugin = new TablePrefixPlugin(this.#tablePrefix, {
|
|
247
|
+
unprefixed: store.unprefixedTables
|
|
248
|
+
});
|
|
249
|
+
this.#storePlugins.set(store.name, plugin);
|
|
250
|
+
}
|
|
251
|
+
return plugin;
|
|
252
|
+
}
|
|
235
253
|
#ensurePreflight() {
|
|
236
254
|
// Snapshot before awaiting so concurrent triggers share one read-only gate.
|
|
237
255
|
// A failed attempt clears the gate so the next caller reruns the read-only checks,
|
package/lib/index.d.ts
CHANGED
|
@@ -6,4 +6,4 @@ export { HozonDB } from './db.js';
|
|
|
6
6
|
export { HozonDBClosedError, InvalidTablePrefixError, SavepointOverlapError, SchemaVersionError, } from './errors.js';
|
|
7
7
|
export { chunk, withKeepSet } from './keep-set.js';
|
|
8
8
|
export { withStoreTransaction } from './store-transaction.js';
|
|
9
|
-
export { TablePrefixPlugin } from './table-prefix.js';
|
|
9
|
+
export { type TablePrefixOptions, TablePrefixPlugin } from './table-prefix.js';
|
package/lib/table-prefix.d.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import type { KyselyPlugin, PluginTransformQueryArgs, PluginTransformResultArgs, QueryResult, RootOperationNode, UnknownRow } from 'kysely';
|
|
2
|
+
export type TablePrefixOptions = {
|
|
3
|
+
/** Logical table names to leave unprefixed, e.g. dynamic tables near identifier length limits. */
|
|
4
|
+
unprefixed?: (name: string) => boolean;
|
|
5
|
+
};
|
|
2
6
|
export declare class TablePrefixPlugin implements KyselyPlugin {
|
|
3
7
|
#private;
|
|
4
|
-
constructor(prefix: string);
|
|
8
|
+
constructor(prefix: string, options?: TablePrefixOptions);
|
|
5
9
|
transformQuery({ node, queryId }: PluginTransformQueryArgs): RootOperationNode;
|
|
6
10
|
transformResult({ result }: PluginTransformResultArgs): Promise<QueryResult<UnknownRow>>;
|
|
7
11
|
}
|
package/lib/table-prefix.js
CHANGED
|
@@ -26,15 +26,17 @@ function isRootOperationNode(node) {
|
|
|
26
26
|
}
|
|
27
27
|
class TablePrefixTransformer extends OperationNodeTransformer {
|
|
28
28
|
#prefix;
|
|
29
|
+
#unprefixed;
|
|
29
30
|
#tables = new Set();
|
|
30
31
|
#ctes = new Set();
|
|
31
32
|
#aliases = new Set();
|
|
32
33
|
#writeTargets = new Set();
|
|
33
34
|
#writeNames = new Set();
|
|
34
35
|
#prefixedIdentifiers = new WeakSet();
|
|
35
|
-
constructor(prefix){
|
|
36
|
+
constructor(prefix, options = {}){
|
|
36
37
|
super();
|
|
37
38
|
this.#prefix = prefix;
|
|
39
|
+
this.#unprefixed = options.unprefixed;
|
|
38
40
|
}
|
|
39
41
|
transformNodeImpl(node, queryId) {
|
|
40
42
|
if (!isRootOperationNode(node) || node.kind === 'RawNode' && this.nodeStack.length > 1) {
|
|
@@ -90,6 +92,7 @@ class TablePrefixTransformer extends OperationNodeTransformer {
|
|
|
90
92
|
#prefixTable(node) {
|
|
91
93
|
if (node.table.schema !== undefined || this.#prefixedIdentifiers.has(node.table.identifier)) return node;
|
|
92
94
|
const name = node.table.identifier.name;
|
|
95
|
+
if (this.#unprefixed?.(name)) return node;
|
|
93
96
|
const transformed = TableNode.create(`${this.#prefix}_${name}`);
|
|
94
97
|
this.#prefixedIdentifiers.add(transformed.table.identifier);
|
|
95
98
|
return transformed;
|
|
@@ -235,8 +238,8 @@ class TablePrefixTransformer extends OperationNodeTransformer {
|
|
|
235
238
|
}
|
|
236
239
|
export class TablePrefixPlugin {
|
|
237
240
|
#transformer;
|
|
238
|
-
constructor(prefix){
|
|
239
|
-
this.#transformer = new TablePrefixTransformer(prefix);
|
|
241
|
+
constructor(prefix, options){
|
|
242
|
+
this.#transformer = new TablePrefixTransformer(prefix, options);
|
|
240
243
|
}
|
|
241
244
|
transformQuery({ node, queryId }) {
|
|
242
245
|
return this.#transformer.transformNode(node, queryId);
|