From d7cc562bf6104c1edb7a8650b307ed54be04ce0a Mon Sep 17 00:00:00 2001 From: "Timothy J. Aveni" Date: Sun, 12 Jul 2026 20:19:44 -0700 Subject: [PATCH] Add Automerge CRDT field APIs to package runtime --- .gitignore | 1 + .yarn/install-state.gz | Bin 7399 -> 8191 bytes package.json | 4 +- src/index.ts | 86 +++++++++++++++++++++++++++++++++++++---- yarn-project.nix | 2 + yarn.lock | 18 +++++++++ 6 files changed, 103 insertions(+), 8 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..deb6b0a --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.yarn/install-state.gz diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz index 6af1922f0bb4278d43d0901fe394a270f99bad54..8c515728cb65e20cacbaabc6144a15644793784b 100644 GIT binary patch literal 8191 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 Ne0!^Y{*0px3;-qbFYN#T literal 7399 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 DaV=@b diff --git a/package.json b/package.json index dcf7c20..908a5fe 100644 --- a/package.json +++ b/package.json @@ -16,9 +16,11 @@ "typecheck": "yarn generate && tsc --noEmit" }, "dependencies": { + "@automerge/automerge": "^3.3.0", "@bufbuild/protobuf": "^2.12.1", "@connectrpc/connect": "^2.1.2", - "@connectrpc/connect-node": "^2.1.2" + "@connectrpc/connect-node": "^2.1.2", + "@quixos/camino-datatypes": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-datatypes.git#commit=fa50782559944b88c410e264acadf79266b70b43" }, "devDependencies": { "@bufbuild/protoc-gen-es": "^2.12.1", diff --git a/src/index.ts b/src/index.ts index 588fb5f..a5f644f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,12 @@ import { type HandlerContext, type Interceptor, } from "@connectrpc/connect"; +import * as Automerge from "@automerge/automerge"; +import { + encodeAutomergeFieldWrite, + loadAutomergeFieldDoc, + type CaminoAutomergeDoc, +} from "@quixos/camino-datatypes/crdt-automerge"; import { connectNodeAdapter, createConnectTransport, @@ -183,6 +189,11 @@ export type Field = { set(value: T): Promise; }; +export type CrdtField = Field> & { + change(fn: (doc: { value: T }) => void): Promise>; + replaceFromJson(value: T): Promise; +}; + export type LiveFieldValue = { $caminoValue: T; $caminoSource: { @@ -475,6 +486,11 @@ const bytesToBase64 = (value: Uint8Array) => const base64ToBytes = (value: string) => Buffer.from(value, "base64"); +type FieldCodec = { + fieldType?: string; + conflictStrategy?: string; +}; + const sourceToProto = (value: unknown) => { if (!isRecord(value) || !isRecord(value.field)) { return undefined; @@ -507,12 +523,30 @@ const sourceToProto = (value: unknown) => { }); }; +const codecFromSource = (value: unknown): FieldCodec | undefined => { + if (!isRecord(value) || !isRecord(value.field)) { + return undefined; + } + const field = value.field; + return { + ...(typeof field.fieldType === "string" ? { fieldType: field.fieldType } : {}), + ...(typeof field.conflictStrategy === "string" + ? { conflictStrategy: field.conflictStrategy } + : {}), + }; +}; + const withSource = (value: Value, source: unknown): Value => { const sourceValue = sourceToProto(source); return sourceValue ? create(ValueSchema, { ...value, source: sourceValue }) : value; }; -export const jsToProtoValue = (value: unknown): Value => { +export const jsToProtoValue = (value: unknown, codec?: FieldCodec): Value => { + if (codec?.conflictStrategy === "crdt" && codec.fieldType) { + return jsToProtoValue( + encodeAutomergeFieldWrite(value, codec.fieldType), + ); + } if (value === null || value === undefined) { return create(ValueSchema, { kind: { @@ -551,14 +585,17 @@ export const jsToProtoValue = (value: unknown): Value => { kind: { case: "listValue", value: create(ListValueSchema, { - values: value.map(jsToProtoValue), + values: value.map((entry) => jsToProtoValue(entry)), }), }, }); } if (isRecord(value)) { if (Object.hasOwn(value, "$caminoValue") && isRecord(value.$caminoSource)) { - return withSource(jsToProtoValue(value.$caminoValue), value.$caminoSource); + return withSource( + jsToProtoValue(value.$caminoValue, codecFromSource(value.$caminoSource)), + value.$caminoSource, + ); } if (typeof value.$caminoRef === "string") { return create(ValueSchema, { @@ -631,11 +668,12 @@ export const protoValueToJs = (value: Value | undefined): unknown => { case "refValue": return value.kind.value.objectId; case "crdtValue": - return { + return loadAutomergeFieldDoc({ $caminoCrdtType: value.kind.value.type, - $caminoCrdtEncoding: value.kind.value.encoding, + $caminoCrdtEncoding: + value.kind.value.encoding === "base64" ? "base64" : "base64", $caminoCrdtPayload: bytesToBase64(value.kind.value.payload), - }; + }, value.kind.value.type); } }; @@ -761,6 +799,7 @@ export const createField = ( camino: CaminoClient, objectId: string, fieldName: string, + codec?: FieldCodec, ): Field => ({ async get() { recordDerivedDependency({ kind: "field", objectId, fieldName }); @@ -771,11 +810,44 @@ export const createField = ( await camino.setField({ objectId, fieldName, - value: jsToProtoValue(value), + value: jsToProtoValue(value, codec), }); }, }); +export const createCrdtField = ( + camino: CaminoClient, + objectId: string, + fieldName: string, + fieldType: string, +): CrdtField => { + const field = createField>(camino, objectId, fieldName, { + fieldType, + conflictStrategy: "crdt", + }); + return { + ...field, + async change(fn) { + const current = await field.get(); + if (!current) { + throw new Error(`Cannot change missing CRDT field ${fieldName}`); + } + const next = Automerge.change(current, (doc) => { + fn(doc as { value: T }); + }) as CaminoAutomergeDoc; + await field.set(next); + return next; + }, + async replaceFromJson(value) { + await camino.setField({ + objectId, + fieldName, + value: jsToProtoValue(value, { fieldType, conflictStrategy: "crdt" }), + }); + }, + }; +}; + const createDependencyTrackingCaminoClient = ( camino: CaminoClient, ): CaminoClient => diff --git a/yarn-project.nix b/yarn-project.nix index f09f13d..ec9f010 100644 --- a/yarn-project.nix +++ b/yarn-project.nix @@ -156,11 +156,13 @@ let overriddenProject = optionalOverride overrideAttrs project; cacheEntries = { +"@automerge/automerge@npm:3.3.0" = { filename = "@automerge-automerge-npm-3.3.0-3f6661ca17-74b3a8ef9e.zip"; hash = "sha512-dLOo7548v0B2DomHJ+za1RKOUv0rBTgJRUrnsrhbTsZHw8uipjXNv4MfIxVzGtQl2jenhLeb8TvIbsKKfpKv8g=="; }; "@bufbuild/protobuf@npm:2.12.1" = { filename = "@bufbuild-protobuf-npm-2.12.1-d8afb467b3-2d40cfe4ec.zip"; hash = "sha512-LUDP5Ow7sOPwarLih3HcXcVm6qywVfvok9dWIbBAfQJy1I9ApJCtR97YOe1UFRThr3G7E68CSzrjbvDFbVbdqQ=="; }; "@bufbuild/protoc-gen-es@npm:2.12.1" = { filename = "@bufbuild-protoc-gen-es-npm-2.12.1-6e9387fcb8-0e2805376d.zip"; hash = "sha512-DigFN22Lmj/OD9OdkPZa4dH/vqMSvi2tk2f3xyEqvFHcKleC531iqPhh7De9xtG9MM+sVRDRJCeCzXA/kH4f9Q=="; }; "@bufbuild/protoplugin@npm:2.12.1" = { filename = "@bufbuild-protoplugin-npm-2.12.1-e1aebb80bf-c7b401ab31.zip"; hash = "sha512-x7QBqzHZSsaCXLm75c/3XQXSjuINRnskt6XUKZuaygZmhuo5t6OoRTuLWgGD+qd4HoP8LliAZhgHudMVX3Hr6Q=="; }; "@connectrpc/connect-node@npm:2.1.2" = { filename = "@connectrpc-connect-node-npm-2.1.2-021434d5d5-ba8b0c9384.zip"; hash = "sha512-uosMk4TDgps3eqeoJtwM1Qpgk1J9LscBRgSRKDYzbqlgSitazqQrEEd/i+RWnknd7WbKD8d/X1sCcDXgKeE+qQ=="; }; "@connectrpc/connect@npm:2.1.2" = { filename = "@connectrpc-connect-npm-2.1.2-ab34825de3-4e15000890.zip"; hash = "sha512-ThUACJAjt1Lv1E+pQKWuHh+gDhvrxLoM7ScruTWSDYw7hbd0UOZErUQruMqaJ1sMFxNviWuo7/A2F4Ex2wHWFQ=="; }; +"@quixos/camino-datatypes@https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-datatypes.git#commit=fa50782559944b88c410e264acadf79266b70b43" = { filename = "@quixos-camino-datatypes-https-08035afa77-0223fd49ac.zip"; hash = "sha512-AiP9SawAVbzD/srTymZRhXZH01djfCteKuHWkkk2wBr66OPF0AaUy3stZyxwEgJrRh18cFcpTc4zcjWsu7mGAg=="; }; "@types/node@npm:24.13.3" = { filename = "@types-node-npm-24.13.3-b512a0bbeb-a5bc08f49b.zip"; hash = "sha512-pbwI9JuVgdzcqQ4CzXcZejx5mECAdmSULlzVFhpVPU0q6AZPfjKUQQ10jX0Ji1wYSL5/pQwG8pxBk6sWvk5Z6w=="; }; "@typescript/vfs@npm:1.6.4" = { filename = "@typescript-vfs-npm-1.6.4-78935c9d3e-acb9de42f2.zip"; hash = "sha512-rLneQvI/2j9149eQC6EG7zI6L0588OSpTbtFfibTU8pjuzUZPtGjL8hzPzrlkJlhKykLBr1iIWlIYb65v7YvYg=="; }; "debug@npm:4.4.3" = { filename = "debug-npm-4.4.3-0105c6123a-d79136ec6c.zip"; hash = "sha512-15E27GyD7L79D2pVk9pqnJHsTX3cS1TIg9bnHsmsy19noaXpbQCjKBlrW1yG02XpjYo6cIVqrxa057GYXmf1pg=="; }; diff --git a/yarn.lock b/yarn.lock index 855060c..13bc8a7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,6 +5,13 @@ __metadata: version: 9 cacheKey: 10c0 +"@automerge/automerge@npm:^3.3.0": + version: 3.3.0 + resolution: "@automerge/automerge@npm:3.3.0" + checksum: 10c0/74b3a8ef9e3cbf40760e898727ecdad5128e52fd2b053809454ae7b2b85b4ec647c3cba2a635cdbf831f2315731ad425da37a784b79bf13bc86ec28a7e92aff2 + languageName: node + linkType: hard + "@bufbuild/protobuf@npm:2.12.1, @bufbuild/protobuf@npm:^2.12.1": version: 2.12.1 resolution: "@bufbuild/protobuf@npm:2.12.1" @@ -59,14 +66,25 @@ __metadata: languageName: node linkType: hard +"@quixos/camino-datatypes@https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-datatypes.git#commit=fa50782559944b88c410e264acadf79266b70b43": + version: 0.1.0 + resolution: "@quixos/camino-datatypes@https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-datatypes.git#commit=fa50782559944b88c410e264acadf79266b70b43" + dependencies: + "@automerge/automerge": "npm:^3.3.0" + checksum: 10c0/0223fd49ac0055bcc3fecad3ca6651857647d357637c2b5e2ae1d6924936c01afae8e3c5d00694cb7b2d672c7012026b461d7c7057294dce337235acbbb98602 + languageName: node + linkType: hard + "@quixos/camino-package-runtime@workspace:.": version: 0.0.0-use.local resolution: "@quixos/camino-package-runtime@workspace:." dependencies: + "@automerge/automerge": "npm:^3.3.0" "@bufbuild/protobuf": "npm:^2.12.1" "@bufbuild/protoc-gen-es": "npm:^2.12.1" "@connectrpc/connect": "npm:^2.1.2" "@connectrpc/connect-node": "npm:^2.1.2" + "@quixos/camino-datatypes": "https://gitea-external.egads.tutti.syntaxblitz.net/quixos/camino-datatypes.git#commit=fa50782559944b88c410e264acadf79266b70b43" "@types/node": "npm:^24" typescript: "npm:^5.9.3" languageName: unknown