@stacksjs/database 0.75.120 → 0.75.122

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.
@@ -112,6 +112,8 @@ export declare function traitTableIndexSql(): string[];
112
112
  * below would re-enter that barrel at load time.
113
113
  */
114
114
  export declare function likeableTargets(): Promise<Array<{ table: string, foreignKey: string }>>;
115
+ /** Persistent direct messaging. Owned by migrations, never created during requests. */
116
+ export declare function messagingTableSql(sql: SqlHelpers): string[];
115
117
  /**
116
118
  * Create the polymorphic trait tables. Idempotent (`IF NOT EXISTS`), so it's
117
119
  * safe to run on every `buddy migrate`.
@@ -1,4 +1,4 @@
1
- import{log}from"@stacksjs/logging";import{env as envVars}from"@stacksjs/env";import{db}from"./utils";import{sqlHelpers}from"./sql-helpers";import{indexSqlForDialect,isDuplicateColumnError,isDuplicateIndexError}from"./dialect";export{indexSqlForDialect}from"./dialect";function getDbDriver(){return process.env.DB_CONNECTION||envVars.DB_CONNECTION||"sqlite"}function createdAt(sql){return`created_at ${sql.datetime}`}function updatedAt(sql){return`updated_at ${sql.nullableTimestamp}`}export function traitTableNames(){return["commentables","taggables","categorizables","commentable_upvotes","taggable_models","categorizable_models"]}export const UNSCOPED_OWNER_ID=0;export function commentablesTableSql(sql){const{pkColumn,boolTrue,utcNow}=sql;return`CREATE TABLE IF NOT EXISTS commentables (
1
+ import{log}from"@stacksjs/logging";import{env as envVars}from"@stacksjs/env";import{db}from"./utils";import{sqlHelpers}from"./sql-helpers";import{indexSqlForDialect,isDuplicateColumnError,isDuplicateIndexError}from"./dialect";export{indexSqlForDialect}from"./dialect";function getDbDriver(){return process.env.DB_CONNECTION||envVars.DB_CONNECTION||"sqlite"}function createdAt(sql){return`created_at ${sql.datetime}`}function updatedAt(sql){return`updated_at ${sql.nullableTimestamp}`}export function traitTableNames(){return["chat_messages","chat_conversations","commentables","taggables","categorizables","commentable_upvotes","taggable_models","categorizable_models"]}export const UNSCOPED_OWNER_ID=0;export function commentablesTableSql(sql){const{pkColumn,boolTrue,utcNow}=sql;return`CREATE TABLE IF NOT EXISTS commentables (
2
2
  ${pkColumn},
3
3
  title VARCHAR(255) NOT NULL,
4
4
  body TEXT NOT NULL,
@@ -60,4 +60,20 @@ import{log}from"@stacksjs/logging";import{env as envVars}from"@stacksjs/env";imp
60
60
  upvoteable_id INTEGER NOT NULL,
61
61
  upvoteable_type VARCHAR(255) NOT NULL,
62
62
  ${createdAt(sql)}
63
- )`}export function traitTableColumnGuarantees(sql){return[{table:"taggables",column:"taggable_id",definition:`INTEGER NOT NULL DEFAULT ${UNSCOPED_OWNER_ID}`},{table:"categorizables",column:"categorizable_id",definition:`INTEGER NOT NULL DEFAULT ${UNSCOPED_OWNER_ID}`},{table:"commentables",column:"author_name",definition:"VARCHAR(255)"},{table:"commentables",column:"author_email",definition:"VARCHAR(255)"}]}export function traitTableIndexSql(){return["CREATE INDEX IF NOT EXISTS commentables_owner_index ON commentables (commentables_type, commentables_id)","CREATE INDEX IF NOT EXISTS commentables_status_index ON commentables (status)","CREATE UNIQUE INDEX IF NOT EXISTS taggables_owner_slug_unique ON taggables (taggable_type, taggable_id, slug)","CREATE UNIQUE INDEX IF NOT EXISTS categorizables_owner_slug_unique ON categorizables (categorizable_type, categorizable_id, slug)","CREATE INDEX IF NOT EXISTS commentable_upvotes_target_index ON commentable_upvotes (upvoteable_type, upvoteable_id)","CREATE UNIQUE INDEX IF NOT EXISTS commentable_upvotes_user_unique ON commentable_upvotes (upvoteable_type, upvoteable_id, user_id)","CREATE UNIQUE INDEX IF NOT EXISTS taggable_models_owner_unique ON taggable_models (tag_id, taggable_id, taggable_type)","CREATE UNIQUE INDEX IF NOT EXISTS categorizable_models_owner_unique ON categorizable_models (category_id, categorizable_id, categorizable_type)"]}export async function likeableTargets(){const{path}=await import("@stacksjs/path"),{globSync}=await import("@stacksjs/storage"),{getTableName}=await import("@stacksjs/model-meta"),{getLikeableForeignKey,getUpvoteTableName}=await import("./drivers/helpers"),modelFiles=globSync([path.userModelsPath("*.ts"),path.storagePath("framework/defaults/app/Models/**/*.ts")],{absolute:!0}),targets=new Map;for(const modelFile of modelFiles){let model;try{model=(await import(modelFile)).default}catch{continue}if(!model?.traits?.likeable)continue;const tableName=await getTableName(model,modelFile);if(!tableName)continue;const table=getUpvoteTableName(model,tableName);if(!table||!/^[a-z_]\w*$/i.test(table))continue;const foreignKey=getLikeableForeignKey(model,tableName);if(!/^[a-z_]\w*$/i.test(foreignKey))continue;targets.set(table,{table,foreignKey})}return[...targets.values()]}export async function migrateTraitTables(options={}){const dbDriver=getDbDriver(),sql=sqlHelpers(dbDriver);if(options.verbose)log.info(`Creating polymorphic trait tables for ${dbDriver}...`);try{if(options.verbose)log.info("Creating commentables table...");await db.unsafe(commentablesTableSql(sql)).execute();if(options.verbose)log.info("Creating taggables table...");await db.unsafe(taggablesTableSql(sql)).execute();if(options.verbose)log.info("Creating categorizables table...");await db.unsafe(categorizablesTableSql(sql)).execute();if(options.verbose)log.info("Creating commentable_upvotes table...");await db.unsafe(commentableUpvotesTableSql(sql)).execute();if(options.verbose)log.info("Creating taggable_models pivot...");await db.unsafe(taggableModelsTableSql(sql)).execute();if(options.verbose)log.info("Creating categorizable_models pivot...");await db.unsafe(categorizableModelsTableSql(sql)).execute();try{for(const{table,foreignKey}of await likeableTargets()){if(options.verbose)log.info(`Creating ${table} table...`);await db.unsafe(likesTableSql(sql,table,foreignKey)).execute()}}catch(error){log.debug(`[trait-tables] Skipped likeable tables: ${error instanceof Error?error.message:String(error)}`)}for(const{table,column,definition}of traitTableColumnGuarantees(sql))try{await db.unsafe(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`).execute();if(options.verbose)log.info(`Added missing ${table}.${column}`)}catch(error){if(!isDuplicateColumnError(error))throw error}for(const statement of traitTableIndexSql())try{await db.unsafe(indexSqlForDialect(statement,dbDriver)).execute()}catch(error){if(!isDuplicateIndexError(error))throw error}if(options.verbose)log.success("Polymorphic trait tables created");return{success:!0}}catch(error){const message=error instanceof Error?error.message:String(error);log.error(`Failed to create polymorphic trait tables: ${message}`);return{success:!1,error:message}}}
63
+ )`}export function traitTableColumnGuarantees(sql){return[{table:"taggables",column:"taggable_id",definition:`INTEGER NOT NULL DEFAULT ${UNSCOPED_OWNER_ID}`},{table:"categorizables",column:"categorizable_id",definition:`INTEGER NOT NULL DEFAULT ${UNSCOPED_OWNER_ID}`},{table:"commentables",column:"author_name",definition:"VARCHAR(255)"},{table:"commentables",column:"author_email",definition:"VARCHAR(255)"}]}export function traitTableIndexSql(){return["CREATE INDEX IF NOT EXISTS chat_messages_thread_index ON chat_messages (conversation_id, id)","CREATE INDEX IF NOT EXISTS chat_conversations_first_index ON chat_conversations (scope, participant_type, first_id)","CREATE INDEX IF NOT EXISTS chat_conversations_second_index ON chat_conversations (scope, participant_type, second_id)","CREATE INDEX IF NOT EXISTS commentables_owner_index ON commentables (commentables_type, commentables_id)","CREATE INDEX IF NOT EXISTS commentables_status_index ON commentables (status)","CREATE UNIQUE INDEX IF NOT EXISTS taggables_owner_slug_unique ON taggables (taggable_type, taggable_id, slug)","CREATE UNIQUE INDEX IF NOT EXISTS categorizables_owner_slug_unique ON categorizables (categorizable_type, categorizable_id, slug)","CREATE INDEX IF NOT EXISTS commentable_upvotes_target_index ON commentable_upvotes (upvoteable_type, upvoteable_id)","CREATE UNIQUE INDEX IF NOT EXISTS commentable_upvotes_user_unique ON commentable_upvotes (upvoteable_type, upvoteable_id, user_id)","CREATE UNIQUE INDEX IF NOT EXISTS taggable_models_owner_unique ON taggable_models (tag_id, taggable_id, taggable_type)","CREATE UNIQUE INDEX IF NOT EXISTS categorizable_models_owner_unique ON categorizable_models (category_id, categorizable_id, categorizable_type)"]}export async function likeableTargets(){const{path}=await import("@stacksjs/path"),{globSync}=await import("@stacksjs/storage"),{getTableName}=await import("@stacksjs/model-meta"),{getLikeableForeignKey,getUpvoteTableName}=await import("./drivers/helpers"),modelFiles=globSync([path.userModelsPath("*.ts"),path.storagePath("framework/defaults/app/Models/**/*.ts")],{absolute:!0}),targets=new Map;for(const modelFile of modelFiles){let model;try{model=(await import(modelFile)).default}catch{continue}if(!model?.traits?.likeable)continue;const tableName=await getTableName(model,modelFile);if(!tableName)continue;const table=getUpvoteTableName(model,tableName);if(!table||!/^[a-z_]\w*$/i.test(table))continue;const foreignKey=getLikeableForeignKey(model,tableName);if(!/^[a-z_]\w*$/i.test(foreignKey))continue;targets.set(table,{table,foreignKey})}return[...targets.values()]}export function messagingTableSql(sql){return[`CREATE TABLE IF NOT EXISTS chat_conversations (
64
+ id VARCHAR(64) PRIMARY KEY,
65
+ scope VARCHAR(255) NOT NULL,
66
+ participant_type VARCHAR(255) NOT NULL,
67
+ first_id BIGINT NOT NULL,
68
+ second_id BIGINT NOT NULL,
69
+ created_at ${sql.datetime} NOT NULL
70
+ )`,`CREATE TABLE IF NOT EXISTS chat_messages (
71
+ ${sql.pkColumn},
72
+ conversation_id VARCHAR(64) NOT NULL REFERENCES chat_conversations(id) ON DELETE CASCADE,
73
+ sender_id BIGINT NOT NULL,
74
+ body TEXT NOT NULL,
75
+ client_key VARCHAR(100) NOT NULL,
76
+ created_at ${sql.datetime} NOT NULL,
77
+ read_at ${sql.nullableTimestamp},
78
+ UNIQUE (conversation_id, sender_id, client_key)
79
+ )`]}export async function migrateTraitTables(options={}){const dbDriver=getDbDriver(),sql=sqlHelpers(dbDriver);if(options.verbose)log.info(`Creating polymorphic trait tables for ${dbDriver}...`);try{for(const statement of messagingTableSql(sql))await db.unsafe(statement).execute();if(options.verbose)log.info("Creating commentables table...");await db.unsafe(commentablesTableSql(sql)).execute();if(options.verbose)log.info("Creating taggables table...");await db.unsafe(taggablesTableSql(sql)).execute();if(options.verbose)log.info("Creating categorizables table...");await db.unsafe(categorizablesTableSql(sql)).execute();if(options.verbose)log.info("Creating commentable_upvotes table...");await db.unsafe(commentableUpvotesTableSql(sql)).execute();if(options.verbose)log.info("Creating taggable_models pivot...");await db.unsafe(taggableModelsTableSql(sql)).execute();if(options.verbose)log.info("Creating categorizable_models pivot...");await db.unsafe(categorizableModelsTableSql(sql)).execute();try{for(const{table,foreignKey}of await likeableTargets()){if(options.verbose)log.info(`Creating ${table} table...`);await db.unsafe(likesTableSql(sql,table,foreignKey)).execute()}}catch(error){log.debug(`[trait-tables] Skipped likeable tables: ${error instanceof Error?error.message:String(error)}`)}for(const{table,column,definition}of traitTableColumnGuarantees(sql))try{await db.unsafe(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`).execute();if(options.verbose)log.info(`Added missing ${table}.${column}`)}catch(error){if(!isDuplicateColumnError(error))throw error}for(const statement of traitTableIndexSql())try{await db.unsafe(indexSqlForDialect(statement,dbDriver)).execute()}catch(error){if(!isDuplicateIndexError(error))throw error}if(options.verbose)log.success("Polymorphic trait tables created");return{success:!0}}catch(error){const message=error instanceof Error?error.message:String(error);log.error(`Failed to create polymorphic trait tables: ${message}`);return{success:!1,error:message}}}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/database",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.75.120",
5
+ "version": "0.75.122",
6
6
  "description": "The Stacks database integration.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -66,26 +66,26 @@
