@voyantjs/plugin-sanity-cms 0.1.0 → 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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAE5D;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAA;IACjB,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAA;IACf;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,oEAAoE;IACpE,KAAK,CAAC,EAAE,WAAW,CAAA;CACpB;AAWD,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,gBAAgB,CACd,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;IAC7C;;;OAGG;IACH,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAC1E,+EAA+E;IAC/E,cAAc,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAA;CACxF;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,YAAY,CAiH7E"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AACpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAChD,YAAY,EACV,sBAAsB,EACtB,YAAY,EACZ,WAAW,EACX,oBAAoB,GACrB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC7C,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAc,MAAM,gBAAgB,CAAA;AAExD,OAAO,EAAsB,KAAK,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAC1E,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAElE;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,KAAK,EAAE,iBAAiB,KAAK,aAAa,CAAA;AAErE;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;IAChD,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;CACjD;AAED,MAAM,WAAW,sBAAuB,SAAQ,mBAAmB;IACjE;;;OAGG;IACH,YAAY,EAAE,MAAM,CAAA;IACpB;;;OAGG;IACH,MAAM,CAAC,EAAE,oBAAoB,CAAA;IAC7B;;;OAGG;IACH,QAAQ,CAAC,EAAE,WAAW,CAAA;IACtB,8CAA8C;IAC9C,MAAM,CAAC,EAAE,YAAY,CAAA;CACtB;AAcD;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,MAAM,CAsDvE"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAA;IACV,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAEnD;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,CACxB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE;IACJ,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,KACE,OAAO,CAAC;IACX,EAAE,EAAE,OAAO,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;IAC5B,IAAI,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAA;CAC5B,CAAC,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voyantjs/plugin-sanity-cms",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "license": "FSL-1.1-Apache-2.0",
5
5
  "type": "module",
6
6
  "exports": {
@@ -22,7 +22,7 @@
22
22
  }
23
23
  },
24
24
  "dependencies": {
25
- "@voyantjs/core": "0.1.0"
25
+ "@voyantjs/core": "0.2.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "typescript": "^6.0.2",
@@ -35,11 +35,16 @@
35
35
  "publishConfig": {
36
36
  "access": "public"
37
37
  },
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "https://github.com/voyantjs/voyant.git",
41
+ "directory": "packages/plugins/sanity-cms"
42
+ },
38
43
  "scripts": {
39
44
  "typecheck": "tsc --noEmit",
40
45
  "lint": "biome check src/",
41
46
  "test": "vitest run",
42
- "build": "tsc -p tsconfig.json",
47
+ "build": "pnpm run clean && tsc -p tsconfig.json",
43
48
  "clean": "rm -rf dist"
44
49
  },
45
50
  "main": "./dist/index.js",
