@shetty4l/core 0.1.35 → 0.1.36

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.
@@ -1,10 +1,12 @@
1
1
  /**
2
2
  * Schema management for SQLite persistence.
3
3
  *
4
- * Handles table creation and additive schema migrations.
4
+ * Handles table creation and additive schema migrations for both
5
+ * singleton (@Persisted) and collection (@PersistedCollection) tables.
5
6
  */
6
7
 
7
8
  import type { Database } from "bun:sqlite";
9
+ import type { CollectionMeta } from "./collection/types";
8
10
  import { sqliteType } from "./serialization";
9
11
  import type { ClassMeta } from "./types";
10
12
 
@@ -67,3 +69,104 @@ export function migrateAdditive(db: Database, meta: ClassMeta): void {
67
69
  db.exec(`ALTER TABLE ${meta.table} ADD COLUMN updated_at TEXT`);
68
70
  }
69
71
  }
72
+
73
+ // --------------------------------------------------------------------------
74
+ // Collection schema functions
75
+ // --------------------------------------------------------------------------
76
+
77
+ /**
78
+ * Ensure a collection table exists for the given collection metadata.
79
+ *
80
+ * Creates a table with:
81
+ * - @Id field as PRIMARY KEY
82
+ * - One column per @Field (snake_case names)
83
+ * - `created_at` TEXT for tracking creation time
84
+ * - `updated_at` TEXT for tracking modifications
85
+ *
86
+ * @param db - SQLite database instance
87
+ * @param meta - Collection metadata from @PersistedCollection decorators
88
+ */
89
+ export function ensureCollectionTable(
90
+ db: Database,
91
+ meta: CollectionMeta,
92
+ ): void {
93
+ // Start with primary key column
94
+ const idSqlType = sqliteType(meta.idType);
95
+ const columns: string[] = [`${meta.idColumn} ${idSqlType} PRIMARY KEY`];
96
+
97
+ // Add all field columns
98
+ for (const field of meta.fields.values()) {
99
+ const fieldSqlType = sqliteType(field.type);
100
+ columns.push(`${field.column} ${fieldSqlType}`);
101
+ }
102
+
103
+ // Add auto-managed timestamp columns
104
+ columns.push("created_at TEXT");
105
+ columns.push("updated_at TEXT");
106
+
107
+ const sql = `CREATE TABLE IF NOT EXISTS ${meta.table} (${columns.join(", ")})`;
108
+ db.exec(sql);
109
+ }
110
+
111
+ /**
112
+ * Create indices for a collection table.
113
+ *
114
+ * Creates indices as defined in the collection metadata:
115
+ * - Index name format: {table}_idx_{col1}_{col2}_...
116
+ * - Uses CREATE INDEX IF NOT EXISTS for idempotency
117
+ *
118
+ * @param db - SQLite database instance
119
+ * @param meta - Collection metadata from @PersistedCollection decorators
120
+ */
121
+ export function ensureIndices(db: Database, meta: CollectionMeta): void {
122
+ for (const index of meta.indices) {
123
+ const indexName = `${meta.table}_idx_${index.columns.join("_")}`;
124
+ const columnList = index.columns.join(", ");
125
+ const sql = `CREATE INDEX IF NOT EXISTS ${indexName} ON ${meta.table} (${columnList})`;
126
+ db.exec(sql);
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Perform additive migration for a collection table: add any missing columns.
132
+ *
133
+ * This function:
134
+ * - Reads existing columns via PRAGMA table_info
135
+ * - Adds columns for any @Field not yet in the table
136
+ * - Ensures created_at and updated_at columns exist
137
+ * - Never drops or modifies existing columns
138
+ * - Is idempotent (safe to call multiple times)
139
+ *
140
+ * @param db - SQLite database instance
141
+ * @param meta - Collection metadata from @PersistedCollection decorators
142
+ */
143
+ export function migrateCollectionAdditive(
144
+ db: Database,
145
+ meta: CollectionMeta,
146
+ ): void {
147
+ // Get existing columns
148
+ const info = db.prepare(`PRAGMA table_info(${meta.table})`).all() as {
149
+ name: string;
150
+ }[];
151
+ const existingColumns = new Set(info.map((row) => row.name));
152
+
153
+ // Add missing field columns
154
+ for (const field of meta.fields.values()) {
155
+ if (!existingColumns.has(field.column)) {
156
+ const fieldSqlType = sqliteType(field.type);
157
+ db.exec(
158
+ `ALTER TABLE ${meta.table} ADD COLUMN ${field.column} ${fieldSqlType}`,
159
+ );
160
+ }
161
+ }
162
+
163
+ // Ensure created_at exists
164
+ if (!existingColumns.has("created_at")) {
165
+ db.exec(`ALTER TABLE ${meta.table} ADD COLUMN created_at TEXT`);
166
+ }
167
+
168
+ // Ensure updated_at exists
169
+ if (!existingColumns.has("updated_at")) {
170
+ db.exec(`ALTER TABLE ${meta.table} ADD COLUMN updated_at TEXT`);
171
+ }
172
+ }
@@ -28,3 +28,6 @@ export interface ClassMeta {
28
28
  * Keyed by constructor function, holds ClassMeta.
29
29
  */
30
30
  export const classMeta = new WeakMap<object, ClassMeta>();
31
+
32
+ // Re-export collection metadata for convenience
33
+ export { collectionMeta } from "./collection/types";