@stacksjs/database 0.70.258 → 0.70.260

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.
Files changed (50) hide show
  1. package/dist/auth-tables.js +18 -137
  2. package/dist/column.js +1 -26
  3. package/dist/custom/audits.js +20 -54
  4. package/dist/custom/errors.js +16 -46
  5. package/dist/custom/index.js +1 -3
  6. package/dist/custom/jobs.js +13 -137
  7. package/dist/database.js +1 -181
  8. package/dist/datetime-columns.js +2 -79
  9. package/dist/ddl-constraints.js +7 -111
  10. package/dist/defaults.js +1 -48
  11. package/dist/dialect.js +1 -79
  12. package/dist/driver-config.js +1 -172
  13. package/dist/drivers/defaults/index.js +1 -1
  14. package/dist/drivers/defaults/traits.js +1 -29
  15. package/dist/drivers/dynamodb.js +1 -607
  16. package/dist/drivers/helpers.js +1 -206
  17. package/dist/drivers/index.js +1 -9
  18. package/dist/drivers/mysql.js +58 -299
  19. package/dist/drivers/postgres.js +78 -368
  20. package/dist/drivers/sqlite.js +61 -379
  21. package/dist/ensure-database.js +1 -145
  22. package/dist/fk-audit.js +3 -187
  23. package/dist/index.js +1 -64
  24. package/dist/managed-columns.js +1 -59
  25. package/dist/migration-dialect.js +4 -107
  26. package/dist/migration-ledger.js +1 -382
  27. package/dist/migration-lock.js +1 -143
  28. package/dist/migrations.js +15 -1118
  29. package/dist/model-sources.js +1 -76
  30. package/dist/notification-tables.js +4 -49
  31. package/dist/query-logger.js +2 -241
  32. package/dist/query-parser.js +1 -93
  33. package/dist/rbac-tables.js +6 -61
  34. package/dist/relation-columns.js +1 -66
  35. package/dist/replicas.js +1 -74
  36. package/dist/safe-migrations.js +2 -52
  37. package/dist/schema.js +1 -10
  38. package/dist/seeder.js +1 -457
  39. package/dist/sql-helpers.js +1 -50
  40. package/dist/table.js +1 -26
  41. package/dist/tools/setup.js +1 -6
  42. package/dist/trait-tables.js +8 -153
  43. package/dist/transaction-context.js +1 -62
  44. package/dist/types.js +1 -98
  45. package/dist/unique-audit.js +3 -155
  46. package/dist/utils.js +1 -285
  47. package/dist/uuid-columns.js +1 -68
  48. package/dist/validators.js +1 -122
  49. package/dist/vschema.js +2 -121
  50. package/package.json +20 -13
package/dist/utils.js CHANGED
@@ -1,285 +1 @@
1
- import { AsyncLocalStorage } from "node:async_hooks";
2
- import { createQueryBuilder, registerPersistentQueryHooks, setConfig } from "@stacksjs/query-builder";
3
- import { SQL } from "bun";
4
- import { env as envVars } from "@stacksjs/env";
5
- import { getConnectionDefaults } from "./defaults";
6
- import { isMysqlWire, toQueryBuilderDialect } from "./dialect";
7
- import { contextInTransaction, markContextWrote, resolveReplicaConnection, selectReplica, shouldRouteToReplica, withTransactionContext } from "./replicas";
8
- import { aggregateFunctions } from "./types";
9
- const sqliteDefaults = getConnectionDefaults("sqlite", envVars), mysqlDefaults = getConnectionDefaults("mysql", envVars), postgresDefaults = getConnectionDefaults("postgres", envVars);
10
- let appEnv = envVars.APP_ENV || "local", dbDriver = envVars.DB_CONNECTION || "sqlite", dbConfig = {
11
- connections: {
12
- sqlite: { database: sqliteDefaults.database, prefix: "" },
13
- mysql: { name: mysqlDefaults.database, host: mysqlDefaults.host, username: mysqlDefaults.username, password: mysqlDefaults.password, port: mysqlDefaults.port, prefix: "" },
14
- singlestore: { name: mysqlDefaults.database, host: mysqlDefaults.host, username: mysqlDefaults.username, password: mysqlDefaults.password, port: mysqlDefaults.port, prefix: "" },
15
- vitess: { name: mysqlDefaults.database, host: mysqlDefaults.host, username: mysqlDefaults.username, password: mysqlDefaults.password, port: 15306, prefix: "" },
16
- postgres: { name: postgresDefaults.database, host: postgresDefaults.host, username: postgresDefaults.username, password: postgresDefaults.password, port: postgresDefaults.port, prefix: "" }
17
- }
18
- }, dbConfigLockTail = Promise.resolve();
19
- export function acquireDbConfigLock() {
20
- let release = () => {};
21
- const held = new Promise((resolve) => {
22
- release = resolve;
23
- }), acquired = dbConfigLockTail.then(() => release);
24
- dbConfigLockTail = dbConfigLockTail.then(() => held);
25
- return acquired;
26
- }
27
- export function initializeDbConfig(config) {
28
- if (config?.app?.env)
29
- appEnv = config.app.env;
30
- if (config?.database?.default)
31
- dbDriver = config.database.default;
32
- if (config?.database)
33
- dbConfig = config.database;
34
- updateQueryBuilderConfig();
35
- _dbInstance = null;
36
- _replicaInstances = new Map;
37
- }
38
- function getEnv() {
39
- return appEnv;
40
- }
41
- function getDriver() {
42
- return dbDriver;
43
- }
44
- function getDatabaseConfig() {
45
- return dbConfig;
46
- }
47
- function getDialect() {
48
- return toQueryBuilderDialect(getDriver());
49
- }
50
- function getDbConfig() {
51
- const driver = getDriver(), database = getDatabaseConfig(), env = getEnv();
52
- if (driver === "sqlite") {
53
- const defaultName = env !== "testing" ? "database/stacks.sqlite" : "database/stacks_testing.sqlite";
54
- return {
55
- database: database.connections?.sqlite?.database ?? defaultName
56
- };
57
- }
58
- if (driver === "mysql")
59
- return {
60
- database: database.connections?.mysql?.name || "stacks",
61
- host: database.connections?.mysql?.host ?? "127.0.0.1",
62
- username: database.connections?.mysql?.username ?? "root",
63
- password: database.connections?.mysql?.password ?? "",
64
- port: database.connections?.mysql?.port ?? 3306
65
- };
66
- if (driver === "singlestore")
67
- return {
68
- database: database.connections?.singlestore?.name || "stacks",
69
- host: database.connections?.singlestore?.host ?? "127.0.0.1",
70
- username: database.connections?.singlestore?.username ?? "root",
71
- password: database.connections?.singlestore?.password ?? "",
72
- port: database.connections?.singlestore?.port ?? 3306
73
- };
74
- if (driver === "vitess")
75
- return {
76
- database: database.connections?.vitess?.name || "stacks",
77
- host: database.connections?.vitess?.host ?? "127.0.0.1",
78
- username: database.connections?.vitess?.username ?? "root",
79
- password: database.connections?.vitess?.password ?? "",
80
- port: database.connections?.vitess?.port ?? 15306
81
- };
82
- if (driver === "postgres") {
83
- const dbName = database.connections?.postgres?.name ?? "stacks";
84
- return {
85
- database: env === "testing" ? `${dbName}_testing` : dbName,
86
- host: database.connections?.postgres?.host ?? "127.0.0.1",
87
- username: database.connections?.postgres?.username ?? "",
88
- password: database.connections?.postgres?.password ?? "",
89
- port: database.connections?.postgres?.port ?? 5432
90
- };
91
- }
92
- return { database: ":memory:" };
93
- }
94
- export const QB_SNAPSHOT_DIR = "storage/framework/database", RAW_QUERY_SOFT_DELETE_CONFIG = {
95
- enabled: !1,
96
- column: "deleted_at",
97
- defaultFilter: !0
98
- };
99
- export function createDatabaseQueryHooks(dispatch) {
100
- function forward(event) {
101
- try {
102
- Promise.resolve(dispatch(event)).catch(() => {});
103
- } catch {}
104
- }
105
- return {
106
- onQueryEnd: (event) => forward({
107
- query: {
108
- sql: event.sql,
109
- parameters: event.params
110
- },
111
- queryDurationMillis: event.durationMs
112
- }),
113
- onQueryError: (event) => forward({
114
- query: {
115
- sql: event.sql,
116
- parameters: event.params
117
- },
118
- queryDurationMillis: event.durationMs,
119
- error: event.error
120
- })
121
- };
122
- }
123
- function forwardDatabaseQuery(event) {
124
- import("./query-logger").then(({ logQuery }) => logQuery(event)).catch(() => {});
125
- }
126
- registerPersistentQueryHooks(createDatabaseQueryHooks(forwardDatabaseQuery));
127
- function getPoolConfig() {
128
- const driver = getDriver();
129
- if (driver === "sqlite")
130
- return;
131
- return getDatabaseConfig().connections?.[driver]?.pool;
132
- }
133
- function getReplicas() {
134
- const driver = getDriver();
135
- if (driver === "sqlite")
136
- return [];
137
- return getDatabaseConfig().connections?.[driver]?.replicas ?? [];
138
- }
139
- function getReadPolicy() {
140
- return getDatabaseConfig().reads ?? {};
141
- }
142
- function toBunPoolOptions(pool) {
143
- if (!pool)
144
- return {};
145
- const options = {};
146
- if (pool.max !== void 0)
147
- options.max = pool.max;
148
- if (pool.idleTimeoutMs !== void 0)
149
- options.idleTimeout = Math.round(pool.idleTimeoutMs / 1000);
150
- if (pool.acquireTimeoutMs !== void 0)
151
- options.connectionTimeout = Math.round(pool.acquireTimeoutMs / 1000);
152
- if (pool.maxLifetimeMs !== void 0)
153
- options.maxLifetime = Math.round(pool.maxLifetimeMs / 1000);
154
- return options;
155
- }
156
- function updateQueryBuilderConfig() {
157
- const dialect = getDialect(), dbConfigForQb = getDbConfig(), pool = getPoolConfig();
158
- setConfig({
159
- dialect,
160
- database: pool ? { ...dbConfigForQb, pool } : dbConfigForQb,
161
- verbose: getEnv() !== "production",
162
- snapshotDir: QB_SNAPSHOT_DIR,
163
- timestamps: {
164
- createdAt: "created_at",
165
- updatedAt: "updated_at",
166
- defaultOrderColumn: "created_at"
167
- },
168
- softDeletes: RAW_QUERY_SOFT_DELETE_CONFIG
169
- });
170
- }
171
- updateQueryBuilderConfig();
172
- let _dbInstance = null, _configInitPromise = null;
173
- function ensureConfigLoaded() {
174
- if (!_configInitPromise)
175
- _configInitPromise = (async () => {
176
- try {
177
- const { config, overridesReady } = await import("@stacksjs/config");
178
- await overridesReady;
179
- if (config) {
180
- initializeDbConfig(config);
181
- _dbInstance = null;
182
- }
183
- } catch {}
184
- })();
185
- return _configInitPromise;
186
- }
187
- export async function ensureDatabaseConfigLoaded() {
188
- await ensureConfigLoaded();
189
- }
190
- export { applySqlitePragmas, SQLITE_BOOTSTRAP_PRAGMAS } from "@stacksjs/query-builder";
191
- const sqliteTxOwner = new AsyncLocalStorage;
192
- let sqliteTxTail = Promise.resolve();
193
- function serializeSqliteTransaction(run) {
194
- if (sqliteTxOwner.getStore())
195
- return run();
196
- const result = sqliteTxTail.then(() => sqliteTxOwner.run(!0, run));
197
- sqliteTxTail = result.then(() => {
198
- return;
199
- }, () => {
200
- return;
201
- });
202
- return result;
203
- }
204
- function applySqliteTransactionSerialization(instance) {
205
- const original = instance.transaction.bind(instance);
206
- instance.transaction = (...args) => serializeSqliteTransaction(() => original(...args));
207
- }
208
- function applyTransactionRoutingContext(instance) {
209
- const original = instance.transaction.bind(instance);
210
- instance.transaction = (...args) => withTransactionContext(() => original(...args));
211
- }
212
- function getDb() {
213
- if (!_dbInstance) {
214
- updateQueryBuilderConfig();
215
- _dbInstance = createQueryBuilder();
216
- if (getDialect() === "sqlite")
217
- applySqliteTransactionSerialization(_dbInstance);
218
- applyTransactionRoutingContext(_dbInstance);
219
- }
220
- return _dbInstance;
221
- }
222
- let _replicaInstances = new Map;
223
- function getReplicaDb(replica) {
224
- const primary = getDbConfig(), resolved = resolveReplicaConnection(replica, primary), key = `${resolved.host}:${resolved.port ?? ""}`, cached = _replicaInstances.get(key);
225
- if (cached)
226
- return cached;
227
- const scheme = isMysqlWire(getDriver()) ? "mysql" : "postgres", auth = resolved.username ? `${encodeURIComponent(resolved.username)}:${encodeURIComponent(resolved.password ?? "")}@` : "", url = `${scheme}://${auth}${resolved.host}:${resolved.port}/${resolved.database}`, sql = new SQL({ url, ...toBunPoolOptions(getPoolConfig()) }), instance = createQueryBuilder({ sql });
228
- _replicaInstances.set(key, instance);
229
- return instance;
230
- }
231
- function getReadDb() {
232
- const replicas = getReplicas(), policy = getReadPolicy();
233
- if (!shouldRouteToReplica({ policy, replicas }))
234
- return getDb();
235
- const replica = selectReplica(replicas, policy.strategy);
236
- return replica ? getReplicaDb(replica) : getDb();
237
- }
238
- function getExplicitReadDb() {
239
- const replicas = getReplicas();
240
- if (!replicas.length || contextInTransaction())
241
- return getDb();
242
- const replica = selectReplica(replicas, getReadPolicy().strategy);
243
- return replica ? getReplicaDb(replica) : getDb();
244
- }
245
- const WRITE_ENTRY_POINTS = new Set([
246
- "insertInto",
247
- "updateTable",
248
- "deleteFrom",
249
- "create",
250
- "createMany",
251
- "insertOrIgnore",
252
- "insertGetId",
253
- "updateOrInsert",
254
- "upsert"
255
- ]), READ_ENTRY_POINTS = new Set([
256
- "selectFrom",
257
- "selectFromSub",
258
- "select"
259
- ]);
260
- ensureConfigLoaded();
261
- export const db = new Proxy({}, {
262
- get(_target, prop) {
263
- if (prop === "fn")
264
- return aggregateFunctions;
265
- if (prop === "read")
266
- return readDb;
267
- if (typeof prop === "string" && WRITE_ENTRY_POINTS.has(prop))
268
- markContextWrote();
269
- const instance = typeof prop === "string" && READ_ENTRY_POINTS.has(prop) ? getReadDb() : getDb(), value = instance[prop];
270
- if (typeof value === "function")
271
- return value.bind(instance);
272
- return value;
273
- }
274
- }), readDb = new Proxy({}, {
275
- get(_target, prop) {
276
- if (prop === "fn")
277
- return aggregateFunctions;
278
- const instance = getExplicitReadDb(), value = instance[prop];
279
- if (typeof value === "function")
280
- return value.bind(instance);
281
- return value;
282
- }
283
- });
284
-
285
- export { setConfig };
1
+ import{AsyncLocalStorage}from"node:async_hooks";import{createQueryBuilder,registerPersistentQueryHooks,setConfig}from"@stacksjs/query-builder";import{SQL}from"bun";import{env as envVars}from"@stacksjs/env";import{getConnectionDefaults}from"./defaults";import{isMysqlWire,toQueryBuilderDialect}from"./dialect";import{contextInTransaction,markContextWrote,resolveReplicaConnection,selectReplica,shouldRouteToReplica,withTransactionContext}from"./replicas";import{aggregateFunctions}from"./types";const sqliteDefaults=getConnectionDefaults("sqlite",envVars),mysqlDefaults=getConnectionDefaults("mysql",envVars),postgresDefaults=getConnectionDefaults("postgres",envVars);let appEnv=envVars.APP_ENV||"local",dbDriver=envVars.DB_CONNECTION||"sqlite",dbConfig={connections:{sqlite:{database:sqliteDefaults.database,prefix:""},mysql:{name:mysqlDefaults.database,host:mysqlDefaults.host,username:mysqlDefaults.username,password:mysqlDefaults.password,port:mysqlDefaults.port,prefix:""},singlestore:{name:mysqlDefaults.database,host:mysqlDefaults.host,username:mysqlDefaults.username,password:mysqlDefaults.password,port:mysqlDefaults.port,prefix:""},vitess:{name:mysqlDefaults.database,host:mysqlDefaults.host,username:mysqlDefaults.username,password:mysqlDefaults.password,port:15306,prefix:""},postgres:{name:postgresDefaults.database,host:postgresDefaults.host,username:postgresDefaults.username,password:postgresDefaults.password,port:postgresDefaults.port,prefix:""}}},dbConfigLockTail=Promise.resolve();export function acquireDbConfigLock(){let release=()=>{};const held=new Promise((resolve)=>{release=resolve}),acquired=dbConfigLockTail.then(()=>release);dbConfigLockTail=dbConfigLockTail.then(()=>held);return acquired}export function initializeDbConfig(config){if(config?.app?.env)appEnv=config.app.env;if(config?.database?.default)dbDriver=config.database.default;if(config?.database)dbConfig=config.database;updateQueryBuilderConfig();_dbInstance=null;_replicaInstances=new Map}function getEnv(){return appEnv}function getDriver(){return dbDriver}function getDatabaseConfig(){return dbConfig}function getDialect(){return toQueryBuilderDialect(getDriver())}function getDbConfig(){const driver=getDriver(),database=getDatabaseConfig(),env=getEnv();if(driver==="sqlite"){const defaultName=env!=="testing"?"database/stacks.sqlite":"database/stacks_testing.sqlite";return{database:database.connections?.sqlite?.database??defaultName}}if(driver==="mysql")return{database:database.connections?.mysql?.name||"stacks",host:database.connections?.mysql?.host??"127.0.0.1",username:database.connections?.mysql?.username??"root",password:database.connections?.mysql?.password??"",port:database.connections?.mysql?.port??3306};if(driver==="singlestore")return{database:database.connections?.singlestore?.name||"stacks",host:database.connections?.singlestore?.host??"127.0.0.1",username:database.connections?.singlestore?.username??"root",password:database.connections?.singlestore?.password??"",port:database.connections?.singlestore?.port??3306};if(driver==="vitess")return{database:database.connections?.vitess?.name||"stacks",host:database.connections?.vitess?.host??"127.0.0.1",username:database.connections?.vitess?.username??"root",password:database.connections?.vitess?.password??"",port:database.connections?.vitess?.port??15306};if(driver==="postgres"){const dbName=database.connections?.postgres?.name??"stacks";return{database:env==="testing"?`${dbName}_testing`:dbName,host:database.connections?.postgres?.host??"127.0.0.1",username:database.connections?.postgres?.username??"",password:database.connections?.postgres?.password??"",port:database.connections?.postgres?.port??5432}}return{database:":memory:"}}export const QB_SNAPSHOT_DIR="storage/framework/database",RAW_QUERY_SOFT_DELETE_CONFIG={enabled:!1,column:"deleted_at",defaultFilter:!0};export function createDatabaseQueryHooks(dispatch){function forward(event){try{Promise.resolve(dispatch(event)).catch(()=>{})}catch{}}return{onQueryEnd:(event)=>forward({query:{sql:event.sql,parameters:event.params},queryDurationMillis:event.durationMs}),onQueryError:(event)=>forward({query:{sql:event.sql,parameters:event.params},queryDurationMillis:event.durationMs,error:event.error})}}function forwardDatabaseQuery(event){import("./query-logger").then(({logQuery})=>logQuery(event)).catch(()=>{})}registerPersistentQueryHooks(createDatabaseQueryHooks(forwardDatabaseQuery));function getPoolConfig(){const driver=getDriver();if(driver==="sqlite")return;return getDatabaseConfig().connections?.[driver]?.pool}function getReplicas(){const driver=getDriver();if(driver==="sqlite")return[];return getDatabaseConfig().connections?.[driver]?.replicas??[]}function getReadPolicy(){return getDatabaseConfig().reads??{}}function toBunPoolOptions(pool){if(!pool)return{};const options={};if(pool.max!==void 0)options.max=pool.max;if(pool.idleTimeoutMs!==void 0)options.idleTimeout=Math.round(pool.idleTimeoutMs/1000);if(pool.acquireTimeoutMs!==void 0)options.connectionTimeout=Math.round(pool.acquireTimeoutMs/1000);if(pool.maxLifetimeMs!==void 0)options.maxLifetime=Math.round(pool.maxLifetimeMs/1000);return options}function updateQueryBuilderConfig(){const dialect=getDialect(),dbConfigForQb=getDbConfig(),pool=getPoolConfig();setConfig({dialect,database:pool?{...dbConfigForQb,pool}:dbConfigForQb,verbose:getEnv()!=="production",snapshotDir:QB_SNAPSHOT_DIR,timestamps:{createdAt:"created_at",updatedAt:"updated_at",defaultOrderColumn:"created_at"},softDeletes:RAW_QUERY_SOFT_DELETE_CONFIG})}updateQueryBuilderConfig();let _dbInstance=null,_configInitPromise=null;function ensureConfigLoaded(){if(!_configInitPromise)_configInitPromise=(async()=>{try{const{config,overridesReady}=await import("@stacksjs/config");await overridesReady;if(config){initializeDbConfig(config);_dbInstance=null}}catch{}})();return _configInitPromise}export async function ensureDatabaseConfigLoaded(){await ensureConfigLoaded()}export{applySqlitePragmas,SQLITE_BOOTSTRAP_PRAGMAS}from"@stacksjs/query-builder";const sqliteTxOwner=new AsyncLocalStorage;let sqliteTxTail=Promise.resolve();function serializeSqliteTransaction(run){if(sqliteTxOwner.getStore())return run();const result=sqliteTxTail.then(()=>sqliteTxOwner.run(!0,run));sqliteTxTail=result.then(()=>{return},()=>{return});return result}function applySqliteTransactionSerialization(instance){const original=instance.transaction.bind(instance);instance.transaction=(...args)=>serializeSqliteTransaction(()=>original(...args))}function applyTransactionRoutingContext(instance){const original=instance.transaction.bind(instance);instance.transaction=(...args)=>withTransactionContext(()=>original(...args))}function getDb(){if(!_dbInstance){updateQueryBuilderConfig();_dbInstance=createQueryBuilder();if(getDialect()==="sqlite")applySqliteTransactionSerialization(_dbInstance);applyTransactionRoutingContext(_dbInstance)}return _dbInstance}let _replicaInstances=new Map;function getReplicaDb(replica){const primary=getDbConfig(),resolved=resolveReplicaConnection(replica,primary),key=`${resolved.host}:${resolved.port??""}`,cached=_replicaInstances.get(key);if(cached)return cached;const scheme=isMysqlWire(getDriver())?"mysql":"postgres",auth=resolved.username?`${encodeURIComponent(resolved.username)}:${encodeURIComponent(resolved.password??"")}@`:"",url=`${scheme}://${auth}${resolved.host}:${resolved.port}/${resolved.database}`,sql=new SQL({url,...toBunPoolOptions(getPoolConfig())}),instance=createQueryBuilder({sql});_replicaInstances.set(key,instance);return instance}function getReadDb(){const replicas=getReplicas(),policy=getReadPolicy();if(!shouldRouteToReplica({policy,replicas}))return getDb();const replica=selectReplica(replicas,policy.strategy);return replica?getReplicaDb(replica):getDb()}function getExplicitReadDb(){const replicas=getReplicas();if(!replicas.length||contextInTransaction())return getDb();const replica=selectReplica(replicas,getReadPolicy().strategy);return replica?getReplicaDb(replica):getDb()}const WRITE_ENTRY_POINTS=new Set(["insertInto","updateTable","deleteFrom","create","createMany","insertOrIgnore","insertGetId","updateOrInsert","upsert"]),READ_ENTRY_POINTS=new Set(["selectFrom","selectFromSub","select"]);ensureConfigLoaded();export const db=new Proxy({},{get(_target,prop){if(prop==="fn")return aggregateFunctions;if(prop==="read")return readDb;if(typeof prop==="string"&&WRITE_ENTRY_POINTS.has(prop))markContextWrote();const instance=typeof prop==="string"&&READ_ENTRY_POINTS.has(prop)?getReadDb():getDb(),value=instance[prop];if(typeof value==="function")return value.bind(instance);return value}}),readDb=new Proxy({},{get(_target,prop){if(prop==="fn")return aggregateFunctions;const instance=getExplicitReadDb(),value=instance[prop];if(typeof value==="function")return value.bind(instance);return value}});export{setConfig};
@@ -1,68 +1 @@
1
- import process from "node:process";
2
- import { log } from "@stacksjs/logging";
3
- import { path } from "@stacksjs/path";
4
- import { getModelName, getTableName } from "@stacksjs/orm";
5
- import { fs } from "@stacksjs/storage";
6
- import { db } from "./utils";
7
- import { sqlHelpers } from "./sql-helpers";
8
- function getDbDriver() {
9
- return process.env.DB_CONNECTION || "sqlite";
10
- }
11
- function uuidColumnType(sql) {
12
- if (sql.isPostgres)
13
- return "UUID";
14
- if (sql.isMysql)
15
- return "VARCHAR(255)";
16
- return "TEXT";
17
- }
18
- export function uuidColumnSql(table, sql) {
19
- return `ALTER TABLE ${table} ADD COLUMN uuid ${uuidColumnType(sql)}`;
20
- }
21
- async function loadModelsFrom(dir) {
22
- const out = [];
23
- if (!fs.existsSync(dir))
24
- return out;
25
- const entries = fs.readdirSync(dir, { withFileTypes: !0 });
26
- for (const entry of entries) {
27
- const fullPath = path.join(dir, entry.name);
28
- if (entry.isDirectory()) {
29
- out.push(...await loadModelsFrom(fullPath));
30
- continue;
31
- }
32
- if (!entry.name.endsWith(".ts"))
33
- continue;
34
- if (entry.name.startsWith("_") || entry.name.startsWith("index"))
35
- continue;
36
- try {
37
- const imported = (await import(fullPath)).default;
38
- if (imported?.name || imported?.table)
39
- out.push({ filePath: fullPath, model: imported });
40
- } catch {}
41
- }
42
- return out;
43
- }
44
- export async function findUuidTables() {
45
- const dirs = [path.userModelsPath(), path.frameworkPath("defaults/app/Models")], tables = new Set;
46
- for (const dir of dirs)
47
- for (const { filePath, model } of await loadModelsFrom(dir)) {
48
- if (!model.traits?.useUuid)
49
- continue;
50
- tables.add(getTableName(model, filePath));
51
- }
52
- return [...tables];
53
- }
54
- export async function ensureUuidColumns(sql, options = {}) {
55
- const tables = await findUuidTables();
56
- for (const table of tables)
57
- try {
58
- await db.unsafe(uuidColumnSql(table, sql)).execute();
59
- if (options.verbose)
60
- log.debug(`[uuid-columns] Added uuid column to ${table}`);
61
- } catch {
62
- if (options.verbose)
63
- log.debug(`[uuid-columns] Skipped (already applied or ${table} missing): uuid column`);
64
- }
65
- }
66
- export async function ensureUuidColumnsForCurrentDriver(options = {}) {
67
- await ensureUuidColumns(sqlHelpers(getDbDriver()), options);
68
- }
1
+ import process from"node:process";import{log}from"@stacksjs/logging";import{path}from"@stacksjs/path";import{getModelName,getTableName}from"@stacksjs/orm";import{fs}from"@stacksjs/storage";import{db}from"./utils";import{sqlHelpers}from"./sql-helpers";function getDbDriver(){return process.env.DB_CONNECTION||"sqlite"}function uuidColumnType(sql){if(sql.isPostgres)return"UUID";if(sql.isMysql)return"VARCHAR(255)";return"TEXT"}export function uuidColumnSql(table,sql){return`ALTER TABLE ${table} ADD COLUMN uuid ${uuidColumnType(sql)}`}async function loadModelsFrom(dir){const out=[];if(!fs.existsSync(dir))return out;const entries=fs.readdirSync(dir,{withFileTypes:!0});for(const entry of entries){const fullPath=path.join(dir,entry.name);if(entry.isDirectory()){out.push(...await loadModelsFrom(fullPath));continue}if(!entry.name.endsWith(".ts"))continue;if(entry.name.startsWith("_")||entry.name.startsWith("index"))continue;try{const imported=(await import(fullPath)).default;if(imported?.name||imported?.table)out.push({filePath:fullPath,model:imported})}catch{}}return out}export async function findUuidTables(){const dirs=[path.userModelsPath(),path.frameworkPath("defaults/app/Models")],tables=new Set;for(const dir of dirs)for(const{filePath,model}of await loadModelsFrom(dir)){if(!model.traits?.useUuid)continue;tables.add(getTableName(model,filePath))}return[...tables]}export async function ensureUuidColumns(sql,options={}){const tables=await findUuidTables();for(const table of tables)try{await db.unsafe(uuidColumnSql(table,sql)).execute();if(options.verbose)log.debug(`[uuid-columns] Added uuid column to ${table}`)}catch{if(options.verbose)log.debug(`[uuid-columns] Skipped (already applied or ${table} missing): uuid column`)}}export async function ensureUuidColumnsForCurrentDriver(options={}){await ensureUuidColumns(sqlHelpers(getDbDriver()),options)}
@@ -1,122 +1 @@
1
- export function isStringValidator(v) {
2
- return v.name === "string";
3
- }
4
- export function isNumberValidator(v) {
5
- return v.name === "number";
6
- }
7
- export function enumValidator(v) {
8
- return v.name === "enum";
9
- }
10
- export function isBooleanValidator(v) {
11
- return v.name === "boolean";
12
- }
13
- export function isDateValidator(v) {
14
- return v.name === "date";
15
- }
16
- export function isUnixValidator(v) {
17
- return v.name === "unix";
18
- }
19
- export function isFloatValidator(v) {
20
- return v.name === "float";
21
- }
22
- export function isDatetimeValidator(v) {
23
- return v.name === "datetime";
24
- }
25
- export function isTimestampValidator(v) {
26
- return v.name === "timestamp";
27
- }
28
- export function isTimestampTzValidator(v) {
29
- return v.name === "timestampTz";
30
- }
31
- export function isDecimalValidator(v) {
32
- return v.name === "decimal";
33
- }
34
- export function isSmallintValidator(v) {
35
- return v.name === "smallint";
36
- }
37
- export function isIntegerValidator(v) {
38
- return v.name === "integer";
39
- }
40
- export function isBigintValidator(v) {
41
- return v.name === "bigint";
42
- }
43
- export function isBinaryValidator(v) {
44
- return v.name === "binary";
45
- }
46
- export function isBlobValidator(v) {
47
- return v.name === "blob";
48
- }
49
- export function isJsonValidator(v) {
50
- return v.name === "json";
51
- }
52
- export function checkValidator(validator, driver) {
53
- if (enumValidator(validator))
54
- return prepareEnumColumnType(validator, driver);
55
- if (isStringValidator(validator))
56
- return prepareTextColumnType(validator, driver);
57
- if (isNumberValidator(validator))
58
- return prepareNumberColumnType(validator, driver);
59
- if (isBooleanValidator(validator))
60
- return "'boolean'";
61
- if (isDateValidator(validator))
62
- return "'date'";
63
- if (isDatetimeValidator(validator))
64
- return "'datetime'";
65
- if (isUnixValidator(validator))
66
- return "'bigint'";
67
- if (isTimestampValidator(validator))
68
- return "'timestamp'";
69
- if (isTimestampTzValidator(validator))
70
- return "'timestamp'";
71
- if (isFloatValidator(validator))
72
- return "'float'";
73
- if (isSmallintValidator(validator))
74
- return "'smallint'";
75
- if (isDecimalValidator(validator))
76
- return "'decimal'";
77
- if (isIntegerValidator(validator))
78
- return "'integer'";
79
- if (isBigintValidator(validator))
80
- return "'bigint'";
81
- if (isBinaryValidator(validator))
82
- return "'binary'";
83
- return "";
84
- }
85
- export function prepareNumberColumnType(validator, driver = "mysql") {
86
- if (driver === "sqlite")
87
- return "'integer'";
88
- if ("getRules" in validator) {
89
- const minRule = validator.getRules().find((rule) => rule.name === "min"), maxRule = validator.getRules().find((rule) => rule.name === "max"), min = minRule?.params?.min ?? -2147483648, max = maxRule?.params?.max ?? 2147483647;
90
- return min >= -2147483648 && max <= 2147483647 ? "'integer'" : "'bigint'";
91
- }
92
- return "'integer'";
93
- }
94
- export function prepareEnumColumnType(validator, driver = "mysql") {
95
- const allowedValues = validator.getAllowedValues();
96
- if (!allowedValues)
97
- throw Error("Enum rule found but no allowedValues defined");
98
- const enumStructure = allowedValues.map((value) => `'${value}'`).join(", ");
99
- if (driver === "sqlite")
100
- return "'text'";
101
- return `sql\`enum(${enumStructure})\``;
102
- }
103
- export function prepareTextColumnType(validator, driver = "mysql") {
104
- if (driver === "sqlite")
105
- return "'text'";
106
- return `'varchar(${findCharacterLength(validator)})'`;
107
- }
108
- export function prepareDateTimeColumnType(validator, driver = "mysql") {
109
- if (driver === "sqlite")
110
- return "'text'";
111
- const name = validator.name;
112
- if (name === "unix")
113
- return "'bigint'";
114
- return name || "date";
115
- }
116
- export function findCharacterLength(validator) {
117
- if ("getRules" in validator) {
118
- const maxLengthRule = validator.getRules().find((rule) => rule.name === "max");
119
- return maxLengthRule?.params?.length || maxLengthRule?.params?.max || 255;
120
- }
121
- return 255;
122
- }
1
+ export function isStringValidator(v){return v.name==="string"}export function isNumberValidator(v){return v.name==="number"}export function enumValidator(v){return v.name==="enum"}export function isBooleanValidator(v){return v.name==="boolean"}export function isDateValidator(v){return v.name==="date"}export function isUnixValidator(v){return v.name==="unix"}export function isFloatValidator(v){return v.name==="float"}export function isDatetimeValidator(v){return v.name==="datetime"}export function isTimestampValidator(v){return v.name==="timestamp"}export function isTimestampTzValidator(v){return v.name==="timestampTz"}export function isDecimalValidator(v){return v.name==="decimal"}export function isSmallintValidator(v){return v.name==="smallint"}export function isIntegerValidator(v){return v.name==="integer"}export function isBigintValidator(v){return v.name==="bigint"}export function isBinaryValidator(v){return v.name==="binary"}export function isBlobValidator(v){return v.name==="blob"}export function isJsonValidator(v){return v.name==="json"}export function checkValidator(validator,driver){if(enumValidator(validator))return prepareEnumColumnType(validator,driver);if(isStringValidator(validator))return prepareTextColumnType(validator,driver);if(isNumberValidator(validator))return prepareNumberColumnType(validator,driver);if(isBooleanValidator(validator))return"'boolean'";if(isDateValidator(validator))return"'date'";if(isDatetimeValidator(validator))return"'datetime'";if(isUnixValidator(validator))return"'bigint'";if(isTimestampValidator(validator))return"'timestamp'";if(isTimestampTzValidator(validator))return"'timestamp'";if(isFloatValidator(validator))return"'float'";if(isSmallintValidator(validator))return"'smallint'";if(isDecimalValidator(validator))return"'decimal'";if(isIntegerValidator(validator))return"'integer'";if(isBigintValidator(validator))return"'bigint'";if(isBinaryValidator(validator))return"'binary'";return""}export function prepareNumberColumnType(validator,driver="mysql"){if(driver==="sqlite")return"'integer'";if("getRules"in validator){const minRule=validator.getRules().find((rule)=>rule.name==="min"),maxRule=validator.getRules().find((rule)=>rule.name==="max"),min=minRule?.params?.min??-2147483648,max=maxRule?.params?.max??2147483647;return min>=-2147483648&&max<=2147483647?"'integer'":"'bigint'"}return"'integer'"}export function prepareEnumColumnType(validator,driver="mysql"){const allowedValues=validator.getAllowedValues();if(!allowedValues)throw Error("Enum rule found but no allowedValues defined");const enumStructure=allowedValues.map((value)=>`'${value}'`).join(", ");if(driver==="sqlite")return"'text'";return`sql\`enum(${enumStructure})\``}export function prepareTextColumnType(validator,driver="mysql"){if(driver==="sqlite")return"'text'";return`'varchar(${findCharacterLength(validator)})'`}export function prepareDateTimeColumnType(validator,driver="mysql"){if(driver==="sqlite")return"'text'";const name=validator.name;if(name==="unix")return"'bigint'";return name||"date"}export function findCharacterLength(validator){if("getRules"in validator){const maxLengthRule=validator.getRules().find((rule)=>rule.name==="max");return maxLengthRule?.params?.length||maxLengthRule?.params?.max||255}return 255}
package/dist/vschema.js CHANGED
@@ -1,121 +1,2 @@
1
- export function foreignKeyForModel(modelName) {
2
- return `${modelName.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").toLowerCase()}_id`;
3
- }
4
- export function decideSharding(model, tableByModel) {
5
- const declared = model.sharding;
6
- if (declared?.unsharded)
7
- return {
8
- table: model.table,
9
- column: null,
10
- vindex: null,
11
- reason: "reference table"
12
- };
13
- if (declared?.column)
14
- return {
15
- table: model.table,
16
- column: declared.column,
17
- vindex: declared.vindex ?? "hash",
18
- reason: "explicit"
19
- };
20
- const parentModel = model.belongsTo[0];
21
- if (parentModel) {
22
- const parentTable = tableByModel.get(parentModel);
23
- return {
24
- table: model.table,
25
- column: foreignKeyForModel(parentModel),
26
- vindex: declared?.vindex ?? "hash",
27
- reason: "co-located with parent",
28
- parent: parentTable ?? parentModel,
29
- warning: model.belongsTo.length > 1 ? `belongs to ${model.belongsTo.length} parents; sharded by ${parentModel} only, so joins through ${model.belongsTo.slice(1).join(", ")} will scatter` : void 0
30
- };
31
- }
32
- return {
33
- table: model.table,
34
- column: "id",
35
- vindex: declared?.vindex ?? "hash",
36
- reason: "root entity"
37
- };
38
- }
39
- export function deriveVSchema(models) {
40
- const tableByModel = new Map(models.map((m) => [m.name, m.table])), decisions = models.map((model) => decideSharding(model, tableByModel)), vindexes = {}, tables = {};
41
- for (const [index, decision] of decisions.entries()) {
42
- const model = models[index];
43
- if (decision.reason === "reference table") {
44
- tables[decision.table] = { type: "reference" };
45
- continue;
46
- }
47
- const vindexType = decision.vindex ?? "hash";
48
- vindexes[vindexType] = { type: vindexType };
49
- const table = {
50
- column_vindexes: [{ column: decision.column, name: vindexType }]
51
- };
52
- if (!model.useUuid)
53
- table.auto_increment = {
54
- column: "id",
55
- sequence: model.sharding?.sequence ?? `${model.table}_seq`
56
- };
57
- tables[decision.table] = table;
58
- }
59
- return {
60
- vschema: { sharded: !0, vindexes, tables },
61
- decisions
62
- };
63
- }
64
- export function toShardableModel(definition, table) {
65
- const raw = definition?.belongsTo;
66
- let belongsTo = [];
67
- if (typeof raw === "string")
68
- belongsTo = [raw];
69
- else if (Array.isArray(raw))
70
- belongsTo = raw.map((entry) => typeof entry === "string" ? entry : entry?.model).filter(Boolean);
71
- else if (raw && typeof raw === "object")
72
- belongsTo = Object.keys(raw);
73
- return {
74
- name: definition?.name ?? table,
75
- table,
76
- belongsTo,
77
- useUuid: Boolean(definition?.traits?.useUuid),
78
- sharding: definition?.traits?.sharding
79
- };
80
- }
81
- export function formatShardingReport(decisions) {
82
- const lines = [], byReason = {
83
- explicit: decisions.filter((d) => d.reason === "explicit"),
84
- "co-located with parent": decisions.filter((d) => d.reason === "co-located with parent"),
85
- "root entity": decisions.filter((d) => d.reason === "root entity"),
86
- "reference table": decisions.filter((d) => d.reason === "reference table")
87
- };
88
- if (byReason["root entity"].length) {
89
- lines.push("Root entities (sharded by their own id):");
90
- for (const d of byReason["root entity"])
91
- lines.push(` ${d.table} -> ${d.column} (${d.vindex})`);
92
- lines.push("");
93
- }
94
- if (byReason["co-located with parent"].length) {
95
- lines.push("Co-located with a parent (joins to that parent stay on one shard):");
96
- for (const d of byReason["co-located with parent"])
97
- lines.push(` ${d.table} -> ${d.column} (${d.vindex}), with ${d.parent}`);
98
- lines.push("");
99
- }
100
- if (byReason.explicit.length) {
101
- lines.push("Explicitly declared:");
102
- for (const d of byReason.explicit)
103
- lines.push(` ${d.table} -> ${d.column} (${d.vindex})`);
104
- lines.push("");
105
- }
106
- if (byReason["reference table"].length) {
107
- lines.push("Reference tables (copied to every shard):");
108
- for (const d of byReason["reference table"])
109
- lines.push(` ${d.table}`);
110
- lines.push("");
111
- }
112
- const warnings = decisions.filter((d) => d.warning);
113
- if (warnings.length) {
114
- lines.push("Warnings:");
115
- for (const d of warnings)
116
- lines.push(` ${d.table}: ${d.warning}`);
117
- lines.push("");
118
- }
119
- return lines.join(`
120
- `);
121
- }
1
+ export function foreignKeyForModel(modelName){return`${modelName.replace(/([a-z0-9])([A-Z])/g,"$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").toLowerCase()}_id`}export function decideSharding(model,tableByModel){const declared=model.sharding;if(declared?.unsharded)return{table:model.table,column:null,vindex:null,reason:"reference table"};if(declared?.column)return{table:model.table,column:declared.column,vindex:declared.vindex??"hash",reason:"explicit"};const parentModel=model.belongsTo[0];if(parentModel){const parentTable=tableByModel.get(parentModel);return{table:model.table,column:foreignKeyForModel(parentModel),vindex:declared?.vindex??"hash",reason:"co-located with parent",parent:parentTable??parentModel,warning:model.belongsTo.length>1?`belongs to ${model.belongsTo.length} parents; sharded by ${parentModel} only, so joins through ${model.belongsTo.slice(1).join(", ")} will scatter`:void 0}}return{table:model.table,column:"id",vindex:declared?.vindex??"hash",reason:"root entity"}}export function deriveVSchema(models){const tableByModel=new Map(models.map((m)=>[m.name,m.table])),decisions=models.map((model)=>decideSharding(model,tableByModel)),vindexes={},tables={};for(const[index,decision]of decisions.entries()){const model=models[index];if(decision.reason==="reference table"){tables[decision.table]={type:"reference"};continue}const vindexType=decision.vindex??"hash";vindexes[vindexType]={type:vindexType};const table={column_vindexes:[{column:decision.column,name:vindexType}]};if(!model.useUuid)table.auto_increment={column:"id",sequence:model.sharding?.sequence??`${model.table}_seq`};tables[decision.table]=table}return{vschema:{sharded:!0,vindexes,tables},decisions}}export function toShardableModel(definition,table){const raw=definition?.belongsTo;let belongsTo=[];if(typeof raw==="string")belongsTo=[raw];else if(Array.isArray(raw))belongsTo=raw.map((entry)=>typeof entry==="string"?entry:entry?.model).filter(Boolean);else if(raw&&typeof raw==="object")belongsTo=Object.keys(raw);return{name:definition?.name??table,table,belongsTo,useUuid:Boolean(definition?.traits?.useUuid),sharding:definition?.traits?.sharding}}export function formatShardingReport(decisions){const lines=[],byReason={explicit:decisions.filter((d)=>d.reason==="explicit"),"co-located with parent":decisions.filter((d)=>d.reason==="co-located with parent"),"root entity":decisions.filter((d)=>d.reason==="root entity"),"reference table":decisions.filter((d)=>d.reason==="reference table")};if(byReason["root entity"].length){lines.push("Root entities (sharded by their own id):");for(const d of byReason["root entity"])lines.push(` ${d.table} -> ${d.column} (${d.vindex})`);lines.push("")}if(byReason["co-located with parent"].length){lines.push("Co-located with a parent (joins to that parent stay on one shard):");for(const d of byReason["co-located with parent"])lines.push(` ${d.table} -> ${d.column} (${d.vindex}), with ${d.parent}`);lines.push("")}if(byReason.explicit.length){lines.push("Explicitly declared:");for(const d of byReason.explicit)lines.push(` ${d.table} -> ${d.column} (${d.vindex})`);lines.push("")}if(byReason["reference table"].length){lines.push("Reference tables (copied to every shard):");for(const d of byReason["reference table"])lines.push(` ${d.table}`);lines.push("")}const warnings=decisions.filter((d)=>d.warning);if(warnings.length){lines.push("Warnings:");for(const d of warnings)lines.push(` ${d.table}: ${d.warning}`);lines.push("")}return lines.join(`
2
+ `)}