@voyantjs/plugin-sanity-cms 0.4.5 → 0.6.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.
package/README.md CHANGED
@@ -1,6 +1,15 @@
1
1
  # @voyantjs/plugin-sanity-cms
2
2
 
3
- Sanity CMS sync plugin for Voyant. Subscribes to module events and mirrors documents into a Sanity dataset keyed by a `voyantId` field.
3
+ Sanity CMS sync adapter bundle for Voyant.
4
+
5
+ Architecturally, this package is primarily:
6
+
7
+ - a Sanity sync adapter
8
+ - a subscriber bundle that mirrors Voyant module records into Sanity
9
+ - an optional plugin bundle for distribution
10
+
11
+ It subscribes to module events and mirrors documents into a Sanity dataset keyed
12
+ by a `voyantId` field.
4
13
 
5
14
  ## Install
6
15
 
@@ -14,20 +23,24 @@ pnpm add @voyantjs/plugin-sanity-cms
14
23
  import { sanityCmsPlugin } from "@voyantjs/plugin-sanity-cms"
15
24
  import { createApp } from "@voyantjs/hono"
16
25
 
26
+ const sanitySync = sanityCmsPlugin({
27
+ projectId: env.SANITY_PROJECT_ID,
28
+ dataset: "production",
29
+ token: env.SANITY_TOKEN,
30
+ documentType: "product",
31
+ // optional: apiVersion, voyantIdField, apiHost, events, mapEvent, logger
32
+ })
33
+
17
34
  const app = createApp({
18
- plugins: [
19
- sanityCmsPlugin({
20
- projectId: env.SANITY_PROJECT_ID,
21
- dataset: "production",
22
- token: env.SANITY_TOKEN,
23
- documentType: "product",
24
- // optional: apiVersion, voyantIdField, apiHost, events, mapEvent, logger
25
- }),
26
- ],
35
+ plugins: [sanitySync],
27
36
  })