66
66
  "prepublishOnly": "bun run build"
67
67
  },
68
68
  "dependencies": {
69
- "@stacksjs/config": "0.75.120",
70
- "@stacksjs/env": "0.75.120",
71
- "@stacksjs/error-handling": "0.75.120",
72
- "@stacksjs/faker": "^0.75.120",
73
- "@stacksjs/features": "0.75.120",
74
- "@stacksjs/logging": "0.75.120",
75
- "@stacksjs/model-meta": "0.75.120",
76
- "@stacksjs/path": "0.75.120",
77
- "@stacksjs/query-builder": "^0.75.120",
78
- "@stacksjs/security": "0.75.120",
79
- "@stacksjs/storage": "0.75.120",
80
- "@stacksjs/strings": "0.75.120",
69
+ "@stacksjs/config": "0.75.122",
70
+ "@stacksjs/env": "0.75.122",
71
+ "@stacksjs/error-handling": "0.75.122",
72
+ "@stacksjs/faker": "^0.75.122",
73
+ "@stacksjs/features": "0.75.122",
74
+ "@stacksjs/logging": "0.75.122",
75
+ "@stacksjs/model-meta": "0.75.122",
76
+ "@stacksjs/path": "0.75.122",
77
+ "@stacksjs/query-builder": "^0.75.122",
78
+ "@stacksjs/security": "0.75.122",
79
+ "@stacksjs/storage": "0.75.122",
80
+ "@stacksjs/strings": "0.75.122",
81
81
  "@stacksjs/ts-validation": "^0.5.6",
82
82
  "bun-query-builder": "^0.3.5",
83
83
  "dynamodb-tooling": "^0.3.6"
84
84
  },
85
85
  "devDependencies": {
86
- "@stacksjs/cli": "0.75.120",
87
- "@stacksjs/router": "0.75.120",
88
- "@stacksjs/utils": "0.75.120",
86
+ "@stacksjs/cli": "0.75.122",
87
+ "@stacksjs/router": "0.75.122",
88
+ "@stacksjs/utils": "0.75.122",
89
89
  "better-dx": "^0.2.24"
90
90
  }
91
91
  }