@payload-auth-link/migrate 0.5.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 +88 -27
- 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 +2 -0
- package/dist/importer/apply.d.ts.map +1 -1
- package/dist/importer/apply.js +88 -24
- 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 +60 -13
- package/dist/importer/lifecycle.d.ts.map +1 -1
- package/dist/importer/lifecycle.js +130 -24
- package/dist/importer/lifecycle.js.map +1 -1
- package/dist/importer/manifest-mac.d.ts +27 -0
- package/dist/importer/manifest-mac.d.ts.map +1 -0
- package/dist/importer/manifest-mac.js +93 -0
- package/dist/importer/manifest-mac.js.map +1 -0
- package/dist/importer/prelink.d.ts +2 -0
- package/dist/importer/prelink.d.ts.map +1 -1
- package/dist/importer/prelink.js +20 -3
- package/dist/importer/prelink.js.map +1 -1
- package/dist/importer/run-import.d.ts +24 -5
- package/dist/importer/run-import.d.ts.map +1 -1
- package/dist/importer/run-import.js +36 -7
- package/dist/importer/run-import.js.map +1 -1
- package/dist/importer/types.d.ts +12 -1
- 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 +10 -10
- package/src/cli.ts +87 -27
- 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 +103 -23
- package/src/importer/guards.ts +223 -0
- package/src/importer/host-writes.ts +15 -4
- package/src/importer/lifecycle.ts +193 -32
- package/src/importer/manifest-mac.ts +125 -0
- package/src/importer/prelink.ts +24 -3
- package/src/importer/run-import.ts +64 -12
- package/src/importer/types.ts +12 -1
- 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":"AAAA,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAIvF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,MAAM,UAAU,GAAG,KAAK,CAAA;AAExB,MAAM,QAAQ,GAAG,CAAC,UAAkB,EAAE,EAAmB,EAAE,KAAa,EAAU,EAAE,CAClF,GAAG,UAAU,SAAS,MAAM,CAAC,EAAE,CAAC,SAAS,KAAK,EAAE,CAAA;AAElD,SAAS,MAAM,CAAC,MAAc;IAC5B,OAAO,UAAU,CAAC,QAAQ,CAAC;SACxB,MAAM,CAAC,kDAAkD,CAAC;SAC1D,MAAM,CAAC,MAAM,CAAC;SACd,MAAM,EAAE,CAAA;AACb,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,MAAc,EAAE,SAAiB,EAAE,KAAa;IACxE,MAAM,EAAE,GAAG,WAAW,CAAC,EAAE,CAAC,CAAA;IAC1B,MAAM,MAAM,GAAG,cAAc,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,CAAA;IAChE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAA;IACzC,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAA;IAC9E,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,EAAE,CAAA;IAC/B,OAAO,GAAG,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAA;AAC/E,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,WAAW,CAAC,MAAc,EAAE,MAAc,EAAE,KAAa;IACvE,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAA;IAC5D,CAAC;IACD,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC,CAAA;IACrE,MAAM,EAAE,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;IAC9B,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC,CAAA;IAChC,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;IAC7B,MAAM,QAAQ,GAAG,gBAAgB,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,CAAA;IACpE,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,CAAA;IACxB,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAA;IAC3C,IAAI,CAAC;QACH,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;IAClF,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CACb,2NAA2N,CAC5N,CAAA;IACH,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAwB,IAAI,GAAG,CAAC;IAC3D,YAAY;IACZ,OAAO;IACP,UAAU;IACV,eAAe;IACf,WAAW;CACZ,CAAC,CAAA;AAEF,sEAAsE;AACtE,MAAM,UAAU,mBAAmB,CAAC,UAAkB;IACpD,OAAO,IAAI,GAAG,CAAC,CAAC,GAAG,gBAAgB,EAAE,UAAU,CAAC,CAAC,CAAA;AACnD,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,UAAU,CACxB,MAAc,EACd,KAAiB,EACjB,aAAkC,gBAAgB;IAElD,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CACb,uBAAuB,KAAK,CAAC,UAAU,IAAI,KAAK,CAAC,KAAK,6EAA6E,CACpI,CAAA;IACH,CAAC;IACD,IAAI,KAAK,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;QACrC,OAAO,IAAI,CAAC,KAAK,CACf,WAAW,CAAC,MAAM,EAAE,KAAK,CAAC,YAAY,EAAE,QAAQ,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAChF,CAAA;IACd,CAAC;IACD,qDAAqD;IACrD,OAAO,KAAK,CAAC,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS;QACxD,CAAC,CAAC,IAAI;QACN,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAa,CAAA;AAC3C,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,YAAY,CAAC,MAA6B;IACxD,OAAO,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AAC1F,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,OAOnC;IACC,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,OAAO,CAAA;IACnE,MAAM,GAAG,GAAY,MAAM,OAAO,CAAC,QAAQ,CAAC;QAC1C,UAAU;QACV,EAAE;QACF,KAAK,EAAE,CAAC;QACR,cAAc,EAAE,IAAI;KACrB,CAAC,CAAA;IACF,MAAM,MAAM,GACV,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,CAAC,CAAC,CAAE,GAA+B,CAAC,CAAC,CAAC,SAAS,CAAA;IACxF,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAA;IACjC,IAAI,aAAa,GAAG,CAAC,CAAA;IACrB,KAAK,MAAM,KAAK,IAAI,CAAC,GAAG,MAAM,EAAE,WAAW,CAAC,EAAE,CAAC;QAC7C,IAAI,MAAM,KAAK,SAAS,IAAI,CAAC,CAAC,KAAK,IAAI,MAAM,CAAC,EAAE,CAAC;YAC/C,SAAQ;QACV,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAClB,MAAM,GAAG,GAAG,QAAQ,CAAC,UAAU,EAAE,EAAE,EAAE,KAAK,CAAC,CAAA;QAC3C,IAAI,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACtB,gEAAgE;YAChE,0DAA0D;YAC1D,SAAQ;QACV,CAAC;QACD,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACjB,IAAI,CAAC,IAAI,CAAC;YACR,UAAU;YACV,EAAE;YACF,KAAK;YACL,YAAY,EAAE,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,EAAE,GAAG,CAAC;SACpF,CAAC,CAAA;QACF,aAAa,IAAI,CAAC,CAAA;IACpB,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,CAAA;AACnC,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,OAAgB,EAChB,QAA6C,EAC7C,UAAwD,EAAE;IAE1D,MAAM,UAAU,GAAG,mBAAmB,CAAC,OAAO,CAAC,UAAU,IAAI,OAAO,CAAC,CAAA;IACrE,MAAM,KAAK,GAAG,IAAI,GAAG,EAGlB,CAAA;IACH,KAAK,MAAM,KAAK,IAAI,QAAQ,CAAC,WAAW,EAAE,CAAC;QACzC,MAAM,GAAG,GAAG,GAAG,KAAK,CAAC,UAAU,SAAS,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAA;QAC1D,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAA;QACxF,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,UAAU,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,UAAU,CAAC,CAAA;QACvE,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;IACvB,CAAC;IACD,IAAI,QAAQ,GAAG,CAAC,CAAA;IAChB,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;QACnC,MAAM,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC,eAAe,IAAI,EAAE,CAAA;QAClF,MAAM,eAAe,GAAG,CAAC,IAAY,EAAW,EAAE;YAChD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,KAAK,IAAI,CAAC,CAAA;YACjE,OAAO,CACL,KAAK,KAAK,SAAS;gBACnB,CAAC,KAAK,CAAC,IAAI,KAAK,OAAO;oBACrB,KAAK,CAAC,IAAI,KAAK,QAAQ;oBACvB,CAAC,SAAS,IAAI,KAAK,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC,CAClD,CAAA;QACH,CAAC,CAAA;QACD,MAAM,SAAS,GAA4B,EAAE,CAAA;QAC7C,MAAM,OAAO,GAA4B,EAAE,CAAA;QAC3C,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACxD,IAAI,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC3B,SAAS,CAAC,KAAK,CAAC,GAAG,KAAK,CAAA;YAC1B,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK,CAAA;YACxB,CAAC;QACH,CAAC;QACD,mEAAmE;QACnE,iEAAiE;QACjE,wCAAwC;QACxC,IAAI,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtC,MAAM,OAAO,CAAC,MAAM,CAAC;gBACnB,UAAU,EAAE,KAAK,CAAC,UAAU;gBAC5B,EAAE,EAAE,KAAK,CAAC,EAAE;gBACZ,IAAI,EAAE,SAAS;gBACf,cAAc,EAAE,IAAI;aACrB,CAAC,CAAA;QACJ,CAAC;QACD,kEAAkE;QAClE,gEAAgE;QAChE,yCAAyC;QACzC,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpC,MAAM,OAAO,CAAC,EAAE,CAAC,SAAS,CAAC;gBACzB,UAAU,EAAE,KAAK,CAAC,UAAU;gBAC5B,EAAE,EAAE,KAAK,CAAC,EAAE;gBACZ,IAAI,EAAE,OAAO;gBACb,SAAS,EAAE,KAAK;aACjB,CAAC,CAAA;QACJ,CAAC;QACD,QAAQ,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAA;IAC5C,CAAC;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC"}
|
|
@@ -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,10 +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
|
+
*/
|
|
82
|
+
readonly allowUnsignedManifest?: boolean | undefined;
|
|
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<{
|
|
80
110
|
deleted: number;
|
|
81
111
|
restored: number;
|
|
82
112
|
}>;
|
|
83
|
-
/**
|
|
84
|
-
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>;
|
|
85
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,12 +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';
|
|
5
|
+
import { authenticateManifest, signManifest } from './manifest-mac.js';
|
|
4
6
|
import { defaultTargetSlugs, resolveSourceTables, sourceModelNames, } from './types.js';
|
|
5
7
|
/**
|
|
6
8
|
* Manifest lifecycle (issue #50): `verify` checks both sides, `rollback`
|
|
7
|
-
* undoes exactly what the manifest records, and `finalize`
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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.
|
|
10
13
|
*/
|
|
11
14
|
/**
|
|
12
15
|
* The importer's default manifest location: the CURRENT WORKING
|
|
@@ -26,8 +29,8 @@ export function readManifest(path) {
|
|
|
26
29
|
const version = typeof parsed === 'object' && parsed !== null
|
|
27
30
|
? parsed.version
|
|
28
31
|
: undefined;
|
|
29
|
-
if (version !== 1 && version !== 2) {
|
|
30
|
-
throw new Error(`manifest at ${path} is not a version-1/2 import manifest`);
|
|
32
|
+
if (version !== 1 && version !== 2 && version !== 3) {
|
|
33
|
+
throw new Error(`manifest at ${path} is not a version-1/2/3 import manifest`);
|
|
31
34
|
}
|
|
32
35
|
return parsed;
|
|
33
36
|
}
|
|
@@ -200,6 +203,9 @@ export async function verifyImport(options) {
|
|
|
200
203
|
// lookup findByProviderIdentity issues on the next provider
|
|
201
204
|
// sign-in. The host docs also carry the stored roles (issue #68's
|
|
202
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);
|
|
203
209
|
let identityIssues = 0;
|
|
204
210
|
let roleMismatchDocs = 0;
|
|
205
211
|
let hostDocsWithRoles = 0;
|
|
@@ -237,7 +243,7 @@ export async function verifyImport(options) {
|
|
|
237
243
|
if (identities.length !== 1 || resolving.length !== 1) {
|
|
238
244
|
identityIssues += 1;
|
|
239
245
|
}
|
|
240
|
-
const roles = record[
|
|
246
|
+
const roles = record[rolesField];
|
|
241
247
|
if (Array.isArray(roles)) {
|
|
242
248
|
hostDocsWithRoles += 1;
|
|
243
249
|
const stored = new Set(roles.map((role) => String(role)));
|
|
@@ -248,11 +254,11 @@ export async function verifyImport(options) {
|
|
|
248
254
|
}
|
|
249
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`);
|
|
250
256
|
if (hostDocsWithRoles > 0) {
|
|
251
|
-
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}'`);
|
|
252
258
|
}
|
|
253
259
|
else if (sourceTables['users_role'] !== null && users.some((user) => user.roles.length > 0)) {
|
|
254
260
|
checks.push({
|
|
255
|
-
name:
|
|
261
|
+
name: `source roles carried onto the host docs (skipped: the host users carry no '${rolesField}' field)`,
|
|
256
262
|
passed: true,
|
|
257
263
|
});
|
|
258
264
|
}
|
|
@@ -390,6 +396,18 @@ export async function verifyImport(options) {
|
|
|
390
396
|
* restore every captured field value. The manifest records exactly
|
|
391
397
|
* what to undo — nothing else is touched, and the source tables were
|
|
392
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.
|
|
393
411
|
*/
|
|
394
412
|
export async function rollbackImport(options) {
|
|
395
413
|
const { payload, manifest, log = () => { } } = options;
|
|
@@ -398,42 +416,130 @@ export async function rollbackImport(options) {
|
|
|
398
416
|
// the migration's history, not a TODO to undo.
|
|
399
417
|
throw new Error('the migration is finalized; rollback is no longer possible');
|
|
400
418
|
}
|
|
401
|
-
|
|
419
|
+
// Every row below is deleted, and every field restored, with
|
|
420
|
+
// overrideAccess: only a manifest this app signed may drive that.
|
|
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;
|
|
402
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) {
|
|
403
468
|
try {
|
|
404
469
|
await payload.delete({ collection: row.collection, id: row.id, overrideAccess: true });
|
|
405
470
|
deleted += 1;
|
|
406
471
|
}
|
|
407
|
-
catch {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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)}`);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
if (failed.length > 0) {
|
|
482
|
+
for (const line of failed) {
|
|
483
|
+
log(`rollback: FAILED to delete ${line}`);
|
|
411
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`);
|
|
412
489
|
}
|
|
413
490
|
// Captured values go back through the adapter, one write per
|
|
414
491
|
// document, `updatedAt` included — see host-writes.ts for why.
|
|
415
|
-
const restored = await restoreCaptured(payload, manifest
|
|
492
|
+
const restored = await restoreCaptured(payload, manifest, {
|
|
493
|
+
rolesField: hostRolesField(payload),
|
|
494
|
+
});
|
|
416
495
|
if (options.manifestPath !== undefined) {
|
|
417
|
-
const reset = {
|
|
496
|
+
const reset = signManifest(payload.secret, {
|
|
418
497
|
...manifest,
|
|
419
|
-
version: 2,
|
|
420
498
|
created: [],
|
|
421
499
|
fieldWrites: [],
|
|
422
500
|
totals: {},
|
|
423
501
|
rolledBackAt: (options.now ?? (() => new Date()))().toISOString(),
|
|
424
|
-
};
|
|
502
|
+
});
|
|
425
503
|
writeManifest(reset, options.manifestPath);
|
|
426
504
|
}
|
|
427
505
|
log(`rollback: ${deleted} row(s) deleted, ${restored} field value(s) restored`);
|
|
428
506
|
return { deleted, restored };
|
|
429
507
|
}
|
|
430
|
-
/**
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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);
|
|
437
543
|
return finalized;
|
|
438
544
|
}
|
|
439
545
|
//# sourceMappingURL=lifecycle.js.map
|