28
37
  ```
29
38
 
30
- Uses GROQ for reads and Sanity Mutations API for writes. Default `apiVersion` is `"2024-01-01"`. By default the plugin wires up 3 subscribers (`product.created`, `product.updated`, `product.deleted`).
39
+ Uses GROQ for reads and Sanity Mutations API for writes. The exported value is
40
+ an optional distribution bundle; at runtime the package is primarily a
41
+ subscriber-driven Sanity sync adapter. Default `apiVersion` is `"2024-01-01"`.
42
+ By default it wires up 3 subscribers
43
+ (`product.created`, `product.updated`, `product.deleted`).
31
44
 
32
45
  ## Exports
33
46
 
package/dist/index.d.ts CHANGED
@@ -2,5 +2,7 @@ export type { SanityClient, SanityClientOptions } from "./client.js";
2
2
  export { createSanityClient } from "./client.js";
3
3
  export type { SanityCmsPluginOptions, SanityLogger, SanityMapFn, SanitySyncEventNames, } from "./plugin.js";
4
4
  export { sanityCmsPlugin } from "./plugin.js";
5
+ export type { ResolvedSanitySyncEventNames, SanitySyncRuntime } from "./runtime.js";
6
+ export { createSanitySyncRuntime } from "./runtime.js";
5
7
  export type { SanityDocBody, SanityFetch, VoyantEntityEvent } from "./types.js";
6
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +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"}
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,4BAA4B,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AACnF,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAA;AACtD,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA"}
package/dist/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export { createSanityClient } from "./client.js";
2
2
  export { sanityCmsPlugin } from "./plugin.js";
3
+ export { createSanitySyncRuntime } from "./runtime.js";
package/dist/plugin.d.ts CHANGED
@@ -1,61 +1,21 @@
1
1
  import type { Plugin } from "@voyantjs/core";
2
- import { type SanityClientOptions } from "./client.js";
2
+ import type { SanityClientOptions } from "./client.js";
3
3
  import type { SanityDocBody, VoyantEntityEvent } from "./types.js";
4
- /**
5
- * Event names the plugin subscribes to. Defaults match the
6
- * `<resource>.<pastTenseAction>` naming convention documented by the
7
- * EventBus contract.
8
- */
9
4
  export interface SanitySyncEventNames {
10
5
  created?: string;
11
6
  updated?: string;
12
7
  deleted?: string;
13
8
  }
14
- /**
15
- * Mapper from a Voyant event payload (assumed to carry at least `id: string`)
16
- * to a Sanity document body. The `voyantId` field + `_type` are injected
17
- * automatically by the client — do not set either here.
18
- */
19
9
  export type SanityMapFn = (event: VoyantEntityEvent) => SanityDocBody;
20
- /**
21
- * Logger shape used to surface plugin errors without coupling to any specific
22
- * runtime. Defaults to `console`.
23
- */
24
10
  export interface SanityLogger {
25
11
  error: (message: string, meta?: unknown) => void;
26
12
  info?: (message: string, meta?: unknown) => void;
27
13
  }
28
14
  export interface SanityCmsPluginOptions extends SanityClientOptions {
29
- /**
30
- * Sanity document type to sync Voyant records into (e.g. `"product"`).
31
- * Equivalent to a Payload collection / content model.
32
- */
33
15
  documentType: string;
34
- /**
35
- * Event names this plugin subscribes to. Defaults to
36
- * `product.created` / `product.updated` / `product.deleted`.
37
- */
38
16
  events?: SanitySyncEventNames;
39
- /**
40
- * Map a Voyant event payload into a Sanity document body. Defaults to
41
- * passing through every property except `id` (which becomes `voyantId`).
42
- */
43
17
  mapEvent?: SanityMapFn;
44
- /** Override logger. Defaults to `console`. */
45
18
  logger?: SanityLogger;
46
19
  }
47
- /**
48
- * Build a Voyant {@link Plugin} that pushes event payloads to a Sanity
49
- * document type, keyed by `voyantId` for idempotent upserts.
50
- *
51
- * The plugin subscribes to three events (configurable via
52
- * {@link SanityCmsPluginOptions.events}):
53
- * - `product.created` → upsert document
54
- * - `product.updated` → upsert document
55
- * - `product.deleted` → delete document by `voyantId`
56
- *
57
- * Errors are caught and logged — subscribers are fire-and-forget per the
58
- * EventBus contract, so a Sanity outage never blocks the emitter.
59
- */
60
20
  export declare function sanityCmsPlugin(options: SanityCmsPluginOptions): Plugin;
61
21
  //# sourceMappingURL=plugin.d.ts.map
@@ -1 +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"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAc,MAAM,gBAAgB,CAAA;AAGxD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAEtD,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAGlE,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,MAAM,WAAW,GAAG,CAAC,KAAK,EAAE,iBAAiB,KAAK,aAAa,CAAA;AAErE,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,YAAY,EAAE,MAAM,CAAA;IACpB,MAAM,CAAC,EAAE,oBAAoB,CAAA;IAC7B,QAAQ,CAAC,EAAE,WAAW,CAAA;IACtB,MAAM,CAAC,EAAE,YAAY,CAAA;CACtB;AASD,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,MAAM,CAgDvE"}
package/dist/plugin.js CHANGED
@@ -1,8 +1,6 @@
1
- import { createSanityClient } from "./client.js";
2
- function defaultMapEvent(event) {
3
- const { id: _id, ...rest } = event;
4
- return rest;
5
- }
1
+ import { ZodError } from "zod";
2
+ import { createSanitySyncRuntime } from "./runtime.js";
3
+ import { sanityCmsPluginOptionsSchema } from "./validation.js";
6
4
  function coerceEvent(data) {
7
5
  if (data == null || typeof data !== "object")
8
6
  return null;
@@ -11,37 +9,18 @@ function coerceEvent(data) {
11
9
  return null;
12
10
  return maybe;
13
11
  }
14
- /**
15
- * Build a Voyant {@link Plugin} that pushes event payloads to a Sanity
16
- * document type, keyed by `voyantId` for idempotent upserts.
17
- *
18
- * The plugin subscribes to three events (configurable via
19
- * {@link SanityCmsPluginOptions.events}):
20
- * - `product.created` → upsert document
21
- * - `product.updated` → upsert document
22
- * - `product.deleted` → delete document by `voyantId`
23
- *
24
- * Errors are caught and logged — subscribers are fire-and-forget per the
25
- * EventBus contract, so a Sanity outage never blocks the emitter.
26
- */
27
12
  export function sanityCmsPlugin(options) {
28
- const client = createSanityClient(options);
29
- const mapEvent = options.mapEvent ?? defaultMapEvent;
30
- const logger = options.logger ?? console;
31
- const eventNames = {
32
- created: options.events?.created ?? "product.created",
33
- updated: options.events?.updated ?? "product.updated",
34
- deleted: options.events?.deleted ?? "product.deleted",
35
- };
13
+ const validatedOptions = parseSanityCmsPluginOptions(options);
14
+ const { client, mapEvent, logger, eventNames } = createSanitySyncRuntime(validatedOptions);
36
15
  const subscribers = [
37
16
  {
38
17
  event: eventNames.created,
39
- handler: async (data) => {
40
- const event = coerceEvent(data);
18
+ handler: async (envelope) => {
19
+ const event = coerceEvent(envelope.data);
41
20
  if (!event)
42
21
  return;
43
22
  try {
44
- await client.upsertByVoyantId(options.documentType, event.id, mapEvent(event));
23
+ await client.upsertByVoyantId(validatedOptions.documentType, event.id, mapEvent(event));
45
24
  }
46
25
  catch (err) {
47
26
  logger.error(`[sanity-cms] upsert on "${eventNames.created}" failed for ${event.id}`, err);
@@ -50,12 +29,12 @@ export function sanityCmsPlugin(options) {
50
29
  },
51
30
  {
52
31
  event: eventNames.updated,
53
- handler: async (data) => {
54
- const event = coerceEvent(data);
32
+ handler: async (envelope) => {
33
+ const event = coerceEvent(envelope.data);
55
34
  if (!event)
56
35
  return;
57
36
  try {
58
- await client.upsertByVoyantId(options.documentType, event.id, mapEvent(event));
37
+ await client.upsertByVoyantId(validatedOptions.documentType, event.id, mapEvent(event));
59
38
  }
60
39
  catch (err) {
61
40
  logger.error(`[sanity-cms] upsert on "${eventNames.updated}" failed for ${event.id}`, err);
@@ -64,12 +43,12 @@ export function sanityCmsPlugin(options) {
64
43
  },
65
44
  {
66
45
  event: eventNames.deleted,
67
- handler: async (data) => {
68
- const event = coerceEvent(data);
46
+ handler: async (envelope) => {
47
+ const event = coerceEvent(envelope.data);
69
48
  if (!event)
70
49
  return;
71
50
  try {
72
- await client.deleteByVoyantId(options.documentType, event.id);
51
+ await client.deleteByVoyantId(validatedOptions.documentType, event.id);
73
52
  }
74
53
  catch (err) {
75
54
  logger.error(`[sanity-cms] delete on "${eventNames.deleted}" failed for ${event.id}`, err);
@@ -83,3 +62,20 @@ export function sanityCmsPlugin(options) {
83
62
  subscribers,
84
63
  };
85
64
  }
65
+ function parseSanityCmsPluginOptions(options) {
66
+ try {
67
+ return sanityCmsPluginOptionsSchema.parse(options);
68
+ }
69
+ catch (error) {
70
+ if (error instanceof ZodError) {
71
+ const detail = error.issues
72
+ .map((issue) => {
73
+ const path = issue.path.join(".") || "options";
74
+ return `${path}: ${issue.message}`;
75
+ })
76
+ .join("; ");
77
+ throw new Error(`Invalid Sanity CMS plugin options: ${detail}`);
78
+ }
79
+ throw error;
80
+ }
81
+ }
@@ -0,0 +1,15 @@
1
+ import { createSanityClient } from "./client.js";
2
+ import type { SanityCmsPluginOptions, SanityLogger, SanityMapFn } from "./plugin.js";
3
+ export interface ResolvedSanitySyncEventNames {
4
+ created: string;
5
+ updated: string;
6
+ deleted: string;
7
+ }
8
+ export interface SanitySyncRuntime {
9
+ client: ReturnType<typeof createSanityClient>;
10
+ logger: SanityLogger;
11
+ mapEvent: SanityMapFn;
12
+ eventNames: ResolvedSanitySyncEventNames;
13
+ }
14
+ export declare function createSanitySyncRuntime(options: SanityCmsPluginOptions): SanitySyncRuntime;
15
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,KAAK,EAAE,sBAAsB,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAGpF,MAAM,WAAW,4BAA4B;IAC3C,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAA;IAC7C,MAAM,EAAE,YAAY,CAAA;IACpB,QAAQ,EAAE,WAAW,CAAA;IACrB,UAAU,EAAE,4BAA4B,CAAA;CACzC;AAOD,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,sBAAsB,GAAG,iBAAiB,CAW1F"}
@@ -0,0 +1,17 @@
1
+ import { createSanityClient } from "./client.js";
2
+ function defaultMapEvent(event) {
3
+ const { id: _id, ...rest } = event;
4
+ return rest;
5
+ }
6
+ export function createSanitySyncRuntime(options) {
7
+ return {
8
+ client: createSanityClient(options),
9
+ logger: options.logger ?? console,
10
+ mapEvent: options.mapEvent ?? defaultMapEvent,
11
+ eventNames: {
12
+ created: options.events?.created ?? "product.created",
13
+ updated: options.events?.updated ?? "product.updated",
14
+ deleted: options.events?.deleted ?? "product.deleted",
15
+ },
16
+ };
17
+ }
@@ -0,0 +1,17 @@
1
+ import { z } from "zod";
2
+ import type { SanityLogger, SanityMapFn, SanitySyncEventNames } from "./plugin.js";
3
+ import type { SanityFetch } from "./types.js";
4
+ export declare const sanityCmsPluginOptionsSchema: z.ZodObject<{
5
+ projectId: z.ZodString;
6
+ dataset: z.ZodString;
7
+ token: z.ZodString;
8
+ documentType: z.ZodString;
9
+ apiVersion: z.ZodOptional<z.ZodString>;
10
+ voyantIdField: z.ZodOptional<z.ZodString>;
11
+ apiHost: z.ZodOptional<z.ZodString>;
12
+ fetch: z.ZodOptional<z.ZodCustom<SanityFetch | undefined, SanityFetch | undefined>>;
13
+ events: z.ZodOptional<z.ZodCustom<SanitySyncEventNames | undefined, SanitySyncEventNames | undefined>>;
14
+ mapEvent: z.ZodOptional<z.ZodCustom<SanityMapFn | undefined, SanityMapFn | undefined>>;
15
+ logger: z.ZodOptional<z.ZodCustom<SanityLogger | undefined, SanityLogger | undefined>>;
16
+ }, z.core.$strip>;
17
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAEV,YAAY,EACZ,WAAW,EACX,oBAAoB,EACrB,MAAM,aAAa,CAAA;AACpB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAkC7C,eAAO,MAAM,4BAA4B;;;;;;;;;;;;iBAYK,CAAA"}
@@ -0,0 +1,31 @@
1
+ import { z } from "zod";
2
+ const optionalString = z.string().trim().min(1).optional();
3
+ const optionalFetch = z.custom((value) => value === undefined || typeof value === "function", "Expected a fetch implementation function");
4
+ const optionalLogger = z.custom((value) => value === undefined ||
5
+ (typeof value === "object" &&
6
+ value !== null &&
7
+ typeof value.error === "function" &&
8
+ ((value.info ?? undefined) === undefined ||
9
+ typeof value.info === "function")), "Expected a logger with an error function");
10
+ const optionalMapEvent = z.custom((value) => value === undefined || typeof value === "function", "Expected a mapEvent function");
11
+ const optionalEvents = z.custom((value) => {
12
+ if (value === undefined)
13
+ return true;
14
+ if (typeof value !== "object" || value === null)
15
+ return false;
16
+ const events = value;
17
+ return [events.created, events.updated, events.deleted].every((entry) => entry === undefined || (typeof entry === "string" && entry.trim().length > 0));
18
+ }, "Expected event names to be non-empty strings");
19
+ export const sanityCmsPluginOptionsSchema = z.object({
20
+ projectId: z.string().trim().min(1),
21
+ dataset: z.string().trim().min(1),
22
+ token: z.string().trim().min(1),
23
+ documentType: z.string().trim().min(1),
24
+ apiVersion: optionalString,
25
+ voyantIdField: optionalString,
26
+ apiHost: optionalString,
27
+ fetch: optionalFetch.optional(),
28
+ events: optionalEvents.optional(),
29
+ mapEvent: optionalMapEvent.optional(),
30
+ logger: optionalLogger.optional(),
31
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voyantjs/plugin-sanity-cms",
3
- "version": "0.4.5",
3
+ "version": "0.6.0",
4
4
  "license": "FSL-1.1-Apache-2.0",
5
5
  "type": "module",
6
6
  "exports": {
@@ -22,7 +22,8 @@
22
22
  }
23
23
  },
24
24
  "dependencies": {
25
- "@voyantjs/core": "0.4.5"
25
+ "zod": "^4.3.6",
26
+ "@voyantjs/core": "0.6.0"
26
27
  },
27
28
  "devDependencies": {
28
29
  "typescript": "^6.0.2",