@kysera/timestamps 0.7.4 → 0.8.1
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/README.md +76 -1
- package/dist/index.d.ts +1 -20
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/schema.d.ts +49 -0
- package/dist/schema.js +2 -0
- package/dist/schema.js.map +1 -0
- package/package.json +11 -7
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @kysera/timestamps
|
|
2
2
|
|
|
3
|
-
> Automatic timestamp management plugin for Kysera
|
|
3
|
+
> Automatic timestamp management plugin for Kysera - Zero-configuration `created_at` and `updated_at` tracking through @kysera/executor's Unified Execution Layer with powerful query helpers.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@kysera/timestamps)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -1079,6 +1079,81 @@ const plugin = timestampsPlugin({
|
|
|
1079
1079
|
|
|
1080
1080
|
---
|
|
1081
1081
|
|
|
1082
|
+
## 🔒 Schema Validation (Optional)
|
|
1083
|
+
|
|
1084
|
+
The timestamps plugin provides optional Zod schemas for configuration validation. These are exported from a separate subpath to keep Zod as an optional dependency.
|
|
1085
|
+
|
|
1086
|
+
### Installation
|
|
1087
|
+
|
|
1088
|
+
```bash
|
|
1089
|
+
# Zod is optional - only needed if you use schema validation
|
|
1090
|
+
npm install zod
|
|
1091
|
+
```
|
|
1092
|
+
|
|
1093
|
+
### Usage
|
|
1094
|
+
|
|
1095
|
+
```typescript
|
|
1096
|
+
import { TimestampsOptionsSchema } from '@kysera/timestamps/schema'
|
|
1097
|
+
|
|
1098
|
+
// Validate configuration
|
|
1099
|
+
const result = TimestampsOptionsSchema.safeParse({
|
|
1100
|
+
createdAtColumn: 'created_at',
|
|
1101
|
+
updatedAtColumn: 'updated_at',
|
|
1102
|
+
setUpdatedAtOnInsert: true,
|
|
1103
|
+
dateFormat: 'iso'
|
|
1104
|
+
})
|
|
1105
|
+
|
|
1106
|
+
if (result.success) {
|
|
1107
|
+
console.log('Valid options:', result.data)
|
|
1108
|
+
const plugin = timestampsPlugin(result.data)
|
|
1109
|
+
} else {
|
|
1110
|
+
console.error('Invalid configuration:', result.error.issues)
|
|
1111
|
+
}
|
|
1112
|
+
```
|
|
1113
|
+
|
|
1114
|
+
### Schema Fields
|
|
1115
|
+
|
|
1116
|
+
The `TimestampsOptionsSchema` validates:
|
|
1117
|
+
|
|
1118
|
+
| Field | Type | Description |
|
|
1119
|
+
|-------|------|-------------|
|
|
1120
|
+
| `createdAtColumn` | `string?` | Column name for creation timestamp |
|
|
1121
|
+
| `updatedAtColumn` | `string?` | Column name for update timestamp |
|
|
1122
|
+
| `setUpdatedAtOnInsert` | `boolean?` | Set updated_at on insert |
|
|
1123
|
+
| `tables` | `string[]?` | Whitelist of tables |
|
|
1124
|
+
| `excludeTables` | `string[]?` | Blacklist of tables |
|
|
1125
|
+
| `getTimestamp` | `function?` | Custom timestamp generator |
|
|
1126
|
+
| `dateFormat` | `'iso' \| 'unix' \| 'date'?` | Timestamp format |
|
|
1127
|
+
| `primaryKeyColumn` | `string?` | Primary key column name |
|
|
1128
|
+
|
|
1129
|
+
### Type Inference
|
|
1130
|
+
|
|
1131
|
+
```typescript
|
|
1132
|
+
import { TimestampsOptionsSchema, type TimestampsOptionsSchemaType } from '@kysera/timestamps/schema'
|
|
1133
|
+
|
|
1134
|
+
// Type is inferred from the schema
|
|
1135
|
+
const options: TimestampsOptionsSchemaType = {
|
|
1136
|
+
createdAtColumn: 'created',
|
|
1137
|
+
updatedAtColumn: 'modified',
|
|
1138
|
+
dateFormat: 'unix'
|
|
1139
|
+
}
|
|
1140
|
+
```
|
|
1141
|
+
|
|
1142
|
+
### CLI Integration
|
|
1143
|
+
|
|
1144
|
+
The schema is particularly useful for CLI tools and configuration files:
|
|
1145
|
+
|
|
1146
|
+
```typescript
|
|
1147
|
+
import { TimestampsOptionsSchema } from '@kysera/timestamps/schema'
|
|
1148
|
+
import { readFileSync } from 'fs'
|
|
1149
|
+
|
|
1150
|
+
// Validate config file
|
|
1151
|
+
const config = JSON.parse(readFileSync('timestamps.config.json', 'utf-8'))
|
|
1152
|
+
const validated = TimestampsOptionsSchema.parse(config)
|
|
1153
|
+
```
|
|
1154
|
+
|
|
1155
|
+
---
|
|
1156
|
+
|
|
1082
1157
|
## 📖 API Reference
|
|
1083
1158
|
|
|
1084
1159
|
### timestampsPlugin(options?)
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Plugin } from '@kysera/executor';
|
|
2
2
|
import { Repository } from '@kysera/repository';
|
|
3
3
|
import { KyseraLogger } from '@kysera/core';
|
|
4
|
-
import { z } from 'zod';
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
6
|
* Timestamp methods added to repositories
|
|
@@ -73,24 +72,6 @@ interface TimestampsOptions {
|
|
|
73
72
|
*/
|
|
74
73
|
logger?: KyseraLogger;
|
|
75
74
|
}
|
|
76
|
-
/**
|
|
77
|
-
* Zod schema for TimestampsOptions
|
|
78
|
-
* Used for validation and configuration in the kysera-cli
|
|
79
|
-
*/
|
|
80
|
-
declare const TimestampsOptionsSchema: z.ZodObject<{
|
|
81
|
-
createdAtColumn: z.ZodOptional<z.ZodString>;
|
|
82
|
-
updatedAtColumn: z.ZodOptional<z.ZodString>;
|
|
83
|
-
setUpdatedAtOnInsert: z.ZodOptional<z.ZodBoolean>;
|
|
84
|
-
tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
85
|
-
excludeTables: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
86
|
-
getTimestamp: z.ZodOptional<z.ZodFunction<z.core.$ZodFunctionArgs, z.core.$ZodFunctionOut>>;
|
|
87
|
-
dateFormat: z.ZodOptional<z.ZodEnum<{
|
|
88
|
-
iso: "iso";
|
|
89
|
-
unix: "unix";
|
|
90
|
-
date: "date";
|
|
91
|
-
}>>;
|
|
92
|
-
primaryKeyColumn: z.ZodOptional<z.ZodString>;
|
|
93
|
-
}, z.core.$strip>;
|
|
94
75
|
/**
|
|
95
76
|
* Repository extended with timestamp methods
|
|
96
77
|
*/
|
|
@@ -165,4 +146,4 @@ type TimestampsRepository<Entity, DB> = Repository<Entity, DB> & TimestampMethod
|
|
|
165
146
|
*/
|
|
166
147
|
declare const timestampsPlugin: (options?: TimestampsOptions) => Plugin;
|
|
167
148
|
|
|
168
|
-
export { type TimestampMethods, type TimestampsOptions,
|
|
149
|
+
export { type TimestampMethods, type TimestampsOptions, type TimestampsRepository, timestampsPlugin };
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {silentLogger,detectDialect}from'@kysera/core';
|
|
1
|
+
import {silentLogger,detectDialect}from'@kysera/core';var f="__VERSION__",v=f.startsWith("__")?"0.0.0-dev":f;function d(s){if(s.getTimestamp)return s.getTimestamp();let n=new Date;switch(s.dateFormat){case "unix":return Math.floor(n.getTime()/1e3);case "date":return n;case "iso":default:return n.toISOString()}}function x(s,n){return n.excludeTables?.includes(s)?false:n.tables?n.tables.includes(s):true}function w(s,n,i){return {select(){return s.selectFrom(n)},where(g,l){return s.selectFrom(n).where(i,g,l)}}}function P(s){let n=detectDialect(s);return n!=="mysql"&&n!=="mssql"}var C=(s={})=>{let{createdAtColumn:n="created_at",updatedAtColumn:i="updated_at",setUpdatedAtOnInsert:g=false,primaryKeyColumn:l="id",logger:m=silentLogger}=s;return {name:"@kysera/timestamps",version:v,priority:50,onInit(){},onDestroy(){return m.debug("Timestamps plugin destroyed"),Promise.resolve()},extendRepository(p){if(!("tableName"in p)||!("executor"in p))return p;let t=p;if(!x(t.tableName,s))return m.debug(`Table ${t.tableName} excluded from timestamps, skipping extension`),p;m.debug(`Extending repository for table ${t.tableName} with timestamp methods`);let b=t.create.bind(t),T=t.update.bind(t),o=t.executor;return {...t,async create(e){let r=e,a=d(s),u={...r,[n]:r[n]??a};return g&&(u[i]=r[i]??a),m.debug(`Creating record in ${t.tableName} with timestamp ${a}`),await b(u)},async update(e,r){let a=r,u=d(s),c={...a,[i]:a[i]??u};return m.debug(`Updating record ${e} in ${t.tableName} with timestamp ${u}`),await T(e,c)},async findCreatedAfter(e){return await w(o,t.tableName,n).where(">",String(e)).selectAll().execute()},async findCreatedBefore(e){return await w(o,t.tableName,n).where("<",String(e)).selectAll().execute()},async findCreatedBetween(e,r){return await o.selectFrom(t.tableName).selectAll().where(n,">=",e).where(n,"<=",r).execute()},async findUpdatedAfter(e){return await w(o,t.tableName,i).where(">",String(e)).selectAll().execute()},async findRecentlyUpdated(e=10){return await o.selectFrom(t.tableName).selectAll().orderBy(i,"desc").limit(e).execute()},async findRecentlyCreated(e=10){return await o.selectFrom(t.tableName).selectAll().orderBy(n,"desc").limit(e).execute()},async createWithoutTimestamps(e){return m.debug(`Creating record in ${t.tableName} without timestamps`),await b(e)},async updateWithoutTimestamp(e,r){return m.debug(`Updating record ${e} in ${t.tableName} without timestamp`),await T(e,r)},async touch(e){let r=d(s),a={[i]:r};m.info(`Touching record ${e} in ${t.tableName}`),await o.updateTable(t.tableName).set(a).where(l,"=",e).execute();},getTimestampColumns(){return {createdAt:n,updatedAt:i}},async createMany(e){if(!e||e.length===0)return [];let r=d(s),a=e.map(u=>{let c=u,y={...c,[n]:c[n]??r};return g&&(y[i]=c[i]??r),y});if(m.info(`Creating ${e.length} records in ${t.tableName} with timestamp ${r}`),P(o))return await o.insertInto(t.tableName).values(a).returningAll().execute();{await o.insertInto(t.tableName).values(a).execute();let u=a.length;return (await o.selectFrom(t.tableName).selectAll().where(n,"=",r).orderBy(l,"desc").limit(u).execute()).reverse()}},async updateMany(e,r){if(!e||e.length===0)return [];let a=r,u=d(s),c={...a,[i]:a[i]??u};return m.info(`Updating ${e.length} records in ${t.tableName} with timestamp ${u}`),await o.updateTable(t.tableName).set(c).where(l,"in",e).execute(),await o.selectFrom(t.tableName).selectAll().where(l,"in",e).execute()},async touchMany(e){if(!e||e.length===0)return;let r=d(s),a={[i]:r};m.info(`Touching ${e.length} records in ${t.tableName}`),await o.updateTable(t.tableName).set(a).where(l,"in",e).execute();}}}}};export{C as timestampsPlugin};//# sourceMappingURL=index.js.map
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/version.ts","../src/index.ts"],"names":["RAW_VERSION","VERSION","TimestampsOptionsSchema","z","getTimestamp","options","now","shouldApplyTimestamps","tableName","createTimestampQuery","executor","column","operator","value","supportsReturning","dialect","detectDialect","timestampsPlugin","createdAtColumn","updatedAtColumn","setUpdatedAtOnInsert","primaryKeyColumn","logger","silentLogger","repo","baseRepo","originalCreate","originalUpdate","input","data","timestamp","dataWithTimestamps","id","dataWithTimestamp","date","startDate","endDate","limit","updateData","inputs","result","insertedCount","ids"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Package version - injected at build time by tsup\n * Falls back to development version if not replaced\n * @internal\n */\nconst RAW_VERSION = '__VERSION__'\nexport const VERSION = RAW_VERSION.startsWith('__') ? '0.0.0-dev' : RAW_VERSION\n","import type { Plugin } from '@kysera/executor'\nimport type { Repository } from '@kysera/repository'\nimport type { Kysely, SelectQueryBuilder } from 'kysely'\nimport { silentLogger, detectDialect } from '@kysera/core'\nimport type { KyseraLogger } from '@kysera/core'\nimport { z } from 'zod'\nimport { VERSION } from './version.js'\n\n/**\n * Database schema with timestamp columns\n */\ntype TimestampedTable = Record<string, unknown>\n\n/**\n * Timestamp methods added to repositories\n */\nexport interface TimestampMethods<T> {\n findCreatedAfter(date: Date | string): Promise<T[]>\n findCreatedBefore(date: Date | string): Promise<T[]>\n findCreatedBetween(startDate: Date | string, endDate: Date | string): Promise<T[]>\n findUpdatedAfter(date: Date | string): Promise<T[]>\n findRecentlyUpdated(limit?: number): Promise<T[]>\n findRecentlyCreated(limit?: number): Promise<T[]>\n createWithoutTimestamps(input: unknown): Promise<T>\n updateWithoutTimestamp(id: number, input: unknown): Promise<T>\n touch(id: number): Promise<void>\n getTimestampColumns(): { createdAt: string; updatedAt: string }\n createMany(inputs: unknown[]): Promise<T[]>\n updateMany(ids: (number | string)[], input: unknown): Promise<T[]>\n touchMany(ids: (number | string)[]): Promise<void>\n}\n\n/**\n * Options for the timestamps plugin\n */\nexport interface TimestampsOptions {\n /**\n * Name of the created_at column\n * @default 'created_at'\n */\n createdAtColumn?: string\n\n /**\n * Name of the updated_at column\n * @default 'updated_at'\n */\n updatedAtColumn?: string\n\n /**\n * Whether to set updated_at on insert\n * @default false\n */\n setUpdatedAtOnInsert?: boolean\n\n /**\n * List of tables that should have timestamps\n * If not specified, all tables will have timestamps\n */\n tables?: string[]\n\n /**\n * Tables that should be excluded from timestamps\n */\n excludeTables?: string[]\n\n /**\n * Custom timestamp function (defaults to new Date().toISOString())\n */\n getTimestamp?: () => Date | string | number\n\n /**\n * Date format for database (ISO string by default)\n */\n dateFormat?: 'iso' | 'unix' | 'date'\n\n /**\n * Name of the primary key column used by touch() method\n * @default 'id'\n */\n primaryKeyColumn?: string\n\n /**\n * Logger for plugin operations.\n * Uses KyseraLogger interface from @kysera/core.\n *\n * @default silentLogger (no output)\n */\n logger?: KyseraLogger\n}\n\n/**\n * Zod schema for TimestampsOptions\n * Used for validation and configuration in the kysera-cli\n */\nexport const TimestampsOptionsSchema = z.object({\n createdAtColumn: z.string().optional(),\n updatedAtColumn: z.string().optional(),\n setUpdatedAtOnInsert: z.boolean().optional(),\n tables: z.array(z.string()).optional(),\n excludeTables: z.array(z.string()).optional(),\n getTimestamp: z.function().optional(),\n dateFormat: z.enum(['iso', 'unix', 'date']).optional(),\n primaryKeyColumn: z.string().optional()\n})\n\n/**\n * Repository extended with timestamp methods\n */\nexport type TimestampsRepository<Entity, DB> = Repository<Entity, DB> & TimestampMethods<Entity>\n\n/**\n * Get the current timestamp based on options\n */\nfunction getTimestamp(options: TimestampsOptions): Date | string | number {\n if (options.getTimestamp) {\n return options.getTimestamp()\n }\n\n const now = new Date()\n\n switch (options.dateFormat) {\n case 'unix':\n return Math.floor(now.getTime() / 1000)\n case 'date':\n return now\n case 'iso':\n default:\n return now.toISOString()\n }\n}\n\n/**\n * Check if a table should have timestamps\n */\nfunction shouldApplyTimestamps(tableName: string, options: TimestampsOptions): boolean {\n if (options.excludeTables?.includes(tableName)) {\n return false\n }\n\n if (options.tables) {\n return options.tables.includes(tableName)\n }\n\n return true\n}\n\n/**\n * Type-safe query builder for timestamp operations\n */\nfunction createTimestampQuery(\n executor: Kysely<Record<string, TimestampedTable>>,\n tableName: string,\n column: string\n): {\n select(): SelectQueryBuilder<Record<string, TimestampedTable>, typeof tableName, {}>\n where<V>(\n operator: string,\n value: V\n ): SelectQueryBuilder<Record<string, TimestampedTable>, typeof tableName, {}>\n} {\n return {\n select() {\n return executor.selectFrom(tableName as never)\n },\n where<V>(operator: string, value: V) {\n return executor\n .selectFrom(tableName as never)\n .where(column as never, operator as never, value as never)\n }\n }\n}\n\n/**\n * Check if dialect supports RETURNING clause\n *\n * Database compatibility:\n * - PostgreSQL: Full RETURNING support ✅\n * - SQLite: RETURNING supported in 3.35+ ✅ (most modern versions)\n * - MySQL: No RETURNING support ❌\n * - MSSQL: Uses OUTPUT clause ❌ (different syntax, not compatible with returningAll())\n *\n * **Implementation Notes:**\n * - We only check for MySQL since it's the only major dialect without RETURNING\n * - SQLite 3.35+ is widely deployed (released 2021-03-12)\n * - MSSQL support is minimal in Kysera ecosystem, treated as unsupported here\n * - If MSSQL full support is needed, this function should be extended to detect MSSQL\n *\n * @param executor - Kysely database executor\n * @returns true if RETURNING clause is supported\n */\nfunction supportsReturning<DB>(executor: Kysely<DB>): boolean {\n const dialect = detectDialect(executor)\n // Return false for MySQL (doesn't support RETURNING)\n // Return false for MSSQL (uses OUTPUT, not RETURNING)\n // Return true for PostgreSQL and SQLite\n return dialect !== 'mysql' && dialect !== 'mssql'\n}\n\n/**\n * Timestamps Plugin\n *\n * Automatically manages created_at and updated_at timestamps for database records.\n * Works by overriding repository methods to add timestamp values.\n *\n * ## Features\n *\n * - Automatic `created_at` on insert\n * - Automatic `updated_at` on every update\n * - Configurable column names\n * - Configurable timestamp format (ISO, Unix, Date)\n * - Query helpers: findCreatedAfter, findUpdatedAfter, etc.\n * - Bulk operations: createMany, updateMany, touchMany\n * - **Cross-database support**: Works with PostgreSQL, MySQL, SQLite, and MSSQL\n *\n * ## Transaction Behavior\n *\n * **IMPORTANT**: Timestamp operations respect ACID properties and work correctly with transactions:\n *\n * - ✅ **Commits with transaction**: Timestamps are set using the same executor as the\n * repository operation, so they commit together\n * - ✅ **Rolls back with transaction**: If a transaction is rolled back, all timestamp\n * changes are also rolled back\n * - ✅ **Consistent timestamps**: All operations within a transaction can use the same\n * timestamp by providing a custom `getTimestamp` function\n *\n * ### Correct Transaction Usage\n *\n * ```typescript\n * // ✅ CORRECT: Timestamps are part of transaction\n * await db.transaction().execute(async (trx) => {\n * const repos = createRepositories(trx) // Use transaction executor\n * await repos.users.create({ email: 'test@example.com' }) // created_at auto-set\n * await repos.posts.createMany([...]) // All created_at set consistently\n * // If transaction rolls back, all changes including timestamps roll back\n * })\n * ```\n *\n * ### Consistent Timestamps Across Operations\n *\n * ```typescript\n * // Use shared timestamp for all operations in a transaction\n * const now = new Date()\n * const timestampsWithFixedTime = timestampsPlugin({\n * getTimestamp: () => now\n * })\n *\n * await db.transaction().execute(async (trx) => {\n * // All operations will have the exact same timestamp\n * await repos.users.create(...) // created_at = now\n * await repos.posts.update(...) // updated_at = now\n * })\n * ```\n *\n * @example\n * ```typescript\n * import { timestampsPlugin } from '@kysera/timestamps'\n *\n * const plugin = timestampsPlugin({\n * createdAtColumn: 'created_at',\n * updatedAtColumn: 'updated_at',\n * tables: ['users', 'posts', 'comments']\n * })\n *\n * const orm = createORM(db, [plugin])\n * ```\n */\nexport const timestampsPlugin = (options: TimestampsOptions = {}): Plugin => {\n const {\n createdAtColumn = 'created_at',\n updatedAtColumn = 'updated_at',\n setUpdatedAtOnInsert = false,\n primaryKeyColumn = 'id',\n logger = silentLogger\n } = options\n\n return {\n name: '@kysera/timestamps',\n version: VERSION,\n priority: 50, // Run in the middle, after filtering but before audit\n\n /**\n * Lifecycle: No initialization needed for timestamps plugin\n */\n onInit() {\n // No initialization required\n },\n\n /**\n * Lifecycle: Cleanup resources when executor is destroyed\n */\n async onDestroy() {\n // No cleanup required - timestamps plugin has no persistent resources\n logger.debug('Timestamps plugin destroyed')\n },\n\n extendRepository<T extends object>(repo: T): T {\n // Check if it's actually a repository (has required properties)\n if (!('tableName' in repo) || !('executor' in repo)) {\n return repo\n }\n\n // Type assertion is safe here as we've checked for properties\n const baseRepo = repo as T & {\n tableName: string\n executor: unknown\n create: Function\n update: Function\n }\n\n // Skip if table doesn't support timestamps\n if (!shouldApplyTimestamps(baseRepo.tableName, options)) {\n logger.debug(`Table ${baseRepo.tableName} excluded from timestamps, skipping extension`)\n return repo\n }\n\n logger.debug(`Extending repository for table ${baseRepo.tableName} with timestamp methods`)\n\n // Save original methods\n const originalCreate = baseRepo.create.bind(baseRepo)\n const originalUpdate = baseRepo.update.bind(baseRepo)\n const executor = baseRepo.executor as Kysely<Record<string, TimestampedTable>>\n\n const extendedRepo = {\n ...baseRepo,\n\n // Override create to add timestamps\n async create(input: unknown): Promise<unknown> {\n const data = input as Record<string, unknown>\n const timestamp = getTimestamp(options)\n const dataWithTimestamps: Record<string, unknown> = {\n ...data,\n [createdAtColumn]: data[createdAtColumn] ?? timestamp\n }\n\n if (setUpdatedAtOnInsert) {\n dataWithTimestamps[updatedAtColumn] = data[updatedAtColumn] ?? timestamp\n }\n\n logger.debug(`Creating record in ${baseRepo.tableName} with timestamp ${timestamp}`)\n return await originalCreate(dataWithTimestamps)\n },\n\n // Override update to set updated_at\n async update(id: number, input: unknown): Promise<unknown> {\n const data = input as Record<string, unknown>\n const timestamp = getTimestamp(options)\n const dataWithTimestamp: Record<string, unknown> = {\n ...data,\n [updatedAtColumn]: data[updatedAtColumn] ?? timestamp\n }\n\n logger.debug(`Updating record ${id} in ${baseRepo.tableName} with timestamp ${timestamp}`)\n return await originalUpdate(id, dataWithTimestamp)\n },\n\n /**\n * Find records created after a specific date\n */\n async findCreatedAfter(date: Date | string | number): Promise<unknown[]> {\n const query = createTimestampQuery(executor, baseRepo.tableName, createdAtColumn)\n const result = await query.where('>', String(date)).selectAll().execute()\n return result\n },\n\n /**\n * Find records created before a specific date\n */\n async findCreatedBefore(date: Date | string | number): Promise<unknown[]> {\n const query = createTimestampQuery(executor, baseRepo.tableName, createdAtColumn)\n const result = await query.where('<', String(date)).selectAll().execute()\n return result\n },\n\n /**\n * Find records created between two dates\n */\n async findCreatedBetween(\n startDate: Date | string | number,\n endDate: Date | string | number\n ): Promise<unknown[]> {\n const result = await executor\n .selectFrom(baseRepo.tableName as never)\n .selectAll()\n .where(createdAtColumn as never, '>=', startDate as never)\n .where(createdAtColumn as never, '<=', endDate as never)\n .execute()\n return result\n },\n\n /**\n * Find records updated after a specific date\n */\n async findUpdatedAfter(date: Date | string | number): Promise<unknown[]> {\n const query = createTimestampQuery(executor, baseRepo.tableName, updatedAtColumn)\n const result = await query.where('>', String(date)).selectAll().execute()\n return result\n },\n\n /**\n * Find recently updated records\n */\n async findRecentlyUpdated(limit = 10): Promise<unknown[]> {\n const result = await executor\n .selectFrom(baseRepo.tableName as never)\n .selectAll()\n .orderBy(updatedAtColumn as never, 'desc')\n .limit(limit)\n .execute()\n return result\n },\n\n /**\n * Find recently created records\n */\n async findRecentlyCreated(limit = 10): Promise<unknown[]> {\n const result = await executor\n .selectFrom(baseRepo.tableName as never)\n .selectAll()\n .orderBy(createdAtColumn as never, 'desc')\n .limit(limit)\n .execute()\n return result\n },\n\n /**\n * Create without adding timestamps\n */\n async createWithoutTimestamps(input: unknown): Promise<unknown> {\n logger.debug(`Creating record in ${baseRepo.tableName} without timestamps`)\n return await originalCreate(input)\n },\n\n /**\n * Update without modifying timestamp\n */\n async updateWithoutTimestamp(id: number, input: unknown): Promise<unknown> {\n logger.debug(`Updating record ${id} in ${baseRepo.tableName} without timestamp`)\n return await originalUpdate(id, input)\n },\n\n /**\n * Touch a record (update its timestamp)\n */\n async touch(id: number): Promise<void> {\n const timestamp = getTimestamp(options)\n const updateData = { [updatedAtColumn]: timestamp }\n\n logger.info(`Touching record ${id} in ${baseRepo.tableName}`)\n await executor\n .updateTable(baseRepo.tableName as never)\n .set(updateData as never)\n .where(primaryKeyColumn as never, '=', id as never)\n .execute()\n },\n\n /**\n * Get the timestamp column names\n */\n getTimestampColumns(): { createdAt: string; updatedAt: string } {\n return {\n createdAt: createdAtColumn,\n updatedAt: updatedAtColumn\n }\n },\n\n /**\n * Create multiple records with timestamps\n * Uses efficient bulk INSERT with automatic timestamp injection.\n * Supports PostgreSQL, MySQL, SQLite, and MSSQL with appropriate fallbacks.\n */\n async createMany(inputs: unknown[]): Promise<unknown[]> {\n // Handle empty arrays gracefully\n if (!inputs || inputs.length === 0) {\n return []\n }\n\n const timestamp = getTimestamp(options)\n const dataWithTimestamps = inputs.map(input => {\n const data = input as Record<string, unknown>\n const result: Record<string, unknown> = {\n ...data,\n [createdAtColumn]: data[createdAtColumn] ?? timestamp\n }\n\n if (setUpdatedAtOnInsert) {\n result[updatedAtColumn] = data[updatedAtColumn] ?? timestamp\n }\n\n return result\n })\n\n logger.info(\n `Creating ${inputs.length} records in ${baseRepo.tableName} with timestamp ${timestamp}`\n )\n\n // Check if dialect supports RETURNING\n if (supportsReturning(executor)) {\n // Use RETURNING for PostgreSQL/SQLite - most efficient\n const result = await executor\n .insertInto(baseRepo.tableName as never)\n .values(dataWithTimestamps as never)\n .returningAll()\n .execute()\n\n return result\n } else {\n // Fallback for MySQL/MSSQL - insert then select\n // This approach works for auto-increment primary keys\n await executor\n .insertInto(baseRepo.tableName as never)\n .values(dataWithTimestamps as never)\n .execute()\n\n // Fetch inserted records by matching on unique columns\n // For simplicity, we fetch the most recently created records\n // This works well when created_at is set to the same timestamp\n const insertedCount = dataWithTimestamps.length\n const result = await executor\n .selectFrom(baseRepo.tableName as never)\n .selectAll()\n .where(createdAtColumn as never, '=', timestamp as never)\n .orderBy(primaryKeyColumn as never, 'desc')\n .limit(insertedCount)\n .execute()\n\n // Reverse to maintain insertion order\n return result.reverse()\n }\n },\n\n /**\n * Update multiple records (sets updated_at for all)\n * Updates all specified records with the same data\n */\n async updateMany(ids: (number | string)[], input: unknown): Promise<unknown[]> {\n // Handle empty arrays gracefully\n if (!ids || ids.length === 0) {\n return []\n }\n\n const data = input as Record<string, unknown>\n const timestamp = getTimestamp(options)\n const dataWithTimestamp: Record<string, unknown> = {\n ...data,\n [updatedAtColumn]: data[updatedAtColumn] ?? timestamp\n }\n\n logger.info(\n `Updating ${ids.length} records in ${baseRepo.tableName} with timestamp ${timestamp}`\n )\n\n // Use Kysely's update with IN clause for efficient bulk update\n await executor\n .updateTable(baseRepo.tableName as never)\n .set(dataWithTimestamp as never)\n .where(primaryKeyColumn as never, 'in', ids as never)\n .execute()\n\n // Fetch and return updated records\n const result = await executor\n .selectFrom(baseRepo.tableName as never)\n .selectAll()\n .where(primaryKeyColumn as never, 'in', ids as never)\n .execute()\n\n return result\n },\n\n /**\n * Touch multiple records (update updated_at only)\n * Efficiently updates only the timestamp column\n */\n async touchMany(ids: (number | string)[]): Promise<void> {\n // Handle empty arrays gracefully\n if (!ids || ids.length === 0) {\n return\n }\n\n const timestamp = getTimestamp(options)\n const updateData = { [updatedAtColumn]: timestamp }\n\n logger.info(`Touching ${ids.length} records in ${baseRepo.tableName}`)\n await executor\n .updateTable(baseRepo.tableName as never)\n .set(updateData as never)\n .where(primaryKeyColumn as never, 'in', ids as never)\n .execute()\n }\n }\n\n return extendedRepo as T\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/version.ts","../src/index.ts"],"names":["RAW_VERSION","VERSION","getTimestamp","options","now","shouldApplyTimestamps","tableName","createTimestampQuery","executor","column","operator","value","supportsReturning","dialect","detectDialect","timestampsPlugin","createdAtColumn","updatedAtColumn","setUpdatedAtOnInsert","primaryKeyColumn","logger","silentLogger","repo","baseRepo","originalCreate","originalUpdate","input","data","timestamp","dataWithTimestamps","id","dataWithTimestamp","date","startDate","endDate","limit","updateData","inputs","result","insertedCount","ids"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Package version - injected at build time by tsup\n * Falls back to development version if not replaced\n * @internal\n */\nconst RAW_VERSION = '__VERSION__'\nexport const VERSION = RAW_VERSION.startsWith('__') ? '0.0.0-dev' : RAW_VERSION\n","import type { Plugin } from '@kysera/executor'\nimport type { Repository } from '@kysera/repository'\nimport type { Kysely, SelectQueryBuilder } from 'kysely'\nimport { silentLogger, detectDialect } from '@kysera/core'\nimport type { KyseraLogger } from '@kysera/core'\nimport { VERSION } from './version.js'\n\n/**\n * Database schema with timestamp columns\n */\ntype TimestampedTable = Record<string, unknown>\n\n/**\n * Timestamp methods added to repositories\n */\nexport interface TimestampMethods<T> {\n findCreatedAfter(date: Date | string): Promise<T[]>\n findCreatedBefore(date: Date | string): Promise<T[]>\n findCreatedBetween(startDate: Date | string, endDate: Date | string): Promise<T[]>\n findUpdatedAfter(date: Date | string): Promise<T[]>\n findRecentlyUpdated(limit?: number): Promise<T[]>\n findRecentlyCreated(limit?: number): Promise<T[]>\n createWithoutTimestamps(input: unknown): Promise<T>\n updateWithoutTimestamp(id: number, input: unknown): Promise<T>\n touch(id: number): Promise<void>\n getTimestampColumns(): { createdAt: string; updatedAt: string }\n createMany(inputs: unknown[]): Promise<T[]>\n updateMany(ids: (number | string)[], input: unknown): Promise<T[]>\n touchMany(ids: (number | string)[]): Promise<void>\n}\n\n/**\n * Options for the timestamps plugin\n */\nexport interface TimestampsOptions {\n /**\n * Name of the created_at column\n * @default 'created_at'\n */\n createdAtColumn?: string\n\n /**\n * Name of the updated_at column\n * @default 'updated_at'\n */\n updatedAtColumn?: string\n\n /**\n * Whether to set updated_at on insert\n * @default false\n */\n setUpdatedAtOnInsert?: boolean\n\n /**\n * List of tables that should have timestamps\n * If not specified, all tables will have timestamps\n */\n tables?: string[]\n\n /**\n * Tables that should be excluded from timestamps\n */\n excludeTables?: string[]\n\n /**\n * Custom timestamp function (defaults to new Date().toISOString())\n */\n getTimestamp?: () => Date | string | number\n\n /**\n * Date format for database (ISO string by default)\n */\n dateFormat?: 'iso' | 'unix' | 'date'\n\n /**\n * Name of the primary key column used by touch() method\n * @default 'id'\n */\n primaryKeyColumn?: string\n\n /**\n * Logger for plugin operations.\n * Uses KyseraLogger interface from @kysera/core.\n *\n * @default silentLogger (no output)\n */\n logger?: KyseraLogger\n}\n\n/**\n * Repository extended with timestamp methods\n */\nexport type TimestampsRepository<Entity, DB> = Repository<Entity, DB> & TimestampMethods<Entity>\n\n/**\n * Get the current timestamp based on options\n */\nfunction getTimestamp(options: TimestampsOptions): Date | string | number {\n if (options.getTimestamp) {\n return options.getTimestamp()\n }\n\n const now = new Date()\n\n switch (options.dateFormat) {\n case 'unix':\n return Math.floor(now.getTime() / 1000)\n case 'date':\n return now\n case 'iso':\n default:\n return now.toISOString()\n }\n}\n\n/**\n * Check if a table should have timestamps\n */\nfunction shouldApplyTimestamps(tableName: string, options: TimestampsOptions): boolean {\n if (options.excludeTables?.includes(tableName)) {\n return false\n }\n\n if (options.tables) {\n return options.tables.includes(tableName)\n }\n\n return true\n}\n\n/**\n * Type-safe query builder for timestamp operations\n */\nfunction createTimestampQuery(\n executor: Kysely<Record<string, TimestampedTable>>,\n tableName: string,\n column: string\n): {\n select(): SelectQueryBuilder<Record<string, TimestampedTable>, typeof tableName, {}>\n where<V>(\n operator: string,\n value: V\n ): SelectQueryBuilder<Record<string, TimestampedTable>, typeof tableName, {}>\n} {\n return {\n select() {\n return executor.selectFrom(tableName as never)\n },\n where<V>(operator: string, value: V) {\n return executor\n .selectFrom(tableName as never)\n .where(column as never, operator as never, value as never)\n }\n }\n}\n\n/**\n * Check if dialect supports RETURNING clause\n *\n * Database compatibility:\n * - PostgreSQL: Full RETURNING support ✅\n * - SQLite: RETURNING supported in 3.35+ ✅ (most modern versions)\n * - MySQL: No RETURNING support ❌\n * - MSSQL: Uses OUTPUT clause ❌ (different syntax, not compatible with returningAll())\n *\n * **Implementation Notes:**\n * - We only check for MySQL since it's the only major dialect without RETURNING\n * - SQLite 3.35+ is widely deployed (released 2021-03-12)\n * - MSSQL support is minimal in Kysera ecosystem, treated as unsupported here\n * - If MSSQL full support is needed, this function should be extended to detect MSSQL\n *\n * @param executor - Kysely database executor\n * @returns true if RETURNING clause is supported\n */\nfunction supportsReturning<DB>(executor: Kysely<DB>): boolean {\n const dialect = detectDialect(executor)\n // Return false for MySQL (doesn't support RETURNING)\n // Return false for MSSQL (uses OUTPUT, not RETURNING)\n // Return true for PostgreSQL and SQLite\n return dialect !== 'mysql' && dialect !== 'mssql'\n}\n\n/**\n * Timestamps Plugin\n *\n * Automatically manages created_at and updated_at timestamps for database records.\n * Works by overriding repository methods to add timestamp values.\n *\n * ## Features\n *\n * - Automatic `created_at` on insert\n * - Automatic `updated_at` on every update\n * - Configurable column names\n * - Configurable timestamp format (ISO, Unix, Date)\n * - Query helpers: findCreatedAfter, findUpdatedAfter, etc.\n * - Bulk operations: createMany, updateMany, touchMany\n * - **Cross-database support**: Works with PostgreSQL, MySQL, SQLite, and MSSQL\n *\n * ## Transaction Behavior\n *\n * **IMPORTANT**: Timestamp operations respect ACID properties and work correctly with transactions:\n *\n * - ✅ **Commits with transaction**: Timestamps are set using the same executor as the\n * repository operation, so they commit together\n * - ✅ **Rolls back with transaction**: If a transaction is rolled back, all timestamp\n * changes are also rolled back\n * - ✅ **Consistent timestamps**: All operations within a transaction can use the same\n * timestamp by providing a custom `getTimestamp` function\n *\n * ### Correct Transaction Usage\n *\n * ```typescript\n * // ✅ CORRECT: Timestamps are part of transaction\n * await db.transaction().execute(async (trx) => {\n * const repos = createRepositories(trx) // Use transaction executor\n * await repos.users.create({ email: 'test@example.com' }) // created_at auto-set\n * await repos.posts.createMany([...]) // All created_at set consistently\n * // If transaction rolls back, all changes including timestamps roll back\n * })\n * ```\n *\n * ### Consistent Timestamps Across Operations\n *\n * ```typescript\n * // Use shared timestamp for all operations in a transaction\n * const now = new Date()\n * const timestampsWithFixedTime = timestampsPlugin({\n * getTimestamp: () => now\n * })\n *\n * await db.transaction().execute(async (trx) => {\n * // All operations will have the exact same timestamp\n * await repos.users.create(...) // created_at = now\n * await repos.posts.update(...) // updated_at = now\n * })\n * ```\n *\n * @example\n * ```typescript\n * import { timestampsPlugin } from '@kysera/timestamps'\n *\n * const plugin = timestampsPlugin({\n * createdAtColumn: 'created_at',\n * updatedAtColumn: 'updated_at',\n * tables: ['users', 'posts', 'comments']\n * })\n *\n * const orm = createORM(db, [plugin])\n * ```\n */\nexport const timestampsPlugin = (options: TimestampsOptions = {}): Plugin => {\n const {\n createdAtColumn = 'created_at',\n updatedAtColumn = 'updated_at',\n setUpdatedAtOnInsert = false,\n primaryKeyColumn = 'id',\n logger = silentLogger\n } = options\n\n return {\n name: '@kysera/timestamps',\n version: VERSION,\n priority: 50, // Run in the middle, after filtering but before audit\n\n /**\n * Lifecycle: No initialization needed for timestamps plugin\n */\n onInit() {\n // No initialization required\n },\n\n /**\n * Lifecycle: Cleanup resources when executor is destroyed\n */\n onDestroy(): Promise<void> {\n // No cleanup required - timestamps plugin has no persistent resources\n logger.debug('Timestamps plugin destroyed')\n return Promise.resolve()\n },\n\n extendRepository<T extends object>(repo: T): T {\n // Check if it's actually a repository (has required properties)\n if (!('tableName' in repo) || !('executor' in repo)) {\n return repo\n }\n\n // Type assertion is safe here as we've checked for properties\n const baseRepo = repo as T & {\n tableName: string\n executor: unknown\n create: Function\n update: Function\n }\n\n // Skip if table doesn't support timestamps\n if (!shouldApplyTimestamps(baseRepo.tableName, options)) {\n logger.debug(`Table ${baseRepo.tableName} excluded from timestamps, skipping extension`)\n return repo\n }\n\n logger.debug(`Extending repository for table ${baseRepo.tableName} with timestamp methods`)\n\n // Save original methods\n const originalCreate = baseRepo.create.bind(baseRepo)\n const originalUpdate = baseRepo.update.bind(baseRepo)\n const executor = baseRepo.executor as Kysely<Record<string, TimestampedTable>>\n\n const extendedRepo = {\n ...baseRepo,\n\n // Override create to add timestamps\n async create(input: unknown): Promise<unknown> {\n const data = input as Record<string, unknown>\n const timestamp = getTimestamp(options)\n const dataWithTimestamps: Record<string, unknown> = {\n ...data,\n [createdAtColumn]: data[createdAtColumn] ?? timestamp\n }\n\n if (setUpdatedAtOnInsert) {\n dataWithTimestamps[updatedAtColumn] = data[updatedAtColumn] ?? timestamp\n }\n\n logger.debug(`Creating record in ${baseRepo.tableName} with timestamp ${timestamp}`)\n return await originalCreate(dataWithTimestamps)\n },\n\n // Override update to set updated_at\n async update(id: number, input: unknown): Promise<unknown> {\n const data = input as Record<string, unknown>\n const timestamp = getTimestamp(options)\n const dataWithTimestamp: Record<string, unknown> = {\n ...data,\n [updatedAtColumn]: data[updatedAtColumn] ?? timestamp\n }\n\n logger.debug(`Updating record ${id} in ${baseRepo.tableName} with timestamp ${timestamp}`)\n return await originalUpdate(id, dataWithTimestamp)\n },\n\n /**\n * Find records created after a specific date\n */\n async findCreatedAfter(date: Date | string | number): Promise<unknown[]> {\n const query = createTimestampQuery(executor, baseRepo.tableName, createdAtColumn)\n const result = await query.where('>', String(date)).selectAll().execute()\n return result\n },\n\n /**\n * Find records created before a specific date\n */\n async findCreatedBefore(date: Date | string | number): Promise<unknown[]> {\n const query = createTimestampQuery(executor, baseRepo.tableName, createdAtColumn)\n const result = await query.where('<', String(date)).selectAll().execute()\n return result\n },\n\n /**\n * Find records created between two dates\n */\n async findCreatedBetween(\n startDate: Date | string | number,\n endDate: Date | string | number\n ): Promise<unknown[]> {\n const result = await executor\n .selectFrom(baseRepo.tableName as never)\n .selectAll()\n .where(createdAtColumn as never, '>=', startDate as never)\n .where(createdAtColumn as never, '<=', endDate as never)\n .execute()\n return result\n },\n\n /**\n * Find records updated after a specific date\n */\n async findUpdatedAfter(date: Date | string | number): Promise<unknown[]> {\n const query = createTimestampQuery(executor, baseRepo.tableName, updatedAtColumn)\n const result = await query.where('>', String(date)).selectAll().execute()\n return result\n },\n\n /**\n * Find recently updated records\n */\n async findRecentlyUpdated(limit = 10): Promise<unknown[]> {\n const result = await executor\n .selectFrom(baseRepo.tableName as never)\n .selectAll()\n .orderBy(updatedAtColumn as never, 'desc')\n .limit(limit)\n .execute()\n return result\n },\n\n /**\n * Find recently created records\n */\n async findRecentlyCreated(limit = 10): Promise<unknown[]> {\n const result = await executor\n .selectFrom(baseRepo.tableName as never)\n .selectAll()\n .orderBy(createdAtColumn as never, 'desc')\n .limit(limit)\n .execute()\n return result\n },\n\n /**\n * Create without adding timestamps\n */\n async createWithoutTimestamps(input: unknown): Promise<unknown> {\n logger.debug(`Creating record in ${baseRepo.tableName} without timestamps`)\n return await originalCreate(input)\n },\n\n /**\n * Update without modifying timestamp\n */\n async updateWithoutTimestamp(id: number, input: unknown): Promise<unknown> {\n logger.debug(`Updating record ${id} in ${baseRepo.tableName} without timestamp`)\n return await originalUpdate(id, input)\n },\n\n /**\n * Touch a record (update its timestamp)\n */\n async touch(id: number): Promise<void> {\n const timestamp = getTimestamp(options)\n const updateData = { [updatedAtColumn]: timestamp }\n\n logger.info(`Touching record ${id} in ${baseRepo.tableName}`)\n await executor\n .updateTable(baseRepo.tableName as never)\n .set(updateData as never)\n .where(primaryKeyColumn as never, '=', id as never)\n .execute()\n },\n\n /**\n * Get the timestamp column names\n */\n getTimestampColumns(): { createdAt: string; updatedAt: string } {\n return {\n createdAt: createdAtColumn,\n updatedAt: updatedAtColumn\n }\n },\n\n /**\n * Create multiple records with timestamps\n * Uses efficient bulk INSERT with automatic timestamp injection.\n * Supports PostgreSQL, MySQL, SQLite, and MSSQL with appropriate fallbacks.\n */\n async createMany(inputs: unknown[]): Promise<unknown[]> {\n // Handle empty arrays gracefully\n if (!inputs || inputs.length === 0) {\n return []\n }\n\n const timestamp = getTimestamp(options)\n const dataWithTimestamps = inputs.map(input => {\n const data = input as Record<string, unknown>\n const result: Record<string, unknown> = {\n ...data,\n [createdAtColumn]: data[createdAtColumn] ?? timestamp\n }\n\n if (setUpdatedAtOnInsert) {\n result[updatedAtColumn] = data[updatedAtColumn] ?? timestamp\n }\n\n return result\n })\n\n logger.info(\n `Creating ${inputs.length} records in ${baseRepo.tableName} with timestamp ${timestamp}`\n )\n\n // Check if dialect supports RETURNING\n if (supportsReturning(executor)) {\n // Use RETURNING for PostgreSQL/SQLite - most efficient\n const result = await executor\n .insertInto(baseRepo.tableName as never)\n .values(dataWithTimestamps as never)\n .returningAll()\n .execute()\n\n return result\n } else {\n // Fallback for MySQL/MSSQL - insert then select\n // This approach works for auto-increment primary keys\n await executor\n .insertInto(baseRepo.tableName as never)\n .values(dataWithTimestamps as never)\n .execute()\n\n // Fetch inserted records by matching on unique columns\n // For simplicity, we fetch the most recently created records\n // This works well when created_at is set to the same timestamp\n const insertedCount = dataWithTimestamps.length\n const result = await executor\n .selectFrom(baseRepo.tableName as never)\n .selectAll()\n .where(createdAtColumn as never, '=', timestamp as never)\n .orderBy(primaryKeyColumn as never, 'desc')\n .limit(insertedCount)\n .execute()\n\n // Reverse to maintain insertion order\n return result.reverse()\n }\n },\n\n /**\n * Update multiple records (sets updated_at for all)\n * Updates all specified records with the same data\n */\n async updateMany(ids: (number | string)[], input: unknown): Promise<unknown[]> {\n // Handle empty arrays gracefully\n if (!ids || ids.length === 0) {\n return []\n }\n\n const data = input as Record<string, unknown>\n const timestamp = getTimestamp(options)\n const dataWithTimestamp: Record<string, unknown> = {\n ...data,\n [updatedAtColumn]: data[updatedAtColumn] ?? timestamp\n }\n\n logger.info(\n `Updating ${ids.length} records in ${baseRepo.tableName} with timestamp ${timestamp}`\n )\n\n // Use Kysely's update with IN clause for efficient bulk update\n await executor\n .updateTable(baseRepo.tableName as never)\n .set(dataWithTimestamp as never)\n .where(primaryKeyColumn as never, 'in', ids as never)\n .execute()\n\n // Fetch and return updated records\n const result = await executor\n .selectFrom(baseRepo.tableName as never)\n .selectAll()\n .where(primaryKeyColumn as never, 'in', ids as never)\n .execute()\n\n return result\n },\n\n /**\n * Touch multiple records (update updated_at only)\n * Efficiently updates only the timestamp column\n */\n async touchMany(ids: (number | string)[]): Promise<void> {\n // Handle empty arrays gracefully\n if (!ids || ids.length === 0) {\n return\n }\n\n const timestamp = getTimestamp(options)\n const updateData = { [updatedAtColumn]: timestamp }\n\n logger.info(`Touching ${ids.length} records in ${baseRepo.tableName}`)\n await executor\n .updateTable(baseRepo.tableName as never)\n .set(updateData as never)\n .where(primaryKeyColumn as never, 'in', ids as never)\n .execute()\n }\n }\n\n return extendedRepo as T\n }\n }\n}\n"]}
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Zod schemas for timestamps plugin configuration.
|
|
5
|
+
* This file is separate from the main index to allow the package to work without Zod installed.
|
|
6
|
+
* Only import this file if you need Zod validation (e.g., for CLI or configuration validation).
|
|
7
|
+
*
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Zod schema for TimestampsOptions
|
|
13
|
+
* Used for validation and configuration in the kysera-cli
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* import { TimestampsOptionsSchema } from '@kysera/timestamps/schema'
|
|
18
|
+
*
|
|
19
|
+
* const result = TimestampsOptionsSchema.safeParse({
|
|
20
|
+
* createdAtColumn: 'created_at',
|
|
21
|
+
* updatedAtColumn: 'updated_at',
|
|
22
|
+
* setUpdatedAtOnInsert: true
|
|
23
|
+
* })
|
|
24
|
+
*
|
|
25
|
+
* if (result.success) {
|
|
26
|
+
* console.log('Valid options:', result.data)
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
declare const TimestampsOptionsSchema: z.ZodObject<{
|
|
31
|
+
createdAtColumn: z.ZodOptional<z.ZodString>;
|
|
32
|
+
updatedAtColumn: z.ZodOptional<z.ZodString>;
|
|
33
|
+
setUpdatedAtOnInsert: z.ZodOptional<z.ZodBoolean>;
|
|
34
|
+
tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
35
|
+
excludeTables: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
36
|
+
getTimestamp: z.ZodOptional<z.ZodFunction<z.core.$ZodFunctionArgs, z.core.$ZodFunctionOut>>;
|
|
37
|
+
dateFormat: z.ZodOptional<z.ZodEnum<{
|
|
38
|
+
date: "date";
|
|
39
|
+
iso: "iso";
|
|
40
|
+
unix: "unix";
|
|
41
|
+
}>>;
|
|
42
|
+
primaryKeyColumn: z.ZodOptional<z.ZodString>;
|
|
43
|
+
}, z.core.$strip>;
|
|
44
|
+
/**
|
|
45
|
+
* Type inferred from TimestampsOptionsSchema
|
|
46
|
+
*/
|
|
47
|
+
type TimestampsOptionsSchemaType = z.infer<typeof TimestampsOptionsSchema>;
|
|
48
|
+
|
|
49
|
+
export { TimestampsOptionsSchema, type TimestampsOptionsSchemaType };
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import {z}from'zod';var e=z.object({createdAtColumn:z.string().optional(),updatedAtColumn:z.string().optional(),setUpdatedAtOnInsert:z.boolean().optional(),tables:z.array(z.string()).optional(),excludeTables:z.array(z.string()).optional(),getTimestamp:z.function().optional(),dateFormat:z.enum(["iso","unix","date"]).optional(),primaryKeyColumn:z.string().optional()});export{e as TimestampsOptionsSchema};//# sourceMappingURL=schema.js.map
|
|
2
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/schema.ts"],"names":["TimestampsOptionsSchema","z"],"mappings":"oBA6BO,IAAMA,CAAAA,CAA0BC,CAAAA,CAAE,MAAA,CAAO,CAC9C,eAAA,CAAiBA,CAAAA,CAAE,MAAA,EAAO,CAAE,UAAS,CACrC,eAAA,CAAiBA,CAAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAAS,CACrC,oBAAA,CAAsBA,EAAE,OAAA,EAAQ,CAAE,QAAA,EAAS,CAC3C,MAAA,CAAQA,CAAAA,CAAE,KAAA,CAAMA,CAAAA,CAAE,QAAQ,CAAA,CAAE,QAAA,EAAS,CACrC,aAAA,CAAeA,CAAAA,CAAE,KAAA,CAAMA,CAAAA,CAAE,QAAQ,CAAA,CAAE,QAAA,EAAS,CAC5C,YAAA,CAAcA,CAAAA,CAAE,QAAA,EAAS,CAAE,UAAS,CACpC,UAAA,CAAYA,CAAAA,CAAE,IAAA,CAAK,CAAC,KAAA,CAAO,MAAA,CAAQ,MAAM,CAAC,CAAA,CAAE,QAAA,EAAS,CACrD,gBAAA,CAAkBA,CAAAA,CAAE,MAAA,EAAO,CAAE,QAAA,EAC/B,CAAC","file":"schema.js","sourcesContent":["/**\n * Zod schemas for timestamps plugin configuration.\n * This file is separate from the main index to allow the package to work without Zod installed.\n * Only import this file if you need Zod validation (e.g., for CLI or configuration validation).\n *\n * @packageDocumentation\n */\n\nimport { z } from 'zod'\n\n/**\n * Zod schema for TimestampsOptions\n * Used for validation and configuration in the kysera-cli\n *\n * @example\n * ```typescript\n * import { TimestampsOptionsSchema } from '@kysera/timestamps/schema'\n *\n * const result = TimestampsOptionsSchema.safeParse({\n * createdAtColumn: 'created_at',\n * updatedAtColumn: 'updated_at',\n * setUpdatedAtOnInsert: true\n * })\n *\n * if (result.success) {\n * console.log('Valid options:', result.data)\n * }\n * ```\n */\nexport const TimestampsOptionsSchema = z.object({\n createdAtColumn: z.string().optional(),\n updatedAtColumn: z.string().optional(),\n setUpdatedAtOnInsert: z.boolean().optional(),\n tables: z.array(z.string()).optional(),\n excludeTables: z.array(z.string()).optional(),\n getTimestamp: z.function().optional(),\n dateFormat: z.enum(['iso', 'unix', 'date']).optional(),\n primaryKeyColumn: z.string().optional()\n})\n\n/**\n * Type inferred from TimestampsOptionsSchema\n */\nexport type TimestampsOptionsSchemaType = z.infer<typeof TimestampsOptionsSchema>\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kysera/timestamps",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Automatic timestamp management plugin for Kysely repositories",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
".": {
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
11
11
|
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./schema": {
|
|
14
|
+
"types": "./dist/schema.d.ts",
|
|
15
|
+
"import": "./dist/schema.js"
|
|
12
16
|
}
|
|
13
17
|
},
|
|
14
18
|
"files": [
|
|
@@ -27,7 +31,7 @@
|
|
|
27
31
|
"author": "Kysera Team",
|
|
28
32
|
"license": "MIT",
|
|
29
33
|
"dependencies": {
|
|
30
|
-
"@kysera/core": "0.
|
|
34
|
+
"@kysera/core": "0.8.1"
|
|
31
35
|
},
|
|
32
36
|
"devDependencies": {
|
|
33
37
|
"@types/better-sqlite3": "^7.6.13",
|
|
@@ -38,14 +42,14 @@
|
|
|
38
42
|
"tsup": "^8.5.1",
|
|
39
43
|
"typescript": "^5.9.3",
|
|
40
44
|
"vitest": "^4.0.16",
|
|
41
|
-
"zod": "^4.1
|
|
42
|
-
"@kysera/repository": "0.
|
|
45
|
+
"zod": "^4.2.1",
|
|
46
|
+
"@kysera/repository": "0.8.1"
|
|
43
47
|
},
|
|
44
48
|
"peerDependencies": {
|
|
45
49
|
"kysely": ">=0.28.8",
|
|
46
|
-
"zod": "^4.1
|
|
47
|
-
"@kysera/repository": "0.
|
|
48
|
-
"@kysera/executor": "0.
|
|
50
|
+
"zod": "^4.2.1",
|
|
51
|
+
"@kysera/repository": "0.8.1",
|
|
52
|
+
"@kysera/executor": "0.8.1"
|
|
49
53
|
},
|
|
50
54
|
"peerDependenciesMeta": {
|
|
51
55
|
"zod": {
|