mindoodb 0.0.66 → 0.0.69

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.
Files changed (86) hide show
  1. package/README-server.md +77 -16
  2. package/README.md +41 -58
  3. package/dist/appendonlystores/network/HttpTransport.d.ts +2 -0
  4. package/dist/appendonlystores/network/HttpTransport.d.ts.map +1 -1
  5. package/dist/appendonlystores/network/HttpTransport.js +31 -6
  6. package/dist/appendonlystores/network/HttpTransport.js.map +1 -1
  7. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.d.ts +18 -8
  8. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.d.ts.map +1 -1
  9. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.js +30 -16
  10. package/dist/appendonlystores/network/ServerNetworkContentAddressedStore.js.map +1 -1
  11. package/dist/browser/index.mjs +29 -0
  12. package/dist/core/BaseMindooDB.d.ts +168 -8
  13. package/dist/core/BaseMindooDB.d.ts.map +1 -1
  14. package/dist/core/BaseMindooDB.js +763 -74
  15. package/dist/core/BaseMindooDB.js.map +1 -1
  16. package/dist/core/appendonlystores/network/IrohNetworkTransport.d.ts.map +1 -1
  17. package/dist/core/appendonlystores/network/IrohNetworkTransport.js +5 -1
  18. package/dist/core/appendonlystores/network/IrohNetworkTransport.js.map +1 -1
  19. package/dist/core/appendonlystores/network/IrohStreamIO.d.ts +10 -0
  20. package/dist/core/appendonlystores/network/IrohStreamIO.d.ts.map +1 -1
  21. package/dist/core/appendonlystores/network/IrohStreamIO.js.map +1 -1
  22. package/dist/core/appendonlystores/network/types.d.ts +20 -0
  23. package/dist/core/appendonlystores/network/types.d.ts.map +1 -1
  24. package/dist/core/appendonlystores/network/types.js +33 -0
  25. package/dist/core/appendonlystores/network/types.js.map +1 -1
  26. package/dist/core/appendonlystores/syncStores.d.ts +80 -2
  27. package/dist/core/appendonlystores/syncStores.d.ts.map +1 -1
  28. package/dist/core/appendonlystores/syncStores.js +82 -9
  29. package/dist/core/appendonlystores/syncStores.js.map +1 -1
  30. package/dist/core/appendonlystores/types.d.ts +88 -6
  31. package/dist/core/appendonlystores/types.d.ts.map +1 -1
  32. package/dist/core/appendonlystores/types.js +61 -1
  33. package/dist/core/appendonlystores/types.js.map +1 -1
  34. package/dist/core/builtinDbInvariants.d.ts +8 -5
  35. package/dist/core/builtinDbInvariants.d.ts.map +1 -1
  36. package/dist/core/builtinDbInvariants.js +8 -5
  37. package/dist/core/builtinDbInvariants.js.map +1 -1
  38. package/dist/core/expressions/evaluateExpression.d.ts.map +1 -1
  39. package/dist/core/expressions/evaluateExpression.js +7 -1
  40. package/dist/core/expressions/evaluateExpression.js.map +1 -1
  41. package/dist/core/index.d.ts +8 -0
  42. package/dist/core/index.d.ts.map +1 -1
  43. package/dist/core/index.js +45 -11
  44. package/dist/core/index.js.map +1 -1
  45. package/dist/core/indexing/summary/DocumentSummaryStore.d.ts +26 -3
  46. package/dist/core/indexing/summary/DocumentSummaryStore.d.ts.map +1 -1
  47. package/dist/core/indexing/summary/DocumentSummaryStore.js +100 -15
  48. package/dist/core/indexing/summary/DocumentSummaryStore.js.map +1 -1
  49. package/dist/core/indexing/summary/extractSummaryFields.d.ts +10 -1
  50. package/dist/core/indexing/summary/extractSummaryFields.d.ts.map +1 -1
  51. package/dist/core/indexing/summary/extractSummaryFields.js +34 -7
  52. package/dist/core/indexing/summary/extractSummaryFields.js.map +1 -1
  53. package/dist/core/indexing/summary/types.d.ts +6 -0
  54. package/dist/core/indexing/summary/types.d.ts.map +1 -1
  55. package/dist/core/indexing/summary/types.js +8 -1
  56. package/dist/core/indexing/summary/types.js.map +1 -1
  57. package/dist/core/quarantine/QuarantineDocument.d.ts +211 -0
  58. package/dist/core/quarantine/QuarantineDocument.d.ts.map +1 -0
  59. package/dist/core/quarantine/QuarantineDocument.js +314 -0
  60. package/dist/core/quarantine/QuarantineDocument.js.map +1 -0
  61. package/dist/core/quarantine/QuarantineView.d.ts +90 -0
  62. package/dist/core/quarantine/QuarantineView.d.ts.map +1 -0
  63. package/dist/core/quarantine/QuarantineView.js +106 -0
  64. package/dist/core/quarantine/QuarantineView.js.map +1 -0
  65. package/dist/core/quarantine/recordPushRejections.d.ts +73 -0
  66. package/dist/core/quarantine/recordPushRejections.d.ts.map +1 -0
  67. package/dist/core/quarantine/recordPushRejections.js +112 -0
  68. package/dist/core/quarantine/recordPushRejections.js.map +1 -0
  69. package/dist/core/quarantine/resubmitQuarantined.d.ts +81 -0
  70. package/dist/core/quarantine/resubmitQuarantined.d.ts.map +1 -0
  71. package/dist/core/quarantine/resubmitQuarantined.js +120 -0
  72. package/dist/core/quarantine/resubmitQuarantined.js.map +1 -0
  73. package/dist/core/types.d.ts +132 -4
  74. package/dist/core/types.d.ts.map +1 -1
  75. package/dist/core/types.js.map +1 -1
  76. package/dist/core/values.d.ts +72 -0
  77. package/dist/core/values.d.ts.map +1 -0
  78. package/dist/core/values.js +107 -0
  79. package/dist/core/values.js.map +1 -0
  80. package/dist/node/server/MindooDBServer.d.ts.map +1 -1
  81. package/dist/node/server/MindooDBServer.js +6 -1
  82. package/dist/node/server/MindooDBServer.js.map +1 -1
  83. package/dist/node/server/peer/PeerReplicator.d.ts.map +1 -1
  84. package/dist/node/server/peer/PeerReplicator.js +35 -15
  85. package/dist/node/server/peer/PeerReplicator.js.map +1 -1
  86. package/package.json +1 -1
