mindoodb 0.0.44 → 0.0.46
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 +9 -0
- package/dist/appendonlystores/network/HttpTransport.d.ts.map +1 -1
- package/dist/appendonlystores/network/HttpTransport.js +4 -0
- package/dist/appendonlystores/network/HttpTransport.js.map +1 -1
- package/dist/browser/index.mjs +31 -0
- package/dist/core/BaseMindooDB.d.ts +74 -0
- package/dist/core/BaseMindooDB.d.ts.map +1 -1
- package/dist/core/BaseMindooDB.js +160 -0
- package/dist/core/BaseMindooDB.js.map +1 -1
- package/dist/core/BaseMindooTenant.d.ts +22 -0
- package/dist/core/BaseMindooTenant.d.ts.map +1 -1
- package/dist/core/BaseMindooTenant.js +57 -6
- package/dist/core/BaseMindooTenant.js.map +1 -1
- package/dist/core/BaseMindooTenantFactory.d.ts.map +1 -1
- package/dist/core/BaseMindooTenantFactory.js +31 -9
- package/dist/core/BaseMindooTenantFactory.js.map +1 -1
- package/dist/core/copy/attachments.d.ts +53 -0
- package/dist/core/copy/attachments.d.ts.map +1 -0
- package/dist/core/copy/attachments.js +186 -0
- package/dist/core/copy/attachments.js.map +1 -0
- package/dist/core/copy/copyDocument.d.ts +86 -0
- package/dist/core/copy/copyDocument.d.ts.map +1 -0
- package/dist/core/copy/copyDocument.js +369 -0
- package/dist/core/copy/copyDocument.js.map +1 -0
- package/dist/core/copy/copyDocuments.d.ts +57 -0
- package/dist/core/copy/copyDocuments.d.ts.map +1 -0
- package/dist/core/copy/copyDocuments.js +167 -0
- package/dist/core/copy/copyDocuments.js.map +1 -0
- package/dist/core/copy/entryRewrite.d.ts +49 -0
- package/dist/core/copy/entryRewrite.d.ts.map +1 -0
- package/dist/core/copy/entryRewrite.js +74 -0
- package/dist/core/copy/entryRewrite.js.map +1 -0
- package/dist/core/copy/feasibility.d.ts +73 -0
- package/dist/core/copy/feasibility.d.ts.map +1 -0
- package/dist/core/copy/feasibility.js +169 -0
- package/dist/core/copy/feasibility.js.map +1 -0
- package/dist/core/copy/flatten.d.ts +55 -0
- package/dist/core/copy/flatten.d.ts.map +1 -0
- package/dist/core/copy/flatten.js +155 -0
- package/dist/core/copy/flatten.js.map +1 -0
- package/dist/core/copy/host.d.ts +92 -0
- package/dist/core/copy/host.d.ts.map +1 -0
- package/dist/core/copy/host.js +16 -0
- package/dist/core/copy/host.js.map +1 -0
- package/dist/core/copy/preflight.d.ts +62 -0
- package/dist/core/copy/preflight.d.ts.map +1 -0
- package/dist/core/copy/preflight.js +98 -0
- package/dist/core/copy/preflight.js.map +1 -0
- package/dist/core/copy/provenance.d.ts +74 -0
- package/dist/core/copy/provenance.d.ts.map +1 -0
- package/dist/core/copy/provenance.js +91 -0
- package/dist/core/copy/provenance.js.map +1 -0
- package/dist/core/copy/types.d.ts +418 -0
- package/dist/core/copy/types.d.ts.map +1 -0
- package/dist/core/copy/types.js +21 -0
- package/dist/core/copy/types.js.map +1 -0
- package/dist/core/crypto/DomainStatement.d.ts +44 -0
- package/dist/core/crypto/DomainStatement.d.ts.map +1 -0
- package/dist/core/crypto/DomainStatement.js +71 -0
- package/dist/core/crypto/DomainStatement.js.map +1 -0
- package/dist/core/crypto/EntrySignature.d.ts +109 -15
- package/dist/core/crypto/EntrySignature.d.ts.map +1 -1
- package/dist/core/crypto/EntrySignature.js +83 -22
- package/dist/core/crypto/EntrySignature.js.map +1 -1
- package/dist/core/index.d.ts +21 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +54 -3
- package/dist/core/index.js.map +1 -1
- package/dist/core/timestamps/timestampClient.d.ts +88 -0
- package/dist/core/timestamps/timestampClient.d.ts.map +1 -0
- package/dist/core/timestamps/timestampClient.js +157 -0
- package/dist/core/timestamps/timestampClient.js.map +1 -0
- package/dist/core/types.d.ts +134 -4
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/node/server/MindooDBServer.d.ts +37 -0
- package/dist/node/server/MindooDBServer.d.ts.map +1 -1
- package/dist/node/server/MindooDBServer.js +157 -0
- package/dist/node/server/MindooDBServer.js.map +1 -1
- package/dist/node/server/config.d.ts.map +1 -1
- package/dist/node/server/config.js +16 -0
- package/dist/node/server/config.js.map +1 -1
- package/dist/node/server/timestampProxy.d.ts +129 -0
- package/dist/node/server/timestampProxy.d.ts.map +1 -0
- package/dist/node/server/timestampProxy.js +361 -0
- package/dist/node/server/timestampProxy.js.map +1 -0
- package/dist/node/server/types.d.ts +20 -0
- package/dist/node/server/types.d.ts.map +1 -1
- package/dist/node/server/types.js +12 -0
- package/dist/node/server/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure entry-rewriting helpers shared by the graft and replay strategies.
|
|
3
|
+
*
|
|
4
|
+
* @internal
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
import type { StoreEntry, StoreEntryMetadata, StoreEntryType } from "../types";
|
|
8
|
+
/**
|
|
9
|
+
* The entry types that make up a document's revision graph. `doc_snapshot` is
|
|
10
|
+
* included deliberately: after a dense sync it can be the only entry a peer
|
|
11
|
+
* holds for a document, so skipping it would silently drop content.
|
|
12
|
+
*/
|
|
13
|
+
export declare const DOCUMENT_DAG_ENTRY_TYPES: ReadonlySet<StoreEntryType>;
|
|
14
|
+
/** True when the entry is part of the document revision graph. */
|
|
15
|
+
export declare function isDocumentDagEntry(entry: StoreEntryMetadata): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Re-point an entry id from one document to another.
|
|
18
|
+
*
|
|
19
|
+
* Every entry id — document (`<docId>_d_<depsFingerprint>_<automergeHash>`) and
|
|
20
|
+
* attachment chunk (`<docId>_a_<fileUuid7>_<chunkUuid>`) alike — begins with the
|
|
21
|
+
* document id followed by `_`, so re-homing an entry is a prefix swap.
|
|
22
|
+
*
|
|
23
|
+
* This is exact rather than a shortcut, and it is what makes a replay cheap:
|
|
24
|
+
* the deps fingerprint is `SHA-256` over the sorted *Automerge* dependency
|
|
25
|
+
* hashes, which a copy never changes, and the Automerge hash is a property of
|
|
26
|
+
* the change bytes, which a copy reuses verbatim. So the re-derived id is
|
|
27
|
+
* identical to what `generateDocEntryId()` would return under the new document
|
|
28
|
+
* id — without decrypting or decoding anything — and it is deterministic, which
|
|
29
|
+
* is what lets an interrupted copy resume by simply running again.
|
|
30
|
+
*
|
|
31
|
+
* Ids that do not carry the source prefix (a dependency on a foreign entry)
|
|
32
|
+
* are returned unchanged.
|
|
33
|
+
*/
|
|
34
|
+
export declare function remapEntryId(entryId: string, sourceDocId: string, targetDocId: string): string;
|
|
35
|
+
/**
|
|
36
|
+
* Remove the server's witness receipt from a copied entry.
|
|
37
|
+
*
|
|
38
|
+
* The receipt signature binds the database id (see `crypto/WitnessReceipt.ts`),
|
|
39
|
+
* so carrying it into a different database would fail verification and
|
|
40
|
+
* quarantine the entry as `invalid_witness_receipt`. Stripped entries are
|
|
41
|
+
* provisional until the target database is pushed and re-witnessed.
|
|
42
|
+
*
|
|
43
|
+
* `receiptOrder` goes too: it is a per-store sequence the receiving store
|
|
44
|
+
* assigns, meaningless in another one.
|
|
45
|
+
*/
|
|
46
|
+
export declare function stripWitnessReceipt<T extends StoreEntryMetadata>(entry: T): T;
|
|
47
|
+
/** Sum the encrypted payload sizes of a batch, for progress and result totals. */
|
|
48
|
+
export declare function sumEncryptedSize(entries: StoreEntry[]): number;
|
|
49
|
+
//# sourceMappingURL=entryRewrite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entryRewrite.d.ts","sourceRoot":"","sources":["../../../src/core/copy/entryRewrite.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE/E;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,EAAE,WAAW,CAAC,cAAc,CAM/D,CAAC;AAEH,kEAAkE;AAClE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAErE;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,MAAM,CAIR;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,kBAAkB,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAS7E;AAED,kFAAkF;AAClF,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,CAE9D"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Pure entry-rewriting helpers shared by the graft and replay strategies.
|
|
4
|
+
*
|
|
5
|
+
* @internal
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.DOCUMENT_DAG_ENTRY_TYPES = void 0;
|
|
10
|
+
exports.isDocumentDagEntry = isDocumentDagEntry;
|
|
11
|
+
exports.remapEntryId = remapEntryId;
|
|
12
|
+
exports.stripWitnessReceipt = stripWitnessReceipt;
|
|
13
|
+
exports.sumEncryptedSize = sumEncryptedSize;
|
|
14
|
+
/**
|
|
15
|
+
* The entry types that make up a document's revision graph. `doc_snapshot` is
|
|
16
|
+
* included deliberately: after a dense sync it can be the only entry a peer
|
|
17
|
+
* holds for a document, so skipping it would silently drop content.
|
|
18
|
+
*/
|
|
19
|
+
exports.DOCUMENT_DAG_ENTRY_TYPES = new Set([
|
|
20
|
+
"doc_create",
|
|
21
|
+
"doc_change",
|
|
22
|
+
"doc_delete",
|
|
23
|
+
"doc_undelete",
|
|
24
|
+
"doc_snapshot",
|
|
25
|
+
]);
|
|
26
|
+
/** True when the entry is part of the document revision graph. */
|
|
27
|
+
function isDocumentDagEntry(entry) {
|
|
28
|
+
return exports.DOCUMENT_DAG_ENTRY_TYPES.has(entry.entryType);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Re-point an entry id from one document to another.
|
|
32
|
+
*
|
|
33
|
+
* Every entry id — document (`<docId>_d_<depsFingerprint>_<automergeHash>`) and
|
|
34
|
+
* attachment chunk (`<docId>_a_<fileUuid7>_<chunkUuid>`) alike — begins with the
|
|
35
|
+
* document id followed by `_`, so re-homing an entry is a prefix swap.
|
|
36
|
+
*
|
|
37
|
+
* This is exact rather than a shortcut, and it is what makes a replay cheap:
|
|
38
|
+
* the deps fingerprint is `SHA-256` over the sorted *Automerge* dependency
|
|
39
|
+
* hashes, which a copy never changes, and the Automerge hash is a property of
|
|
40
|
+
* the change bytes, which a copy reuses verbatim. So the re-derived id is
|
|
41
|
+
* identical to what `generateDocEntryId()` would return under the new document
|
|
42
|
+
* id — without decrypting or decoding anything — and it is deterministic, which
|
|
43
|
+
* is what lets an interrupted copy resume by simply running again.
|
|
44
|
+
*
|
|
45
|
+
* Ids that do not carry the source prefix (a dependency on a foreign entry)
|
|
46
|
+
* are returned unchanged.
|
|
47
|
+
*/
|
|
48
|
+
function remapEntryId(entryId, sourceDocId, targetDocId) {
|
|
49
|
+
if (sourceDocId === targetDocId)
|
|
50
|
+
return entryId;
|
|
51
|
+
if (!entryId.startsWith(`${sourceDocId}_`))
|
|
52
|
+
return entryId;
|
|
53
|
+
return `${targetDocId}${entryId.slice(sourceDocId.length)}`;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Remove the server's witness receipt from a copied entry.
|
|
57
|
+
*
|
|
58
|
+
* The receipt signature binds the database id (see `crypto/WitnessReceipt.ts`),
|
|
59
|
+
* so carrying it into a different database would fail verification and
|
|
60
|
+
* quarantine the entry as `invalid_witness_receipt`. Stripped entries are
|
|
61
|
+
* provisional until the target database is pushed and re-witnessed.
|
|
62
|
+
*
|
|
63
|
+
* `receiptOrder` goes too: it is a per-store sequence the receiving store
|
|
64
|
+
* assigns, meaningless in another one.
|
|
65
|
+
*/
|
|
66
|
+
function stripWitnessReceipt(entry) {
|
|
67
|
+
const { receivedAt: _receivedAt, receivedByPublicKey: _receivedByPublicKey, receivedDateSignature: _receivedDateSignature, receiptOrder: _receiptOrder, ...rest } = entry;
|
|
68
|
+
return rest;
|
|
69
|
+
}
|
|
70
|
+
/** Sum the encrypted payload sizes of a batch, for progress and result totals. */
|
|
71
|
+
function sumEncryptedSize(entries) {
|
|
72
|
+
return entries.reduce((total, entry) => total + entry.encryptedSize, 0);
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=entryRewrite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entryRewrite.js","sourceRoot":"","sources":["../../../src/core/copy/entryRewrite.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAkBH,gDAEC;AAoBD,oCAQC;AAaD,kDASC;AAGD,4CAEC;AAvED;;;;GAIG;AACU,QAAA,wBAAwB,GAAgC,IAAI,GAAG,CAAiB;IAC3F,YAAY;IACZ,YAAY;IACZ,YAAY;IACZ,cAAc;IACd,cAAc;CACf,CAAC,CAAC;AAEH,kEAAkE;AAClE,SAAgB,kBAAkB,CAAC,KAAyB;IAC1D,OAAO,gCAAwB,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;AACvD,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,SAAgB,YAAY,CAC1B,OAAe,EACf,WAAmB,EACnB,WAAmB;IAEnB,IAAI,WAAW,KAAK,WAAW;QAAE,OAAO,OAAO,CAAC;IAChD,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,WAAW,GAAG,CAAC;QAAE,OAAO,OAAO,CAAC;IAC3D,OAAO,GAAG,WAAW,GAAG,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC;AAC9D,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAgB,mBAAmB,CAA+B,KAAQ;IACxE,MAAM,EACJ,UAAU,EAAE,WAAW,EACvB,mBAAmB,EAAE,oBAAoB,EACzC,qBAAqB,EAAE,sBAAsB,EAC7C,YAAY,EAAE,aAAa,EAC3B,GAAG,IAAI,EACR,GAAG,KAAK,CAAC;IACV,OAAO,IAAS,CAAC;AACnB,CAAC;AAED,kFAAkF;AAClF,SAAgB,gBAAgB,CAAC,OAAqB;IACpD,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,GAAG,KAAK,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC;AAC1E,CAAC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure strategy resolution for document copies.
|
|
3
|
+
*
|
|
4
|
+
* Everything here is a side-effect-free function of a {@link CopyContext} plus
|
|
5
|
+
* the caller's {@link CopyDocumentOptions}, which keeps the "can I do this, and
|
|
6
|
+
* what would it cost me" question answerable without touching a store, and
|
|
7
|
+
* keeps the rules unit-testable in isolation from the copy engine.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
import type { CopyDocumentOptions, CopyFeasibility } from "./types";
|
|
12
|
+
/** The tenant directory database id. Never a valid copy source or target. */
|
|
13
|
+
export declare const DIRECTORY_DB_ID = "directory";
|
|
14
|
+
/**
|
|
15
|
+
* Everything the resolver needs to know about the source and target, gathered
|
|
16
|
+
* by the copy engine before any work starts.
|
|
17
|
+
*
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
export interface CopyContext {
|
|
21
|
+
/** Tenant owning the source database. */
|
|
22
|
+
sourceTenantId: string;
|
|
23
|
+
/** Tenant owning the target database. */
|
|
24
|
+
targetTenantId: string;
|
|
25
|
+
/** Source database (store) id. */
|
|
26
|
+
sourceDbId: string;
|
|
27
|
+
/** Target database (store) id. */
|
|
28
|
+
targetDbId: string;
|
|
29
|
+
/** The document id being read from the source. */
|
|
30
|
+
sourceDocId: string;
|
|
31
|
+
/** The document id that will be written in the target, already resolved. */
|
|
32
|
+
targetDocId: string;
|
|
33
|
+
/** The `decryptionKeyId` the source document's entries currently use. */
|
|
34
|
+
sourceDecryptionKeyId: string;
|
|
35
|
+
/** The `decryptionKeyId` the copy will be written under, already resolved. */
|
|
36
|
+
targetDecryptionKeyId: string;
|
|
37
|
+
/** True when source and target document entries live in the same store. */
|
|
38
|
+
sameStore: boolean;
|
|
39
|
+
/** True when the target store already holds an entry for {@link targetDocId}. */
|
|
40
|
+
targetHasDocId: boolean;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Resolve the effective target `decryptionKeyId` for a copy.
|
|
44
|
+
*
|
|
45
|
+
* Within one tenant the source document's own key is kept, so a copy stays
|
|
46
|
+
* readable by exactly the same people and — crucially — stays eligible for
|
|
47
|
+
* authorship preservation. Across tenants the source key id is meaningless
|
|
48
|
+
* (key ids resolve tenant-scoped), so the copy falls back to the target
|
|
49
|
+
* tenant's `"default"` key.
|
|
50
|
+
*/
|
|
51
|
+
export declare function resolveTargetDecryptionKeyId(requested: string | undefined, sourceDecryptionKeyId: string, sameTenant: boolean): string;
|
|
52
|
+
/**
|
|
53
|
+
* Decide what a copy would do, and whether it can run at all.
|
|
54
|
+
*
|
|
55
|
+
* The rules, in order:
|
|
56
|
+
*
|
|
57
|
+
* 1. Neither side may be the tenant directory database.
|
|
58
|
+
* 2. `flatten` mode always resolves to the flatten strategy and never preserves
|
|
59
|
+
* authorship (there is no original entry left to preserve).
|
|
60
|
+
* 3. `history` mode preserves authorship only in the exact configuration where
|
|
61
|
+
* the original `metadataSignature` remains valid: same tenant, same document
|
|
62
|
+
* id, same key, different database. Anything else re-authors.
|
|
63
|
+
* 4. Asking for `authorship: "preserve"` outside that configuration is refused
|
|
64
|
+
* rather than silently downgraded, because a caller who asked for it is
|
|
65
|
+
* relying on the signatures being real.
|
|
66
|
+
*/
|
|
67
|
+
export declare function resolveCopyFeasibility(context: CopyContext, options?: CopyDocumentOptions): CopyFeasibility;
|
|
68
|
+
/**
|
|
69
|
+
* Turn an unusable {@link CopyFeasibility} into the error thrown by
|
|
70
|
+
* `copyDocumentTo` / `copyDocumentsTo`.
|
|
71
|
+
*/
|
|
72
|
+
export declare function copyFeasibilityError(feasibility: CopyFeasibility): Error;
|
|
73
|
+
//# sourceMappingURL=feasibility.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feasibility.d.ts","sourceRoot":"","sources":["../../../src/core/copy/feasibility.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EACV,mBAAmB,EACnB,eAAe,EAIhB,MAAM,SAAS,CAAC;AAEjB,6EAA6E;AAC7E,eAAO,MAAM,eAAe,cAAc,CAAC;AAE3C;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,yCAAyC;IACzC,cAAc,EAAE,MAAM,CAAC;IACvB,yCAAyC;IACzC,cAAc,EAAE,MAAM,CAAC;IACvB,kCAAkC;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,kCAAkC;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,WAAW,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,WAAW,EAAE,MAAM,CAAC;IACpB,yEAAyE;IACzE,qBAAqB,EAAE,MAAM,CAAC;IAC9B,8EAA8E;IAC9E,qBAAqB,EAAE,MAAM,CAAC;IAC9B,2EAA2E;IAC3E,SAAS,EAAE,OAAO,CAAC;IACnB,iFAAiF;IACjF,cAAc,EAAE,OAAO,CAAC;CACzB;AAED;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAC1C,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,qBAAqB,EAAE,MAAM,EAC7B,UAAU,EAAE,OAAO,GAClB,MAAM,CAGR;AAUD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,WAAW,EACpB,OAAO,GAAE,mBAAwB,GAChC,eAAe,CAgJjB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,eAAe,GAAG,KAAK,CAKxE"}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Pure strategy resolution for document copies.
|
|
4
|
+
*
|
|
5
|
+
* Everything here is a side-effect-free function of a {@link CopyContext} plus
|
|
6
|
+
* the caller's {@link CopyDocumentOptions}, which keeps the "can I do this, and
|
|
7
|
+
* what would it cost me" question answerable without touching a store, and
|
|
8
|
+
* keeps the rules unit-testable in isolation from the copy engine.
|
|
9
|
+
*
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.DIRECTORY_DB_ID = void 0;
|
|
14
|
+
exports.resolveTargetDecryptionKeyId = resolveTargetDecryptionKeyId;
|
|
15
|
+
exports.resolveCopyFeasibility = resolveCopyFeasibility;
|
|
16
|
+
exports.copyFeasibilityError = copyFeasibilityError;
|
|
17
|
+
/** The tenant directory database id. Never a valid copy source or target. */
|
|
18
|
+
exports.DIRECTORY_DB_ID = "directory";
|
|
19
|
+
/**
|
|
20
|
+
* Resolve the effective target `decryptionKeyId` for a copy.
|
|
21
|
+
*
|
|
22
|
+
* Within one tenant the source document's own key is kept, so a copy stays
|
|
23
|
+
* readable by exactly the same people and — crucially — stays eligible for
|
|
24
|
+
* authorship preservation. Across tenants the source key id is meaningless
|
|
25
|
+
* (key ids resolve tenant-scoped), so the copy falls back to the target
|
|
26
|
+
* tenant's `"default"` key.
|
|
27
|
+
*/
|
|
28
|
+
function resolveTargetDecryptionKeyId(requested, sourceDecryptionKeyId, sameTenant) {
|
|
29
|
+
if (requested)
|
|
30
|
+
return requested;
|
|
31
|
+
return sameTenant ? sourceDecryptionKeyId : "default";
|
|
32
|
+
}
|
|
33
|
+
/** Build a reason record with a stable code and a human-readable message. */
|
|
34
|
+
function reason(code, message) {
|
|
35
|
+
return { code, message };
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Decide what a copy would do, and whether it can run at all.
|
|
39
|
+
*
|
|
40
|
+
* The rules, in order:
|
|
41
|
+
*
|
|
42
|
+
* 1. Neither side may be the tenant directory database.
|
|
43
|
+
* 2. `flatten` mode always resolves to the flatten strategy and never preserves
|
|
44
|
+
* authorship (there is no original entry left to preserve).
|
|
45
|
+
* 3. `history` mode preserves authorship only in the exact configuration where
|
|
46
|
+
* the original `metadataSignature` remains valid: same tenant, same document
|
|
47
|
+
* id, same key, different database. Anything else re-authors.
|
|
48
|
+
* 4. Asking for `authorship: "preserve"` outside that configuration is refused
|
|
49
|
+
* rather than silently downgraded, because a caller who asked for it is
|
|
50
|
+
* relying on the signatures being real.
|
|
51
|
+
*/
|
|
52
|
+
function resolveCopyFeasibility(context, options = {}) {
|
|
53
|
+
const mode = options.mode ?? "flatten";
|
|
54
|
+
const authorship = options.authorship ?? "reauthor";
|
|
55
|
+
const wantsPreserve = authorship === "preserve";
|
|
56
|
+
const sameTenant = context.sourceTenantId === context.targetTenantId;
|
|
57
|
+
const sameDocId = context.sourceDocId === context.targetDocId;
|
|
58
|
+
const sameKey = context.sourceDecryptionKeyId === context.targetDecryptionKeyId;
|
|
59
|
+
// Key ids resolve per tenant, so an identical id across tenants still names a
|
|
60
|
+
// different key and still forces re-encryption.
|
|
61
|
+
const requiresReEncryption = !sameTenant || !sameKey;
|
|
62
|
+
const reasons = [];
|
|
63
|
+
const isDirectory = (dbId) => dbId === exports.DIRECTORY_DB_ID;
|
|
64
|
+
if (isDirectory(context.sourceDbId) || isDirectory(context.targetDbId)) {
|
|
65
|
+
reasons.push(reason("directory_database", "The tenant directory database cannot be used as a copy source or target."));
|
|
66
|
+
return {
|
|
67
|
+
allowed: false,
|
|
68
|
+
strategy: mode === "flatten" ? "flatten" : "replay",
|
|
69
|
+
authorshipPreserved: false,
|
|
70
|
+
willMergeIntoExisting: false,
|
|
71
|
+
sameStore: context.sameStore,
|
|
72
|
+
requiresReEncryption,
|
|
73
|
+
reasons,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
if (context.sameStore && sameDocId) {
|
|
77
|
+
reasons.push(reason("same_database_same_doc_id", `Source and target are the same database (${context.sourceDbId}) and the ` +
|
|
78
|
+
`target document id is unchanged (${context.targetDocId}), so there is ` +
|
|
79
|
+
"nothing to copy. Use targetDocId 'new' to duplicate in place."));
|
|
80
|
+
return {
|
|
81
|
+
allowed: false,
|
|
82
|
+
strategy: mode === "flatten" ? "flatten" : "replay",
|
|
83
|
+
authorshipPreserved: false,
|
|
84
|
+
willMergeIntoExisting: true,
|
|
85
|
+
sameStore: true,
|
|
86
|
+
requiresReEncryption,
|
|
87
|
+
reasons,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
if (mode === "flatten") {
|
|
91
|
+
if (wantsPreserve) {
|
|
92
|
+
reasons.push(reason("flatten_mode", "Flatten mode writes a single new change, so there is no original " +
|
|
93
|
+
"entry whose authorship could be preserved. Use mode 'history' with " +
|
|
94
|
+
"authorship 'preserve'."));
|
|
95
|
+
return {
|
|
96
|
+
allowed: false,
|
|
97
|
+
strategy: "flatten",
|
|
98
|
+
authorshipPreserved: false,
|
|
99
|
+
willMergeIntoExisting: context.targetHasDocId,
|
|
100
|
+
sameStore: context.sameStore,
|
|
101
|
+
requiresReEncryption,
|
|
102
|
+
reasons,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
allowed: true,
|
|
107
|
+
strategy: "flatten",
|
|
108
|
+
authorshipPreserved: false,
|
|
109
|
+
willMergeIntoExisting: context.targetHasDocId,
|
|
110
|
+
sameStore: context.sameStore,
|
|
111
|
+
requiresReEncryption,
|
|
112
|
+
reasons,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
// mode === "history": collect every condition that blocks preservation.
|
|
116
|
+
if (!sameTenant) {
|
|
117
|
+
reasons.push(reason("different_tenant", `Source tenant '${context.sourceTenantId}' differs from target tenant ` +
|
|
118
|
+
`'${context.targetTenantId}'. The payload must be re-encrypted under the ` +
|
|
119
|
+
"target tenant key, and the source authors' keys are not in the target " +
|
|
120
|
+
"tenant's directory, so their signatures could not be verified there."));
|
|
121
|
+
}
|
|
122
|
+
if (!sameDocId) {
|
|
123
|
+
reasons.push(reason("different_doc_id", `The copy lands under document id '${context.targetDocId}' instead of ` +
|
|
124
|
+
`'${context.sourceDocId}'. Entry ids embed the document id and the ` +
|
|
125
|
+
"author's metadataSignature binds both, so the original signatures " +
|
|
126
|
+
"cannot carry over. Use targetDocId 'same' to preserve authorship."));
|
|
127
|
+
}
|
|
128
|
+
if (!sameKey) {
|
|
129
|
+
reasons.push(reason("different_key", `The copy is encrypted under key '${context.targetDecryptionKeyId}' instead ` +
|
|
130
|
+
`of '${context.sourceDecryptionKeyId}'. Re-encryption changes contentHash, ` +
|
|
131
|
+
"which the author's metadataSignature binds."));
|
|
132
|
+
}
|
|
133
|
+
const canPreserve = reasons.length === 0;
|
|
134
|
+
if (wantsPreserve && !canPreserve) {
|
|
135
|
+
return {
|
|
136
|
+
allowed: false,
|
|
137
|
+
strategy: "replay",
|
|
138
|
+
authorshipPreserved: false,
|
|
139
|
+
willMergeIntoExisting: context.targetHasDocId,
|
|
140
|
+
sameStore: context.sameStore,
|
|
141
|
+
requiresReEncryption,
|
|
142
|
+
reasons,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
const strategy = wantsPreserve ? "graft" : "replay";
|
|
146
|
+
return {
|
|
147
|
+
allowed: true,
|
|
148
|
+
strategy,
|
|
149
|
+
authorshipPreserved: strategy === "graft",
|
|
150
|
+
willMergeIntoExisting: context.targetHasDocId,
|
|
151
|
+
sameStore: context.sameStore,
|
|
152
|
+
requiresReEncryption,
|
|
153
|
+
// A successful graft has nothing to explain. A replay lists what would have
|
|
154
|
+
// had to be true for authorship to survive, so callers can tell an
|
|
155
|
+
// unavoidable re-author from an accidental one.
|
|
156
|
+
reasons: strategy === "graft" ? [] : reasons,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Turn an unusable {@link CopyFeasibility} into the error thrown by
|
|
161
|
+
* `copyDocumentTo` / `copyDocumentsTo`.
|
|
162
|
+
*/
|
|
163
|
+
function copyFeasibilityError(feasibility) {
|
|
164
|
+
const detail = feasibility.reasons
|
|
165
|
+
.map((entry) => `[${entry.code}] ${entry.message}`)
|
|
166
|
+
.join(" ");
|
|
167
|
+
return new Error(`Document copy is not possible with the requested options. ${detail}`);
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=feasibility.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feasibility.js","sourceRoot":"","sources":["../../../src/core/copy/feasibility.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;;;AAmDH,oEAOC;AAyBD,wDAmJC;AAMD,oDAKC;AAvOD,6EAA6E;AAChE,QAAA,eAAe,GAAG,WAAW,CAAC;AA+B3C;;;;;;;;GAQG;AACH,SAAgB,4BAA4B,CAC1C,SAA6B,EAC7B,qBAA6B,EAC7B,UAAmB;IAEnB,IAAI,SAAS;QAAE,OAAO,SAAS,CAAC;IAChC,OAAO,UAAU,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,SAAS,CAAC;AACxD,CAAC;AAED,6EAA6E;AAC7E,SAAS,MAAM,CACb,IAA+B,EAC/B,OAAe;IAEf,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;AAC3B,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,SAAgB,sBAAsB,CACpC,OAAoB,EACpB,UAA+B,EAAE;IAEjC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,SAAS,CAAC;IACvC,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,UAAU,CAAC;IACpD,MAAM,aAAa,GAAG,UAAU,KAAK,UAAU,CAAC;IAEhD,MAAM,UAAU,GAAG,OAAO,CAAC,cAAc,KAAK,OAAO,CAAC,cAAc,CAAC;IACrE,MAAM,SAAS,GAAG,OAAO,CAAC,WAAW,KAAK,OAAO,CAAC,WAAW,CAAC;IAC9D,MAAM,OAAO,GAAG,OAAO,CAAC,qBAAqB,KAAK,OAAO,CAAC,qBAAqB,CAAC;IAChF,8EAA8E;IAC9E,gDAAgD;IAChD,MAAM,oBAAoB,GAAG,CAAC,UAAU,IAAI,CAAC,OAAO,CAAC;IAErD,MAAM,OAAO,GAA4B,EAAE,CAAC;IAE5C,MAAM,WAAW,GAAG,CAAC,IAAY,EAAW,EAAE,CAAC,IAAI,KAAK,uBAAe,CAAC;IACxE,IAAI,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;QACvE,OAAO,CAAC,IAAI,CACV,MAAM,CACJ,oBAAoB,EACpB,0EAA0E,CAC3E,CACF,CAAC;QACF,OAAO;YACL,OAAO,EAAE,KAAK;YACd,QAAQ,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ;YACnD,mBAAmB,EAAE,KAAK;YAC1B,qBAAqB,EAAE,KAAK;YAC5B,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,oBAAoB;YACpB,OAAO;SACR,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,CAAC,SAAS,IAAI,SAAS,EAAE,CAAC;QACnC,OAAO,CAAC,IAAI,CACV,MAAM,CACJ,2BAA2B,EAC3B,4CAA4C,OAAO,CAAC,UAAU,YAAY;YACxE,oCAAoC,OAAO,CAAC,WAAW,iBAAiB;YACxE,+DAA+D,CAClE,CACF,CAAC;QACF,OAAO;YACL,OAAO,EAAE,KAAK;YACd,QAAQ,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ;YACnD,mBAAmB,EAAE,KAAK;YAC1B,qBAAqB,EAAE,IAAI;YAC3B,SAAS,EAAE,IAAI;YACf,oBAAoB;YACpB,OAAO;SACR,CAAC;IACJ,CAAC;IAED,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,IAAI,aAAa,EAAE,CAAC;YAClB,OAAO,CAAC,IAAI,CACV,MAAM,CACJ,cAAc,EACd,mEAAmE;gBACjE,qEAAqE;gBACrE,wBAAwB,CAC3B,CACF,CAAC;YACF,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,QAAQ,EAAE,SAAS;gBACnB,mBAAmB,EAAE,KAAK;gBAC1B,qBAAqB,EAAE,OAAO,CAAC,cAAc;gBAC7C,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,oBAAoB;gBACpB,OAAO;aACR,CAAC;QACJ,CAAC;QACD,OAAO;YACL,OAAO,EAAE,IAAI;YACb,QAAQ,EAAE,SAAS;YACnB,mBAAmB,EAAE,KAAK;YAC1B,qBAAqB,EAAE,OAAO,CAAC,cAAc;YAC7C,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,oBAAoB;YACpB,OAAO;SACR,CAAC;IACJ,CAAC;IAED,wEAAwE;IACxE,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,CAAC,IAAI,CACV,MAAM,CACJ,kBAAkB,EAClB,kBAAkB,OAAO,CAAC,cAAc,+BAA+B;YACrE,IAAI,OAAO,CAAC,cAAc,gDAAgD;YAC1E,wEAAwE;YACxE,sEAAsE,CACzE,CACF,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CACV,MAAM,CACJ,kBAAkB,EAClB,qCAAqC,OAAO,CAAC,WAAW,eAAe;YACrE,IAAI,OAAO,CAAC,WAAW,6CAA6C;YACpE,oEAAoE;YACpE,mEAAmE,CACtE,CACF,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CACV,MAAM,CACJ,eAAe,EACf,oCAAoC,OAAO,CAAC,qBAAqB,YAAY;YAC3E,OAAO,OAAO,CAAC,qBAAqB,wCAAwC;YAC5E,6CAA6C,CAChD,CACF,CAAC;IACJ,CAAC;IAED,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC;IACzC,IAAI,aAAa,IAAI,CAAC,WAAW,EAAE,CAAC;QAClC,OAAO;YACL,OAAO,EAAE,KAAK;YACd,QAAQ,EAAE,QAAQ;YAClB,mBAAmB,EAAE,KAAK;YAC1B,qBAAqB,EAAE,OAAO,CAAC,cAAc;YAC7C,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,oBAAoB;YACpB,OAAO;SACR,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAiB,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC;IAClE,OAAO;QACL,OAAO,EAAE,IAAI;QACb,QAAQ;QACR,mBAAmB,EAAE,QAAQ,KAAK,OAAO;QACzC,qBAAqB,EAAE,OAAO,CAAC,cAAc;QAC7C,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,oBAAoB;QACpB,4EAA4E;QAC5E,mEAAmE;QACnE,gDAAgD;QAChD,OAAO,EAAE,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO;KAC7C,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAgB,oBAAoB,CAAC,WAA4B;IAC/D,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO;SAC/B,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC;SAClD,IAAI,CAAC,GAAG,CAAC,CAAC;IACb,OAAO,IAAI,KAAK,CAAC,6DAA6D,MAAM,EAAE,CAAC,CAAC;AAC1F,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The flatten copy strategy.
|
|
3
|
+
*
|
|
4
|
+
* Reads the source document's state — at its head, or as of
|
|
5
|
+
* {@link CopyDocumentOptions.atTimestamp} — and writes it into the target as a
|
|
6
|
+
* brand-new document with a single revision. History, original authors and the
|
|
7
|
+
* CRDT structure are all deliberately left behind; what arrives is a clean
|
|
8
|
+
* document that happens to have the same content.
|
|
9
|
+
*
|
|
10
|
+
* Unlike the two history strategies this one goes entirely through the public
|
|
11
|
+
* document API, which is what makes it work regardless of tenant, key or
|
|
12
|
+
* document id: `createDocument` + one `changeDoc`, with attachments re-uploaded
|
|
13
|
+
* through `addAttachmentStream` so they get fresh chunk ids in the target.
|
|
14
|
+
*
|
|
15
|
+
* **Caveat worth knowing:** materializing an Automerge document to plain JS
|
|
16
|
+
* collapses its text fields into ordinary strings. A flattened copy therefore
|
|
17
|
+
* loses the character-level text CRDT structure, and concurrent edits to those
|
|
18
|
+
* fields merge as whole-value conflicts rather than per-character. That is
|
|
19
|
+
* usually the right trade for a fresh document, but it is the reason
|
|
20
|
+
* `mode: "history"` exists.
|
|
21
|
+
*
|
|
22
|
+
* @module
|
|
23
|
+
*/
|
|
24
|
+
import type { CopyEngineHost } from "./host";
|
|
25
|
+
import type { CopyContext } from "./feasibility";
|
|
26
|
+
import type { CopyProgressReporter } from "./copyDocument";
|
|
27
|
+
import type { CopyDocumentOptions, CopyDocumentResult } from "./types";
|
|
28
|
+
/**
|
|
29
|
+
* Document-level provenance for a flattened copy.
|
|
30
|
+
*
|
|
31
|
+
* Per-change provenance would be meaningless here — the copy has exactly one
|
|
32
|
+
* change, authored by the copying user, that corresponds to no single source
|
|
33
|
+
* revision — so the origin is recorded once in the payload instead, which also
|
|
34
|
+
* keeps flatten free of any store-format concern.
|
|
35
|
+
*/
|
|
36
|
+
export interface FlattenedDocumentProvenance {
|
|
37
|
+
/** When the copy ran (epoch millis). */
|
|
38
|
+
copiedAt: number;
|
|
39
|
+
/** Tenant the content came from. */
|
|
40
|
+
sourceTenantId: string;
|
|
41
|
+
/** Database the content came from. */
|
|
42
|
+
sourceDbId: string;
|
|
43
|
+
/** Document the content came from. */
|
|
44
|
+
sourceDocId: string;
|
|
45
|
+
/**
|
|
46
|
+
* The point in the source's history that was captured, when the caller asked
|
|
47
|
+
* for a specific one via {@link CopyDocumentOptions.atTimestamp}.
|
|
48
|
+
*/
|
|
49
|
+
sourceAtTimestamp?: number;
|
|
50
|
+
/** Signing key of the user who made the copy. */
|
|
51
|
+
copiedByPublicKey: string;
|
|
52
|
+
}
|
|
53
|
+
/** Flatten one document into the target database. */
|
|
54
|
+
export declare function flattenDocument(source: CopyEngineHost, target: CopyEngineHost, context: CopyContext, options: CopyDocumentOptions, reporter: CopyProgressReporter): Promise<CopyDocumentResult>;
|
|
55
|
+
//# sourceMappingURL=flatten.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flatten.d.ts","sourceRoot":"","sources":["../../../src/core/copy/flatten.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAC7C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,KAAK,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAEvE;;;;;;;GAOG;AACH,MAAM,WAAW,2BAA2B;IAC1C,wCAAwC;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,oCAAoC;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,sCAAsC;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,sCAAsC;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iDAAiD;IACjD,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAqBD,qDAAqD;AACrD,wBAAsB,eAAe,CACnC,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,mBAAmB,EAC5B,QAAQ,EAAE,oBAAoB,GAC7B,OAAO,CAAC,kBAAkB,CAAC,CAyI7B"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The flatten copy strategy.
|
|
4
|
+
*
|
|
5
|
+
* Reads the source document's state — at its head, or as of
|
|
6
|
+
* {@link CopyDocumentOptions.atTimestamp} — and writes it into the target as a
|
|
7
|
+
* brand-new document with a single revision. History, original authors and the
|
|
8
|
+
* CRDT structure are all deliberately left behind; what arrives is a clean
|
|
9
|
+
* document that happens to have the same content.
|
|
10
|
+
*
|
|
11
|
+
* Unlike the two history strategies this one goes entirely through the public
|
|
12
|
+
* document API, which is what makes it work regardless of tenant, key or
|
|
13
|
+
* document id: `createDocument` + one `changeDoc`, with attachments re-uploaded
|
|
14
|
+
* through `addAttachmentStream` so they get fresh chunk ids in the target.
|
|
15
|
+
*
|
|
16
|
+
* **Caveat worth knowing:** materializing an Automerge document to plain JS
|
|
17
|
+
* collapses its text fields into ordinary strings. A flattened copy therefore
|
|
18
|
+
* loses the character-level text CRDT structure, and concurrent edits to those
|
|
19
|
+
* fields merge as whole-value conflicts rather than per-character. That is
|
|
20
|
+
* usually the right trade for a fresh document, but it is the reason
|
|
21
|
+
* `mode: "history"` exists.
|
|
22
|
+
*
|
|
23
|
+
* @module
|
|
24
|
+
*/
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.flattenDocument = flattenDocument;
|
|
27
|
+
const types_1 = require("../types");
|
|
28
|
+
/** Payload key holding {@link FlattenedDocumentProvenance}. */
|
|
29
|
+
const PROVENANCE_FIELD = "_provenance";
|
|
30
|
+
/**
|
|
31
|
+
* Materialize the source payload as plain data the target can accept.
|
|
32
|
+
*
|
|
33
|
+
* `_`-prefixed keys are MindooDB's own namespace (`_attachments`,
|
|
34
|
+
* `_lastModified`) and are managed by the target database, so they are dropped
|
|
35
|
+
* rather than copied over stale.
|
|
36
|
+
*/
|
|
37
|
+
function copyablePayload(payload) {
|
|
38
|
+
const copy = {};
|
|
39
|
+
for (const [key, value] of Object.entries(payload)) {
|
|
40
|
+
if (key.startsWith("_"))
|
|
41
|
+
continue;
|
|
42
|
+
copy[key] = structuredClone(value);
|
|
43
|
+
}
|
|
44
|
+
return copy;
|
|
45
|
+
}
|
|
46
|
+
/** Flatten one document into the target database. */
|
|
47
|
+
async function flattenDocument(source, target, context, options, reporter) {
|
|
48
|
+
const { sourceDocId, targetDocId } = context;
|
|
49
|
+
reporter.emit("planning", `Reading ${sourceDocId} for a flattened copy`);
|
|
50
|
+
const sourceDoc = options.atTimestamp != null
|
|
51
|
+
? await source.db.getDocumentAtTimestamp(sourceDocId, options.atTimestamp)
|
|
52
|
+
: await source.db.getDocument(sourceDocId);
|
|
53
|
+
if (!sourceDoc) {
|
|
54
|
+
throw new Error(options.atTimestamp != null
|
|
55
|
+
? `Document ${sourceDocId} did not exist at timestamp ${options.atTimestamp}.`
|
|
56
|
+
: `Document ${sourceDocId} was not found in database ${context.sourceDbId}.`);
|
|
57
|
+
}
|
|
58
|
+
const usesGeneratedId = (options.targetDocId ?? "new") === "new";
|
|
59
|
+
if (!usesGeneratedId && !types_1.CUSTOM_DOC_ID_REGEX.test(targetDocId)) {
|
|
60
|
+
// Ids minted by MindooDB may start with a digit, which the caller-provided
|
|
61
|
+
// id rules reject. Say so here rather than letting createDocument fail with
|
|
62
|
+
// a message that gives no hint about the copy.
|
|
63
|
+
throw new Error(`Cannot flatten into document id '${targetDocId}': caller-provided ids must ` +
|
|
64
|
+
"match /^[A-Za-z][A-Za-z0-9_]*$/. Copy with mode 'history' to keep a " +
|
|
65
|
+
"MindooDB-generated id, or pass an explicit targetDocId that satisfies the rule.");
|
|
66
|
+
}
|
|
67
|
+
const copiedByPublicKey = options.signingKeyPair?.publicKey ??
|
|
68
|
+
(await target.tenant.getCurrentUserId()).userSigningPublicKey;
|
|
69
|
+
const payload = copyablePayload(sourceDoc.getData());
|
|
70
|
+
let provenance;
|
|
71
|
+
if (options.provenance !== false) {
|
|
72
|
+
provenance = {
|
|
73
|
+
copiedAt: Date.now(),
|
|
74
|
+
sourceTenantId: context.sourceTenantId,
|
|
75
|
+
sourceDbId: context.sourceDbId,
|
|
76
|
+
sourceDocId,
|
|
77
|
+
copiedByPublicKey,
|
|
78
|
+
};
|
|
79
|
+
if (options.atTimestamp != null) {
|
|
80
|
+
// Automerge rejects an explicit `undefined`, so optional fields have to be
|
|
81
|
+
// left out entirely rather than assigned an absent value.
|
|
82
|
+
provenance.sourceAtTimestamp = options.atTimestamp;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const includeAttachments = options.includeAttachments !== false;
|
|
86
|
+
const sourceAttachments = includeAttachments ? sourceDoc.getAttachments() : [];
|
|
87
|
+
// Fold the payload into the `doc_create` change instead of writing a second
|
|
88
|
+
// entry for it. A caller-provided id normally cannot do this: it is seeded
|
|
89
|
+
// from a hard-coded Automerge change so independent replicas converge on the
|
|
90
|
+
// same hash, and baking content into that change would diverge it per
|
|
91
|
+
// document — `createDocument` rejects the combination. `assumeUniqueId` is
|
|
92
|
+
// the caller's assertion that concurrent same-id creation cannot happen, so
|
|
93
|
+
// convergence is moot and the create can take the generated-id path.
|
|
94
|
+
//
|
|
95
|
+
// `copyablePayload` has already dropped every `_`-prefixed key, so nothing is
|
|
96
|
+
// silently lost to `initialValues`' own reserved-field sanitizer.
|
|
97
|
+
const assumeUniqueId = !usesGeneratedId && options.assumeUniqueTargetDocId === true;
|
|
98
|
+
const seedPayloadIntoCreate = usesGeneratedId || assumeUniqueId;
|
|
99
|
+
const idsBefore = new Set((await target.store.findNewEntriesForDoc([], targetDocId)).map((entry) => entry.id));
|
|
100
|
+
reporter.emit("transferring", `Writing flattened copy as ${targetDocId}`);
|
|
101
|
+
const targetDoc = await target.db.createDocument({
|
|
102
|
+
...(usesGeneratedId
|
|
103
|
+
? { idPrefix: options.idPrefix }
|
|
104
|
+
: { id: targetDocId, assumeUniqueId }),
|
|
105
|
+
...(seedPayloadIntoCreate ? { initialValues: payload } : {}),
|
|
106
|
+
decryptionKeyId: context.targetDecryptionKeyId,
|
|
107
|
+
signingKeyPair: options.signingKeyPair,
|
|
108
|
+
signingKeyPassword: options.signingKeyPassword,
|
|
109
|
+
bypassAccessControlPrecheck: options.bypassAccessControlPrecheck,
|
|
110
|
+
});
|
|
111
|
+
// Whatever the create could not carry. `_provenance` is in MindooDB's
|
|
112
|
+
// reserved namespace, which `initialValues` refuses to seed, so asking for
|
|
113
|
+
// provenance costs the second entry — the price of keeping the marker out of
|
|
114
|
+
// the caller's field namespace, where a later re-copy would treat it as
|
|
115
|
+
// ordinary content.
|
|
116
|
+
const needsFollowUpChange = !seedPayloadIntoCreate || provenance !== undefined || sourceAttachments.length > 0;
|
|
117
|
+
if (needsFollowUpChange) {
|
|
118
|
+
await target.db.changeDoc(targetDoc, async (draft) => {
|
|
119
|
+
if (!seedPayloadIntoCreate) {
|
|
120
|
+
Object.assign(draft.getData(), payload);
|
|
121
|
+
}
|
|
122
|
+
if (provenance) {
|
|
123
|
+
draft.getData()[PROVENANCE_FIELD] = provenance;
|
|
124
|
+
}
|
|
125
|
+
for (const attachment of sourceAttachments) {
|
|
126
|
+
if (options.signal?.aborted)
|
|
127
|
+
break;
|
|
128
|
+
await draft.addAttachmentStream(sourceDoc.streamAttachment(attachment.attachmentId), attachment.fileName, attachment.mimeType);
|
|
129
|
+
}
|
|
130
|
+
}, {
|
|
131
|
+
signingKeyPair: options.signingKeyPair,
|
|
132
|
+
signingKeyPassword: options.signingKeyPassword,
|
|
133
|
+
bypassAccessControlPrecheck: options.bypassAccessControlPrecheck,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
const written = (await target.store.findNewEntriesForDoc([], targetDoc.getId())).filter((entry) => !idsBefore.has(entry.id));
|
|
137
|
+
reporter.copiedEntries += written.length;
|
|
138
|
+
for (const entry of written) {
|
|
139
|
+
reporter.copiedBytes += entry.encryptedSize;
|
|
140
|
+
}
|
|
141
|
+
reporter.copiedAttachments += sourceAttachments.length;
|
|
142
|
+
reporter.emit("complete", `Flattened ${sourceDocId} into ${targetDoc.getId()}`);
|
|
143
|
+
return {
|
|
144
|
+
sourceDocId,
|
|
145
|
+
targetDocId: targetDoc.getId(),
|
|
146
|
+
strategy: "flatten",
|
|
147
|
+
authorshipPreserved: false,
|
|
148
|
+
mergedIntoExisting: context.targetHasDocId,
|
|
149
|
+
copiedEntries: written.length,
|
|
150
|
+
copiedBytes: written.reduce((total, entry) => total + entry.encryptedSize, 0),
|
|
151
|
+
copiedAttachments: sourceAttachments.length,
|
|
152
|
+
cancelled: options.signal?.aborted ?? false,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=flatten.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flatten.js","sourceRoot":"","sources":["../../../src/core/copy/flatten.ts"],"names":[],"mappings":"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"}
|