edinburgh 0.4.4 → 0.4.5
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 +58 -101
- package/build/src/migrate-cli.d.ts +1 -16
- package/build/src/migrate-cli.js +56 -42
- package/build/src/migrate-cli.js.map +1 -1
- package/build/src/migrate.d.ts +15 -11
- package/build/src/migrate.js +53 -39
- package/build/src/migrate.js.map +1 -1
- package/build/src/models.d.ts +1 -1
- package/build/src/models.js +2 -2
- package/build/src/models.js.map +1 -1
- package/package.json +6 -4
- package/skill/BaseIndex.md +16 -0
- package/skill/BaseIndex_batchProcess.md +10 -0
- package/skill/BaseIndex_find.md +7 -0
- package/skill/DatabaseError.md +9 -0
- package/skill/Model.md +22 -0
- package/skill/Model_delete.md +14 -0
- package/skill/Model_findAll.md +12 -0
- package/skill/Model_getPrimaryKeyHash.md +5 -0
- package/skill/Model_isValid.md +14 -0
- package/skill/Model_migrate.md +34 -0
- package/skill/Model_preCommit.md +28 -0
- package/skill/Model_preventPersist.md +15 -0
- package/skill/Model_replaceInto.md +16 -0
- package/skill/Model_validate.md +21 -0
- package/skill/PrimaryIndex.md +8 -0
- package/skill/PrimaryIndex_get.md +17 -0
- package/skill/PrimaryIndex_getLazy.md +13 -0
- package/skill/SKILL.md +90 -720
- package/skill/SecondaryIndex.md +9 -0
- package/skill/UniqueIndex.md +9 -0
- package/skill/UniqueIndex_get.md +17 -0
- package/skill/array.md +23 -0
- package/skill/dump.md +8 -0
- package/skill/field.md +29 -0
- package/skill/index.md +32 -0
- package/skill/init.md +17 -0
- package/skill/link.md +27 -0
- package/skill/literal.md +22 -0
- package/skill/opt.md +22 -0
- package/skill/or.md +22 -0
- package/skill/primary.md +26 -0
- package/skill/record.md +21 -0
- package/skill/registerModel.md +26 -0
- package/skill/runMigration.md +10 -0
- package/skill/set.md +23 -0
- package/skill/setMaxRetryCount.md +10 -0
- package/skill/setOnSaveCallback.md +12 -0
- package/skill/transact.md +49 -0
- package/skill/unique.md +32 -0
- package/src/migrate-cli.ts +44 -46
- package/src/migrate.ts +64 -46
- package/src/models.ts +2 -2
package/src/migrate.ts
CHANGED
|
@@ -11,12 +11,14 @@ const INDEX_ID_PREFIX = -2;
|
|
|
11
11
|
export interface MigrationOptions {
|
|
12
12
|
/** Limit migration to specific table names. */
|
|
13
13
|
tables?: string[];
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
|
|
14
|
+
/** Populate secondary indexes for rows at old schema versions (default: true). */
|
|
15
|
+
populateSecondaries?: boolean;
|
|
16
|
+
/** Convert old primary indices when primary key fields changed (default: true). */
|
|
17
|
+
migratePrimaries?: boolean;
|
|
18
|
+
/** Rewrite all row data to the latest schema version (default: false). */
|
|
19
|
+
rewriteData?: boolean;
|
|
20
|
+
/** Delete orphaned secondary/unique index entries (default: true). */
|
|
21
|
+
removeOrphans?: boolean;
|
|
20
22
|
/** Progress callback. */
|
|
21
23
|
onProgress?: (info: ProgressInfo) => void;
|
|
22
24
|
}
|
|
@@ -29,14 +31,16 @@ export interface ProgressInfo {
|
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
export interface MigrationResult {
|
|
32
|
-
/** Per-table
|
|
34
|
+
/** Per-table counts of secondary index entries populated. */
|
|
33
35
|
secondaries: Record<string, number>;
|
|
34
|
-
/** Per-table
|
|
36
|
+
/** Per-table counts of old primary rows migrated. */
|
|
35
37
|
primaries: Record<string, number>;
|
|
36
38
|
/** Per-table conversion failure counts by reason. */
|
|
37
39
|
conversionFailures: Record<string, Record<string, number>>;
|
|
40
|
+
/** Per-table counts of rows rewritten to latest version. */
|
|
41
|
+
rewritten: Record<string, number>;
|
|
38
42
|
/** Number of orphaned index entries deleted. */
|
|
39
|
-
|
|
43
|
+
orphans: number;
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
interface IndexDef {
|
|
@@ -91,23 +95,25 @@ async function forEachRow(
|
|
|
91
95
|
}
|
|
92
96
|
|
|
93
97
|
/**
|
|
94
|
-
* Run database migration:
|
|
95
|
-
* convert old primary indices, and clean up orphaned
|
|
98
|
+
* Run database migration: populate secondary indexes for old-version rows,
|
|
99
|
+
* convert old primary indices, rewrite row data, and clean up orphaned indices.
|
|
96
100
|
*/
|
|
97
101
|
export async function runMigration(options: MigrationOptions = {}): Promise<MigrationResult> {
|
|
98
102
|
// Ensure any pending model/index inits are completed before building index maps
|
|
99
103
|
await transact(() => {});
|
|
100
104
|
|
|
101
|
-
const
|
|
102
|
-
const
|
|
103
|
-
const
|
|
105
|
+
const populateSecondaries = options.populateSecondaries ?? true;
|
|
106
|
+
const migratePrimaries = options.migratePrimaries ?? true;
|
|
107
|
+
const rewriteData = options.rewriteData ?? false;
|
|
108
|
+
const removeOrphans = options.removeOrphans ?? true;
|
|
104
109
|
const onProgress = options.onProgress;
|
|
105
110
|
|
|
106
111
|
const result: MigrationResult = {
|
|
107
112
|
secondaries: {},
|
|
108
113
|
primaries: {},
|
|
109
114
|
conversionFailures: {},
|
|
110
|
-
|
|
115
|
+
rewritten: {},
|
|
116
|
+
orphans: 0,
|
|
111
117
|
};
|
|
112
118
|
|
|
113
119
|
// Build maps of known index IDs
|
|
@@ -147,10 +153,11 @@ export async function runMigration(options: MigrationOptions = {}): Promise<Migr
|
|
|
147
153
|
allIndexDefs.push({ id, tableName, typeName, fieldNames, fieldTypes });
|
|
148
154
|
});
|
|
149
155
|
|
|
150
|
-
// Phase 1:
|
|
151
|
-
if (
|
|
156
|
+
// Phase 1: Populate secondary indexes and/or rewrite row data
|
|
157
|
+
if (populateSecondaries || rewriteData) {
|
|
152
158
|
for (const [indexId, { model, primary }] of primaryByIndexId) {
|
|
153
|
-
let
|
|
159
|
+
let secondaryCount = 0;
|
|
160
|
+
let rewrittenCount = 0;
|
|
154
161
|
const migrateFn = (model as any).migrate as ((record: Record<string, any>) => void) | undefined;
|
|
155
162
|
const secondaries = model._secondaries || [];
|
|
156
163
|
|
|
@@ -176,45 +183,56 @@ export async function runMigration(options: MigrationOptions = {}): Promise<Migr
|
|
|
176
183
|
const preMigrate = migrateFn ? structuredClone(record) : undefined;
|
|
177
184
|
if (migrateFn) migrateFn(record);
|
|
178
185
|
|
|
179
|
-
//
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if (
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
sec._write(txn, keyBuf, record as any);
|
|
193
|
-
upgraded++;
|
|
194
|
-
}
|
|
195
|
-
} else {
|
|
196
|
-
// Existing secondary, update if migrate changed any of its fields
|
|
197
|
-
for (const [field, type] of sec._fieldTypes.entries()) {
|
|
198
|
-
if (!type.equals(preMigrate[field], record[field])) {
|
|
186
|
+
// Populate/update secondary indexes
|
|
187
|
+
if (populateSecondaries) {
|
|
188
|
+
for (const sec of secondaries) {
|
|
189
|
+
if (!versionInfo.secondaryKeys.has(sec._signature!)) {
|
|
190
|
+
// New secondary, write entry
|
|
191
|
+
sec._write(txn, keyBuf, record as any);
|
|
192
|
+
secondaryCount++;
|
|
193
|
+
} else if (preMigrate) {
|
|
194
|
+
if (sec._computeFn) {
|
|
195
|
+
// Computed indexes: compare serialized keys to avoid unnecessary re-indexing
|
|
196
|
+
const oldKeyBytes = sec._serializeKeyFields(preMigrate).toUint8Array();
|
|
197
|
+
const newKeyBytes = sec._serializeKeyFields(record).toUint8Array();
|
|
198
|
+
if (!bytesEqual(oldKeyBytes, newKeyBytes)) {
|
|
199
199
|
sec._delete(txn, keyBuf, preMigrate as any);
|
|
200
200
|
sec._write(txn, keyBuf, record as any);
|
|
201
|
-
|
|
202
|
-
|
|
201
|
+
secondaryCount++;
|
|
202
|
+
}
|
|
203
|
+
} else {
|
|
204
|
+
// Existing secondary, update if migrate changed any of its fields
|
|
205
|
+
for (const [field, type] of sec._fieldTypes.entries()) {
|
|
206
|
+
if (!type.equals(preMigrate[field], record[field])) {
|
|
207
|
+
sec._delete(txn, keyBuf, preMigrate as any);
|
|
208
|
+
sec._write(txn, keyBuf, record as any);
|
|
209
|
+
secondaryCount++;
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
203
212
|
}
|
|
204
213
|
}
|
|
205
214
|
}
|
|
206
215
|
}
|
|
207
216
|
}
|
|
208
217
|
|
|
218
|
+
// Rewrite primary row data to current version
|
|
219
|
+
if (rewriteData) {
|
|
220
|
+
primary._write(txn, keyBuf, record);
|
|
221
|
+
rewrittenCount++;
|
|
222
|
+
}
|
|
209
223
|
});
|
|
210
224
|
|
|
211
|
-
onProgress?.({ phase: 'secondaries', processed:
|
|
212
|
-
if (
|
|
225
|
+
onProgress?.({ phase: 'secondaries', processed: secondaryCount, table: model.tableName });
|
|
226
|
+
if (secondaryCount > 0) result.secondaries[model.tableName] = secondaryCount;
|
|
227
|
+
if (rewrittenCount > 0) {
|
|
228
|
+
onProgress?.({ phase: 'rewritten', processed: rewrittenCount, table: model.tableName });
|
|
229
|
+
result.rewritten[model.tableName] = rewrittenCount;
|
|
230
|
+
}
|
|
213
231
|
}
|
|
214
232
|
}
|
|
215
233
|
|
|
216
234
|
// Phase 2: Convert old primary indices with known table names
|
|
217
|
-
if (
|
|
235
|
+
if (migratePrimaries) {
|
|
218
236
|
for (const oldDef of allIndexDefs) {
|
|
219
237
|
if (oldDef.typeName !== 'primary') continue;
|
|
220
238
|
if (knownIndexIds.has(oldDef.id)) continue; // Known index, skip
|
|
@@ -266,14 +284,14 @@ export async function runMigration(options: MigrationOptions = {}): Promise<Migr
|
|
|
266
284
|
}
|
|
267
285
|
|
|
268
286
|
// Phase 3: Delete orphaned secondary/unique index entries
|
|
269
|
-
if (
|
|
287
|
+
if (removeOrphans) {
|
|
270
288
|
for (const def of allIndexDefs) {
|
|
271
289
|
if (knownIndexIds.has(def.id) || def.typeName === 'primary') continue;
|
|
272
290
|
await forEachRow(def.id, (txn, keyBuf) => {
|
|
273
291
|
dbDel(txn.id, keyBuf);
|
|
274
|
-
result.
|
|
292
|
+
result.orphans++;
|
|
275
293
|
});
|
|
276
|
-
onProgress?.({ phase: '
|
|
294
|
+
onProgress?.({ phase: 'orphans', processed: result.orphans });
|
|
277
295
|
}
|
|
278
296
|
}
|
|
279
297
|
|
package/src/models.ts
CHANGED
|
@@ -166,7 +166,7 @@ export interface Model<SUB> {
|
|
|
166
166
|
*
|
|
167
167
|
* Models represent database entities with typed fields, automatic serialization,
|
|
168
168
|
* change tracking, and relationship management. All model classes should extend
|
|
169
|
-
* this base class and be decorated with `@registerModel`.
|
|
169
|
+
* this base class and be decorated with `@E.registerModel`.
|
|
170
170
|
*
|
|
171
171
|
* ### Schema Evolution
|
|
172
172
|
*
|
|
@@ -283,7 +283,7 @@ export abstract class Model<SUB> {
|
|
|
283
283
|
// This constructor will only be called once, from `initModels`. All other instances will
|
|
284
284
|
// be created by the 'fake' constructor. The typing for `initial` *is* important though.
|
|
285
285
|
if (initial as any === INIT_INSTANCE_SYMBOL) return;
|
|
286
|
-
throw new DatabaseError("The model needs a @registerModel decorator", 'INIT_ERROR');
|
|
286
|
+
throw new DatabaseError("The model needs a @E.registerModel decorator", 'INIT_ERROR');
|
|
287
287
|
}
|
|
288
288
|
|
|
289
289
|
/**
|