@@ -0,0 +1,90 @@
1
+ /**
2
+ * One list for both kinds of quarantine.
3
+ *
4
+ * A change can be held back in two directions, and from the user's side they are
5
+ * the same complaint — "something I have is not where it should be, and nobody
6
+ * is retrying it":
7
+ *
8
+ * - **inbound** — an entry arrived here (over peer-to-peer sync, usually) and
9
+ * was refused at materialization, so it sits in the local store without
10
+ * affecting any document. Recorded in the metadata checkpoint, local to this
11
+ * device, keyed per document. See `QuarantineRecord`.
12
+ * - **outbound** — an entry was written here and the server refused to ingest
13
+ * it, so the push moved on without it. Recorded as a `qtn_` document in
14
+ * `userdirectory`, which syncs to the whole tenant. See
15
+ * {@link QuarantineRecordDoc}.
16
+ *
17
+ * The two live in different places for good reasons — an inbound refusal is a
18
+ * local verdict that every replica reaches on its own and would be pointless to
19
+ * broadcast, whereas an outbound one is exactly what other members need to see —
20
+ * but a user looking for a missing change does not know which of the two
21
+ * happened. So the storage stays split and the *reading* is unified here.
22
+ *
23
+ * ## Why the directions keep their own vocabulary
24
+ *
25
+ * Their reasons are genuinely different: an inbound refusal names the check that
26
+ * failed on this device (`tier2_denied`, `cascade_dependent`, …), an outbound one
27
+ * names what the server said (`policy`, `purged`, …). Flattening both into one
28
+ * enum would invent equivalences that do not hold, so {@link QuarantineViewRecord}
29
+ * is a discriminated union: `direction` tells a caller which vocabulary `reason`
30
+ * is drawn from, and which fields are even present.
31
+ */
32
+ import type { QuarantineRecord, QuarantineReason } from "../accesscontrol/materializationGuard";
33
+ import type { PutRejectionClass } from "../appendonlystores/types";
34
+ import { type QuarantineRecordDoc } from "./QuarantineDocument";
35
+ /** Which way the refused change was going. */
36
+ export type QuarantineDirection = "inbound" | "outbound";
37
+ /** What both directions can answer. */
38
+ interface QuarantineViewCommon {
39
+ /** Database the refused entries belong to. */
40
+ dbid: string;
41
+ /** Document the refused entries belong to. */
42
+ documentId: string;
43
+ /** The refusing side's own wording, for the audit view. */
44
+ detail: string;
45
+ /** Refused entry ids, capped at {@link MAX_QUARANTINED_ENTRY_IDS}. */
46
+ entryIds: string[];
47
+ /** How many refused entries did not fit into {@link entryIds}. */
48
+ omittedEntryCount: number;
49
+ /** When this refusal was first seen. */
50
+ firstSeenAt: number;
51
+ /** When it was last confirmed. */
52
+ updatedAt: number;
53
+ }
54
+ /**
55
+ * One row of the unified quarantine view: all the entries of one document held
56
+ * back for one reason, in one direction.
57
+ */
58
+ export type QuarantineViewRecord = (QuarantineViewCommon & {
59
+ direction: "inbound";
60
+ /** Which materialization check refused the entries on this device. */
61
+ reason: QuarantineReason;
62
+ }) | (QuarantineViewCommon & {
63
+ direction: "outbound";
64
+ /** How the target classified its refusal. */
65
+ reason: PutRejectionClass;
66
+ /** Fingerprint of the signing key whose push was refused. */
67
+ signingKeyFingerprint: string;
68
+ /** The `qtn_` document holding this record, so a caller can withdraw it. */
69
+ recordDocId: string;
70
+ });
71
+ /**
72
+ * Fold the local materialization log into view rows.
73
+ *
74
+ * The log holds one record per entry, because an inbound refusal is decided per
75
+ * entry; the view groups them per (database, document, reason) so one poisoned
76
+ * entry dragging fifty dependents along reads as one problem rather than
77
+ * fifty-one — matching how the outbound records are already shaped.
78
+ */
79
+ export declare function inboundQuarantineView(log: readonly QuarantineRecord[]): QuarantineViewRecord[];
80
+ /** Turn verified `qtn_` records into view rows. */
81
+ export declare function outboundQuarantineView(records: readonly QuarantineRecordDoc[]): QuarantineViewRecord[];
82
+ /**
83
+ * The whole quarantine view, newest first.
84
+ *
85
+ * Newest first because the list is read when a change has gone missing, and the
86
+ * change someone is looking for is almost always the last one they made.
87
+ */
88
+ export declare function mergeQuarantineViews(...groups: readonly (readonly QuarantineViewRecord[])[]): QuarantineViewRecord[];
89
+ export {};
90
+ //# sourceMappingURL=QuarantineView.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"QuarantineView.d.ts","sourceRoot":"","sources":["../../../src/core/quarantine/QuarantineView.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAChG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAA6B,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAE3F,8CAA8C;AAC9C,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,UAAU,CAAC;AAEzD,uCAAuC;AACvC,UAAU,oBAAoB;IAC5B,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,8CAA8C;IAC9C,UAAU,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,MAAM,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,kEAAkE;IAClE,iBAAiB,EAAE,MAAM,CAAC;IAC1B,wCAAwC;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,kCAAkC;IAClC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,MAAM,oBAAoB,GAC5B,CAAC,oBAAoB,GAAG;IACtB,SAAS,EAAE,SAAS,CAAC;IACrB,sEAAsE;IACtE,MAAM,EAAE,gBAAgB,CAAC;CAC1B,CAAC,GACF,CAAC,oBAAoB,GAAG;IACtB,SAAS,EAAE,UAAU,CAAC;IACtB,6CAA6C;IAC7C,MAAM,EAAE,iBAAiB,CAAC;IAC1B,6DAA6D;IAC7D,qBAAqB,EAAE,MAAM,CAAC;IAC9B,4EAA4E;IAC5E,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC,CAAC;AAEP;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,SAAS,gBAAgB,EAAE,GAC/B,oBAAoB,EAAE,CA4BxB;AAED,mDAAmD;AACnD,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,SAAS,mBAAmB,EAAE,GACtC,oBAAoB,EAAE,CAcxB;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,MAAM,EAAE,SAAS,CAAC,SAAS,oBAAoB,EAAE,CAAC,EAAE,GACtD,oBAAoB,EAAE,CAUxB"}
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ /**
3
+ * One list for both kinds of quarantine.
4
+ *
5
+ * A change can be held back in two directions, and from the user's side they are
6
+ * the same complaint — "something I have is not where it should be, and nobody
7
+ * is retrying it":
8
+ *
9
+ * - **inbound** — an entry arrived here (over peer-to-peer sync, usually) and
10
+ * was refused at materialization, so it sits in the local store without
11
+ * affecting any document. Recorded in the metadata checkpoint, local to this
12
+ * device, keyed per document. See `QuarantineRecord`.
13
+ * - **outbound** — an entry was written here and the server refused to ingest
14
+ * it, so the push moved on without it. Recorded as a `qtn_` document in
15
+ * `userdirectory`, which syncs to the whole tenant. See
16
+ * {@link QuarantineRecordDoc}.
17
+ *
18
+ * The two live in different places for good reasons — an inbound refusal is a
19
+ * local verdict that every replica reaches on its own and would be pointless to
20
+ * broadcast, whereas an outbound one is exactly what other members need to see —
21
+ * but a user looking for a missing change does not know which of the two
22
+ * happened. So the storage stays split and the *reading* is unified here.
23
+ *
24
+ * ## Why the directions keep their own vocabulary
25
+ *
26
+ * Their reasons are genuinely different: an inbound refusal names the check that
27
+ * failed on this device (`tier2_denied`, `cascade_dependent`, …), an outbound one
28
+ * names what the server said (`policy`, `purged`, …). Flattening both into one
29
+ * enum would invent equivalences that do not hold, so {@link QuarantineViewRecord}
30
+ * is a discriminated union: `direction` tells a caller which vocabulary `reason`
31
+ * is drawn from, and which fields are even present.
32
+ */
33
+ Object.defineProperty(exports, "__esModule", { value: true });
34
+ exports.inboundQuarantineView = inboundQuarantineView;
35
+ exports.outboundQuarantineView = outboundQuarantineView;
36
+ exports.mergeQuarantineViews = mergeQuarantineViews;
37
+ const QuarantineDocument_1 = require("./QuarantineDocument");
38
+ /**
39
+ * Fold the local materialization log into view rows.
40
+ *
41
+ * The log holds one record per entry, because an inbound refusal is decided per
42
+ * entry; the view groups them per (database, document, reason) so one poisoned
43
+ * entry dragging fifty dependents along reads as one problem rather than
44
+ * fifty-one — matching how the outbound records are already shaped.
45
+ */
46
+ function inboundQuarantineView(log) {
47
+ const rows = new Map();
48
+ for (const rec of log) {
49
+ const key = `${rec.dbid}\u0000${rec.docId}\u0000${rec.reason}`;
50
+ const existing = rows.get(key);
51
+ if (!existing) {
52
+ rows.set(key, {
53
+ direction: "inbound",
54
+ dbid: rec.dbid,
55
+ documentId: rec.docId,
56
+ reason: rec.reason,
57
+ detail: rec.detail,
58
+ entryIds: [rec.entryId],
59
+ omittedEntryCount: 0,
60
+ firstSeenAt: rec.recordedAt,
61
+ updatedAt: rec.recordedAt,
62
+ });
63
+ continue;
64
+ }
65
+ if (existing.entryIds.length < QuarantineDocument_1.MAX_QUARANTINED_ENTRY_IDS) {
66
+ existing.entryIds.push(rec.entryId);
67
+ }
68
+ else {
69
+ existing.omittedEntryCount += 1;
70
+ }
71
+ existing.firstSeenAt = Math.min(existing.firstSeenAt, rec.recordedAt);
72
+ existing.updatedAt = Math.max(existing.updatedAt, rec.recordedAt);
73
+ }
74
+ return [...rows.values()];
75
+ }
76
+ /** Turn verified `qtn_` records into view rows. */
77
+ function outboundQuarantineView(records) {
78
+ return records.map((rec) => ({
79
+ direction: "outbound",
80
+ dbid: rec.dbid,
81
+ documentId: rec.quarantinedDocId,
82
+ reason: rec.rejectionClass,
83
+ detail: rec.reason,
84
+ entryIds: [...rec.entryIds],
85
+ omittedEntryCount: rec.omittedEntryCount,
86
+ firstSeenAt: rec.firstSeenAt,
87
+ updatedAt: rec.updatedAt,
88
+ signingKeyFingerprint: rec.signingKeyFingerprint,
89
+ recordDocId: rec.docId,
90
+ }));
91
+ }
92
+ /**
93
+ * The whole quarantine view, newest first.
94
+ *
95
+ * Newest first because the list is read when a change has gone missing, and the
96
+ * change someone is looking for is almost always the last one they made.
97
+ */
98
+ function mergeQuarantineViews(...groups) {
99
+ const merged = groups.flat();
100
+ merged.sort((a, b) => b.updatedAt - a.updatedAt ||
101
+ a.dbid.localeCompare(b.dbid) ||
102
+ a.documentId.localeCompare(b.documentId) ||
103
+ a.reason.localeCompare(b.reason));
104
+ return merged;
105
+ }
106
+ //# sourceMappingURL=QuarantineView.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"QuarantineView.js","sourceRoot":"","sources":["../../../src/core/quarantine/QuarantineView.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Turn what a push left behind into quarantine records.
3
+ *
4
+ * A client push uses the `"advance"` rejection policy: an entry the server
5
+ * refuses is stepped over so the rest of the database keeps syncing. That is the
6
+ * right trade — one refused entry must not stop a device from ever pushing
7
+ * again — but it means the cursor will never offer that entry a second time. If
8
+ * nothing writes it down at this moment, the change is gone with the process.
9
+ *
10
+ * This is the one place that happens, and it is deliberately *not* inside
11
+ * `pushChangesTo`: the records live in `userdirectory`, so recording them is a
12
+ * write to a different database than the one being pushed. A core sync call that
13
+ * quietly wrote somewhere else would be a surprise to every caller, and an
14
+ * expensive one — it would open the directory on every push. Callers that want
15
+ * the records ask for them.
16
+ */
17
+ import type { MindooDB, SigningKeyPair } from "../types";
18
+ import type { PutRejectionClass, RejectedPutEntry } from "../appendonlystores/types";
19
+ /**
20
+ * Stands in for the document of an entry whose id follows neither the document
21
+ * nor the attachment scheme. Such an entry still has to be recorded — losing it
22
+ * silently is the whole problem — and `$` cannot begin a real document id, so
23
+ * this cannot collide with one.
24
+ */
25
+ export declare const UNKNOWN_QUARANTINED_DOC_ID = "$unknown";
26
+ /** What {@link recordPushRejections} managed to write. */
27
+ export interface PushQuarantineResult {
28
+ /** One per record written or extended. */
29
+ recorded: Array<{
30
+ documentId: string;
31
+ rejectionClass: PutRejectionClass;
32
+ entryCount: number;
33
+ }>;
34
+ /**
35
+ * Refused entries that belong to a quarantine record themselves. Counted
36
+ * rather than recorded: a record about a refused record would be refused in
37
+ * turn, and each refusal would name a new document, so one server that turns
38
+ * away `qtn_` writes could otherwise make a device generate them without end.
39
+ */
40
+ skippedOwnRecords: number;
41
+ /** Records that could not be written, so a caller can log or retry them. */
42
+ failures: Array<{
43
+ documentId: string;
44
+ rejectionClass: PutRejectionClass;
45
+ error: string;
46
+ }>;
47
+ }
48
+ /**
49
+ * Write a quarantine record for every document a push was refused on.
50
+ *
51
+ * Grouped by (document, class) to match how the records are keyed, so a refusal
52
+ * that took a document's whole causal tail with it becomes one record holding
53
+ * many entry ids instead of one record per entry.
54
+ *
55
+ * Never throws: a device that cannot write the record has still lost the entry,
56
+ * and a failure here must not turn a partially-successful push into a failed
57
+ * one. Failures come back in {@link PushQuarantineResult.failures}.
58
+ */
59
+ export declare function recordPushRejections(input: {
60
+ /** Id of the database whose push was refused. Not `userdirectory` as a rule. */
61
+ dbid: string;
62
+ /** The tenant's `userdirectory`, where the records live. */
63
+ userdirectoryDb: MindooDB;
64
+ /** What the push reported as refused. */
65
+ rejected: readonly RejectedPutEntry[];
66
+ /** Signing public key (PEM) of this device. */
67
+ signingPublicKey: string;
68
+ subtle: SubtleCrypto;
69
+ signingKeyPair?: SigningKeyPair;
70
+ signingKeyPassword?: string;
71
+ now?: number;
72
+ }): Promise<PushQuarantineResult>;
73
+ //# sourceMappingURL=recordPushRejections.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recordPushRejections.d.ts","sourceRoot":"","sources":["../../../src/core/quarantine/recordPushRejections.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AACzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAKrF;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,aAAa,CAAC;AAErD,0DAA0D;AAC1D,MAAM,WAAW,oBAAoB;IACnC,0CAA0C;IAC1C,QAAQ,EAAE,KAAK,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;QACnB,cAAc,EAAE,iBAAiB,CAAC;QAClC,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC,CAAC;IACH;;;;;OAKG;IACH,iBAAiB,EAAE,MAAM,CAAC;IAC1B,4EAA4E;IAC5E,QAAQ,EAAE,KAAK,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;QACnB,cAAc,EAAE,iBAAiB,CAAC;QAClC,KAAK,EAAE,MAAM,CAAC;KACf,CAAC,CAAC;CACJ;AAWD;;;;;;;;;;GAUG;AACH,wBAAsB,oBAAoB,CAAC,KAAK,EAAE;IAChD,gFAAgF;IAChF,IAAI,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,eAAe,EAAE,QAAQ,CAAC;IAC1B,yCAAyC;IACzC,QAAQ,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACtC,+CAA+C;IAC/C,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,YAAY,CAAC;IACrB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAyEhC"}
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ /**
3
+ * Turn what a push left behind into quarantine records.
4
+ *
5
+ * A client push uses the `"advance"` rejection policy: an entry the server
6
+ * refuses is stepped over so the rest of the database keeps syncing. That is the
7
+ * right trade — one refused entry must not stop a device from ever pushing
8
+ * again — but it means the cursor will never offer that entry a second time. If
9
+ * nothing writes it down at this moment, the change is gone with the process.
10
+ *
11
+ * This is the one place that happens, and it is deliberately *not* inside
12
+ * `pushChangesTo`: the records live in `userdirectory`, so recording them is a
13
+ * write to a different database than the one being pushed. A core sync call that
14
+ * quietly wrote somewhere else would be a surprise to every caller, and an
15
+ * expensive one — it would open the directory on every push. Callers that want
16
+ * the records ask for them.
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.UNKNOWN_QUARANTINED_DOC_ID = void 0;
20
+ exports.recordPushRejections = recordPushRejections;
21
+ const types_1 = require("../appendonlystores/types");
22
+ const idGeneration_1 = require("../utils/idGeneration");
23
+ const QuarantineDocument_1 = require("./QuarantineDocument");
24
+ /**
25
+ * Stands in for the document of an entry whose id follows neither the document
26
+ * nor the attachment scheme. Such an entry still has to be recorded — losing it
27
+ * silently is the whole problem — and `$` cannot begin a real document id, so
28
+ * this cannot collide with one.
29
+ */
30
+ exports.UNKNOWN_QUARANTINED_DOC_ID = "$unknown";
31
+ /** Which document a refused entry belongs to. */
32
+ function documentIdOf(entryId) {
33
+ return ((0, idGeneration_1.parseDocEntryId)(entryId)?.docId ??
34
+ (0, idGeneration_1.parseAttachmentChunkId)(entryId)?.docId ??
35
+ exports.UNKNOWN_QUARANTINED_DOC_ID);
36
+ }
37
+ /**
38
+ * Write a quarantine record for every document a push was refused on.
39
+ *
40
+ * Grouped by (document, class) to match how the records are keyed, so a refusal
41
+ * that took a document's whole causal tail with it becomes one record holding
42
+ * many entry ids instead of one record per entry.
43
+ *
44
+ * Never throws: a device that cannot write the record has still lost the entry,
45
+ * and a failure here must not turn a partially-successful push into a failed
46
+ * one. Failures come back in {@link PushQuarantineResult.failures}.
47
+ */
48
+ async function recordPushRejections(input) {
49
+ const result = {
50
+ recorded: [],
51
+ skippedOwnRecords: 0,
52
+ failures: [],
53
+ };
54
+ if (input.rejected.length === 0) {
55
+ return result;
56
+ }
57
+ const groups = new Map();
58
+ for (const entry of input.rejected) {
59
+ const documentId = documentIdOf(entry.id);
60
+ if ((0, QuarantineDocument_1.isQuarantineDocId)(documentId)) {
61
+ result.skippedOwnRecords += 1;
62
+ continue;
63
+ }
64
+ const rejectionClass = (0, types_1.rejectionClassOf)(entry);
65
+ const key = `${documentId}\u0000${rejectionClass}`;
66
+ const group = groups.get(key);
67
+ if (group) {
68
+ group.entryIds.push(entry.id);
69
+ }
70
+ else {
71
+ groups.set(key, {
72
+ documentId,
73
+ rejectionClass,
74
+ // The first refusal's wording stands for the group: the target repeats
75
+ // itself across a causal cascade, and the class is what differs.
76
+ reason: entry.reason,
77
+ entryIds: [entry.id],
78
+ });
79
+ }
80
+ }
81
+ for (const group of groups.values()) {
82
+ try {
83
+ await (0, QuarantineDocument_1.recordPushQuarantine)({
84
+ db: input.userdirectoryDb,
85
+ signingPublicKey: input.signingPublicKey,
86
+ dbid: input.dbid,
87
+ quarantinedDocId: group.documentId,
88
+ rejectionClass: group.rejectionClass,
89
+ reason: group.reason,
90
+ entryIds: group.entryIds,
91
+ subtle: input.subtle,
92
+ signingKeyPair: input.signingKeyPair,
93
+ signingKeyPassword: input.signingKeyPassword,
94
+ now: input.now,
95
+ });
96
+ result.recorded.push({
97
+ documentId: group.documentId,
98
+ rejectionClass: group.rejectionClass,
99
+ entryCount: group.entryIds.length,
100
+ });
101
+ }
102
+ catch (error) {
103
+ result.failures.push({
104
+ documentId: group.documentId,
105
+ rejectionClass: group.rejectionClass,
106
+ error: error instanceof Error ? error.message : String(error),
107
+ });
108
+ }
109
+ }
110
+ return result;
111
+ }
112
+ //# sourceMappingURL=recordPushRejections.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recordPushRejections.js","sourceRoot":"","sources":["../../../src/core/quarantine/recordPushRejections.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Offer quarantined entries to the server again.
3
+ *
4
+ * The push cursor has long since moved past these entries, so nothing will ever
5
+ * offer them on its own — that is the whole reason the records exist. Re-submit
6
+ * therefore goes straight to the target store with the specific entries, rather
7
+ * than rewinding the cursor: rewinding would re-scan the entire database to find
8
+ * a handful of entries, and would drag every *other* entry behind that position
9
+ * back through the refusal that made the cursor advance in the first place.
10
+ *
11
+ * ## Not every record may be tried again
12
+ *
13
+ * Filtered through {@link isResubmittableRejection}, and one of the exclusions is
14
+ * a matter of correctness rather than thrift: a `"purged"` refusal means the
15
+ * document's history was deliberately erased, so re-offering the entry is an
16
+ * attempt to undo that erasure. This function must never do it, whether it is
17
+ * called by a scheduler or by someone pressing a button.
18
+ */
19
+ import type { ContentAddressedStore, PutRejectionClass, RejectedPutEntry } from "../appendonlystores/types";
20
+ import type { MindooDB } from "../types";
21
+ import { type QuarantineRecordDoc } from "./QuarantineDocument";
22
+ /**
23
+ * How many records one pass tries.
24
+ *
25
+ * A pass runs whenever the tenant directory changes, and a record that still
26
+ * fails costs a round trip to learn nothing new. The budget keeps a backlog from
27
+ * turning every directory write into a burst of pushes; the remainder is tried
28
+ * by the next pass.
29
+ */
30
+ export declare const DEFAULT_MAX_RESUBMIT_RECORDS = 25;
31
+ /** What happened to one record. */
32
+ export interface ResubmitOutcome {
33
+ /** The `qtn_` document this came from. */
34
+ recordDocId: string;
35
+ documentId: string;
36
+ rejectionClass: PutRejectionClass;
37
+ /** Entries the target took this time. */
38
+ accepted: string[];
39
+ /** Entries refused again. */
40
+ refused: RejectedPutEntry[];
41
+ /** Entries no longer in the local store, so nothing can be offered for them. */
42
+ missing: string[];
43
+ /** Whether the record was withdrawn. */
44
+ cleared: boolean;
45
+ /** Set when the attempt itself failed, rather than the entries being refused. */
46
+ error?: string;
47
+ }
48
+ export interface ResubmitResult {
49
+ outcomes: ResubmitOutcome[];
50
+ /**
51
+ * Records whose class rules out another attempt — permanently refused, or
52
+ * about a document that was purged on purpose.
53
+ */
54
+ skippedFinal: number;
55
+ /** Records left for the next pass by {@link DEFAULT_MAX_RESUBMIT_RECORDS}. */
56
+ notAttempted: number;
57
+ }
58
+ /**
59
+ * Re-offer the entries named by `records`, all of which must describe the
60
+ * database that `localStore` and `remoteStore` are two ends of.
61
+ *
62
+ * A record is withdrawn only when nothing about it is refused any more. A record
63
+ * that is *partly* accepted is deliberately left standing rather than rewritten
64
+ * to name fewer entries: rewriting it is a write, a write is something to push,
65
+ * and a push is what brings this function round again. It over-reports until the
66
+ * rest passes too, which is the cheaper of the two inaccuracies.
67
+ *
68
+ * Never throws — a re-submit is a background courtesy, and one unreachable
69
+ * server must not take the caller down with it.
70
+ */
71
+ export declare function resubmitQuarantinedEntries(input: {
72
+ /** Records about one database. */
73
+ records: readonly QuarantineRecordDoc[];
74
+ localStore: ContentAddressedStore;
75
+ remoteStore: ContentAddressedStore;
76
+ /** Where the records live, so accepted ones can be withdrawn. */
77
+ userdirectoryDb: MindooDB;
78
+ subtle: SubtleCrypto;
79
+ maxRecords?: number;
80
+ }): Promise<ResubmitResult>;
81
+ //# sourceMappingURL=resubmitQuarantined.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resubmitQuarantined.d.ts","sourceRoot":"","sources":["../../../src/core/quarantine/resubmitQuarantined.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EACV,qBAAqB,EACrB,iBAAiB,EACjB,gBAAgB,EACjB,MAAM,2BAA2B,CAAC;AAGnC,OAAO,KAAK,EAAE,QAAQ,EAAc,MAAM,UAAU,CAAC;AACrD,OAAO,EAAyB,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEvF;;;;;;;GAOG;AACH,eAAO,MAAM,4BAA4B,KAAK,CAAC;AAE/C,mCAAmC;AACnC,MAAM,WAAW,eAAe;IAC9B,0CAA0C;IAC1C,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,iBAAiB,CAAC;IAClC,yCAAyC;IACzC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,6BAA6B;IAC7B,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,gFAAgF;IAChF,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,wCAAwC;IACxC,OAAO,EAAE,OAAO,CAAC;IACjB,iFAAiF;IACjF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B;;;OAGG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB,8EAA8E;IAC9E,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,0BAA0B,CAAC,KAAK,EAAE;IACtD,kCAAkC;IAClC,OAAO,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACxC,UAAU,EAAE,qBAAqB,CAAC;IAClC,WAAW,EAAE,qBAAqB,CAAC;IACnC,iEAAiE;IACjE,eAAe,EAAE,QAAQ,CAAC;IAC1B,MAAM,EAAE,YAAY,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,cAAc,CAAC,CAkE1B"}
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ /**
3
+ * Offer quarantined entries to the server again.
4
+ *
5
+ * The push cursor has long since moved past these entries, so nothing will ever
6
+ * offer them on its own — that is the whole reason the records exist. Re-submit
7
+ * therefore goes straight to the target store with the specific entries, rather
8
+ * than rewinding the cursor: rewinding would re-scan the entire database to find
9
+ * a handful of entries, and would drag every *other* entry behind that position
10
+ * back through the refusal that made the cursor advance in the first place.
11
+ *
12
+ * ## Not every record may be tried again
13
+ *
14
+ * Filtered through {@link isResubmittableRejection}, and one of the exclusions is
15
+ * a matter of correctness rather than thrift: a `"purged"` refusal means the
16
+ * document's history was deliberately erased, so re-offering the entry is an
17
+ * attempt to undo that erasure. This function must never do it, whether it is
18
+ * called by a scheduler or by someone pressing a button.
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.DEFAULT_MAX_RESUBMIT_RECORDS = void 0;
22
+ exports.resubmitQuarantinedEntries = resubmitQuarantinedEntries;
23
+ const types_1 = require("../appendonlystores/types");
24
+ const syncStores_1 = require("../appendonlystores/syncStores");
25
+ const QuarantineDocument_1 = require("./QuarantineDocument");
26
+ /**
27
+ * How many records one pass tries.
28
+ *
29
+ * A pass runs whenever the tenant directory changes, and a record that still
30
+ * fails costs a round trip to learn nothing new. The budget keeps a backlog from
31
+ * turning every directory write into a burst of pushes; the remainder is tried
32
+ * by the next pass.
33
+ */
34
+ exports.DEFAULT_MAX_RESUBMIT_RECORDS = 25;
35
+ /**
36
+ * Re-offer the entries named by `records`, all of which must describe the
37
+ * database that `localStore` and `remoteStore` are two ends of.
38
+ *
39
+ * A record is withdrawn only when nothing about it is refused any more. A record
40
+ * that is *partly* accepted is deliberately left standing rather than rewritten
41
+ * to name fewer entries: rewriting it is a write, a write is something to push,
42
+ * and a push is what brings this function round again. It over-reports until the
43
+ * rest passes too, which is the cheaper of the two inaccuracies.
44
+ *
45
+ * Never throws — a re-submit is a background courtesy, and one unreachable
46
+ * server must not take the caller down with it.
47
+ */
48
+ async function resubmitQuarantinedEntries(input) {
49
+ const budget = input.maxRecords ?? exports.DEFAULT_MAX_RESUBMIT_RECORDS;
50
+ const result = { outcomes: [], skippedFinal: 0, notAttempted: 0 };
51
+ const candidates = [];
52
+ for (const record of input.records) {
53
+ if ((0, types_1.isResubmittableRejection)(record.rejectionClass)) {
54
+ candidates.push(record);
55
+ }
56
+ else {
57
+ result.skippedFinal += 1;
58
+ }
59
+ }
60
+ if (candidates.length > budget) {
61
+ result.notAttempted = candidates.length - budget;
62
+ candidates.length = budget;
63
+ }
64
+ for (const record of candidates) {
65
+ const outcome = {
66
+ recordDocId: record.docId,
67
+ documentId: record.quarantinedDocId,
68
+ rejectionClass: record.rejectionClass,
69
+ accepted: [],
70
+ refused: [],
71
+ missing: [],
72
+ cleared: false,
73
+ };
74
+ result.outcomes.push(outcome);
75
+ try {
76
+ const entries = await input.localStore.getEntries([...record.entryIds]);
77
+ const found = new Map(entries.map((entry) => [entry.id, entry]));
78
+ outcome.missing = record.entryIds.filter((id) => !found.has(id));
79
+ if (entries.length === 0) {
80
+ // Every entry is gone locally. Nothing can ever be offered for this
81
+ // record, so leaving it would make it permanent.
82
+ await clearRecord(input, record);
83
+ outcome.cleared = true;
84
+ continue;
85
+ }
86
+ const { receipts, batchRejected } = (0, syncStores_1.normalizePutResult)(await input.remoteStore.putEntries(entries));
87
+ // Without this the entries stay provisional locally even though the target
88
+ // has now witnessed them, and their trusted time never settles.
89
+ if (receipts.length > 0 && input.localStore.applyWitnessReceipts) {
90
+ await input.localStore.applyWitnessReceipts(receipts);
91
+ }
92
+ const refusedIds = new Set(batchRejected.map((entry) => entry.id));
93
+ outcome.refused = batchRejected;
94
+ outcome.accepted = entries
95
+ .map((entry) => entry.id)
96
+ .filter((id) => !refusedIds.has(id));
97
+ if (outcome.refused.length === 0) {
98
+ await clearRecord(input, record);
99
+ outcome.cleared = true;
100
+ }
101
+ }
102
+ catch (error) {
103
+ outcome.error = error instanceof Error ? error.message : String(error);
104
+ }
105
+ }
106
+ return result;
107
+ }
108
+ async function clearRecord(input, record) {
109
+ await (0, QuarantineDocument_1.clearQuarantineRecord)({
110
+ db: input.userdirectoryDb,
111
+ key: {
112
+ signingKeyFingerprint: record.signingKeyFingerprint,
113
+ dbid: record.dbid,
114
+ quarantinedDocId: record.quarantinedDocId,
115
+ rejectionClass: record.rejectionClass,
116
+ },
117
+ subtle: input.subtle,
118
+ });
119
+ }
120
+ //# sourceMappingURL=resubmitQuarantined.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resubmitQuarantined.js","sourceRoot":"","sources":["../../../src/core/quarantine/resubmitQuarantined.ts"],"names":[],"mappings":"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"}