@famgia/omnify-core 0.0.8 → 0.0.9
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/index.cjs +301 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +210 -1
- package/dist/index.d.ts +210 -1
- package/dist/index.js +299 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -33,6 +33,7 @@ __export(index_exports, {
|
|
|
33
33
|
Omnify: () => Omnify,
|
|
34
34
|
OmnifyError: () => OmnifyError,
|
|
35
35
|
PluginManager: () => PluginManager,
|
|
36
|
+
VersionStore: () => VersionStore,
|
|
36
37
|
atlasError: () => atlasError,
|
|
37
38
|
atlasNotFoundError: () => atlasNotFoundError,
|
|
38
39
|
circularReferenceError: () => circularReferenceError,
|
|
@@ -40,6 +41,7 @@ __export(index_exports, {
|
|
|
40
41
|
configNotFoundError: () => configNotFoundError,
|
|
41
42
|
createOmnify: () => createOmnify,
|
|
42
43
|
createPluginManager: () => createPluginManager,
|
|
44
|
+
createVersionStore: () => createVersionStore,
|
|
43
45
|
duplicateSchemaError: () => duplicateSchemaError,
|
|
44
46
|
err: () => import_omnify_types.err,
|
|
45
47
|
expandProperty: () => expandProperty,
|
|
@@ -3042,11 +3044,309 @@ var Omnify = class {
|
|
|
3042
3044
|
function createOmnify(options) {
|
|
3043
3045
|
return new Omnify(options);
|
|
3044
3046
|
}
|
|
3047
|
+
|
|
3048
|
+
// src/history/version-store.ts
|
|
3049
|
+
var import_promises = require("fs/promises");
|
|
3050
|
+
var import_path = require("path");
|
|
3051
|
+
var import_js_yaml2 = __toESM(require("js-yaml"), 1);
|
|
3052
|
+
var OMNIFY_DIR = ".omnify";
|
|
3053
|
+
var VERSIONS_DIR = "versions";
|
|
3054
|
+
var CURRENT_FILE = "current.lock";
|
|
3055
|
+
var VERSION_FILE_PATTERN = /^(\d{4})(?:_(.+))?\.lock$/;
|
|
3056
|
+
function formatVersionNumber(version) {
|
|
3057
|
+
return version.toString().padStart(4, "0");
|
|
3058
|
+
}
|
|
3059
|
+
function parseVersionFilename(filename) {
|
|
3060
|
+
const match = filename.match(VERSION_FILE_PATTERN);
|
|
3061
|
+
if (!match || !match[1]) return null;
|
|
3062
|
+
const result = {
|
|
3063
|
+
version: parseInt(match[1], 10)
|
|
3064
|
+
};
|
|
3065
|
+
if (match[2] !== void 0) {
|
|
3066
|
+
result.name = match[2];
|
|
3067
|
+
}
|
|
3068
|
+
return result;
|
|
3069
|
+
}
|
|
3070
|
+
function generateVersionFilename(version, migration) {
|
|
3071
|
+
const versionStr = formatVersionNumber(version);
|
|
3072
|
+
if (migration) {
|
|
3073
|
+
const name = migration.replace(/^\d+_/, "").replace(/\.sql$/, "");
|
|
3074
|
+
return `${versionStr}_${name}.lock`;
|
|
3075
|
+
}
|
|
3076
|
+
return `${versionStr}.lock`;
|
|
3077
|
+
}
|
|
3078
|
+
async function dirExists(path2) {
|
|
3079
|
+
try {
|
|
3080
|
+
await (0, import_promises.access)(path2);
|
|
3081
|
+
return true;
|
|
3082
|
+
} catch {
|
|
3083
|
+
return false;
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3086
|
+
var VersionStore = class {
|
|
3087
|
+
omnifyDir;
|
|
3088
|
+
versionsDir;
|
|
3089
|
+
maxVersions;
|
|
3090
|
+
constructor(config) {
|
|
3091
|
+
this.omnifyDir = (0, import_path.join)(config.baseDir, OMNIFY_DIR);
|
|
3092
|
+
this.versionsDir = (0, import_path.join)(this.omnifyDir, VERSIONS_DIR);
|
|
3093
|
+
this.maxVersions = config.maxVersions ?? 0;
|
|
3094
|
+
}
|
|
3095
|
+
/**
|
|
3096
|
+
* Initialize the version store directory structure.
|
|
3097
|
+
*/
|
|
3098
|
+
async initialize() {
|
|
3099
|
+
await (0, import_promises.mkdir)(this.versionsDir, { recursive: true });
|
|
3100
|
+
}
|
|
3101
|
+
/**
|
|
3102
|
+
* Get the path to the versions directory.
|
|
3103
|
+
*/
|
|
3104
|
+
getVersionsDir() {
|
|
3105
|
+
return this.versionsDir;
|
|
3106
|
+
}
|
|
3107
|
+
/**
|
|
3108
|
+
* Get the path to a specific version file.
|
|
3109
|
+
*/
|
|
3110
|
+
getVersionPath(version, migration) {
|
|
3111
|
+
const filename = generateVersionFilename(version, migration);
|
|
3112
|
+
return (0, import_path.join)(this.versionsDir, filename);
|
|
3113
|
+
}
|
|
3114
|
+
/**
|
|
3115
|
+
* List all available versions.
|
|
3116
|
+
*/
|
|
3117
|
+
async listVersions() {
|
|
3118
|
+
if (!await dirExists(this.versionsDir)) {
|
|
3119
|
+
return [];
|
|
3120
|
+
}
|
|
3121
|
+
const files = await (0, import_promises.readdir)(this.versionsDir);
|
|
3122
|
+
const versions = [];
|
|
3123
|
+
for (const file of files) {
|
|
3124
|
+
const parsed = parseVersionFilename(file);
|
|
3125
|
+
if (!parsed) continue;
|
|
3126
|
+
try {
|
|
3127
|
+
const filePath = (0, import_path.join)(this.versionsDir, file);
|
|
3128
|
+
const content = await (0, import_promises.readFile)(filePath, "utf-8");
|
|
3129
|
+
const versionFile = import_js_yaml2.default.load(content);
|
|
3130
|
+
const summary = {
|
|
3131
|
+
version: versionFile.version,
|
|
3132
|
+
timestamp: versionFile.timestamp,
|
|
3133
|
+
...versionFile.migration !== void 0 && { migration: versionFile.migration },
|
|
3134
|
+
...versionFile.description !== void 0 && { description: versionFile.description },
|
|
3135
|
+
schemaCount: Object.keys(versionFile.snapshot).length,
|
|
3136
|
+
changeCount: versionFile.changes.length
|
|
3137
|
+
};
|
|
3138
|
+
versions.push(summary);
|
|
3139
|
+
} catch {
|
|
3140
|
+
}
|
|
3141
|
+
}
|
|
3142
|
+
return versions.sort((a, b) => a.version - b.version);
|
|
3143
|
+
}
|
|
3144
|
+
/**
|
|
3145
|
+
* Get the latest version number.
|
|
3146
|
+
*/
|
|
3147
|
+
async getLatestVersion() {
|
|
3148
|
+
const versions = await this.listVersions();
|
|
3149
|
+
if (versions.length === 0) return 0;
|
|
3150
|
+
const lastVersion = versions[versions.length - 1];
|
|
3151
|
+
return lastVersion?.version ?? 0;
|
|
3152
|
+
}
|
|
3153
|
+
/**
|
|
3154
|
+
* Read a specific version file.
|
|
3155
|
+
*/
|
|
3156
|
+
async readVersion(version) {
|
|
3157
|
+
if (!await dirExists(this.versionsDir)) {
|
|
3158
|
+
return null;
|
|
3159
|
+
}
|
|
3160
|
+
const files = await (0, import_promises.readdir)(this.versionsDir);
|
|
3161
|
+
for (const file of files) {
|
|
3162
|
+
const parsed = parseVersionFilename(file);
|
|
3163
|
+
if (parsed?.version === version) {
|
|
3164
|
+
const filePath = (0, import_path.join)(this.versionsDir, file);
|
|
3165
|
+
const content = await (0, import_promises.readFile)(filePath, "utf-8");
|
|
3166
|
+
return import_js_yaml2.default.load(content);
|
|
3167
|
+
}
|
|
3168
|
+
}
|
|
3169
|
+
return null;
|
|
3170
|
+
}
|
|
3171
|
+
/**
|
|
3172
|
+
* Read the latest version file.
|
|
3173
|
+
*/
|
|
3174
|
+
async readLatestVersion() {
|
|
3175
|
+
const latestVersion = await this.getLatestVersion();
|
|
3176
|
+
if (latestVersion === 0) return null;
|
|
3177
|
+
return this.readVersion(latestVersion);
|
|
3178
|
+
}
|
|
3179
|
+
/**
|
|
3180
|
+
* Create a new version.
|
|
3181
|
+
*/
|
|
3182
|
+
async createVersion(snapshot, changes, options) {
|
|
3183
|
+
await this.initialize();
|
|
3184
|
+
const latestVersion = await this.getLatestVersion();
|
|
3185
|
+
const newVersion = latestVersion + 1;
|
|
3186
|
+
const versionFile = {
|
|
3187
|
+
version: newVersion,
|
|
3188
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3189
|
+
driver: options.driver,
|
|
3190
|
+
...options.migration !== void 0 && { migration: options.migration },
|
|
3191
|
+
...options.description !== void 0 && { description: options.description },
|
|
3192
|
+
changes,
|
|
3193
|
+
snapshot
|
|
3194
|
+
};
|
|
3195
|
+
const filePath = this.getVersionPath(newVersion, options.migration);
|
|
3196
|
+
const content = import_js_yaml2.default.dump(versionFile, {
|
|
3197
|
+
lineWidth: -1,
|
|
3198
|
+
// Disable line wrapping
|
|
3199
|
+
noRefs: true,
|
|
3200
|
+
quotingType: '"'
|
|
3201
|
+
});
|
|
3202
|
+
await (0, import_promises.writeFile)(filePath, content, "utf-8");
|
|
3203
|
+
await this.updateCurrentLink(versionFile);
|
|
3204
|
+
if (this.maxVersions > 0) {
|
|
3205
|
+
await this.cleanupOldVersions();
|
|
3206
|
+
}
|
|
3207
|
+
return versionFile;
|
|
3208
|
+
}
|
|
3209
|
+
/**
|
|
3210
|
+
* Update the current.lock file to point to latest version.
|
|
3211
|
+
*/
|
|
3212
|
+
async updateCurrentLink(versionFile) {
|
|
3213
|
+
const currentPath = (0, import_path.join)(this.omnifyDir, CURRENT_FILE);
|
|
3214
|
+
const content = import_js_yaml2.default.dump(versionFile, {
|
|
3215
|
+
lineWidth: -1,
|
|
3216
|
+
noRefs: true,
|
|
3217
|
+
quotingType: '"'
|
|
3218
|
+
});
|
|
3219
|
+
await (0, import_promises.writeFile)(currentPath, content, "utf-8");
|
|
3220
|
+
}
|
|
3221
|
+
/**
|
|
3222
|
+
* Cleanup old versions beyond maxVersions limit.
|
|
3223
|
+
*/
|
|
3224
|
+
async cleanupOldVersions() {
|
|
3225
|
+
const versions = await this.listVersions();
|
|
3226
|
+
if (versions.length <= this.maxVersions) return;
|
|
3227
|
+
const toDelete = versions.slice(0, versions.length - this.maxVersions);
|
|
3228
|
+
for (const v of toDelete) {
|
|
3229
|
+
const files = await (0, import_promises.readdir)(this.versionsDir);
|
|
3230
|
+
for (const file of files) {
|
|
3231
|
+
const parsed = parseVersionFilename(file);
|
|
3232
|
+
if (parsed?.version === v.version) {
|
|
3233
|
+
await (0, import_promises.rm)((0, import_path.join)(this.versionsDir, file));
|
|
3234
|
+
break;
|
|
3235
|
+
}
|
|
3236
|
+
}
|
|
3237
|
+
}
|
|
3238
|
+
}
|
|
3239
|
+
/**
|
|
3240
|
+
* Get diff between two versions.
|
|
3241
|
+
*/
|
|
3242
|
+
async diffVersions(fromVersion, toVersion) {
|
|
3243
|
+
const fromFile = await this.readVersion(fromVersion);
|
|
3244
|
+
const toFile = await this.readVersion(toVersion);
|
|
3245
|
+
if (!fromFile || !toFile) return null;
|
|
3246
|
+
const allChanges = [];
|
|
3247
|
+
if (fromVersion < toVersion) {
|
|
3248
|
+
for (let v = fromVersion + 1; v <= toVersion; v++) {
|
|
3249
|
+
const vFile = await this.readVersion(v);
|
|
3250
|
+
if (vFile) {
|
|
3251
|
+
allChanges.push(...vFile.changes);
|
|
3252
|
+
}
|
|
3253
|
+
}
|
|
3254
|
+
} else {
|
|
3255
|
+
const changes = this.computeSnapshotDiff(fromFile.snapshot, toFile.snapshot);
|
|
3256
|
+
allChanges.push(...changes);
|
|
3257
|
+
}
|
|
3258
|
+
return {
|
|
3259
|
+
fromVersion,
|
|
3260
|
+
toVersion,
|
|
3261
|
+
changes: allChanges
|
|
3262
|
+
};
|
|
3263
|
+
}
|
|
3264
|
+
/**
|
|
3265
|
+
* Compute changes between two snapshots.
|
|
3266
|
+
*/
|
|
3267
|
+
computeSnapshotDiff(from, to) {
|
|
3268
|
+
const changes = [];
|
|
3269
|
+
const fromNames = new Set(Object.keys(from));
|
|
3270
|
+
const toNames = new Set(Object.keys(to));
|
|
3271
|
+
for (const name of toNames) {
|
|
3272
|
+
if (!fromNames.has(name)) {
|
|
3273
|
+
changes.push({ action: "schema_added", schema: name });
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
for (const name of fromNames) {
|
|
3277
|
+
if (!toNames.has(name)) {
|
|
3278
|
+
changes.push({ action: "schema_removed", schema: name });
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3281
|
+
for (const name of fromNames) {
|
|
3282
|
+
if (!toNames.has(name)) continue;
|
|
3283
|
+
const fromSchema = from[name];
|
|
3284
|
+
const toSchema = to[name];
|
|
3285
|
+
if (!fromSchema || !toSchema) continue;
|
|
3286
|
+
const fromProps = fromSchema.properties ?? {};
|
|
3287
|
+
const toProps = toSchema.properties ?? {};
|
|
3288
|
+
const fromPropNames = new Set(Object.keys(fromProps));
|
|
3289
|
+
const toPropNames = new Set(Object.keys(toProps));
|
|
3290
|
+
for (const prop of toPropNames) {
|
|
3291
|
+
if (!fromPropNames.has(prop)) {
|
|
3292
|
+
changes.push({
|
|
3293
|
+
action: "property_added",
|
|
3294
|
+
schema: name,
|
|
3295
|
+
property: prop,
|
|
3296
|
+
to: toProps[prop]
|
|
3297
|
+
});
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
for (const prop of fromPropNames) {
|
|
3301
|
+
if (!toPropNames.has(prop)) {
|
|
3302
|
+
changes.push({
|
|
3303
|
+
action: "property_removed",
|
|
3304
|
+
schema: name,
|
|
3305
|
+
property: prop,
|
|
3306
|
+
from: fromProps[prop]
|
|
3307
|
+
});
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
for (const prop of fromPropNames) {
|
|
3311
|
+
if (!toPropNames.has(prop)) continue;
|
|
3312
|
+
if (JSON.stringify(fromProps[prop]) !== JSON.stringify(toProps[prop])) {
|
|
3313
|
+
changes.push({
|
|
3314
|
+
action: "property_modified",
|
|
3315
|
+
schema: name,
|
|
3316
|
+
property: prop,
|
|
3317
|
+
from: fromProps[prop],
|
|
3318
|
+
to: toProps[prop]
|
|
3319
|
+
});
|
|
3320
|
+
}
|
|
3321
|
+
}
|
|
3322
|
+
if (JSON.stringify(fromSchema.options) !== JSON.stringify(toSchema.options)) {
|
|
3323
|
+
changes.push({
|
|
3324
|
+
action: "option_changed",
|
|
3325
|
+
schema: name,
|
|
3326
|
+
from: fromSchema.options,
|
|
3327
|
+
to: toSchema.options
|
|
3328
|
+
});
|
|
3329
|
+
}
|
|
3330
|
+
}
|
|
3331
|
+
return changes;
|
|
3332
|
+
}
|
|
3333
|
+
/**
|
|
3334
|
+
* Get snapshot at a specific version (for rollback).
|
|
3335
|
+
*/
|
|
3336
|
+
async getSnapshotAt(version) {
|
|
3337
|
+
const versionFile = await this.readVersion(version);
|
|
3338
|
+
return versionFile?.snapshot ?? null;
|
|
3339
|
+
}
|
|
3340
|
+
};
|
|
3341
|
+
function createVersionStore(config) {
|
|
3342
|
+
return new VersionStore(config);
|
|
3343
|
+
}
|
|
3045
3344
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3046
3345
|
0 && (module.exports = {
|
|
3047
3346
|
Omnify,
|
|
3048
3347
|
OmnifyError,
|
|
3049
3348
|
PluginManager,
|
|
3349
|
+
VersionStore,
|
|
3050
3350
|
atlasError,
|
|
3051
3351
|
atlasNotFoundError,
|
|
3052
3352
|
circularReferenceError,
|
|
@@ -3054,6 +3354,7 @@ function createOmnify(options) {
|
|
|
3054
3354
|
configNotFoundError,
|
|
3055
3355
|
createOmnify,
|
|
3056
3356
|
createPluginManager,
|
|
3357
|
+
createVersionStore,
|
|
3057
3358
|
duplicateSchemaError,
|
|
3058
3359
|
err,
|
|
3059
3360
|
expandProperty,
|