@@ -1 +0,0 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAE5D;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAA;IACjB,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAA;IACf;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,oEAAoE;IACpE,KAAK,CAAC,EAAE,WAAW,CAAA;CACpB;AAWD,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,gBAAgB,CACd,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;IAC7C;;;OAGG;IACH,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAC1E,+EAA+E;IAC/E,cAAc,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAA;CACxF;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,YAAY,CAiH7E"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AACpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAChD,YAAY,EACV,sBAAsB,EACtB,YAAY,EACZ,WAAW,EACX,oBAAoB,GACrB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC7C,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAc,MAAM,gBAAgB,CAAA;AAExD,OAAO,EAAsB,KAAK,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAC1E,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAElE;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,KAAK,EAAE,iBAAiB,KAAK,aAAa,CAAA;AAErE;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;IAChD,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;CACjD;AAED,MAAM,WAAW,sBAAuB,SAAQ,mBAAmB;IACjE;;;OAGG;IACH,YAAY,EAAE,MAAM,CAAA;IACpB;;;OAGG;IACH,MAAM,CAAC,EAAE,oBAAoB,CAAA;IAC7B;;;OAGG;IACH,QAAQ,CAAC,EAAE,WAAW,CAAA;IACtB,8CAA8C;IAC9C,MAAM,CAAC,EAAE,YAAY,CAAA;CACtB;AAcD;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,MAAM,CAsDvE"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAA;IACV,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAEnD;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,CACxB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE;IACJ,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,KACE,OAAO,CAAC;IACX,EAAE,EAAE,OAAO,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;IAC5B,IAAI,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAA;CAC5B,CAAC,CAAA"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=client.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"client.test.d.ts","sourceRoot":"","sources":["../../../tests/unit/client.test.ts"],"names":[],"mappings":""}
@@ -1,201 +0,0 @@
1
- import { describe, expect, it, vi } from "vitest";
2
- import { createSanityClient } from "../../src/client.js";
3
- function jsonResponse(status, body) {
4
- const text = JSON.stringify(body);
5
- return {
6
- ok: status >= 200 && status < 300,
7
- status,
8
- json: async () => JSON.parse(text),
9
- text: async () => text,
10
- };
11
- }
12
- function textResponse(status, text) {
13
- return {
14
- ok: status >= 200 && status < 300,
15
- status,
16
- json: async () => {
17
- throw new Error("not json");
18
- },
19
- text: async () => text,
20
- };
21
- }
22
- const baseOptions = {
23
- projectId: "abc123",
24
- dataset: "production",
25
- token: "test-token",
26
- };
27
- describe("createSanityClient.findByVoyantId", () => {
28
- it("returns the first matching doc", async () => {
29
- const fetchMock = vi.fn(async () => jsonResponse(200, { result: { _id: "sn_1", name: "a" } }));
30
- const client = createSanityClient({ ...baseOptions, fetch: fetchMock });
31
- const result = await client.findByVoyantId("product", "prod_xyz");
32
- expect(result).toEqual({ _id: "sn_1" });
33
- const [url, init] = fetchMock.mock.calls[0];
34
- expect(url).toContain("https://abc123.api.sanity.io/v2024-01-01/data/query/production?");
35
- // GROQ query with voyantIdField and params
36
- expect(url).toContain("query=");
37
- expect(url).toContain("voyantId%20%3D%3D%20%24vid");
38
- // JSON-encoded param values (`$` is not percent-encoded in param keys)
39
- expect(url).toContain("$type=%22product%22");
40
- expect(url).toContain("$vid=%22prod_xyz%22");
41
- expect(init.method).toBe("GET");
42
- expect(init.headers.Authorization).toBe("Bearer test-token");
43
- });
44
- it("returns null when no docs match", async () => {
45
- const fetchMock = vi.fn(async () => jsonResponse(200, { result: null }));
46
- const client = createSanityClient({ ...baseOptions, fetch: fetchMock });
47
- expect(await client.findByVoyantId("product", "missing")).toBeNull();
48
- });
49
- it("respects a custom voyantIdField", async () => {
50
- const fetchMock = vi.fn(async () => jsonResponse(200, { result: null }));
51
- const client = createSanityClient({
52
- ...baseOptions,
53
- voyantIdField: "externalId",
54
- fetch: fetchMock,
55
- });
56
- await client.findByVoyantId("product", "prod_1");
57
- const [url] = fetchMock.mock.calls[0];
58
- expect(url).toContain("externalId%20%3D%3D%20%24vid");
59
- });
60
- it("respects a custom apiVersion", async () => {
61
- const fetchMock = vi.fn(async () => jsonResponse(200, { result: null }));
62
- const client = createSanityClient({
63
- ...baseOptions,
64
- apiVersion: "2023-10-01",
65
- fetch: fetchMock,
66
- });
67
- await client.findByVoyantId("product", "prod_1");
68
- const [url] = fetchMock.mock.calls[0];
69
- expect(url).toContain("/v2023-10-01/data/query/production?");
70
- });
71
- it("respects a custom apiHost", async () => {
72
- const fetchMock = vi.fn(async () => jsonResponse(200, { result: null }));
73
- const client = createSanityClient({
74
- ...baseOptions,
75
- apiHost: "sanity.internal.example.com",
76
- fetch: fetchMock,
77
- });
78
- await client.findByVoyantId("product", "prod_1");
79
- const [url] = fetchMock.mock.calls[0];
80
- expect(url).toContain("https://abc123.sanity.internal.example.com/");
81
- });
82
- it("throws on non-2xx response", async () => {
83
- const fetchMock = vi.fn(async () => textResponse(500, "boom"));
84
- const client = createSanityClient({ ...baseOptions, fetch: fetchMock });
85
- await expect(client.findByVoyantId("product", "x")).rejects.toThrow(/Sanity findByVoyantId\(product\) failed \(500\)/);
86
- });
87
- });
88
- describe("createSanityClient.upsertByVoyantId", () => {
89
- it("creates a new doc when none exists", async () => {
90
- const fetchMock = vi
91
- .fn()
92
- .mockResolvedValueOnce(jsonResponse(200, { result: null }))
93
- .mockResolvedValueOnce(jsonResponse(200, { results: [{ id: "sn_new", operation: "create" }] }));
94
- const client = createSanityClient({ ...baseOptions, fetch: fetchMock });
95
- const result = await client.upsertByVoyantId("product", "prod_1", { name: "X" });
96
- expect(result).toEqual({ _id: "sn_new", created: true });
97
- const createCall = fetchMock.mock.calls[1];
98
- expect(createCall[0]).toContain("https://abc123.api.sanity.io/v2024-01-01/data/mutate/production");
99
- expect(createCall[0]).toContain("returnIds=true");
100
- expect(createCall[0]).toContain("visibility=sync");
101
- expect(createCall[1].method).toBe("POST");
102
- const body = JSON.parse(createCall[1].body ?? "{}");
103
- expect(body).toEqual({
104
- mutations: [
105
- {
106
- create: { _type: "product", name: "X", voyantId: "prod_1" },
107
- },
108
- ],
109
- });
110
- });
111
- it("updates an existing doc when found", async () => {
112
- const fetchMock = vi
113
- .fn()
114
- .mockResolvedValueOnce(jsonResponse(200, { result: { _id: "sn_existing" } }))
115
- .mockResolvedValueOnce(jsonResponse(200, { results: [{ id: "sn_existing", operation: "update" }] }));
116
- const client = createSanityClient({ ...baseOptions, fetch: fetchMock });
117
- const result = await client.upsertByVoyantId("product", "prod_1", { name: "Y" });
118
- expect(result).toEqual({ _id: "sn_existing", created: false });
119
- const patchCall = fetchMock.mock.calls[1];
120
- expect(patchCall[1].method).toBe("POST");
121
- const body = JSON.parse(patchCall[1].body ?? "{}");
122
- expect(body).toEqual({
123
- mutations: [
124
- {
125
- patch: {
126
- id: "sn_existing",
127
- set: { name: "Y", voyantId: "prod_1" },
128
- },
129
- },
130
- ],
131
- });
132
- });
133
- it("throws if create response has no id", async () => {
134
- const fetchMock = vi
135
- .fn()
136
- .mockResolvedValueOnce(jsonResponse(200, { result: null }))
137
- .mockResolvedValueOnce(jsonResponse(200, { results: [] }));
138
- const client = createSanityClient({ ...baseOptions, fetch: fetchMock });
139
- await expect(client.upsertByVoyantId("product", "p", {})).rejects.toThrow(/response missing id/);
140
- });
141
- it("throws on create error", async () => {
142
- const fetchMock = vi
143
- .fn()
144
- .mockResolvedValueOnce(jsonResponse(200, { result: null }))
145
- .mockResolvedValueOnce(textResponse(400, "bad request"));
146
- const client = createSanityClient({ ...baseOptions, fetch: fetchMock });
147
- await expect(client.upsertByVoyantId("product", "p", {})).rejects.toThrow(/Sanity create\(product\) failed \(400\)/);
148
- });
149
- it("throws on update error", async () => {
150
- const fetchMock = vi
151
- .fn()
152
- .mockResolvedValueOnce(jsonResponse(200, { result: { _id: "sn_1" } }))
153
- .mockResolvedValueOnce(textResponse(409, "conflict"));
154
- const client = createSanityClient({ ...baseOptions, fetch: fetchMock });
155
- await expect(client.upsertByVoyantId("product", "p", {})).rejects.toThrow(/Sanity update\(product\/sn_1\) failed \(409\)/);
156
- });
157
- });
158
- describe("createSanityClient.deleteByVoyantId", () => {
159
- it("deletes when doc exists", async () => {
160
- const fetchMock = vi
161
- .fn()
162
- .mockResolvedValueOnce(jsonResponse(200, { result: { _id: "sn_1" } }))
163
- .mockResolvedValueOnce(jsonResponse(200, { results: [{ id: "sn_1", operation: "delete" }] }));
164
- const client = createSanityClient({ ...baseOptions, fetch: fetchMock });
165
- expect(await client.deleteByVoyantId("product", "p")).toBe(true);
166
- const deleteCall = fetchMock.mock.calls[1];
167
- expect(deleteCall[1].method).toBe("POST");
168
- const body = JSON.parse(deleteCall[1].body ?? "{}");
169
- expect(body).toEqual({ mutations: [{ delete: { id: "sn_1" } }] });
170
- });
171
- it("returns false when doc does not exist", async () => {
172
- const fetchMock = vi
173
- .fn()
174
- .mockResolvedValueOnce(jsonResponse(200, { result: null }));
175
- const client = createSanityClient({ ...baseOptions, fetch: fetchMock });
176
- expect(await client.deleteByVoyantId("product", "p")).toBe(false);
177
- expect(fetchMock).toHaveBeenCalledOnce();
178
- });
179
- it("throws on non-2xx delete response", async () => {
180
- const fetchMock = vi
181
- .fn()
182
- .mockResolvedValueOnce(jsonResponse(200, { result: { _id: "sn_1" } }))
183
- .mockResolvedValueOnce(textResponse(500, "boom"));
184
- const client = createSanityClient({ ...baseOptions, fetch: fetchMock });
185
- await expect(client.deleteByVoyantId("product", "p")).rejects.toThrow(/Sanity delete\(product\/sn_1\) failed \(500\)/);
186
- });
187
- });
188
- describe("createSanityClient — fetch handling", () => {
189
- it("throws when no fetch implementation is available", async () => {
190
- const originalFetch = globalThis.fetch;
191
- globalThis.fetch = undefined;
192
- try {
193
- // biome-ignore lint/suspicious/noExplicitAny: simulating missing fetch
194
- const client = createSanityClient({ ...baseOptions, fetch: undefined });
195
- await expect(client.findByVoyantId("product", "x")).rejects.toThrow(/requires a fetch implementation/);
196
- }
197
- finally {
198
- globalThis.fetch = originalFetch;
199
- }
200
- });
201
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=plugin.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"plugin.test.d.ts","sourceRoot":"","sources":["../../../tests/unit/plugin.test.ts"],"names":[],"mappings":""}
@@ -1,183 +0,0 @@
1
- import { createEventBus, registerPlugins } from "@voyantjs/core";
2
- import { describe, expect, it, vi } from "vitest";
3
- import { sanityCmsPlugin } from "../../src/plugin.js";
4
- function jsonResponse(status, body) {
5
- const text = JSON.stringify(body);
6
- return {
7
- ok: status >= 200 && status < 300,
8
- status,
9
- json: async () => JSON.parse(text),
10
- text: async () => text,
11
- };
12
- }
13
- const baseOptions = {
14
- projectId: "abc123",
15
- dataset: "production",
16
- token: "test-token",
17
- documentType: "product",
18
- };
19
- describe("sanityCmsPlugin", () => {
20
- it("exposes a stable identity", () => {
21
- const plugin = sanityCmsPlugin({ ...baseOptions });
22
- expect(plugin.name).toBe("sanity-cms");
23
- expect(plugin.version).toBeDefined();
24
- expect(plugin.subscribers).toHaveLength(3);
25
- });
26
- it("subscribes to default product.* events", () => {
27
- const plugin = sanityCmsPlugin({ ...baseOptions });
28
- const names = plugin.subscribers?.map((s) => s.event);
29
- expect(names).toEqual(["product.created", "product.updated", "product.deleted"]);
30
- });
31
- it("honors custom event names", () => {
32
- const plugin = sanityCmsPlugin({
33
- ...baseOptions,
34
- events: {
35
- created: "departure.created",
36
- updated: "departure.updated",
37
- deleted: "departure.deleted",
38
- },
39
- });
40
- const names = plugin.subscribers?.map((s) => s.event);
41
- expect(names).toEqual(["departure.created", "departure.updated", "departure.deleted"]);
42
- });
43
- it("pushes product.created to Sanity as a create mutation", async () => {
44
- const fetchMock = vi
45
- .fn()
46
- .mockResolvedValueOnce(jsonResponse(200, { result: null }))
47
- .mockResolvedValueOnce(jsonResponse(200, { results: [{ id: "sn_1", operation: "create" }] }));
48
- const bus = createEventBus();
49
- const plugin = sanityCmsPlugin({ ...baseOptions, fetch: fetchMock });
50
- registerPlugins([plugin], { eventBus: bus });
51
- await bus.emit("product.created", { id: "prod_1", name: "Tour A" });
52
- expect(fetchMock).toHaveBeenCalledTimes(2);
53
- const [, createInit] = fetchMock.mock.calls[1];
54
- expect(createInit.method).toBe("POST");
55
- const body = JSON.parse(createInit.body ?? "{}");
56
- expect(body).toEqual({
57
- mutations: [{ create: { _type: "product", name: "Tour A", voyantId: "prod_1" } }],
58
- });
59
- });
60
- it("pushes product.updated to Sanity as a patch mutation", async () => {
61
- const fetchMock = vi
62
- .fn()
63
- .mockResolvedValueOnce(jsonResponse(200, { result: { _id: "sn_99" } }))
64
- .mockResolvedValueOnce(jsonResponse(200, { results: [{ id: "sn_99", operation: "update" }] }));
65
- const bus = createEventBus();
66
- const plugin = sanityCmsPlugin({ ...baseOptions, fetch: fetchMock });
67
- registerPlugins([plugin], { eventBus: bus });
68
- await bus.emit("product.updated", { id: "prod_1", name: "Tour B" });
69
- const [, init] = fetchMock.mock.calls[1];
70
- expect(init.method).toBe("POST");
71
- const body = JSON.parse(init.body ?? "{}");
72
- expect(body).toEqual({
73
- mutations: [{ patch: { id: "sn_99", set: { name: "Tour B", voyantId: "prod_1" } } }],
74
- });
75
- });
76
- it("deletes from Sanity on product.deleted", async () => {
77
- const fetchMock = vi
78
- .fn()
79
- .mockResolvedValueOnce(jsonResponse(200, { result: { _id: "sn_50" } }))
80
- .mockResolvedValueOnce(jsonResponse(200, { results: [{ id: "sn_50", operation: "delete" }] }));
81
- const bus = createEventBus();
82
- const plugin = sanityCmsPlugin({ ...baseOptions, fetch: fetchMock });
83
- registerPlugins([plugin], { eventBus: bus });
84
- await bus.emit("product.deleted", { id: "prod_1" });
85
- const [, init] = fetchMock.mock.calls[1];
86
- expect(init.method).toBe("POST");
87
- const body = JSON.parse(init.body ?? "{}");
88
- expect(body).toEqual({ mutations: [{ delete: { id: "sn_50" } }] });
89
- });
90
- it("applies a custom mapEvent function", async () => {
91
- const fetchMock = vi
92
- .fn()
93
- .mockResolvedValueOnce(jsonResponse(200, { result: null }))
94
- .mockResolvedValueOnce(jsonResponse(200, { results: [{ id: "sn_1", operation: "create" }] }));
95
- const bus = createEventBus();
96
- const plugin = sanityCmsPlugin({
97
- ...baseOptions,
98
- fetch: fetchMock,
99
- mapEvent: (event) => ({
100
- title: String(event.name ?? ""),
101
- _syncedAt: "2024-01-01",
102
- }),
103
- });
104
- registerPlugins([plugin], { eventBus: bus });
105
- await bus.emit("product.created", { id: "prod_x", name: "Cool Tour" });
106
- const [, init] = fetchMock.mock.calls[1];
107
- const body = JSON.parse(init.body ?? "{}");
108
- expect(body).toEqual({
109
- mutations: [
110
- {
111
- create: {
112
- _type: "product",
113
- title: "Cool Tour",
114
- _syncedAt: "2024-01-01",
115
- voyantId: "prod_x",
116
- },
117
- },
118
- ],
119
- });
120
- });
121
- it("ignores events with no string id", async () => {
122
- const fetchMock = vi.fn();
123
- const bus = createEventBus();
124
- const plugin = sanityCmsPlugin({ ...baseOptions, fetch: fetchMock });
125
- registerPlugins([plugin], { eventBus: bus });
126
- await bus.emit("product.created", { name: "no id here" });
127
- await bus.emit("product.created", null);
128
- await bus.emit("product.created", { id: 42 });
129
- expect(fetchMock).not.toHaveBeenCalled();
130
- });
131
- it("logs and swallows Sanity errors (fire-and-forget)", async () => {
132
- const fetchMock = vi.fn().mockResolvedValue({
133
- ok: false,
134
- status: 500,
135
- json: async () => ({}),
136
- text: async () => "server down",
137
- });
138
- const errorFn = vi.fn();
139
- const bus = createEventBus();
140
- const plugin = sanityCmsPlugin({
141
- ...baseOptions,
142
- fetch: fetchMock,
143
- logger: { error: errorFn },
144
- });
145
- registerPlugins([plugin], { eventBus: bus });
146
- await bus.emit("product.created", { id: "prod_1", name: "X" });
147
- expect(errorFn).toHaveBeenCalledOnce();
148
- const [msg] = errorFn.mock.calls[0];
149
- expect(msg).toContain('[sanity-cms] upsert on "product.created" failed for prod_1');
150
- });
151
- it("uses a custom documentType for the Sanity document", async () => {
152
- const fetchMock = vi
153
- .fn()
154
- .mockResolvedValueOnce(jsonResponse(200, { result: null }))
155
- .mockResolvedValueOnce(jsonResponse(200, { results: [{ id: "sn_1", operation: "create" }] }));
156
- const bus = createEventBus();
157
- const plugin = sanityCmsPlugin({
158
- ...baseOptions,
159
- documentType: "tour",
160
- fetch: fetchMock,
161
- });
162
- registerPlugins([plugin], { eventBus: bus });
163
- await bus.emit("product.created", { id: "prod_1", name: "X" });
164
- // find query uses $type=tour
165
- const [findUrl] = fetchMock.mock.calls[0];
166
- expect(findUrl).toContain("$type=%22tour%22");
167
- // create payload uses _type: "tour"
168
- const [, createInit] = fetchMock.mock.calls[1];
169
- const body = JSON.parse(createInit.body ?? "{}");
170
- expect(body.mutations[0].create._type).toBe("tour");
171
- });
172
- it("does not mutate if the doc is missing on product.deleted", async () => {
173
- const fetchMock = vi
174
- .fn()
175
- .mockResolvedValueOnce(jsonResponse(200, { result: null }));
176
- const bus = createEventBus();
177
- const plugin = sanityCmsPlugin({ ...baseOptions, fetch: fetchMock });
178
- registerPlugins([plugin], { eventBus: bus });
179
- await bus.emit("product.deleted", { id: "prod_missing" });
180
- // Only the find call should have been made.
181
- expect(fetchMock).toHaveBeenCalledOnce();
182
- });
183
- });
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes