@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.
- package/dist/auth-tables.js +18 -137
- package/dist/column.js +1 -26
- package/dist/custom/audits.js +20 -54
- package/dist/custom/errors.js +16 -46
- package/dist/custom/index.js +1 -3
- package/dist/custom/jobs.js +13 -137
- package/dist/database.js +1 -181
- package/dist/datetime-columns.js +2 -79
- package/dist/ddl-constraints.js +7 -111
- package/dist/defaults.js +1 -48
- package/dist/dialect.js +1 -79
- package/dist/driver-config.js +1 -172
- package/dist/drivers/defaults/index.js +1 -1
- package/dist/drivers/defaults/traits.js +1 -29
- package/dist/drivers/dynamodb.js +1 -607
- package/dist/drivers/helpers.js +1 -206
- package/dist/drivers/index.js +1 -9
- package/dist/drivers/mysql.js +58 -299
- package/dist/drivers/postgres.js +78 -368
- package/dist/drivers/sqlite.js +61 -379
- package/dist/ensure-database.js +1 -145
- package/dist/fk-audit.js +3 -187
- package/dist/index.js +1 -64
- package/dist/managed-columns.js +1 -59
- package/dist/migration-dialect.js +4 -107
- package/dist/migration-ledger.js +1 -382
- package/dist/migration-lock.js +1 -143
- package/dist/migrations.js +15 -1118
- package/dist/model-sources.js +1 -76
- package/dist/notification-tables.js +4 -49
- package/dist/query-logger.js +2 -241
- package/dist/query-parser.js +1 -93
- package/dist/rbac-tables.js +6 -61
- package/dist/relation-columns.js +1 -66
- package/dist/replicas.js +1 -74
- package/dist/safe-migrations.js +2 -52
- package/dist/schema.js +1 -10
- package/dist/seeder.js +1 -457
- package/dist/sql-helpers.js +1 -50
- package/dist/table.js +1 -26
- package/dist/tools/setup.js +1 -6
- package/dist/trait-tables.js +8 -153
- package/dist/transaction-context.js +1 -62
- package/dist/types.js +1 -98
- package/dist/unique-audit.js +3 -155
- package/dist/utils.js +1 -285
- package/dist/uuid-columns.js +1 -68
- package/dist/validators.js +1 -122
- package/dist/vschema.js +2 -121
- package/package.json +20 -13
package/dist/relation-columns.js
CHANGED
|
@@ -1,66 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { path } from "@stacksjs/path";
|
|
3
|
-
import { fs } from "@stacksjs/storage";
|
|
4
|
-
function snakeCase(str) {
|
|
5
|
-
return str.replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/([a-z\d])([A-Z])/g, "$1_$2").replace(/(\d)([A-Za-z])/g, "$1_$2").toLowerCase();
|
|
6
|
-
}
|
|
7
|
-
export function belongsToColumn(entry) {
|
|
8
|
-
if (typeof entry === "string")
|
|
9
|
-
return entry.length > 0 ? `${snakeCase(entry)}_id` : null;
|
|
10
|
-
if (entry && typeof entry === "object") {
|
|
11
|
-
const relation = entry;
|
|
12
|
-
if (typeof relation.foreignKey === "string" && relation.foreignKey.length > 0)
|
|
13
|
-
return relation.foreignKey;
|
|
14
|
-
if (typeof relation.model === "string" && relation.model.length > 0)
|
|
15
|
-
return `${snakeCase(relation.model)}_id`;
|
|
16
|
-
}
|
|
17
|
-
return null;
|
|
18
|
-
}
|
|
19
|
-
export function belongsToColumnsOf(model) {
|
|
20
|
-
const declared = model.belongsTo;
|
|
21
|
-
if (!declared)
|
|
22
|
-
return [];
|
|
23
|
-
const entries = Array.isArray(declared) ? declared : Object.entries(declared).map(([model, value]) => value && typeof value === "object" ? { model, ...value } : model), columns = [];
|
|
24
|
-
for (const entry of entries) {
|
|
25
|
-
const column = belongsToColumn(entry);
|
|
26
|
-
if (column)
|
|
27
|
-
columns.push(column);
|
|
28
|
-
}
|
|
29
|
-
return columns;
|
|
30
|
-
}
|
|
31
|
-
async function loadModelsFrom(dir) {
|
|
32
|
-
const out = [];
|
|
33
|
-
if (!fs.existsSync(dir))
|
|
34
|
-
return out;
|
|
35
|
-
for (const entry of fs.readdirSync(dir, { withFileTypes: !0 })) {
|
|
36
|
-
const fullPath = path.join(dir, entry.name);
|
|
37
|
-
if (entry.isDirectory()) {
|
|
38
|
-
out.push(...await loadModelsFrom(fullPath));
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
if (!entry.name.endsWith(".ts"))
|
|
42
|
-
continue;
|
|
43
|
-
if (entry.name.startsWith("_") || entry.name.startsWith("index"))
|
|
44
|
-
continue;
|
|
45
|
-
try {
|
|
46
|
-
const imported = (await import(fullPath)).default;
|
|
47
|
-
if (imported?.name || imported?.table)
|
|
48
|
-
out.push({ filePath: fullPath, model: imported });
|
|
49
|
-
} catch {}
|
|
50
|
-
}
|
|
51
|
-
return out;
|
|
52
|
-
}
|
|
53
|
-
export async function findRelationForeignKeys() {
|
|
54
|
-
const dirs = [path.userModelsPath(), path.frameworkPath("defaults/app/Models")], byTable = new Map;
|
|
55
|
-
for (const dir of dirs)
|
|
56
|
-
for (const { filePath, model } of await loadModelsFrom(dir)) {
|
|
57
|
-
const columns = belongsToColumnsOf(model);
|
|
58
|
-
if (columns.length === 0)
|
|
59
|
-
continue;
|
|
60
|
-
const table = getTableName(model, filePath), existing = byTable.get(table) ?? new Set;
|
|
61
|
-
for (const column of columns)
|
|
62
|
-
existing.add(column);
|
|
63
|
-
byTable.set(table, existing);
|
|
64
|
-
}
|
|
65
|
-
return byTable;
|
|
66
|
-
}
|
|
1
|
+
import{getTableName}from"@stacksjs/orm";import{path}from"@stacksjs/path";import{fs}from"@stacksjs/storage";function snakeCase(str){return str.replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/([a-z\d])([A-Z])/g,"$1_$2").replace(/(\d)([A-Za-z])/g,"$1_$2").toLowerCase()}export function belongsToColumn(entry){if(typeof entry==="string")return entry.length>0?`${snakeCase(entry)}_id`:null;if(entry&&typeof entry==="object"){const relation=entry;if(typeof relation.foreignKey==="string"&&relation.foreignKey.length>0)return relation.foreignKey;if(typeof relation.model==="string"&&relation.model.length>0)return`${snakeCase(relation.model)}_id`}return null}export function belongsToColumnsOf(model){const declared=model.belongsTo;if(!declared)return[];const entries=Array.isArray(declared)?declared:Object.entries(declared).map(([model,value])=>value&&typeof value==="object"?{model,...value}:model),columns=[];for(const entry of entries){const column=belongsToColumn(entry);if(column)columns.push(column)}return columns}async function loadModelsFrom(dir){const out=[];if(!fs.existsSync(dir))return out;for(const entry of fs.readdirSync(dir,{withFileTypes:!0})){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 findRelationForeignKeys(){const dirs=[path.userModelsPath(),path.frameworkPath("defaults/app/Models")],byTable=new Map;for(const dir of dirs)for(const{filePath,model}of await loadModelsFrom(dir)){const columns=belongsToColumnsOf(model);if(columns.length===0)continue;const table=getTableName(model,filePath),existing=byTable.get(table)??new Set;for(const column of columns)existing.add(column);byTable.set(table,existing)}return byTable}
|
package/dist/replicas.js
CHANGED
|
@@ -1,74 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
const routingContext = new AsyncLocalStorage;
|
|
3
|
-
export function withRoutingContext(fn) {
|
|
4
|
-
return routingContext.run({ wroteInContext: !1, inTransaction: !1 }, fn);
|
|
5
|
-
}
|
|
6
|
-
export function markContextWrote() {
|
|
7
|
-
const store = routingContext.getStore();
|
|
8
|
-
if (store)
|
|
9
|
-
store.wroteInContext = !0;
|
|
10
|
-
}
|
|
11
|
-
export async function withTransactionContext(fn) {
|
|
12
|
-
const store = routingContext.getStore();
|
|
13
|
-
if (!store)
|
|
14
|
-
return fn();
|
|
15
|
-
const previous = store.inTransaction;
|
|
16
|
-
store.inTransaction = !0;
|
|
17
|
-
try {
|
|
18
|
-
return await fn();
|
|
19
|
-
} finally {
|
|
20
|
-
store.inTransaction = previous;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
export function contextHasWritten() {
|
|
24
|
-
return routingContext.getStore()?.wroteInContext ?? !1;
|
|
25
|
-
}
|
|
26
|
-
export function contextInTransaction() {
|
|
27
|
-
return routingContext.getStore()?.inTransaction ?? !1;
|
|
28
|
-
}
|
|
29
|
-
export function shouldRouteToReplica(options) {
|
|
30
|
-
const { policy, replicas } = options;
|
|
31
|
-
if (!replicas?.length)
|
|
32
|
-
return !1;
|
|
33
|
-
if (!policy?.autoRoute)
|
|
34
|
-
return !1;
|
|
35
|
-
if (contextInTransaction())
|
|
36
|
-
return !1;
|
|
37
|
-
if (contextHasWritten())
|
|
38
|
-
return !1;
|
|
39
|
-
return !0;
|
|
40
|
-
}
|
|
41
|
-
let roundRobinCursor = 0;
|
|
42
|
-
export function resetReplicaCursor() {
|
|
43
|
-
roundRobinCursor = 0;
|
|
44
|
-
}
|
|
45
|
-
export function selectReplica(replicas, strategy = "round-robin", random = Math.random) {
|
|
46
|
-
if (!replicas.length)
|
|
47
|
-
return;
|
|
48
|
-
if (replicas.length === 1)
|
|
49
|
-
return replicas[0];
|
|
50
|
-
if (strategy === "random")
|
|
51
|
-
return replicas[Math.floor(random() * replicas.length)];
|
|
52
|
-
if (strategy === "weighted") {
|
|
53
|
-
const weights = replicas.map((r) => Math.max(0, r.weight ?? 1)), total = weights.reduce((sum, w) => sum + w, 0);
|
|
54
|
-
if (total <= 0)
|
|
55
|
-
return replicas[roundRobinCursor++ % replicas.length];
|
|
56
|
-
let ticket = random() * total;
|
|
57
|
-
for (let i = 0;i < replicas.length; i++) {
|
|
58
|
-
ticket -= weights[i];
|
|
59
|
-
if (ticket < 0)
|
|
60
|
-
return replicas[i];
|
|
61
|
-
}
|
|
62
|
-
return replicas[replicas.length - 1];
|
|
63
|
-
}
|
|
64
|
-
return replicas[roundRobinCursor++ % replicas.length];
|
|
65
|
-
}
|
|
66
|
-
export function resolveReplicaConnection(replica, primary) {
|
|
67
|
-
return {
|
|
68
|
-
database: primary.name ?? primary.database ?? "",
|
|
69
|
-
host: replica.host,
|
|
70
|
-
port: replica.port ?? primary.port,
|
|
71
|
-
username: replica.username ?? primary.username,
|
|
72
|
-
password: replica.password ?? primary.password
|
|
73
|
-
};
|
|
74
|
-
}
|
|
1
|
+
import{AsyncLocalStorage}from"node:async_hooks";const routingContext=new AsyncLocalStorage;export function withRoutingContext(fn){return routingContext.run({wroteInContext:!1,inTransaction:!1},fn)}export function markContextWrote(){const store=routingContext.getStore();if(store)store.wroteInContext=!0}export async function withTransactionContext(fn){const store=routingContext.getStore();if(!store)return fn();const previous=store.inTransaction;store.inTransaction=!0;try{return await fn()}finally{store.inTransaction=previous}}export function contextHasWritten(){return routingContext.getStore()?.wroteInContext??!1}export function contextInTransaction(){return routingContext.getStore()?.inTransaction??!1}export function shouldRouteToReplica(options){const{policy,replicas}=options;if(!replicas?.length)return!1;if(!policy?.autoRoute)return!1;if(contextInTransaction())return!1;if(contextHasWritten())return!1;return!0}let roundRobinCursor=0;export function resetReplicaCursor(){roundRobinCursor=0}export function selectReplica(replicas,strategy="round-robin",random=Math.random){if(!replicas.length)return;if(replicas.length===1)return replicas[0];if(strategy==="random")return replicas[Math.floor(random()*replicas.length)];if(strategy==="weighted"){const weights=replicas.map((r)=>Math.max(0,r.weight??1)),total=weights.reduce((sum,w)=>sum+w,0);if(total<=0)return replicas[roundRobinCursor++%replicas.length];let ticket=random()*total;for(let i=0;i<replicas.length;i++){ticket-=weights[i];if(ticket<0)return replicas[i]}return replicas[replicas.length-1]}return replicas[roundRobinCursor++%replicas.length]}export function resolveReplicaConnection(replica,primary){return{database:primary.name??primary.database??"",host:replica.host,port:replica.port??primary.port,username:replica.username??primary.username,password:replica.password??primary.password}}
|
package/dist/safe-migrations.js
CHANGED
|
@@ -1,24 +1,4 @@
|
|
|
1
|
-
import { db }
|
|
2
|
-
import { sql } from "./types";
|
|
3
|
-
export async function addColumnSafely(db, tableName, columnName, options) {
|
|
4
|
-
const { type, defaultValue, notNull = !1, batchSize = 1000 } = options, dbAny = db, defaultSql = defaultValue === void 0 ? "" : ` DEFAULT ${formatDefault(defaultValue)}`;
|
|
5
|
-
await execRaw(dbAny, `ALTER TABLE ${quote(tableName)} ADD COLUMN ${quote(columnName)} ${type}${defaultSql}`);
|
|
6
|
-
if (defaultValue !== void 0)
|
|
7
|
-
await backfillInBatches(db, tableName, columnName, defaultValue, batchSize);
|
|
8
|
-
if (notNull)
|
|
9
|
-
await execRaw(dbAny, `ALTER TABLE ${quote(tableName)} ALTER COLUMN ${quote(columnName)} SET NOT NULL`);
|
|
10
|
-
}
|
|
11
|
-
async function execRaw(dbAny, statement) {
|
|
12
|
-
if (typeof dbAny.unsafe === "function")
|
|
13
|
-
return await dbAny.unsafe(statement) ?? {};
|
|
14
|
-
await sql`${sql.raw(statement)}`.execute(dbAny);
|
|
15
|
-
return {};
|
|
16
|
-
}
|
|
17
|
-
export async function backfillInBatches(db, tableName, columnName, value, batchSize = 1000) {
|
|
18
|
-
const dbAny = db;
|
|
19
|
-
let updated = 0, total = 0;
|
|
20
|
-
do {
|
|
21
|
-
const batchSql = `
|
|
1
|
+
import{db}from"./utils";import{sql}from"./types";export async function addColumnSafely(db,tableName,columnName,options){const{type,defaultValue,notNull=!1,batchSize=1000}=options,dbAny=db,defaultSql=defaultValue===void 0?"":` DEFAULT ${formatDefault(defaultValue)}`;await execRaw(dbAny,`ALTER TABLE ${quote(tableName)} ADD COLUMN ${quote(columnName)} ${type}${defaultSql}`);if(defaultValue!==void 0)await backfillInBatches(db,tableName,columnName,defaultValue,batchSize);if(notNull)await execRaw(dbAny,`ALTER TABLE ${quote(tableName)} ALTER COLUMN ${quote(columnName)} SET NOT NULL`)}async function execRaw(dbAny,statement){if(typeof dbAny.unsafe==="function")return await dbAny.unsafe(statement)??{};await sql`${sql.raw(statement)}`.execute(dbAny);return{}}export async function backfillInBatches(db,tableName,columnName,value,batchSize=1000){const dbAny=db;let updated=0,total=0;do{const batchSql=`
|
|
22
2
|
UPDATE ${quote(tableName)} SET ${quote(columnName)} = ${formatDefault(value)}
|
|
23
3
|
WHERE ${quote(columnName)} IS NULL
|
|
24
4
|
AND ${rowIdColumnFor(dbAny)} IN (
|
|
@@ -26,34 +6,4 @@ export async function backfillInBatches(db, tableName, columnName, value, batchS
|
|
|
26
6
|
WHERE ${quote(columnName)} IS NULL
|
|
27
7
|
LIMIT ${batchSize}
|
|
28
8
|
)
|
|
29
|
-
`, result = await execRaw(dbAny,
|
|
30
|
-
updated = result.numAffectedRows != null ? Number(result.numAffectedRows) : 0;
|
|
31
|
-
total += updated;
|
|
32
|
-
} while (updated > 0);
|
|
33
|
-
}
|
|
34
|
-
export async function renameColumnSafely(db, tableName, oldName, newName, options) {
|
|
35
|
-
const dbAny = db;
|
|
36
|
-
if (options.atomic) {
|
|
37
|
-
await execRaw(dbAny, `ALTER TABLE ${quote(tableName)} RENAME COLUMN ${quote(oldName)} TO ${quote(newName)}`);
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
await execRaw(dbAny, `ALTER TABLE ${quote(tableName)} ADD COLUMN ${quote(newName)} ${options.type}`);
|
|
41
|
-
await execRaw(dbAny, `UPDATE ${quote(tableName)} SET ${quote(newName)} = ${quote(oldName)}`);
|
|
42
|
-
}
|
|
43
|
-
function quote(name) {
|
|
44
|
-
if (!/^[a-z_][a-z0-9_]*$/i.test(name))
|
|
45
|
-
throw Error(`Refusing to quote unsafe identifier: ${JSON.stringify(name)}`);
|
|
46
|
-
return `"${name}"`;
|
|
47
|
-
}
|
|
48
|
-
function formatDefault(value) {
|
|
49
|
-
if (value === null)
|
|
50
|
-
return "NULL";
|
|
51
|
-
if (typeof value === "number")
|
|
52
|
-
return String(value);
|
|
53
|
-
if (typeof value === "boolean")
|
|
54
|
-
return value ? "TRUE" : "FALSE";
|
|
55
|
-
return `'${String(value).replace(/'/g, "''")}'`;
|
|
56
|
-
}
|
|
57
|
-
function rowIdColumnFor(_db) {
|
|
58
|
-
return "id";
|
|
59
|
-
}
|
|
9
|
+
`,result=await execRaw(dbAny,batchSql);updated=result.numAffectedRows!=null?Number(result.numAffectedRows):0;total+=updated}while(updated>0)}export async function renameColumnSafely(db,tableName,oldName,newName,options){const dbAny=db;if(options.atomic){await execRaw(dbAny,`ALTER TABLE ${quote(tableName)} RENAME COLUMN ${quote(oldName)} TO ${quote(newName)}`);return}await execRaw(dbAny,`ALTER TABLE ${quote(tableName)} ADD COLUMN ${quote(newName)} ${options.type}`);await execRaw(dbAny,`UPDATE ${quote(tableName)} SET ${quote(newName)} = ${quote(oldName)}`)}function quote(name){if(!/^[a-z_][a-z0-9_]*$/i.test(name))throw Error(`Refusing to quote unsafe identifier: ${JSON.stringify(name)}`);return`"${name}"`}function formatDefault(value){if(value===null)return"NULL";if(typeof value==="number")return String(value);if(typeof value==="boolean")return value?"TRUE":"FALSE";return`'${String(value).replace(/'/g,"''")}'`}function rowIdColumnFor(_db){return"id"}
|
package/dist/schema.js
CHANGED
|
@@ -1,10 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { Table } from "./table";
|
|
3
|
-
export const Schema = {
|
|
4
|
-
async createTable(tableName, callback) {
|
|
5
|
-
const table = new Table;
|
|
6
|
-
callback(table);
|
|
7
|
-
table.execute();
|
|
8
|
-
log.success(`Table "${tableName}" created.`);
|
|
9
|
-
}
|
|
10
|
-
};
|
|
1
|
+
import{log}from"@stacksjs/logging";import{Table}from"./table";export const Schema={async createTable(tableName,callback){const table=new Table;callback(table);table.execute();log.success(`Table "${tableName}" created.`)}};
|