anchordb-sync-server 0.2.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 (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +77 -0
  3. package/dist/cjs/adapters/express.d.ts +33 -0
  4. package/dist/cjs/adapters/express.d.ts.map +1 -0
  5. package/dist/cjs/adapters/express.js +40 -0
  6. package/dist/cjs/adapters/express.js.map +1 -0
  7. package/dist/cjs/adapters/nestjs.d.ts +34 -0
  8. package/dist/cjs/adapters/nestjs.d.ts.map +1 -0
  9. package/dist/cjs/adapters/nestjs.js +98 -0
  10. package/dist/cjs/adapters/nestjs.js.map +1 -0
  11. package/dist/cjs/adapters/nextjs.d.ts +20 -0
  12. package/dist/cjs/adapters/nextjs.d.ts.map +1 -0
  13. package/dist/cjs/adapters/nextjs.js +43 -0
  14. package/dist/cjs/adapters/nextjs.js.map +1 -0
  15. package/dist/cjs/handlers.d.ts +17 -0
  16. package/dist/cjs/handlers.d.ts.map +1 -0
  17. package/dist/cjs/handlers.js +168 -0
  18. package/dist/cjs/handlers.js.map +1 -0
  19. package/dist/cjs/index.d.ts +16 -0
  20. package/dist/cjs/index.d.ts.map +1 -0
  21. package/dist/cjs/index.js +27 -0
  22. package/dist/cjs/index.js.map +1 -0
  23. package/dist/cjs/package.json +3 -0
  24. package/dist/cjs/store/memory.d.ts +25 -0
  25. package/dist/cjs/store/memory.d.ts.map +1 -0
  26. package/dist/cjs/store/memory.js +65 -0
  27. package/dist/cjs/store/memory.js.map +1 -0
  28. package/dist/cjs/store/mongoose.d.ts +90 -0
  29. package/dist/cjs/store/mongoose.d.ts.map +1 -0
  30. package/dist/cjs/store/mongoose.js +129 -0
  31. package/dist/cjs/store/mongoose.js.map +1 -0
  32. package/dist/cjs/store/types.d.ts +62 -0
  33. package/dist/cjs/store/types.d.ts.map +1 -0
  34. package/dist/cjs/store/types.js +3 -0
  35. package/dist/cjs/store/types.js.map +1 -0
  36. package/dist/esm/adapters/express.d.ts +33 -0
  37. package/dist/esm/adapters/express.d.ts.map +1 -0
  38. package/dist/esm/adapters/express.js +37 -0
  39. package/dist/esm/adapters/express.js.map +1 -0
  40. package/dist/esm/adapters/nestjs.d.ts +34 -0
  41. package/dist/esm/adapters/nestjs.d.ts.map +1 -0
  42. package/dist/esm/adapters/nestjs.js +93 -0
  43. package/dist/esm/adapters/nestjs.js.map +1 -0
  44. package/dist/esm/adapters/nextjs.d.ts +20 -0
  45. package/dist/esm/adapters/nextjs.d.ts.map +1 -0
  46. package/dist/esm/adapters/nextjs.js +40 -0
  47. package/dist/esm/adapters/nextjs.js.map +1 -0
  48. package/dist/esm/handlers.d.ts +17 -0
  49. package/dist/esm/handlers.d.ts.map +1 -0
  50. package/dist/esm/handlers.js +162 -0
  51. package/dist/esm/handlers.js.map +1 -0
  52. package/dist/esm/index.d.ts +16 -0
  53. package/dist/esm/index.d.ts.map +1 -0
  54. package/dist/esm/index.js +13 -0
  55. package/dist/esm/index.js.map +1 -0
  56. package/dist/esm/package.json +3 -0
  57. package/dist/esm/store/memory.d.ts +25 -0
  58. package/dist/esm/store/memory.d.ts.map +1 -0
  59. package/dist/esm/store/memory.js +61 -0
  60. package/dist/esm/store/memory.js.map +1 -0
  61. package/dist/esm/store/mongoose.d.ts +90 -0
  62. package/dist/esm/store/mongoose.d.ts.map +1 -0
  63. package/dist/esm/store/mongoose.js +124 -0
  64. package/dist/esm/store/mongoose.js.map +1 -0
  65. package/dist/esm/store/types.d.ts +62 -0
  66. package/dist/esm/store/types.d.ts.map +1 -0
  67. package/dist/esm/store/types.js +2 -0
  68. package/dist/esm/store/types.js.map +1 -0
  69. package/package.json +109 -0
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NESTJS_CONTROLLER_EXAMPLE = exports.ANCHOR_SYNC_SERVICE = exports.createAnchorSyncService = exports.AnchorSyncService = exports.createAnchorRouteHandlers = exports.createAnchorRouter = exports.MongooseStore = exports.MemoryStore = exports.ProtocolError = exports.handlePull = exports.handlePush = void 0;
4
+ /**
5
+ * anchordb-sync-server — the server half of the Anchor sync protocol.
6
+ *
7
+ * The protocol lives in `handlers.ts` as pure functions over a `ServerStore`. Express, NestJS and
8
+ * Next.js adapters are thin translations of those, and all three are held to one conformance suite.
9
+ */
10
+ var handlers_ts_1 = require("./handlers.js");
11
+ Object.defineProperty(exports, "handlePush", { enumerable: true, get: function () { return handlers_ts_1.handlePush; } });
12
+ Object.defineProperty(exports, "handlePull", { enumerable: true, get: function () { return handlers_ts_1.handlePull; } });
13
+ Object.defineProperty(exports, "ProtocolError", { enumerable: true, get: function () { return handlers_ts_1.ProtocolError; } });
14
+ var memory_ts_1 = require("./store/memory.js");
15
+ Object.defineProperty(exports, "MemoryStore", { enumerable: true, get: function () { return memory_ts_1.MemoryStore; } });
16
+ var mongoose_ts_1 = require("./store/mongoose.js");
17
+ Object.defineProperty(exports, "MongooseStore", { enumerable: true, get: function () { return mongoose_ts_1.MongooseStore; } });
18
+ var express_ts_1 = require("./adapters/express.js");
19
+ Object.defineProperty(exports, "createAnchorRouter", { enumerable: true, get: function () { return express_ts_1.createAnchorRouter; } });
20
+ var nextjs_ts_1 = require("./adapters/nextjs.js");
21
+ Object.defineProperty(exports, "createAnchorRouteHandlers", { enumerable: true, get: function () { return nextjs_ts_1.createAnchorRouteHandlers; } });
22
+ var nestjs_ts_1 = require("./adapters/nestjs.js");
23
+ Object.defineProperty(exports, "AnchorSyncService", { enumerable: true, get: function () { return nestjs_ts_1.AnchorSyncService; } });
24
+ Object.defineProperty(exports, "createAnchorSyncService", { enumerable: true, get: function () { return nestjs_ts_1.createAnchorSyncService; } });
25
+ Object.defineProperty(exports, "ANCHOR_SYNC_SERVICE", { enumerable: true, get: function () { return nestjs_ts_1.ANCHOR_SYNC_SERVICE; } });
26
+ Object.defineProperty(exports, "NESTJS_CONTROLLER_EXAMPLE", { enumerable: true, get: function () { return nestjs_ts_1.NESTJS_CONTROLLER_EXAMPLE; } });
27
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;AAAA;;;;;GAKG;AACH,6CAAsE;AAA7D,yGAAA,UAAU,OAAA;AAAE,yGAAA,UAAU,OAAA;AAAE,4GAAA,aAAa,OAAA;AAC9C,+CAAgD;AAAvC,wGAAA,WAAW,OAAA;AACpB,mDAAoD;AAA3C,4GAAA,aAAa,OAAA;AAQtB,oDAA2D;AAAlD,gHAAA,kBAAkB,OAAA;AAC3B,kDAAiE;AAAxD,sHAAA,yBAAyB,OAAA;AAElC,kDAK8B;AAJ5B,8GAAA,iBAAiB,OAAA;AACjB,oHAAA,uBAAuB,OAAA;AACvB,gHAAA,mBAAmB,OAAA;AACnB,sHAAA,yBAAyB,OAAA"}
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "commonjs"
3
+ }
@@ -0,0 +1,25 @@
1
+ import type { ChangeResult } from "anchordb";
2
+ import type { ServerStore, StoredServerDocument } from "./types.js";
3
+ /**
4
+ * In-memory store — the default, so a backend runs with no database installed.
5
+ *
6
+ * This is the reference implementation of the `ServerStore` contract; `MongooseStore` is held to
7
+ * the same conformance suite. Suitable for development, tests and the demos, not for production.
8
+ */
9
+ export declare class MemoryStore implements ServerStore {
10
+ readonly name = "memory";
11
+ private readonly docs;
12
+ private readonly idempotency;
13
+ private seq;
14
+ private key;
15
+ get(collection: string, documentId: string): Promise<StoredServerDocument | null>;
16
+ put(doc: Omit<StoredServerDocument, "seq">): Promise<StoredServerDocument>;
17
+ listChanges(afterSeq: number, limit: number, collections?: string[]): Promise<StoredServerDocument[]>;
18
+ hasMoreAfter(seq: number, collections?: string[]): Promise<boolean>;
19
+ getIdempotent(clientId: string, mutationId: string): Promise<ChangeResult | null>;
20
+ setIdempotent(clientId: string, mutationId: string, result: ChangeResult): Promise<void>;
21
+ findByUniqueField(collection: string, field: string, value: unknown, exceptDocumentId: string): Promise<StoredServerDocument | null>;
22
+ all(collection?: string): StoredServerDocument[];
23
+ reset(): void;
24
+ }
25
+ //# sourceMappingURL=memory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../../src/store/memory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,KAAK,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEpE;;;;;GAKG;AACH,qBAAa,WAAY,YAAW,WAAW;IAC7C,QAAQ,CAAC,IAAI,YAAY;IACzB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA2C;IAChE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAmC;IAC/D,OAAO,CAAC,GAAG,CAAK;IAEhB,OAAO,CAAC,GAAG;IAIL,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IAIjF,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,oBAAoB,EAAE,KAAK,CAAC,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAM1E,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAOrG,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAMnE,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAIjF,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAIxF,iBAAiB,CACrB,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,OAAO,EACd,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IAUvC,GAAG,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,oBAAoB,EAAE;IAIhD,KAAK,IAAI,IAAI;CAKd"}
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MemoryStore = void 0;
4
+ /**
5
+ * In-memory store — the default, so a backend runs with no database installed.
6
+ *
7
+ * This is the reference implementation of the `ServerStore` contract; `MongooseStore` is held to
8
+ * the same conformance suite. Suitable for development, tests and the demos, not for production.
9
+ */
10
+ class MemoryStore {
11
+ constructor() {
12
+ this.name = "memory";
13
+ this.docs = new Map();
14
+ this.idempotency = new Map();
15
+ this.seq = 0;
16
+ }
17
+ key(collection, documentId) {
18
+ return `${collection}/${documentId}`;
19
+ }
20
+ async get(collection, documentId) {
21
+ return this.docs.get(this.key(collection, documentId)) ?? null;
22
+ }
23
+ async put(doc) {
24
+ const stored = { ...doc, seq: ++this.seq };
25
+ this.docs.set(this.key(doc.collection, doc.documentId), stored);
26
+ return stored;
27
+ }
28
+ async listChanges(afterSeq, limit, collections) {
29
+ return [...this.docs.values()]
30
+ .filter((d) => d.seq > afterSeq && (!collections || collections.includes(d.collection)))
31
+ .sort((a, b) => a.seq - b.seq)
32
+ .slice(0, limit);
33
+ }
34
+ async hasMoreAfter(seq, collections) {
35
+ return [...this.docs.values()].some((d) => d.seq > seq && (!collections || collections.includes(d.collection)));
36
+ }
37
+ async getIdempotent(clientId, mutationId) {
38
+ return this.idempotency.get(`${clientId}:${mutationId}`) ?? null;
39
+ }
40
+ async setIdempotent(clientId, mutationId, result) {
41
+ this.idempotency.set(`${clientId}:${mutationId}`, result);
42
+ }
43
+ async findByUniqueField(collection, field, value, exceptDocumentId) {
44
+ for (const doc of this.docs.values()) {
45
+ if (doc.collection !== collection || doc.deleted)
46
+ continue;
47
+ if (doc.documentId === exceptDocumentId)
48
+ continue;
49
+ if (doc.document[field] === value)
50
+ return doc;
51
+ }
52
+ return null;
53
+ }
54
+ // ---- helpers for tests and demos ----
55
+ all(collection) {
56
+ return [...this.docs.values()].filter((d) => !collection || d.collection === collection);
57
+ }
58
+ reset() {
59
+ this.docs.clear();
60
+ this.idempotency.clear();
61
+ this.seq = 0;
62
+ }
63
+ }
64
+ exports.MemoryStore = MemoryStore;
65
+ //# sourceMappingURL=memory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.js","sourceRoot":"","sources":["../../../src/store/memory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,90 @@
1
+ import type { ChangeResult } from "anchordb";
2
+ import type { ServerStore, StoredServerDocument } from "./types.js";
3
+ /**
4
+ * MongoDB store, driven through Mongoose.
5
+ *
6
+ * `mongoose` is a *peer* dependency and is passed in rather than imported, for two reasons: the
7
+ * package must install cleanly for people using MemoryStore, and a backend must use its own single
8
+ * mongoose instance (two copies in one process is a well-known source of "MissingSchemaError" and
9
+ * duplicate connection bugs).
10
+ *
11
+ * ## The schemas
12
+ *
13
+ * `anchorSchemas()` returns plain definition objects rather than constructed schemas, so a backend
14
+ * can extend them, add its own indexes, or point them at different collection names. They are the
15
+ * shape the sync protocol needs; your *domain* collections keep whatever schema you already have.
16
+ */
17
+ export interface MongooseLike {
18
+ Schema: new (definition: unknown, options?: unknown) => unknown;
19
+ model: (name: string, schema?: unknown, collection?: string) => MongooseModel;
20
+ models: Record<string, MongooseModel | undefined>;
21
+ }
22
+ export interface MongooseModel {
23
+ findOne(filter: unknown): {
24
+ lean(): {
25
+ exec(): Promise<Record<string, unknown> | null>;
26
+ };
27
+ };
28
+ find(filter: unknown): {
29
+ sort(spec: unknown): {
30
+ limit(n: number): {
31
+ lean(): {
32
+ exec(): Promise<Array<Record<string, unknown>>>;
33
+ };
34
+ };
35
+ lean(): {
36
+ exec(): Promise<Array<Record<string, unknown>>>;
37
+ };
38
+ };
39
+ };
40
+ findOneAndUpdate(filter: unknown, update: unknown, options: unknown): {
41
+ lean(): {
42
+ exec(): Promise<unknown>;
43
+ };
44
+ };
45
+ countDocuments(filter: unknown): {
46
+ exec(): Promise<number>;
47
+ };
48
+ create(doc: unknown): Promise<unknown>;
49
+ }
50
+ /**
51
+ * Schema definitions for the two collections the protocol needs.
52
+ *
53
+ * Timestamps are `Date`, which Mongoose casts from the ISO-8601 UTC strings the client sends — one
54
+ * of the reasons the wire format uses strings rather than a custom encoding.
55
+ */
56
+ export declare function anchorSchemas(): {
57
+ document: Record<string, unknown>;
58
+ idempotency: Record<string, unknown>;
59
+ };
60
+ export interface MongooseStoreOptions {
61
+ mongoose: MongooseLike;
62
+ documentModelName?: string;
63
+ idempotencyModelName?: string;
64
+ /** Collection names, if you want them to differ from the model names. */
65
+ documentCollection?: string;
66
+ idempotencyCollection?: string;
67
+ }
68
+ export declare class MongooseStore implements ServerStore {
69
+ readonly name = "mongoose";
70
+ private readonly documents;
71
+ private readonly idempotency;
72
+ private readonly counters;
73
+ constructor(options: MongooseStoreOptions);
74
+ get(collection: string, documentId: string): Promise<StoredServerDocument | null>;
75
+ /**
76
+ * Allocate the next sequence number atomically.
77
+ *
78
+ * `findOneAndUpdate` with `$inc` is a single atomic operation in MongoDB, so two concurrent
79
+ * pushes cannot receive the same seq. Reading-then-writing a counter would race, and duplicate
80
+ * sequence numbers would make pull cursors skip changes.
81
+ */
82
+ private nextSeq;
83
+ put(doc: Omit<StoredServerDocument, "seq">): Promise<StoredServerDocument>;
84
+ listChanges(afterSeq: number, limit: number, collections?: string[]): Promise<StoredServerDocument[]>;
85
+ hasMoreAfter(seq: number, collections?: string[]): Promise<boolean>;
86
+ getIdempotent(clientId: string, mutationId: string): Promise<ChangeResult | null>;
87
+ setIdempotent(clientId: string, mutationId: string, result: ChangeResult): Promise<void>;
88
+ findByUniqueField(collection: string, field: string, value: unknown, exceptDocumentId: string): Promise<StoredServerDocument | null>;
89
+ }
90
+ //# sourceMappingURL=mongoose.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mongoose.d.ts","sourceRoot":"","sources":["../../../src/store/mongoose.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,129 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MongooseStore = void 0;
4
+ exports.anchorSchemas = anchorSchemas;
5
+ /**
6
+ * Schema definitions for the two collections the protocol needs.
7
+ *
8
+ * Timestamps are `Date`, which Mongoose casts from the ISO-8601 UTC strings the client sends — one
9
+ * of the reasons the wire format uses strings rather than a custom encoding.
10
+ */
11
+ function anchorSchemas() {
12
+ return {
13
+ document: {
14
+ documentId: { type: String, required: true, index: true },
15
+ collection: { type: String, required: true, index: true },
16
+ document: { type: Object, default: {} },
17
+ version: { type: Number, required: true, default: 0 },
18
+ deleted: { type: Boolean, default: false },
19
+ updatedAt: { type: Date, required: true },
20
+ hlc: { type: String, default: "" },
21
+ // Monotonic global sequence. Indexed because every pull is a range scan over it.
22
+ seq: { type: Number, required: true, index: true },
23
+ },
24
+ idempotency: {
25
+ clientId: { type: String, required: true },
26
+ mutationId: { type: String, required: true },
27
+ result: { type: Object, required: true },
28
+ createdAt: { type: Date, default: Date.now, expires: "30d" },
29
+ },
30
+ };
31
+ }
32
+ class MongooseStore {
33
+ constructor(options) {
34
+ this.name = "mongoose";
35
+ const { mongoose } = options;
36
+ const schemas = anchorSchemas();
37
+ const docName = options.documentModelName ?? "AnchorDocument";
38
+ const idemName = options.idempotencyModelName ?? "AnchorIdempotency";
39
+ const counterName = "AnchorCounter";
40
+ // Reuse an already-registered model rather than re-registering, which Mongoose treats as an
41
+ // error (OverwriteModelError) and which is easy to trigger under hot reload.
42
+ this.documents =
43
+ mongoose.models[docName] ??
44
+ mongoose.model(docName, new mongoose.Schema(schemas.document, { versionKey: false }), options.documentCollection);
45
+ this.idempotency =
46
+ mongoose.models[idemName] ??
47
+ mongoose.model(idemName, new mongoose.Schema(schemas.idempotency, { versionKey: false }), options.idempotencyCollection);
48
+ this.counters =
49
+ mongoose.models[counterName] ??
50
+ mongoose.model(counterName, new mongoose.Schema({ _id: String, value: { type: Number, default: 0 } }, { versionKey: false }));
51
+ }
52
+ async get(collection, documentId) {
53
+ const found = await this.documents.findOne({ collection, documentId }).lean().exec();
54
+ return found ? toStored(found) : null;
55
+ }
56
+ /**
57
+ * Allocate the next sequence number atomically.
58
+ *
59
+ * `findOneAndUpdate` with `$inc` is a single atomic operation in MongoDB, so two concurrent
60
+ * pushes cannot receive the same seq. Reading-then-writing a counter would race, and duplicate
61
+ * sequence numbers would make pull cursors skip changes.
62
+ */
63
+ async nextSeq() {
64
+ const doc = (await this.counters
65
+ .findOneAndUpdate({ _id: "seq" }, { $inc: { value: 1 } }, { upsert: true, new: true })
66
+ .lean()
67
+ .exec());
68
+ return doc?.value ?? 1;
69
+ }
70
+ async put(doc) {
71
+ const seq = await this.nextSeq();
72
+ const stored = { ...doc, seq };
73
+ await this.documents
74
+ .findOneAndUpdate({ collection: doc.collection, documentId: doc.documentId }, { $set: { ...stored, updatedAt: new Date(doc.updatedAt) } }, { upsert: true, new: true })
75
+ .lean()
76
+ .exec();
77
+ return stored;
78
+ }
79
+ async listChanges(afterSeq, limit, collections) {
80
+ const filter = { seq: { $gt: afterSeq } };
81
+ if (collections?.length)
82
+ filter.collection = { $in: collections };
83
+ const rows = await this.documents.find(filter).sort({ seq: 1 }).limit(limit).lean().exec();
84
+ return rows.map(toStored);
85
+ }
86
+ async hasMoreAfter(seq, collections) {
87
+ const filter = { seq: { $gt: seq } };
88
+ if (collections?.length)
89
+ filter.collection = { $in: collections };
90
+ return (await this.documents.countDocuments(filter).exec()) > 0;
91
+ }
92
+ async getIdempotent(clientId, mutationId) {
93
+ const found = (await this.idempotency.findOne({ clientId, mutationId }).lean().exec());
94
+ return found?.result ?? null;
95
+ }
96
+ async setIdempotent(clientId, mutationId, result) {
97
+ await this.idempotency
98
+ .findOneAndUpdate({ clientId, mutationId }, { $set: { result } }, { upsert: true, new: true })
99
+ .lean()
100
+ .exec();
101
+ }
102
+ async findByUniqueField(collection, field, value, exceptDocumentId) {
103
+ const found = await this.documents
104
+ .findOne({
105
+ collection,
106
+ deleted: false,
107
+ documentId: { $ne: exceptDocumentId },
108
+ [`document.${field}`]: value,
109
+ })
110
+ .lean()
111
+ .exec();
112
+ return found ? toStored(found) : null;
113
+ }
114
+ }
115
+ exports.MongooseStore = MongooseStore;
116
+ function toStored(row) {
117
+ const updatedAt = row.updatedAt;
118
+ return {
119
+ documentId: String(row.documentId),
120
+ collection: String(row.collection),
121
+ document: (row.document ?? {}),
122
+ version: Number(row.version ?? 0),
123
+ deleted: Boolean(row.deleted),
124
+ updatedAt: updatedAt instanceof Date ? updatedAt.toISOString() : String(updatedAt),
125
+ hlc: String(row.hlc ?? ""),
126
+ seq: Number(row.seq ?? 0),
127
+ };
128
+ }
129
+ //# sourceMappingURL=mongoose.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mongoose.js","sourceRoot":"","sources":["../../../src/store/mongoose.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ import type { ChangeResult } from "anchordb";
2
+ /**
3
+ * The storage contract the sync protocol needs from a backend.
4
+ *
5
+ * Kept deliberately small — five reads and two writes — so implementing it against a new database
6
+ * is a short afternoon rather than a port. The protocol semantics (version checks, conflict
7
+ * verdicts, idempotency) live in `handlers.ts` and are identical for every store, which is what
8
+ * makes the conformance suite meaningful: MemoryStore and MongooseStore are held to the same tests.
9
+ */
10
+ export interface StoredServerDocument {
11
+ documentId: string;
12
+ collection: string;
13
+ document: Record<string, unknown>;
14
+ /** Monotonic per document. The basis of optimistic concurrency. */
15
+ version: number;
16
+ deleted: boolean;
17
+ updatedAt: string;
18
+ hlc: string;
19
+ /** Position in the global change sequence — what pull cursors point at. */
20
+ seq: number;
21
+ }
22
+ export interface UniqueIndexSpec {
23
+ collection: string;
24
+ field: string;
25
+ }
26
+ export interface ServerStore {
27
+ readonly name: string;
28
+ get(collection: string, documentId: string): Promise<StoredServerDocument | null>;
29
+ /** Persist a document and assign it the next global sequence number. */
30
+ put(doc: Omit<StoredServerDocument, "seq">): Promise<StoredServerDocument>;
31
+ /** Documents whose seq is greater than `afterSeq`, ascending, at most `limit`. */
32
+ listChanges(afterSeq: number, limit: number, collections?: string[]): Promise<StoredServerDocument[]>;
33
+ /** True when more changes remain beyond the returned page. */
34
+ hasMoreAfter(seq: number, collections?: string[]): Promise<boolean>;
35
+ /**
36
+ * Look up a memoised verdict.
37
+ *
38
+ * This is what makes retries safe: a client that times out and re-sends must receive the original
39
+ * decision, not have the write applied a second time.
40
+ */
41
+ getIdempotent(clientId: string, mutationId: string): Promise<ChangeResult | null>;
42
+ setIdempotent(clientId: string, mutationId: string, result: ChangeResult): Promise<void>;
43
+ /**
44
+ * Find a document that already holds `value` in `field`, excluding `exceptDocumentId`.
45
+ *
46
+ * Unique constraints are checked here rather than in the handler because a real database can do
47
+ * it with an index; scanning in the handler would be correct but O(n) per push.
48
+ */
49
+ findByUniqueField(collection: string, field: string, value: unknown, exceptDocumentId: string): Promise<StoredServerDocument | null>;
50
+ }
51
+ export interface SyncServerOptions {
52
+ store: ServerStore;
53
+ /** Unique constraints to enforce, mirroring the indexes declared in the client schema. */
54
+ uniqueIndexes?: UniqueIndexSpec[];
55
+ /** Injectable for deterministic tests. */
56
+ now?: () => number;
57
+ /** Reject collections not in this list, if provided. */
58
+ allowedCollections?: string[];
59
+ /** Resolve the tenant/user this request belongs to. Return null to reject. */
60
+ authenticate?: (headers: Record<string, string | undefined>) => Promise<string | null> | string | null;
61
+ }
62
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/store/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C;;;;;;;GAOG;AAEH,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,mEAAmE;IACnE,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,2EAA2E;IAC3E,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;IAElF,wEAAwE;IACxE,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,oBAAoB,EAAE,KAAK,CAAC,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAE3E,kFAAkF;IAClF,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAAC;IAEtG,8DAA8D;IAC9D,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEpE;;;;;OAKG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IAClF,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzF;;;;;OAKG;IACH,iBAAiB,CACf,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,OAAO,EACd,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;CACzC;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,WAAW,CAAC;IACnB,0FAA0F;IAC1F,aAAa,CAAC,EAAE,eAAe,EAAE,CAAC;IAClC,0CAA0C;IAC1C,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,wDAAwD;IACxD,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC9B,8EAA8E;IAC9E,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC;CACxG"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/store/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,33 @@
1
+ import type { SyncServerOptions } from "../store/types.js";
2
+ /**
3
+ * Express adapter — a thin translation layer, nothing more.
4
+ *
5
+ * All protocol semantics live in `handlers.ts`, so this file only maps HTTP to those calls and
6
+ * back. Typed structurally rather than against `express` types so the package installs without
7
+ * express present.
8
+ */
9
+ interface Req {
10
+ body: unknown;
11
+ headers: Record<string, string | undefined>;
12
+ }
13
+ interface Res {
14
+ status(code: number): Res;
15
+ json(body: unknown): unknown;
16
+ }
17
+ type Next = (err?: unknown) => void;
18
+ type Handler = (req: Req, res: Res, next: Next) => void | Promise<void>;
19
+ interface RouterLike {
20
+ post(path: string, handler: Handler): unknown;
21
+ get(path: string, handler: Handler): unknown;
22
+ }
23
+ export interface ExpressRouterFactory {
24
+ (): RouterLike;
25
+ }
26
+ /**
27
+ * `app.use("/anchor/v1", createAnchorRouter(express.Router, { store }))`
28
+ *
29
+ * The Router factory is injected rather than imported so this module never requires express itself.
30
+ */
31
+ export declare function createAnchorRouter(routerFactory: ExpressRouterFactory, options: SyncServerOptions): RouterLike;
32
+ export {};
33
+ //# sourceMappingURL=express.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../../src/adapters/express.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D;;;;;;GAMG;AAEH,UAAU,GAAG;IACX,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;CAC7C;AACD,UAAU,GAAG;IACX,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC;IAC1B,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC;CAC9B;AACD,KAAK,IAAI,GAAG,CAAC,GAAG,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;AACpC,KAAK,OAAO,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAExE,UAAU,UAAU;IAClB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC;IAC9C,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC;CAC9C;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,UAAU,CAAC;CAChB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,oBAAoB,EAAE,OAAO,EAAE,iBAAiB,GAAG,UAAU,CA+B9G"}
@@ -0,0 +1,37 @@
1
+ import { handlePull, handlePush, ProtocolError } from "../handlers.js";
2
+ /**
3
+ * `app.use("/anchor/v1", createAnchorRouter(express.Router, { store }))`
4
+ *
5
+ * The Router factory is injected rather than imported so this module never requires express itself.
6
+ */
7
+ export function createAnchorRouter(routerFactory, options) {
8
+ const router = routerFactory();
9
+ const guard = (handler) => {
10
+ return async (req, res) => {
11
+ try {
12
+ if (options.authenticate) {
13
+ const identity = await options.authenticate(req.headers);
14
+ if (!identity) {
15
+ res.status(401).json({ error: "unauthorized" });
16
+ return;
17
+ }
18
+ }
19
+ res.status(200).json(await handler(req));
20
+ }
21
+ catch (err) {
22
+ if (err instanceof ProtocolError) {
23
+ res.status(err.status).json({ error: err.message });
24
+ return;
25
+ }
26
+ // Never leak an internal error message to a client; log it server-side instead.
27
+ console.error("[anchor/sync-server]", err);
28
+ res.status(500).json({ error: "internal error" });
29
+ }
30
+ };
31
+ };
32
+ router.post("/sync/push", guard((req) => handlePush(req.body, options)));
33
+ router.post("/sync/pull", guard((req) => handlePull(req.body, options)));
34
+ router.get("/sync/health", guard(async () => ({ ok: true, store: options.store.name, protocolVersion: 1 })));
35
+ return router;
36
+ }
37
+ //# sourceMappingURL=express.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"express.js","sourceRoot":"","sources":["../../../src/adapters/express.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AA+BvE;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,aAAmC,EAAE,OAA0B;IAChG,MAAM,MAAM,GAAG,aAAa,EAAE,CAAC;IAE/B,MAAM,KAAK,GAAG,CAAC,OAAuC,EAAW,EAAE;QACjE,OAAO,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;YACxB,IAAI,CAAC;gBACH,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;oBACzB,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;oBACzD,IAAI,CAAC,QAAQ,EAAE,CAAC;wBACd,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC;wBAChD,OAAO;oBACT,CAAC;gBACH,CAAC;gBACD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;YAC3C,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,IAAI,GAAG,YAAY,aAAa,EAAE,CAAC;oBACjC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;oBACpD,OAAO;gBACT,CAAC;gBACD,gFAAgF;gBAChF,OAAO,CAAC,KAAK,CAAC,sBAAsB,EAAE,GAAG,CAAC,CAAC;gBAC3C,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC;YACpD,CAAC;QACH,CAAC,CAAC;IACJ,CAAC,CAAC;IAEF,MAAM,CAAC,IAAI,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,IAAa,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAClF,MAAM,CAAC,IAAI,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,IAAa,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAClF,MAAM,CAAC,GAAG,CAAC,cAAc,EAAE,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,eAAe,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAE7G,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,34 @@
1
+ import type { SyncServerOptions } from "../store/types.js";
2
+ /**
3
+ * NestJS adapter.
4
+ *
5
+ * NestJS is not imported here — decorators and DI tokens are supplied by the consuming app, so this
6
+ * package installs cleanly for people using Express or Next.js. What we export is:
7
+ *
8
+ * - `AnchorSyncService`, a plain class the app registers as a provider, and
9
+ * - a documented controller you paste into your project (see `NESTJS_CONTROLLER_EXAMPLE`).
10
+ *
11
+ * The alternative — importing `@nestjs/common` to decorate a controller in the library — would make
12
+ * NestJS a hard dependency of everyone, and would bake in route paths the app cannot change.
13
+ */
14
+ export declare class AnchorSyncService {
15
+ private readonly options;
16
+ constructor(options: SyncServerOptions);
17
+ push(body: unknown, headers?: Record<string, string | undefined>): Promise<unknown>;
18
+ pull(body: unknown, headers?: Record<string, string | undefined>): Promise<unknown>;
19
+ health(): {
20
+ ok: true;
21
+ store: string;
22
+ protocolVersion: number;
23
+ };
24
+ private assertAuthorised;
25
+ }
26
+ /** Factory for `useFactory` in a NestJS module. */
27
+ export declare function createAnchorSyncService(options: SyncServerOptions): AnchorSyncService;
28
+ export declare const ANCHOR_SYNC_SERVICE = "ANCHOR_SYNC_SERVICE";
29
+ /**
30
+ * Copy this controller and module into your NestJS app. It is exported as a string rather than as
31
+ * live code precisely so that `@nestjs/common` stays out of this package's dependency graph.
32
+ */
33
+ export declare const NESTJS_CONTROLLER_EXAMPLE: string;
34
+ //# sourceMappingURL=nestjs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nestjs.d.ts","sourceRoot":"","sources":["../../../src/adapters/nestjs.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D;;;;;;;;;;;GAWG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;gBAEhC,OAAO,EAAE,iBAAiB;IAIhC,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKvF,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAK7F,MAAM,IAAI;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE;YAIhD,gBAAgB;CAK/B;AAED,mDAAmD;AACnD,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,iBAAiB,GAAG,iBAAiB,CAErF;AAED,eAAO,MAAM,mBAAmB,wBAAwB,CAAC;AAEzD;;;GAGG;AACH,eAAO,MAAM,yBAAyB,QA8C9B,CAAC"}