@payload-auth-link/migrate 0.6.0 → 0.7.0
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 +20 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +85 -30
- package/dist/cli.js.map +1 -1
- package/dist/doctor/plugins.d.ts.map +1 -1
- package/dist/doctor/plugins.js +6 -2
- package/dist/doctor/plugins.js.map +1 -1
- package/dist/doctor/run-doctor.d.ts.map +1 -1
- package/dist/doctor/run-doctor.js +27 -2
- package/dist/doctor/run-doctor.js.map +1 -1
- package/dist/doctor/users.d.ts +13 -1
- package/dist/doctor/users.d.ts.map +1 -1
- package/dist/doctor/users.js +17 -7
- package/dist/doctor/users.js.map +1 -1
- package/dist/exporters.d.ts +43 -5
- package/dist/exporters.d.ts.map +1 -1
- package/dist/exporters.js +88 -5
- package/dist/exporters.js.map +1 -1
- package/dist/importer/apply.d.ts.map +1 -1
- package/dist/importer/apply.js +79 -23
- package/dist/importer/apply.js.map +1 -1
- package/dist/importer/guards.d.ts +49 -0
- package/dist/importer/guards.d.ts.map +1 -0
- package/dist/importer/guards.js +175 -0
- package/dist/importer/guards.js.map +1 -0
- package/dist/importer/host-writes.d.ts +6 -2
- package/dist/importer/host-writes.d.ts.map +1 -1
- package/dist/importer/host-writes.js +9 -4
- package/dist/importer/host-writes.js.map +1 -1
- package/dist/importer/lifecycle.d.ts +59 -14
- package/dist/importer/lifecycle.d.ts.map +1 -1
- package/dist/importer/lifecycle.js +123 -20
- package/dist/importer/lifecycle.js.map +1 -1
- package/dist/importer/manifest-mac.d.ts +15 -3
- package/dist/importer/manifest-mac.d.ts.map +1 -1
- package/dist/importer/manifest-mac.js +47 -27
- package/dist/importer/manifest-mac.js.map +1 -1
- package/dist/importer/prelink.d.ts.map +1 -1
- package/dist/importer/prelink.js +11 -1
- package/dist/importer/prelink.js.map +1 -1
- package/dist/importer/run-import.d.ts +22 -5
- package/dist/importer/run-import.d.ts.map +1 -1
- package/dist/importer/run-import.js +27 -6
- package/dist/importer/run-import.js.map +1 -1
- package/dist/importer/types.d.ts +7 -4
- package/dist/importer/types.d.ts.map +1 -1
- package/dist/importer/types.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/src/cli.ts +84 -30
- package/src/doctor/plugins.ts +8 -3
- package/src/doctor/run-doctor.ts +30 -3
- package/src/doctor/users.ts +25 -7
- package/src/exporters.ts +113 -12
- package/src/importer/apply.ts +92 -22
- package/src/importer/guards.ts +223 -0
- package/src/importer/host-writes.ts +15 -4
- package/src/importer/lifecycle.ts +185 -29
- package/src/importer/manifest-mac.ts +65 -30
- package/src/importer/prelink.ts +13 -1
- package/src/importer/run-import.ts +52 -11
- package/src/importer/types.ts +7 -4
- package/src/index.ts +10 -1
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { isRecord } from '@payload-auth-link/core';
|
|
2
|
+
import { defaultTargetSlugs, } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* The guards every destructive manifest operation runs, independent of
|
|
5
|
+
* the manifest's signature:
|
|
6
|
+
*
|
|
7
|
+
* - created-row scoping: a rollback only ever deletes rows the importer
|
|
8
|
+
* itself creates — rows in the import's own target collections whose
|
|
9
|
+
* deterministic `baId` matches the source key the manifest names;
|
|
10
|
+
* - the finalize marker: `finalize` records the point of no return in
|
|
11
|
+
* the target DATABASE (Payload's key-value store), so a manifest file
|
|
12
|
+
* alone can never reopen a finalized migration;
|
|
13
|
+
* - the host roles field the app configured on `authLink`.
|
|
14
|
+
*/
|
|
15
|
+
/** The `baId` prefix the importer stamps per model (see apply.ts). */
|
|
16
|
+
const baIdPrefix = {
|
|
17
|
+
user: '',
|
|
18
|
+
account: 'acct-',
|
|
19
|
+
session: 'sess-',
|
|
20
|
+
verification: 'ver-',
|
|
21
|
+
apikey: 'key-',
|
|
22
|
+
jwks: 'jwks-',
|
|
23
|
+
organization: 'org-',
|
|
24
|
+
member: 'member-',
|
|
25
|
+
invitation: 'inv-',
|
|
26
|
+
};
|
|
27
|
+
const isTargetModel = (value) => value in baIdPrefix;
|
|
28
|
+
/**
|
|
29
|
+
* The model and deterministic `baId` a created row's `sourceKey`
|
|
30
|
+
* (`<model>:<source id>`) names, or `null` when it names none.
|
|
31
|
+
*/
|
|
32
|
+
export function expectedBaId(sourceKey) {
|
|
33
|
+
const colon = sourceKey.indexOf(':');
|
|
34
|
+
if (colon <= 0) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
const model = sourceKey.slice(0, colon);
|
|
38
|
+
const sourceId = sourceKey.slice(colon + 1);
|
|
39
|
+
if (!isTargetModel(model) || sourceId === '') {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return { model, baId: `${baIdPrefix[model]}${sourceId}` };
|
|
43
|
+
}
|
|
44
|
+
/** The target slugs with the defaults filled in. */
|
|
45
|
+
export function targetSlugsOf(overrides) {
|
|
46
|
+
return { ...defaultTargetSlugs, ...(overrides ?? {}) };
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Refuse a manifest whose created rows reach outside what the importer
|
|
50
|
+
* creates, BEFORE anything is deleted (or re-signed): an `adopt-prelink`
|
|
51
|
+
* manifest creates no rows at all, and an import's rows must sit in the
|
|
52
|
+
* import's own target collections, under the model their source key
|
|
53
|
+
* names. Signed or not — the signature proves who wrote the file, this
|
|
54
|
+
* proves the file only names the kit's own rows.
|
|
55
|
+
*/
|
|
56
|
+
export function assertCreatedRowsScoped(manifest, slugs) {
|
|
57
|
+
if (manifest.kind === 'adopt-prelink') {
|
|
58
|
+
if (manifest.created.length > 0) {
|
|
59
|
+
throw new Error(`the adopt-prelink manifest lists ${manifest.created.length} created row(s), which adopt-prelink never creates (edited manifest?); refusing to use it — nothing was deleted`);
|
|
60
|
+
}
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const bySlug = new Map();
|
|
64
|
+
for (const model of Object.keys(baIdPrefix)) {
|
|
65
|
+
bySlug.set(slugs[model], model);
|
|
66
|
+
}
|
|
67
|
+
const outside = [];
|
|
68
|
+
for (const row of manifest.created) {
|
|
69
|
+
const expected = expectedBaId(row.sourceKey);
|
|
70
|
+
const model = bySlug.get(row.collection);
|
|
71
|
+
if (model === undefined || expected === null || expected.model !== model) {
|
|
72
|
+
outside.push(`${row.collection}#${String(row.id)} (${row.sourceKey})`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (outside.length > 0) {
|
|
76
|
+
throw new Error(`the manifest lists ${outside.length} created row(s) outside the import's target collections (${[...bySlug.keys()].join(', ')}): ${outside.slice(0, 10).join(', ')}${outside.length > 10 ? ', …' : ''}. The importer only creates rows there; refusing to use the manifest — nothing was deleted. If your engine wiring renames the targets, pass the same --slugs the import ran with`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** A created row whose `baId` no longer matches its source key. */
|
|
80
|
+
export function baIdMismatch(row, doc) {
|
|
81
|
+
const expected = expectedBaId(row.sourceKey);
|
|
82
|
+
if (expected === null) {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
return !isRecord(doc) || String(doc['baId']) !== expected.baId;
|
|
86
|
+
}
|
|
87
|
+
function keyValueStore(payload) {
|
|
88
|
+
const kv = payload.kv;
|
|
89
|
+
if (isRecord(kv) &&
|
|
90
|
+
typeof kv['get'] === 'function' &&
|
|
91
|
+
typeof kv['set'] === 'function' &&
|
|
92
|
+
typeof kv['keys'] === 'function') {
|
|
93
|
+
return kv;
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
const markerPrefix = 'payload-auth-link/migrate/finalized/';
|
|
98
|
+
/** The finalize marker key of one manifest (its kind + first-apply stamp). */
|
|
99
|
+
export function finalizeMarkerKey(manifest) {
|
|
100
|
+
return `${markerPrefix}${manifest.kind ?? 'import'}/${manifest.createdAt}`;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Record the point of no return in the target database. One-way: the
|
|
104
|
+
* kit never deletes the marker. Throws when the store is unavailable —
|
|
105
|
+
* `finalize` must not report success it cannot back.
|
|
106
|
+
*/
|
|
107
|
+
export async function recordFinalizeMarker(payload, manifest, finalizedAt) {
|
|
108
|
+
const kv = keyValueStore(payload);
|
|
109
|
+
if (kv === null) {
|
|
110
|
+
throw new Error("this Payload version exposes no key-value store (payload.kv); finalize records its marker there — upgrade Payload to a release in the kit's supported band");
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
await kv.set(finalizeMarkerKey(manifest), {
|
|
114
|
+
kind: manifest.kind ?? 'import',
|
|
115
|
+
createdAt: manifest.createdAt,
|
|
116
|
+
finalizedAt,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
throw new Error(`could not record the finalize marker in Payload's key-value store (the payload-kv collection): ${error instanceof Error ? error.message : String(error)} — create its table (run your Payload migrations) and re-run finalize; the manifest was not changed`, { cause: error });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Whether the database says this migration was finalized. `anyOfKind`
|
|
125
|
+
* widens the check to every finalized manifest of the same kind — used
|
|
126
|
+
* when the manifest's own `createdAt` is not covered by a signature.
|
|
127
|
+
* Returns `'unavailable'` when the store cannot be read; the caller
|
|
128
|
+
* decides (and logs) what that means.
|
|
129
|
+
*/
|
|
130
|
+
export async function finalizeMarkerState(payload, manifest, options) {
|
|
131
|
+
const kv = keyValueStore(payload);
|
|
132
|
+
if (kv === null) {
|
|
133
|
+
return 'unavailable';
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
if ((await kv.get(finalizeMarkerKey(manifest))) !== null) {
|
|
137
|
+
return 'finalized';
|
|
138
|
+
}
|
|
139
|
+
if (options.anyOfKind) {
|
|
140
|
+
const kindPrefix = `${markerPrefix}${manifest.kind ?? 'import'}/`;
|
|
141
|
+
const keys = await kv.keys();
|
|
142
|
+
if (keys.some((key) => key.startsWith(kindPrefix))) {
|
|
143
|
+
return 'finalized';
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return 'open';
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return 'unavailable';
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* The host roles field the app configured (`authLink({ rolesField })`),
|
|
154
|
+
* read off the plugin entry in the booted config (Payload keeps each
|
|
155
|
+
* plugin's `slug` and `options` for cross-plugin discovery). Defaults to
|
|
156
|
+
* the plugin's own default, `roles`.
|
|
157
|
+
*/
|
|
158
|
+
export function hostRolesField(payload) {
|
|
159
|
+
const plugins = payload.config.plugins;
|
|
160
|
+
if (Array.isArray(plugins)) {
|
|
161
|
+
for (const plugin of plugins) {
|
|
162
|
+
if (typeof plugin !== 'function') {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
const entry = plugin;
|
|
166
|
+
if (entry.slug !== 'payload-auth-link') {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
const rolesField = isRecord(entry.options) ? entry.options['rolesField'] : undefined;
|
|
170
|
+
return typeof rolesField === 'string' && rolesField !== '' ? rolesField : 'roles';
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return 'roles';
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=guards.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guards.js","sourceRoot":"","sources":["../../src/importer/guards.ts"],"names":[],"mappings":"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"}
|
|
@@ -17,8 +17,10 @@ export declare function unsealValue(secret: string, sealed: string, entry: strin
|
|
|
17
17
|
* restored only because the importer writes them).
|
|
18
18
|
*/
|
|
19
19
|
export declare const restorableFields: ReadonlySet<string>;
|
|
20
|
+
/** {@link restorableFields} plus the app's configured roles field. */
|
|
21
|
+
export declare function restorableFieldsFor(rolesField: string): ReadonlySet<string>;
|
|
20
22
|
/** The captured prior value of one manifest entry, JSON-parsed. */
|
|
21
|
-
export declare function priorValue(secret: string, write: FieldWrite): unknown;
|
|
23
|
+
export declare function priorValue(secret: string, write: FieldWrite, restorable?: ReadonlySet<string>): unknown;
|
|
22
24
|
/** Keys of the field writes a manifest already holds (first capture wins). */
|
|
23
25
|
export declare function capturedKeys(writes: readonly FieldWrite[]): Set<string>;
|
|
24
26
|
/**
|
|
@@ -43,5 +45,7 @@ export declare function captureBefore(options: {
|
|
|
43
45
|
* `updatedAt` lands exactly as captured. Returns the number of field
|
|
44
46
|
* values restored.
|
|
45
47
|
*/
|
|
46
|
-
export declare function restoreCaptured(payload: Payload, manifest: Pick<ImportManifest, 'fieldWrites'
|
|
48
|
+
export declare function restoreCaptured(payload: Payload, manifest: Pick<ImportManifest, 'fieldWrites'>, options?: {
|
|
49
|
+
readonly rolesField?: string | undefined;
|
|
50
|
+
}): Promise<number>;
|
|
47
51
|
//# sourceMappingURL=host-writes.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host-writes.d.ts","sourceRoot":"","sources":["../../src/importer/host-writes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAyC5D;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAOlF;AAED,0EAA0E;AAC1E,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAkBjF;AAED;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAM/C,CAAA;AAEF,mEAAmE;AACnE,wBAAgB,UAAU,
|
|
1
|
+
{"version":3,"file":"host-writes.d.ts","sourceRoot":"","sources":["../../src/importer/host-writes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAyC5D;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAOlF;AAED,0EAA0E;AAC1E,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAkBjF;AAED;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAM/C,CAAA;AAEF,sEAAsE;AACtE,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC,CAE3E;AAED,mEAAmE;AACnE,wBAAgB,UAAU,CACxB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,UAAU,EACjB,UAAU,GAAE,WAAW,CAAC,MAAM,CAAoB,GACjD,OAAO,CAeT;AAED,8EAA8E;AAC9E,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,CAEvE;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE;IAC3C,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAAA;IAC5B,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;IAClC,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAC9B,QAAQ,CAAC,IAAI,EAAE,UAAU,EAAE,CAAA;CAC5B,GAAG,OAAO,CAAC;IAAE,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAAC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAAC,CAiCrF;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,IAAI,CAAC,cAAc,EAAE,aAAa,CAAC,EAC7C,OAAO,GAAE;IAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAO,GACzD,OAAO,CAAC,MAAM,CAAC,CA0DjB"}
|
|
@@ -80,9 +80,13 @@ export const restorableFields = new Set([
|
|
|
80
80
|
'linkableEmail',
|
|
81
81
|
'updatedAt',
|
|
82
82
|
]);
|
|
83
|
+
/** {@link restorableFields} plus the app's configured roles field. */
|
|
84
|
+
export function restorableFieldsFor(rolesField) {
|
|
85
|
+
return new Set([...restorableFields, rolesField]);
|
|
86
|
+
}
|
|
83
87
|
/** The captured prior value of one manifest entry, JSON-parsed. */
|
|
84
|
-
export function priorValue(secret, write) {
|
|
85
|
-
if (!
|
|
88
|
+
export function priorValue(secret, write, restorable = restorableFields) {
|
|
89
|
+
if (!restorable.has(write.field)) {
|
|
86
90
|
throw new Error(`manifest entry for '${write.collection}.${write.field}' is not a field this kit writes; refusing to restore it (edited manifest?)`);
|
|
87
91
|
}
|
|
88
92
|
if (write.sealedBefore !== undefined) {
|
|
@@ -141,12 +145,13 @@ export async function captureBefore(options) {
|
|
|
141
145
|
* `updatedAt` lands exactly as captured. Returns the number of field
|
|
142
146
|
* values restored.
|
|
143
147
|
*/
|
|
144
|
-
export async function restoreCaptured(payload, manifest) {
|
|
148
|
+
export async function restoreCaptured(payload, manifest, options = {}) {
|
|
149
|
+
const restorable = restorableFieldsFor(options.rolesField ?? 'roles');
|
|
145
150
|
const byDoc = new Map();
|
|
146
151
|
for (const write of manifest.fieldWrites) {
|
|
147
152
|
const key = `${write.collection}\u0000${String(write.id)}`;
|
|
148
153
|
const entry = byDoc.get(key) ?? { collection: write.collection, id: write.id, data: {} };
|
|
149
|
-
entry.data[write.field] = priorValue(payload.secret, write);
|
|
154
|
+
entry.data[write.field] = priorValue(payload.secret, write, restorable);
|
|
150
155
|
byDoc.set(key, entry);
|
|
151
156
|
}
|
|
152
157
|
let restored = 0;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host-writes.js","sourceRoot":"","sources":["../../src/importer/host-writes.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"host-writes.js","sourceRoot":"","sources":["../../src/importer/host-writes.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,9 +2,10 @@ import type { Payload } from 'payload';
|
|
|
2
2
|
import { type ImportManifest } from './types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Manifest lifecycle (issue #50): `verify` checks both sides, `rollback`
|
|
5
|
-
* undoes exactly what the manifest records, and `finalize`
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* undoes exactly what the manifest records, and `finalize` records the
|
|
6
|
+
* point of no return (a database marker plus the re-signed manifest) so
|
|
7
|
+
* the source tables may be dropped — by the operator, as a separate
|
|
8
|
+
* later step.
|
|
8
9
|
*/
|
|
9
10
|
/**
|
|
10
11
|
* The importer's default manifest location: the CURRENT WORKING
|
|
@@ -58,13 +59,8 @@ export declare function verifyImport(options: {
|
|
|
58
59
|
/** Injectable clock: what counts as an expired source session. */
|
|
59
60
|
readonly now?: (() => Date) | undefined;
|
|
60
61
|
}): Promise<VerifyResult>;
|
|
61
|
-
/**
|
|
62
|
-
|
|
63
|
-
* restore every captured field value. The manifest records exactly
|
|
64
|
-
* what to undo — nothing else is touched, and the source tables were
|
|
65
|
-
* never modified.
|
|
66
|
-
*/
|
|
67
|
-
export declare function rollbackImport(options: {
|
|
62
|
+
/** Options of {@link rollbackImport}. */
|
|
63
|
+
export type RollbackOptions = {
|
|
68
64
|
readonly payload: Payload;
|
|
69
65
|
readonly manifest: ImportManifest;
|
|
70
66
|
readonly log?: (message: string) => void;
|
|
@@ -76,12 +72,61 @@ export declare function rollbackImport(options: {
|
|
|
76
72
|
*/
|
|
77
73
|
readonly manifestPath?: string | undefined;
|
|
78
74
|
readonly now?: (() => Date) | undefined;
|
|
79
|
-
/**
|
|
75
|
+
/** Target slugs the import ran with (`--slugs`); defaults to the ba-* set. */
|
|
76
|
+
readonly slugs?: Readonly<Record<string, string>> | undefined;
|
|
77
|
+
/**
|
|
78
|
+
* Accept a manifest without a full signature (`--allow-unsigned-manifest`):
|
|
79
|
+
* a pre-0.6.0 manifest without a MAC, or a 0.6.0 manifest whose MAC
|
|
80
|
+
* does not cover `finalized`.
|
|
81
|
+
*/
|
|
80
82
|
readonly allowUnsignedManifest?: boolean | undefined;
|
|
81
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Let a rollback driven by an UNSIGNED (pre-0.6.0) manifest delete the
|
|
85
|
+
* rows it lists and restore plaintext (version-1) values
|
|
86
|
+
* (`--allow-unsigned-deletes`). Without it, an unsigned manifest may
|
|
87
|
+
* only restore sealed field values.
|
|
88
|
+
*/
|
|
89
|
+
readonly allowUnsignedDeletes?: boolean | undefined;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Rollback (issue #50): delete the created rows in reverse order and
|
|
93
|
+
* restore every captured field value. The manifest records exactly
|
|
94
|
+
* what to undo — nothing else is touched, and the source tables were
|
|
95
|
+
* never modified.
|
|
96
|
+
*
|
|
97
|
+
* Before anything is written, rollback refuses: a finalized manifest,
|
|
98
|
+
* or a migration the DATABASE records as finalized (the marker
|
|
99
|
+
* `finalize` writes — a manifest file alone cannot reopen it); a
|
|
100
|
+
* manifest that fails its signature; created rows outside the import's
|
|
101
|
+
* own target collections (signed or not), or whose row no longer
|
|
102
|
+
* carries the importer's deterministic `baId`; and any delete or
|
|
103
|
+
* plaintext restore driven by an unsigned manifest without
|
|
104
|
+
* `allowUnsignedDeletes`. A row already absent is fine (the goal is
|
|
105
|
+
* "absent"); a delete that FAILS (a foreign key, a hook, a permission)
|
|
106
|
+
* aborts the rollback with the rows named, before any field is
|
|
107
|
+
* restored or the manifest reset — so it can be re-run once fixed.
|
|
108
|
+
*/
|
|
109
|
+
export declare function rollbackImport(options: RollbackOptions): Promise<{
|
|
82
110
|
deleted: number;
|
|
83
111
|
restored: number;
|
|
84
112
|
}>;
|
|
85
|
-
/**
|
|
86
|
-
export
|
|
113
|
+
/** Options of {@link finalizeManifest}. */
|
|
114
|
+
export type FinalizeOptions = {
|
|
115
|
+
readonly payload: Payload;
|
|
116
|
+
readonly manifest: ImportManifest;
|
|
117
|
+
/** Where the finalized, re-signed manifest is written. */
|
|
118
|
+
readonly path: string;
|
|
119
|
+
readonly now?: (() => Date) | undefined;
|
|
120
|
+
/** Accept a manifest without a full signature (`--allow-unsigned-manifest`). */
|
|
121
|
+
readonly allowUnsignedManifest?: boolean | undefined;
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* Finalize (issue #50): the point of no return. Records a one-way
|
|
125
|
+
* marker in the target database (Payload's key-value store) that every
|
|
126
|
+
* later rollback checks independently of the file, then marks the
|
|
127
|
+
* manifest `finalized` and re-signs it — the signature covers the flag.
|
|
128
|
+
* Idempotent: finalizing a finalized manifest re-records the marker and
|
|
129
|
+
* upgrades its signature.
|
|
130
|
+
*/
|
|
131
|
+
export declare function finalizeManifest(options: FinalizeOptions): Promise<ImportManifest>;
|
|
87
132
|
//# sourceMappingURL=lifecycle.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../src/importer/lifecycle.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../src/importer/lifecycle.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { writePrivateFile } from '../private-file.js';
|
|
3
|
+
import { assertCreatedRowsScoped, baIdMismatch, finalizeMarkerState, hostRolesField, recordFinalizeMarker, targetSlugsOf, } from './guards.js';
|
|
3
4
|
import { restoreCaptured } from './host-writes.js';
|
|
4
5
|
import { authenticateManifest, signManifest } from './manifest-mac.js';
|
|
5
6
|
import { defaultTargetSlugs, resolveSourceTables, sourceModelNames, } from './types.js';
|
|
6
7
|
/**
|
|
7
8
|
* Manifest lifecycle (issue #50): `verify` checks both sides, `rollback`
|
|
8
|
-
* undoes exactly what the manifest records, and `finalize`
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* undoes exactly what the manifest records, and `finalize` records the
|
|
10
|
+
* point of no return (a database marker plus the re-signed manifest) so
|
|
11
|
+
* the source tables may be dropped — by the operator, as a separate
|
|
12
|
+
* later step.
|
|
11
13
|
*/
|
|
12
14
|
/**
|
|
13
15
|
* The importer's default manifest location: the CURRENT WORKING
|
|
@@ -201,6 +203,9 @@ export async function verifyImport(options) {
|
|
|
201
203
|
// lookup findByProviderIdentity issues on the next provider
|
|
202
204
|
// sign-in. The host docs also carry the stored roles (issue #68's
|
|
203
205
|
// users_role accounting) whenever the host declares the field.
|
|
206
|
+
// The roles field the app configured on authLink (issue #110: a
|
|
207
|
+
// hard-coded `roles` silently skipped the check on `role` layouts).
|
|
208
|
+
const rolesField = hostRolesField(payload);
|
|
204
209
|
let identityIssues = 0;
|
|
205
210
|
let roleMismatchDocs = 0;
|
|
206
211
|
let hostDocsWithRoles = 0;
|
|
@@ -238,7 +243,7 @@ export async function verifyImport(options) {
|
|
|
238
243
|
if (identities.length !== 1 || resolving.length !== 1) {
|
|
239
244
|
identityIssues += 1;
|
|
240
245
|
}
|
|
241
|
-
const roles = record[
|
|
246
|
+
const roles = record[rolesField];
|
|
242
247
|
if (Array.isArray(roles)) {
|
|
243
248
|
hostDocsWithRoles += 1;
|
|
244
249
|
const stored = new Set(roles.map((role) => String(role)));
|
|
@@ -249,11 +254,11 @@ export async function verifyImport(options) {
|
|
|
249
254
|
}
|
|
250
255
|
check('every imported user resolves through one better-auth identity', identityIssues === 0, `${identityIssues} user(s) without exactly one better-auth identity resolving to their ba-users id`);
|
|
251
256
|
if (hostDocsWithRoles > 0) {
|
|
252
|
-
check('source roles carried onto the host docs', roleMismatchDocs === 0, `${roleMismatchDocs} host doc(s) missing stored role values`);
|
|
257
|
+
check('source roles carried onto the host docs', roleMismatchDocs === 0, `${roleMismatchDocs} host doc(s) missing stored role values in '${rolesField}'`);
|
|
253
258
|
}
|
|
254
259
|
else if (sourceTables['users_role'] !== null && users.some((user) => user.roles.length > 0)) {
|
|
255
260
|
checks.push({
|
|
256
|
-
name:
|
|
261
|
+
name: `source roles carried onto the host docs (skipped: the host users carry no '${rolesField}' field)`,
|
|
257
262
|
passed: true,
|
|
258
263
|
});
|
|
259
264
|
}
|
|
@@ -391,6 +396,18 @@ export async function verifyImport(options) {
|
|
|
391
396
|
* restore every captured field value. The manifest records exactly
|
|
392
397
|
* what to undo — nothing else is touched, and the source tables were
|
|
393
398
|
* never modified.
|
|
399
|
+
*
|
|
400
|
+
* Before anything is written, rollback refuses: a finalized manifest,
|
|
401
|
+
* or a migration the DATABASE records as finalized (the marker
|
|
402
|
+
* `finalize` writes — a manifest file alone cannot reopen it); a
|
|
403
|
+
* manifest that fails its signature; created rows outside the import's
|
|
404
|
+
* own target collections (signed or not), or whose row no longer
|
|
405
|
+
* carries the importer's deterministic `baId`; and any delete or
|
|
406
|
+
* plaintext restore driven by an unsigned manifest without
|
|
407
|
+
* `allowUnsignedDeletes`. A row already absent is fine (the goal is
|
|
408
|
+
* "absent"); a delete that FAILS (a foreign key, a hook, a permission)
|
|
409
|
+
* aborts the rollback with the rows named, before any field is
|
|
410
|
+
* restored or the manifest reset — so it can be re-run once fixed.
|
|
394
411
|
*/
|
|
395
412
|
export async function rollbackImport(options) {
|
|
396
413
|
const { payload, manifest, log = () => { } } = options;
|
|
@@ -401,22 +418,80 @@ export async function rollbackImport(options) {
|
|
|
401
418
|
}
|
|
402
419
|
// Every row below is deleted, and every field restored, with
|
|
403
420
|
// overrideAccess: only a manifest this app signed may drive that.
|
|
404
|
-
authenticateManifest(payload.secret, manifest, {
|
|
405
|
-
|
|
421
|
+
const trust = authenticateManifest(payload.secret, manifest, {
|
|
422
|
+
allowUnsigned: options.allowUnsignedManifest,
|
|
423
|
+
});
|
|
424
|
+
if (trust === 'signed-legacy' && options.allowUnsignedManifest !== true) {
|
|
425
|
+
throw new Error("the manifest was signed by migrate 0.6.0, whose signature does not cover the 'finalized' flag. If you are sure this migration was never finalized and nobody has edited the manifest, re-run with --allow-unsigned-manifest; the rollback re-signs what it leaves behind");
|
|
426
|
+
}
|
|
427
|
+
// The database's own record of `finalize`, independent of the file.
|
|
428
|
+
// A manifest whose `createdAt` is not covered by a full signature is
|
|
429
|
+
// checked against every finalized migration of its kind.
|
|
430
|
+
const marker = await finalizeMarkerState(payload, manifest, { anyOfKind: trust !== 'signed' });
|
|
431
|
+
if (marker === 'finalized') {
|
|
432
|
+
throw new Error('the database records this migration as finalized; rollback is no longer possible (the manifest file does not override that)');
|
|
433
|
+
}
|
|
434
|
+
if (marker === 'unavailable') {
|
|
435
|
+
log("rollback: Payload's key-value store is unavailable, so the database finalize marker could not be checked; relying on the manifest's own flag");
|
|
436
|
+
}
|
|
437
|
+
assertCreatedRowsScoped(manifest, targetSlugsOf(options.slugs));
|
|
438
|
+
if (trust === 'unsigned' && options.allowUnsignedDeletes !== true) {
|
|
439
|
+
const plaintext = manifest.fieldWrites.filter((write) => write.before !== undefined).length;
|
|
440
|
+
if (manifest.created.length > 0 || plaintext > 0) {
|
|
441
|
+
throw new Error(`the manifest is unsigned: --allow-unsigned-manifest lets it restore sealed field values only, and it lists ${manifest.created.length} row(s) to delete and ${plaintext} plaintext value(s) to restore. Review the manifest's created rows, then re-run with --allow-unsigned-deletes as well — nothing was changed`);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
// Pre-flight every created row BEFORE the first delete: a present row
|
|
445
|
+
// must still carry the importer's deterministic baId for its source
|
|
446
|
+
// key (the imported row, not one that took its id since).
|
|
447
|
+
const present = [];
|
|
448
|
+
const foreign = [];
|
|
449
|
+
let absent = 0;
|
|
406
450
|
for (const row of [...manifest.created].reverse()) {
|
|
451
|
+
const doc = await findRow(payload, row);
|
|
452
|
+
if (doc === null) {
|
|
453
|
+
absent += 1;
|
|
454
|
+
}
|
|
455
|
+
else if (baIdMismatch(row, doc)) {
|
|
456
|
+
foreign.push(`${row.collection}#${String(row.id)} (${row.sourceKey})`);
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
present.push(row);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
if (foreign.length > 0) {
|
|
463
|
+
throw new Error(`${foreign.length} created row(s) no longer carry the baId the importer gave them, so they are not the imported rows: ${listSome(foreign, ', ')}. Refusing to delete anything`);
|
|
464
|
+
}
|
|
465
|
+
let deleted = 0;
|
|
466
|
+
const failed = [];
|
|
467
|
+
for (const row of present) {
|
|
407
468
|
try {
|
|
408
469
|
await payload.delete({ collection: row.collection, id: row.id, overrideAccess: true });
|
|
409
470
|
deleted += 1;
|
|
410
471
|
}
|
|
411
|
-
catch {
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
472
|
+
catch (error) {
|
|
473
|
+
if ((await findRow(payload, row)) === null) {
|
|
474
|
+
// Removed concurrently: the goal state is "row absent".
|
|
475
|
+
absent += 1;
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
failed.push(`${row.collection}#${String(row.id)}: ${error instanceof Error ? error.message : String(error)}`);
|
|
415
479
|
}
|
|
416
480
|
}
|
|
481
|
+
if (failed.length > 0) {
|
|
482
|
+
for (const line of failed) {
|
|
483
|
+
log(`rollback: FAILED to delete ${line}`);
|
|
484
|
+
}
|
|
485
|
+
throw new Error(`rollback deleted ${deleted} row(s) but could not delete ${failed.length}: ${listSome(failed, '; ')}. No field value was restored and the manifest was kept — fix the cause (e.g. rows that reference them) and re-run rollback`);
|
|
486
|
+
}
|
|
487
|
+
if (absent > 0) {
|
|
488
|
+
log(`rollback: ${absent} created row(s) were already absent`);
|
|
489
|
+
}
|
|
417
490
|
// Captured values go back through the adapter, one write per
|
|
418
491
|
// document, `updatedAt` included — see host-writes.ts for why.
|
|
419
|
-
const restored = await restoreCaptured(payload, manifest
|
|
492
|
+
const restored = await restoreCaptured(payload, manifest, {
|
|
493
|
+
rolesField: hostRolesField(payload),
|
|
494
|
+
});
|
|
420
495
|
if (options.manifestPath !== undefined) {
|
|
421
496
|
const reset = signManifest(payload.secret, {
|
|
422
497
|
...manifest,
|
|
@@ -430,13 +505,41 @@ export async function rollbackImport(options) {
|
|
|
430
505
|
log(`rollback: ${deleted} row(s) deleted, ${restored} field value(s) restored`);
|
|
431
506
|
return { deleted, restored };
|
|
432
507
|
}
|
|
433
|
-
/**
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
508
|
+
/** The created row as stored, or `null` when it does not exist. */
|
|
509
|
+
async function findRow(payload, row) {
|
|
510
|
+
const doc = await payload.findByID({
|
|
511
|
+
collection: row.collection,
|
|
512
|
+
id: row.id,
|
|
513
|
+
depth: 0,
|
|
514
|
+
overrideAccess: true,
|
|
515
|
+
disableErrors: true,
|
|
516
|
+
});
|
|
517
|
+
return doc ?? null;
|
|
518
|
+
}
|
|
519
|
+
function listSome(items, separator) {
|
|
520
|
+
return `${items.slice(0, 10).join(separator)}${items.length > 10 ? `${separator}…` : ''}`;
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Finalize (issue #50): the point of no return. Records a one-way
|
|
524
|
+
* marker in the target database (Payload's key-value store) that every
|
|
525
|
+
* later rollback checks independently of the file, then marks the
|
|
526
|
+
* manifest `finalized` and re-signs it — the signature covers the flag.
|
|
527
|
+
* Idempotent: finalizing a finalized manifest re-records the marker and
|
|
528
|
+
* upgrades its signature.
|
|
529
|
+
*/
|
|
530
|
+
export async function finalizeManifest(options) {
|
|
531
|
+
const { payload, manifest } = options;
|
|
532
|
+
authenticateManifest(payload.secret, manifest, { allowUnsigned: options.allowUnsignedManifest });
|
|
533
|
+
const finalizedAt = manifest.finalizedAt ?? (options.now ?? (() => new Date()))().toISOString();
|
|
534
|
+
// The database marker first: a finalize that cannot record it fails
|
|
535
|
+
// with the manifest unchanged.
|
|
536
|
+
await recordFinalizeMarker(payload, manifest, finalizedAt);
|
|
537
|
+
const finalized = signManifest(payload.secret, {
|
|
538
|
+
...manifest,
|
|
539
|
+
finalized: true,
|
|
540
|
+
finalizedAt,
|
|
541
|
+
});
|
|
542
|
+
writeManifest(finalized, options.path);
|
|
440
543
|
return finalized;
|
|
441
544
|
}
|
|
442
545
|
//# sourceMappingURL=lifecycle.js.map
|