@semics-tech/mongolite 1.3.0 → 1.4.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.
Files changed (63) hide show
  1. package/README.md +79 -21
  2. package/dist/cjs/collection.js +8 -1
  3. package/dist/cjs/index.js +108 -1
  4. package/dist/cjs/mongo-client.js +18 -1
  5. package/dist/cjs/server.js +164 -0
  6. package/dist/cjs/sync/commands.js +126 -0
  7. package/dist/cjs/sync/diff.js +174 -0
  8. package/dist/cjs/sync/httpSink.js +159 -0
  9. package/dist/cjs/sync/index.js +40 -0
  10. package/dist/cjs/sync/mongoExecutor.js +306 -0
  11. package/dist/cjs/sync/mongoSink.js +161 -0
  12. package/dist/cjs/sync/outbox.js +358 -0
  13. package/dist/cjs/sync/protocol.js +177 -0
  14. package/dist/cjs/sync/replicator.js +747 -0
  15. package/dist/cjs/sync/shadow.js +178 -0
  16. package/dist/cjs/sync/types.js +2 -0
  17. package/dist/cloudflare.d.ts +1 -1
  18. package/dist/cloudflare.js.map +1 -1
  19. package/dist/collection.js +8 -1
  20. package/dist/collection.js.map +1 -1
  21. package/dist/index.d.ts +68 -2
  22. package/dist/index.js +96 -1
  23. package/dist/index.js.map +1 -1
  24. package/dist/mongo-client.d.ts +13 -0
  25. package/dist/mongo-client.js +18 -1
  26. package/dist/mongo-client.js.map +1 -1
  27. package/dist/server.d.ts +97 -0
  28. package/dist/server.js +158 -0
  29. package/dist/server.js.map +1 -0
  30. package/dist/sync/commands.d.ts +49 -0
  31. package/dist/sync/commands.js +118 -0
  32. package/dist/sync/commands.js.map +1 -0
  33. package/dist/sync/diff.d.ts +55 -0
  34. package/dist/sync/diff.js +169 -0
  35. package/dist/sync/diff.js.map +1 -0
  36. package/dist/sync/httpSink.d.ts +93 -0
  37. package/dist/sync/httpSink.js +155 -0
  38. package/dist/sync/httpSink.js.map +1 -0
  39. package/dist/sync/index.d.ts +17 -0
  40. package/dist/sync/index.js +10 -0
  41. package/dist/sync/index.js.map +1 -0
  42. package/dist/sync/mongoExecutor.d.ts +37 -0
  43. package/dist/sync/mongoExecutor.js +302 -0
  44. package/dist/sync/mongoExecutor.js.map +1 -0
  45. package/dist/sync/mongoSink.d.ts +109 -0
  46. package/dist/sync/mongoSink.js +123 -0
  47. package/dist/sync/mongoSink.js.map +1 -0
  48. package/dist/sync/outbox.d.ts +120 -0
  49. package/dist/sync/outbox.js +355 -0
  50. package/dist/sync/outbox.js.map +1 -0
  51. package/dist/sync/protocol.d.ts +93 -0
  52. package/dist/sync/protocol.js +168 -0
  53. package/dist/sync/protocol.js.map +1 -0
  54. package/dist/sync/replicator.d.ts +177 -0
  55. package/dist/sync/replicator.js +744 -0
  56. package/dist/sync/replicator.js.map +1 -0
  57. package/dist/sync/shadow.d.ts +54 -0
  58. package/dist/sync/shadow.js +173 -0
  59. package/dist/sync/shadow.js.map +1 -0
  60. package/dist/sync/types.d.ts +388 -0
  61. package/dist/sync/types.js +2 -0
  62. package/dist/sync/types.js.map +1 -0
  63. package/package.json +15 -1
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Field-level diffing between the last-known server state and the local document.
3
+ *
4
+ * Replication used to push whole documents, which meant a local edit to one field
5
+ * rewrote every other field too — including fields another writer had just changed,
6
+ * and including fields whose BSON type the local store cannot represent. Diffing
7
+ * narrows each push to `$set`/`$unset` of what genuinely changed, so everything else
8
+ * upstream is left exactly as it was.
9
+ */
10
+ /** A minimal update expressed as dotted-path assignments and removals. */
11
+ export interface DocumentDiff {
12
+ /** Dotted path → new value. */
13
+ set: Record<string, unknown>;
14
+ /** Dotted paths to remove. */
15
+ unset: string[];
16
+ }
17
+ /**
18
+ * Structural equality, insensitive to key order.
19
+ *
20
+ * `JSON.stringify` comparison would report `{a:1,b:2}` and `{b:2,a:1}` as different and
21
+ * push a spurious update on every sync.
22
+ */
23
+ export declare function deepEqual(a: unknown, b: unknown): boolean;
24
+ /**
25
+ * Computes the minimal update that turns `base` into `local`.
26
+ *
27
+ * `base` must be the **lossy JSON projection** of the server document — the same shape
28
+ * the local store would have produced for it (see `projectToLocalShape` in `shadow.ts`).
29
+ * That is what makes type preservation work: a server `Date` projects to the same ISO
30
+ * string the local store holds, compares equal, and so never appears in the diff — the
31
+ * upstream `Date` survives untouched.
32
+ *
33
+ * Nested objects recurse into dotted paths, so two writers editing different sub-fields
34
+ * of the same document do not collide.
35
+ *
36
+ * **Arrays are atomic.** Any change to an array replaces the whole array. Positional
37
+ * diffing of arrays is famously error-prone once elements are inserted or reordered, and
38
+ * a wrong answer here silently corrupts data — so this deliberately does not attempt it.
39
+ *
40
+ * @param base The last-known server state, projected to local shape. `null` if the
41
+ * document has never been upstream, in which case every field is new.
42
+ * @param local The current local document.
43
+ */
44
+ export declare function computeDiff(base: Record<string, unknown> | null, local: Record<string, unknown>): DocumentDiff;
45
+ /** True when the diff would change nothing upstream. */
46
+ export declare function isEmptyDiff(diff: DocumentDiff): boolean;
47
+ /**
48
+ * Applies a diff to a copy of `document`, mirroring what the upstream `$set`/`$unset`
49
+ * does.
50
+ *
51
+ * Used to roll the shadow forward after a successful push without a second round trip.
52
+ * Because it edits the *server* document in place, fields the diff never mentions keep
53
+ * their original BSON values — which is the whole point of diffing in the first place.
54
+ */
55
+ export declare function applyDiff(document: Record<string, unknown>, diff: DocumentDiff): Record<string, unknown>;
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Field-level diffing between the last-known server state and the local document.
3
+ *
4
+ * Replication used to push whole documents, which meant a local edit to one field
5
+ * rewrote every other field too — including fields another writer had just changed,
6
+ * and including fields whose BSON type the local store cannot represent. Diffing
7
+ * narrows each push to `$set`/`$unset` of what genuinely changed, so everything else
8
+ * upstream is left exactly as it was.
9
+ */
10
+ /**
11
+ * True when `value` is a genuine plain object worth recursing into.
12
+ *
13
+ * Class instances are deliberately excluded. A `Date`, `ObjectId` or `Binary` is
14
+ * `typeof 'object'`, and treating one as a bag of fields would copy it into a plain
15
+ * object — destroying exactly the BSON types the shadow exists to preserve.
16
+ */
17
+ function isPlainObject(value) {
18
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
19
+ return false;
20
+ const proto = Object.getPrototypeOf(value);
21
+ return proto === Object.prototype || proto === null;
22
+ }
23
+ /**
24
+ * Structural equality, insensitive to key order.
25
+ *
26
+ * `JSON.stringify` comparison would report `{a:1,b:2}` and `{b:2,a:1}` as different and
27
+ * push a spurious update on every sync.
28
+ */
29
+ export function deepEqual(a, b) {
30
+ if (a === b)
31
+ return true;
32
+ // NaN is the one primitive that isn't equal to itself.
33
+ if (typeof a === 'number' && typeof b === 'number')
34
+ return Number.isNaN(a) && Number.isNaN(b);
35
+ // Two equal instants are separate objects; without this every sync would rewrite
36
+ // any date-valued field.
37
+ if (a instanceof Date || b instanceof Date) {
38
+ return a instanceof Date && b instanceof Date && a.getTime() === b.getTime();
39
+ }
40
+ if (Array.isArray(a) || Array.isArray(b)) {
41
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length)
42
+ return false;
43
+ return a.every((item, index) => deepEqual(item, b[index]));
44
+ }
45
+ if (isPlainObject(a) && isPlainObject(b)) {
46
+ const aKeys = Object.keys(a);
47
+ const bKeys = Object.keys(b);
48
+ if (aKeys.length !== bKeys.length)
49
+ return false;
50
+ return aKeys.every((key) => Object.prototype.hasOwnProperty.call(b, key) && deepEqual(a[key], b[key]));
51
+ }
52
+ return false;
53
+ }
54
+ /**
55
+ * Computes the minimal update that turns `base` into `local`.
56
+ *
57
+ * `base` must be the **lossy JSON projection** of the server document — the same shape
58
+ * the local store would have produced for it (see `projectToLocalShape` in `shadow.ts`).
59
+ * That is what makes type preservation work: a server `Date` projects to the same ISO
60
+ * string the local store holds, compares equal, and so never appears in the diff — the
61
+ * upstream `Date` survives untouched.
62
+ *
63
+ * Nested objects recurse into dotted paths, so two writers editing different sub-fields
64
+ * of the same document do not collide.
65
+ *
66
+ * **Arrays are atomic.** Any change to an array replaces the whole array. Positional
67
+ * diffing of arrays is famously error-prone once elements are inserted or reordered, and
68
+ * a wrong answer here silently corrupts data — so this deliberately does not attempt it.
69
+ *
70
+ * @param base The last-known server state, projected to local shape. `null` if the
71
+ * document has never been upstream, in which case every field is new.
72
+ * @param local The current local document.
73
+ */
74
+ export function computeDiff(base, local) {
75
+ const diff = { set: {}, unset: [] };
76
+ collect(base ?? {}, local, '', diff);
77
+ return diff;
78
+ }
79
+ function collect(base, local, prefix, diff) {
80
+ const keys = new Set([...Object.keys(base), ...Object.keys(local)]);
81
+ for (const key of keys) {
82
+ // `_id` is the identity, never part of the update.
83
+ if (prefix === '' && key === '_id')
84
+ continue;
85
+ const path = prefix === '' ? key : `${prefix}.${key}`;
86
+ const inLocal = Object.prototype.hasOwnProperty.call(local, key);
87
+ const inBase = Object.prototype.hasOwnProperty.call(base, key);
88
+ if (!inLocal) {
89
+ diff.unset.push(path);
90
+ continue;
91
+ }
92
+ const localValue = local[key];
93
+ const baseValue = base[key];
94
+ if (!inBase) {
95
+ diff.set[path] = localValue;
96
+ continue;
97
+ }
98
+ // Recurse only when both sides are objects; otherwise the shapes differ and the
99
+ // whole subtree is replaced.
100
+ if (isPlainObject(baseValue) && isPlainObject(localValue)) {
101
+ collect(baseValue, localValue, path, diff);
102
+ continue;
103
+ }
104
+ if (!deepEqual(baseValue, localValue)) {
105
+ diff.set[path] = localValue;
106
+ }
107
+ }
108
+ }
109
+ /** True when the diff would change nothing upstream. */
110
+ export function isEmptyDiff(diff) {
111
+ return Object.keys(diff.set).length === 0 && diff.unset.length === 0;
112
+ }
113
+ /**
114
+ * Applies a diff to a copy of `document`, mirroring what the upstream `$set`/`$unset`
115
+ * does.
116
+ *
117
+ * Used to roll the shadow forward after a successful push without a second round trip.
118
+ * Because it edits the *server* document in place, fields the diff never mentions keep
119
+ * their original BSON values — which is the whole point of diffing in the first place.
120
+ */
121
+ export function applyDiff(document, diff) {
122
+ const result = clone(document);
123
+ for (const [path, value] of Object.entries(diff.set)) {
124
+ setPath(result, path.split('.'), value);
125
+ }
126
+ for (const path of diff.unset) {
127
+ unsetPath(result, path.split('.'));
128
+ }
129
+ return result;
130
+ }
131
+ /**
132
+ * Structured clone that leaves class instances (BSON `Date`, `ObjectId`, `Binary`, …)
133
+ * by reference. Copying them field-by-field would destroy exactly the types the shadow
134
+ * exists to preserve.
135
+ */
136
+ function clone(value) {
137
+ if (Array.isArray(value))
138
+ return value.map(clone);
139
+ if (isPlainObject(value)) {
140
+ const out = {};
141
+ for (const [key, item] of Object.entries(value))
142
+ out[key] = clone(item);
143
+ return out;
144
+ }
145
+ return value;
146
+ }
147
+ function setPath(target, segments, value) {
148
+ const [head, ...rest] = segments;
149
+ if (rest.length === 0) {
150
+ target[head] = value;
151
+ return;
152
+ }
153
+ const next = target[head];
154
+ // Mirror MongoDB: a dotted path creates intermediate objects as needed.
155
+ const container = isPlainObject(next) ? next : {};
156
+ target[head] = container;
157
+ setPath(container, rest, value);
158
+ }
159
+ function unsetPath(target, segments) {
160
+ const [head, ...rest] = segments;
161
+ if (rest.length === 0) {
162
+ delete target[head];
163
+ return;
164
+ }
165
+ const next = target[head];
166
+ if (isPlainObject(next))
167
+ unsetPath(next, rest);
168
+ }
169
+ //# sourceMappingURL=diff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/sync/diff.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,93 @@
1
+ import type { SyncApplyResult, SyncIdMapping, SyncOperation, SyncSink } from './types.js';
2
+ /** The subset of the global `fetch` signature this sink relies on. */
3
+ export type FetchLike = (url: string, init: {
4
+ method: string;
5
+ headers: Record<string, string>;
6
+ body: string;
7
+ signal?: AbortSignal;
8
+ }) => Promise<{
9
+ ok: boolean;
10
+ status: number;
11
+ headers: {
12
+ get(name: string): string | null;
13
+ };
14
+ text(): Promise<string>;
15
+ }>;
16
+ export interface HttpSinkOptions {
17
+ /** Base URL of the remote API, e.g. `https://api.example.com`. */
18
+ baseUrl: string;
19
+ /** Target database name, included in the request path. */
20
+ database: string;
21
+ /** Path segment before the database. Defaults to `'sync'`. */
22
+ pathPrefix?: string;
23
+ /**
24
+ * Headers to attach to every request, resolved per request so short-lived tokens can
25
+ * be refreshed. Anything static belongs in {@link HttpSinkOptions.headers}.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * getAuthHeaders: async () => ({ Authorization: `Bearer ${await getAccessToken()}` })
30
+ * ```
31
+ */
32
+ getAuthHeaders?: () => Promise<Record<string, string>> | Record<string, string>;
33
+ /** Static headers sent with every request. */
34
+ headers?: Record<string, string>;
35
+ /**
36
+ * Capture and replay `set-cookie`, for APIs that use a session cookie for affinity
37
+ * after the first authenticated request. Defaults to `true`.
38
+ */
39
+ cookies?: boolean;
40
+ /**
41
+ * `fetch` implementation. Defaults to the global one (Node 18+).
42
+ *
43
+ * Supply your own to get interceptors, connection pooling or proxy support — for
44
+ * example `ofetch`. Note that retries are handled by the replicator, so a fetch layer
45
+ * configured to retry will compound with that rather than replace it.
46
+ */
47
+ fetch?: FetchLike;
48
+ /** Per-request timeout in ms. Defaults to `30_000`. */
49
+ timeoutMs?: number;
50
+ /** See {@link SyncIdMapping}. Defaults to `'auto'`. */
51
+ idMapping?: SyncIdMapping;
52
+ /** Conditional writes against `_v`. Defaults to `true`. */
53
+ versioning?: boolean;
54
+ /**
55
+ * Include the built MongoDB command alongside each operation. Defaults to `true`,
56
+ * so a forwarding API can pass it straight through. Turn it off to halve payload
57
+ * size when the receiver rebuilds commands anyway (which it does by default).
58
+ */
59
+ includeCommands?: boolean;
60
+ /** Replicator name, sent for server-side logging. */
61
+ replicator?: string;
62
+ }
63
+ export declare class HttpUpstreamSink implements SyncSink {
64
+ private readonly options;
65
+ readonly name = "http";
66
+ private readonly fetchImpl;
67
+ private readonly idMapping;
68
+ private readonly versioning;
69
+ private cookie;
70
+ constructor(options: HttpSinkOptions);
71
+ apply(operations: SyncOperation[]): Promise<SyncApplyResult>;
72
+ fetch(collection: string, documentIds: string[]): Promise<Record<string, unknown>[]>;
73
+ private endpoint;
74
+ private requestHeaders;
75
+ /**
76
+ * Posts a body and decodes the reply.
77
+ *
78
+ * Retries are deliberately *not* handled here — the replicator already owns backoff,
79
+ * outage buffering and checkpointing. This throws on anything retryable and lets it do
80
+ * its job; throwing twice over would compound the delays.
81
+ */
82
+ private send;
83
+ }
84
+ /**
85
+ * The remote API refused the request outright.
86
+ *
87
+ * Distinct from a transport failure: retrying will produce the same refusal, so the
88
+ * batch is dead-lettered rather than retried.
89
+ */
90
+ export declare class SyncRequestRejected extends Error {
91
+ readonly status: number;
92
+ constructor(message: string, status: number);
93
+ }
@@ -0,0 +1,155 @@
1
+ /**
2
+ * A {@link SyncSink} that ships changes to a remote HTTP API instead of connecting to
3
+ * MongoDB directly, for deployments where the local instance cannot reach the database.
4
+ *
5
+ * The API receives MongoDB-shaped commands and applies them on the client's behalf —
6
+ * see `@semics-tech/mongolite/server` for a receiver that does exactly that.
7
+ *
8
+ * Version predicates travel with each operation and conflicts come back in the response,
9
+ * so replication behaves the same over HTTP as it does over a direct connection: the
10
+ * upstream stays the source of truth, and a concurrent writer is detected rather than
11
+ * overwritten.
12
+ */
13
+ import { buildWriteCommand } from './commands.js';
14
+ import { SYNC_PROTOCOL_VERSION, SYNC_CONTENT_TYPE, decodeBody, encodeBody, toWireOperation, } from './protocol.js';
15
+ export class HttpUpstreamSink {
16
+ constructor(options) {
17
+ this.options = options;
18
+ this.name = 'http';
19
+ this.cookie = null;
20
+ if (!options.baseUrl)
21
+ throw new Error('HttpUpstreamSink requires a `baseUrl`.');
22
+ if (!options.database)
23
+ throw new Error('HttpUpstreamSink requires a `database`.');
24
+ const globalFetch = globalThis.fetch;
25
+ if (!options.fetch && typeof globalFetch !== 'function') {
26
+ throw new Error('HttpUpstreamSink needs a `fetch` implementation. Node 18+ provides one globally; ' +
27
+ 'on older runtimes pass `fetch` explicitly.');
28
+ }
29
+ this.fetchImpl = options.fetch ?? globalFetch;
30
+ this.idMapping = options.idMapping ?? 'auto';
31
+ this.versioning = options.versioning ?? true;
32
+ }
33
+ async apply(operations) {
34
+ const includeCommands = this.options.includeCommands ?? true;
35
+ const request = {
36
+ protocol: SYNC_PROTOCOL_VERSION,
37
+ replicator: this.options.replicator,
38
+ operations: operations.map((op) => toWireOperation(op, includeCommands
39
+ ? buildWriteCommand(op, { idMapping: this.idMapping, versioning: this.versioning })
40
+ : undefined)),
41
+ };
42
+ let response;
43
+ try {
44
+ response = await this.send('_sync', request);
45
+ }
46
+ catch (err) {
47
+ // A request the API rejects outright will be rejected again on every retry, so
48
+ // it is reported as a per-operation failure — dead-lettered, not spun on. The
49
+ // sink's contract is that throwing means "transient", and this is not that.
50
+ if (err instanceof SyncRequestRejected) {
51
+ return {
52
+ applied: 0,
53
+ failures: operations.map((_op, index) => ({
54
+ index,
55
+ message: err.message,
56
+ code: err.status,
57
+ })),
58
+ };
59
+ }
60
+ throw err;
61
+ }
62
+ return {
63
+ applied: response.applied ?? 0,
64
+ failures: response.failures,
65
+ conflicts: response.conflicts,
66
+ };
67
+ }
68
+ async fetch(collection, documentIds) {
69
+ if (documentIds.length === 0)
70
+ return [];
71
+ const response = await this.send('_sync/fetch', {
72
+ protocol: SYNC_PROTOCOL_VERSION,
73
+ collection,
74
+ documentIds,
75
+ });
76
+ return response.documents ?? [];
77
+ }
78
+ // ---------------------------------------------------------------- internals
79
+ endpoint(path) {
80
+ const base = this.options.baseUrl.replace(/\/+$/, '');
81
+ const prefix = (this.options.pathPrefix ?? 'sync').replace(/^\/+|\/+$/g, '');
82
+ return `${base}/${prefix}/${encodeURIComponent(this.options.database)}/${path}`;
83
+ }
84
+ async requestHeaders() {
85
+ const headers = {
86
+ 'content-type': SYNC_CONTENT_TYPE,
87
+ accept: SYNC_CONTENT_TYPE,
88
+ ...this.options.headers,
89
+ ...(this.options.getAuthHeaders ? await this.options.getAuthHeaders() : {}),
90
+ };
91
+ if (this.cookie && (this.options.cookies ?? true))
92
+ headers.cookie = this.cookie;
93
+ return headers;
94
+ }
95
+ /**
96
+ * Posts a body and decodes the reply.
97
+ *
98
+ * Retries are deliberately *not* handled here — the replicator already owns backoff,
99
+ * outage buffering and checkpointing. This throws on anything retryable and lets it do
100
+ * its job; throwing twice over would compound the delays.
101
+ */
102
+ async send(path, body) {
103
+ const timeoutMs = this.options.timeoutMs ?? 30000;
104
+ const controller = new AbortController();
105
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
106
+ let response;
107
+ try {
108
+ response = await this.fetchImpl(this.endpoint(path), {
109
+ method: 'POST',
110
+ headers: await this.requestHeaders(),
111
+ body: encodeBody(body),
112
+ signal: controller.signal,
113
+ });
114
+ }
115
+ catch (err) {
116
+ // Network failure, DNS, timeout — all transient as far as we can tell.
117
+ throw new Error(`Sync request to ${this.endpoint(path)} failed: ${err instanceof Error ? err.message : String(err)}`);
118
+ }
119
+ finally {
120
+ clearTimeout(timer);
121
+ }
122
+ if (this.options.cookies ?? true) {
123
+ const setCookie = response.headers.get('set-cookie');
124
+ if (setCookie)
125
+ this.cookie = setCookie;
126
+ }
127
+ const text = await response.text();
128
+ if (!response.ok) {
129
+ const detail = text.slice(0, 500);
130
+ // 4xx means the request itself is wrong and will stay wrong; throwing would spin
131
+ // forever. Surfacing it as a per-operation failure dead-letters the batch instead.
132
+ // 408 and 429 are the exceptions — they explicitly invite a retry.
133
+ const retryable = response.status >= 500 || response.status === 408 || response.status === 429;
134
+ if (retryable) {
135
+ throw new Error(`Sync request failed with ${response.status}: ${detail}`);
136
+ }
137
+ throw new SyncRequestRejected(`Sync request rejected with ${response.status}: ${detail}`, response.status);
138
+ }
139
+ return decodeBody(text);
140
+ }
141
+ }
142
+ /**
143
+ * The remote API refused the request outright.
144
+ *
145
+ * Distinct from a transport failure: retrying will produce the same refusal, so the
146
+ * batch is dead-lettered rather than retried.
147
+ */
148
+ export class SyncRequestRejected extends Error {
149
+ constructor(message, status) {
150
+ super(message);
151
+ this.status = status;
152
+ this.name = 'SyncRequestRejected';
153
+ }
154
+ }
155
+ //# sourceMappingURL=httpSink.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"httpSink.js","sourceRoot":"","sources":["../../src/sync/httpSink.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ export { SyncReplicator } from './replicator.js';
2
+ export { SyncOutbox, OUTBOX_TABLE, STATE_TABLE, DEAD_LETTER_TABLE, SYNC_OBJECT_PREFIX, } from './outbox.js';
3
+ export { MongoUpstreamSink, VERSION_FIELD, UPDATED_AT_FIELD } from './mongoSink.js';
4
+ export type { MongoSinkOptions, MongoUpstreamAuth } from './mongoSink.js';
5
+ export { HttpUpstreamSink, SyncRequestRejected } from './httpSink.js';
6
+ export type { HttpSinkOptions, FetchLike } from './httpSink.js';
7
+ export { buildWriteCommand, toUpstreamId } from './commands.js';
8
+ export type { MongoWriteCommand, BuildCommandOptions, ObjectIdFactory } from './commands.js';
9
+ export { executeWrites, fetchDocuments } from './mongoExecutor.js';
10
+ export type { PreparedWrite, ExecuteOptions } from './mongoExecutor.js';
11
+ export { SYNC_PROTOCOL_VERSION, SyncProtocolError, encodeBody, decodeBody, toWireOperation, fromWireOperation, } from './protocol.js';
12
+ export type { SyncApplyRequest, SyncApplyResponse, SyncFetchRequest, SyncFetchResponse, WireOperation, } from './protocol.js';
13
+ export { SyncShadow, SHADOW_TABLE, projectToLocalShape } from './shadow.js';
14
+ export type { ShadowEntry } from './shadow.js';
15
+ export { computeDiff, applyDiff, isEmptyDiff, deepEqual } from './diff.js';
16
+ export type { DocumentDiff } from './diff.js';
17
+ export type { MongoBulkWriteResultLike, SyncApplyConflict, SyncApplyFailure, SyncApplyResult, SyncConflictContext, SyncConflictReason, SyncConflictResolution, SyncCollectionOptions, SyncDeadLetter, SyncEvents, SyncIdMapping, SyncInitialMode, SyncOperation, SyncOperationType, SyncOptions, SyncOutboxRecord, SyncOverflowStrategy, SyncSink, SyncStatus, } from './types.js';
@@ -0,0 +1,10 @@
1
+ export { SyncReplicator } from './replicator.js';
2
+ export { SyncOutbox, OUTBOX_TABLE, STATE_TABLE, DEAD_LETTER_TABLE, SYNC_OBJECT_PREFIX, } from './outbox.js';
3
+ export { MongoUpstreamSink, VERSION_FIELD, UPDATED_AT_FIELD } from './mongoSink.js';
4
+ export { HttpUpstreamSink, SyncRequestRejected } from './httpSink.js';
5
+ export { buildWriteCommand, toUpstreamId } from './commands.js';
6
+ export { executeWrites, fetchDocuments } from './mongoExecutor.js';
7
+ export { SYNC_PROTOCOL_VERSION, SyncProtocolError, encodeBody, decodeBody, toWireOperation, fromWireOperation, } from './protocol.js';
8
+ export { SyncShadow, SHADOW_TABLE, projectToLocalShape } from './shadow.js';
9
+ export { computeDiff, applyDiff, isEmptyDiff, deepEqual } from './diff.js';
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sync/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EACL,UAAU,EACV,YAAY,EACZ,WAAW,EACX,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEpF,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAEtE,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAEhE,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEnE,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,UAAU,EACV,UAAU,EACV,eAAe,EACf,iBAAiB,GAClB,MAAM,eAAe,CAAC;AAQvB,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAE5E,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC"}
@@ -0,0 +1,37 @@
1
+ import type { MongoWriteCommand, ObjectIdFactory } from './commands.js';
2
+ import type { MongoDbLike, SyncApplyResult, SyncIdMapping, SyncOperation } from './types.js';
3
+ /** One operation paired with the command that realises it. */
4
+ export interface PreparedWrite {
5
+ /** Index into the caller's original operations array, echoed back in results. */
6
+ index: number;
7
+ op: SyncOperation;
8
+ command: MongoWriteCommand;
9
+ }
10
+ export interface ExecuteOptions {
11
+ writeConcern?: Record<string, unknown>;
12
+ versioning?: boolean;
13
+ idMapping?: SyncIdMapping;
14
+ ObjectId?: ObjectIdFactory;
15
+ }
16
+ /**
17
+ * Runs prepared writes against a database, one `bulkWrite` per collection.
18
+ *
19
+ * Throws on transient failure (network, election, timeout) so the caller retries the
20
+ * whole batch without checkpointing. Returns per-operation failures and conflicts for
21
+ * everything it could classify.
22
+ */
23
+ export declare function executeWrites(db: MongoDbLike, writes: PreparedWrite[], options?: ExecuteOptions): Promise<SyncApplyResult>;
24
+ /**
25
+ * Splits a driver error into per-operation permanent failures.
26
+ *
27
+ * @returns The failures to dead-letter, or `null` when the error is transient and the
28
+ * whole batch should be retried.
29
+ */
30
+ export declare function classifyBulkError(err: unknown, operationCount: number): Array<{
31
+ index: number;
32
+ message: string;
33
+ code?: number | string;
34
+ keyPattern?: Record<string, unknown>;
35
+ }> | null;
36
+ /** Reads documents back by `_id`, for refreshing a shadow after a conflict. */
37
+ export declare function fetchDocuments(db: MongoDbLike, collection: string, documentIds: string[], idMapping?: SyncIdMapping, ObjectId?: ObjectIdFactory): Promise<Record<string, unknown>[]>;