@hozon/db 0.2.1 → 0.2.3
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 +25 -10
- 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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getLogger } from '@sozai/log';
|
|
2
|
-
import { Kysely,
|
|
2
|
+
import { Kysely, sql } from 'kysely';
|
|
3
3
|
import { Migrator } from 'kysely/migration';
|
|
4
4
|
import { withTransactionalDDL } from './dialect.js';
|
|
5
5
|
import { HozonDBClosedError, InvalidTablePrefixError, SavepointOverlapError } from './errors.js';
|
|
@@ -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,15 +26,13 @@ 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.
|
|
30
|
-
dialect: this.#adapter.kind === 'sqlite' ? withTransactionalDDL(this.#adapter.dialect) : this.#adapter.dialect
|
|
31
|
-
plugins: [
|
|
32
|
-
new ParseJSONResultsPlugin()
|
|
33
|
-
]
|
|
33
|
+
dialect: this.#adapter.kind === 'sqlite' ? withTransactionalDDL(this.#adapter.dialect) : this.#adapter.dialect
|
|
34
34
|
});
|
|
35
|
-
this.#storeDB = this.#db.withPlugin(
|
|
35
|
+
this.#storeDB = this.#db.withPlugin(this.#defaultPlugin);
|
|
36
36
|
this.#logger = params.logger ?? getLogger([
|
|
37
37
|
'hozon',
|
|
38
38
|
'db'
|
|
@@ -63,7 +63,8 @@ export class HozonDB {
|
|
|
63
63
|
if (store == null) {
|
|
64
64
|
throw new Error(`Store "${name}" is not registered`);
|
|
65
65
|
}
|
|
66
|
-
|
|
66
|
+
const storeDB = store.unprefixedTables == null ? this.#storeDB : this.#db.withPlugin(this.#getStorePlugin(store));
|
|
67
|
+
this.#storeAPIs.set(name, store.createAPI(storeDB, this.#adapter));
|
|
67
68
|
}
|
|
68
69
|
return this.#storeAPIs.get(name);
|
|
69
70
|
}
|
|
@@ -95,8 +96,10 @@ export class HozonDB {
|
|
|
95
96
|
let result;
|
|
96
97
|
let savepointID = 0;
|
|
97
98
|
try {
|
|
98
|
-
|
|
99
|
+
// The transaction runs on the unprefixed base so each store applies its own plugin.
|
|
100
|
+
result = await this.#db.transaction().execute(async (trx)=>{
|
|
99
101
|
const txKysely = trx;
|
|
102
|
+
const txStoreDB = txKysely.withPlugin(this.#defaultPlugin);
|
|
100
103
|
const txStoreAPIs = new Map();
|
|
101
104
|
const createScopedProvider = (scopedCommitHooks, scopedRollbackHooks)=>{
|
|
102
105
|
// Savepoints on one connection form a stack: a scope holds at most
|
|
@@ -110,7 +113,8 @@ export class HozonDB {
|
|
|
110
113
|
if (store == null) {
|
|
111
114
|
throw new Error(`Store "${name}" is not registered`);
|
|
112
115
|
}
|
|
113
|
-
|
|
116
|
+
const storeDB = store.unprefixedTables == null ? txStoreDB : txKysely.withPlugin(this.#getStorePlugin(store));
|
|
117
|
+
txStoreAPIs.set(name, store.createAPI(storeDB, this.#adapter));
|
|
114
118
|
}
|
|
115
119
|
return txStoreAPIs.get(name);
|
|
116
120
|
},
|
|
@@ -217,7 +221,7 @@ export class HozonDB {
|
|
|
217
221
|
types: this.#adapter.types,
|
|
218
222
|
functions: this.#adapter.functions
|
|
219
223
|
}) : store.migrations;
|
|
220
|
-
const plugin =
|
|
224
|
+
const plugin = this.#getStorePlugin(store);
|
|
221
225
|
migrations = Object.fromEntries(Object.entries(storeMigrations).map(([name, migration])=>{
|
|
222
226
|
const down = migration.down;
|
|
223
227
|
return [
|
|
@@ -232,6 +236,17 @@ export class HozonDB {
|
|
|
232
236
|
}
|
|
233
237
|
return migrations;
|
|
234
238
|
}
|
|
239
|
+
#getStorePlugin(store) {
|
|
240
|
+
if (store.unprefixedTables == null) return this.#defaultPlugin;
|
|
241
|
+
let plugin = this.#storePlugins.get(store.name);
|
|
242
|
+
if (plugin == null) {
|
|
243
|
+
plugin = new TablePrefixPlugin(this.#tablePrefix, {
|
|
244
|
+
unprefixed: store.unprefixedTables
|
|
245
|
+
});
|
|
246
|
+
this.#storePlugins.set(store.name, plugin);
|
|
247
|
+
}
|
|
248
|
+
return plugin;
|
|
249
|
+
}
|
|
235
250
|
#ensurePreflight() {
|
|
236
251
|
// Snapshot before awaiting so concurrent triggers share one read-only gate.
|
|
237
252
|
// 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);
|