@newhomestar/sdk 0.8.9 → 0.8.10

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/dist/events.d.ts CHANGED
@@ -1,3 +1,25 @@
1
+ /**
2
+ * Register a Zod payload schema for emit-time validation.
3
+ * Called by `novaEndpoint()` in next.ts for each event that has a `payload` field.
4
+ * @internal
5
+ */
6
+ export declare function registerEventPayloadSchema(slug: string, schema: {
7
+ safeParse: (data: unknown) => any;
8
+ }): void;
9
+ /**
10
+ * Look up a registered payload schema by event slug.
11
+ * Returns `undefined` if no schema was registered (validation is skipped).
12
+ * @internal
13
+ */
14
+ export declare function getEventPayloadSchema(slug: string): {
15
+ safeParse: (data: unknown) => {
16
+ success: boolean;
17
+ error?: {
18
+ message?: string;
19
+ issues?: unknown[];
20
+ };
21
+ };
22
+ } | undefined;
1
23
  /** Full request body for POST /events/queue */
2
24
  export interface QueueEventPayload {
3
25
  /**
package/dist/events.js CHANGED
@@ -36,6 +36,34 @@ import { Agent as UndiciAgent } from 'undici';
36
36
  if (!process.env.NOVA_EVENTS_SERVICE_URL) {
37
37
  dotenv.config({ path: '.env.local', override: false });
38
38
  }
39
+ // ── Event Payload Schema Registry ──────────────────────────────────────────────
40
+ //
41
+ // Module-level registry mapping event slugs to their Zod payload schemas.
42
+ // Populated by `novaEndpoint()` (in next.ts) when events declare a `payload`
43
+ // field. Used by `withServiceEventOutbox()` for emit-time validation:
44
+ // - development (NODE_ENV=development): validation failure throws immediately
45
+ // - production: validation failure logs a warning, event is still emitted
46
+ //
47
+ // This avoids coupling events.ts to Zod at the import level — we only call
48
+ // `.safeParse()` on whatever object was registered (duck-typed).
49
+ /** @internal */
50
+ const _eventPayloadSchemas = new Map();
51
+ /**
52
+ * Register a Zod payload schema for emit-time validation.
53
+ * Called by `novaEndpoint()` in next.ts for each event that has a `payload` field.
54
+ * @internal
55
+ */
56
+ export function registerEventPayloadSchema(slug, schema) {
57
+ _eventPayloadSchemas.set(slug, schema);
58
+ }
59
+ /**
60
+ * Look up a registered payload schema by event slug.
61
+ * Returns `undefined` if no schema was registered (validation is skipped).
62
+ * @internal
63
+ */
64
+ export function getEventPayloadSchema(slug) {
65
+ return _eventPayloadSchemas.get(slug);
66
+ }
39
67
  // ── Internal helpers ───────────────────────────────────────────────────────────
40
68
  function _getEnvOrThrow(key, context) {
41
69
  const val = process.env[key];
@@ -178,8 +206,24 @@ export async function withServiceEventOutbox(db, reqOrCallback, maybeCallback) {
178
206
  const outboxRows = [];
179
207
  // ── Atomic: callback + outbox INSERTs in one transaction ─────────────────
180
208
  const result = await db.$transaction(async (tx) => {
181
- // The emit fn is synchronous — events are staged and written after callback returns
209
+ // The emit fn is synchronous — events are staged and written after callback returns.
210
+ // If the event has a registered payload schema (from novaEndpoint), validate at emit time.
182
211
  const emit = (topic, payload, idempotencyKey) => {
212
+ // ── Emit-time payload validation ──────────────────────────────────
213
+ const payloadSchema = getEventPayloadSchema(topic);
214
+ if (payloadSchema) {
215
+ const result = payloadSchema.safeParse(payload);
216
+ if (!result.success) {
217
+ const errorMsg = result.error?.message ?? JSON.stringify(result.error?.issues ?? 'unknown');
218
+ const isDev = process.env.NODE_ENV === 'development';
219
+ if (isDev) {
220
+ throw new Error(`[nova/events] Payload validation failed for event "${topic}": ${errorMsg}`);
221
+ }
222
+ else {
223
+ console.warn(`[nova/events] Payload validation warning for event "${topic}": ${errorMsg}`);
224
+ }
225
+ }
226
+ }
183
227
  stagedEmits.push({ topic, payload, idempotencyKey });
184
228
  };
185
229
  const callbackResult = await callback(tx, emit);
package/dist/next.d.ts CHANGED
@@ -296,6 +296,34 @@ export interface NovaEndpointEventDef {
296
296
  priority_level?: 'low' | 'medium' | 'high';
297
297
  /** Whether this event should trigger a push notification. Defaults to false. */
298
298
  triggers_notification?: boolean;
299
+ /**
300
+ * Optional Zod schema describing the shape of this event's payload (attributes).
301
+ *
302
+ * Used for:
303
+ * 1. **Emit-time validation** — `withServiceEventOutbox()` validates the payload
304
+ * against this schema at emit time. In development mode (`NODE_ENV=development`),
305
+ * validation failures throw immediately. In production, failures are logged as
306
+ * warnings but the event is still emitted.
307
+ * 2. **JSON Schema generation** — `nova services push` converts this Zod schema to
308
+ * JSON Schema via `zodToJsonSchema()` and stores it as `payload_schema` on the
309
+ * event_type record in the Events Service. The Gamification Rule Builder UI uses
310
+ * the JSON Schema to dynamically render filter forms.
311
+ *
312
+ * @example
313
+ * ```ts
314
+ * events: [{
315
+ * slug: 'EMPLOYEE_UPDATED',
316
+ * name: 'Employee Updated',
317
+ * category: 'hris',
318
+ * payload: z.object({
319
+ * employee_id: z.string().uuid(),
320
+ * changed_fields: z.array(z.string()),
321
+ * department: z.string().optional(),
322
+ * }),
323
+ * }]
324
+ * ```
325
+ */
326
+ payload?: ZodTypeAny;
299
327
  }
300
328
  /**
301
329
  * Human-readable metadata for a single permission slug.
package/dist/next.js CHANGED
@@ -28,6 +28,7 @@
28
28
  * ```
29
29
  */
30
30
  import { z } from 'zod';
31
+ import { registerEventPayloadSchema } from './events.js';
31
32
  // ─── Factory function ─────────────────────────────────────────────────────────
32
33
  /**
33
34
  * novaEndpoint() — define a Nova service endpoint co-located with its route.
@@ -46,6 +47,17 @@ import { z } from 'zod';
46
47
  * ```
47
48
  */
48
49
  export function novaEndpoint(cfg) {
50
+ // Register event payload schemas for emit-time validation.
51
+ // When a route file is imported, novaEndpoint() runs and populates the
52
+ // module-level registry in events.ts. withServiceEventOutbox() then uses
53
+ // the registry to validate payloads at emit time (throw in dev, warn in prod).
54
+ if (cfg.events) {
55
+ for (const evt of cfg.events) {
56
+ if (evt.payload) {
57
+ registerEventPayloadSchema(evt.slug, evt.payload);
58
+ }
59
+ }
60
+ }
49
61
  return {
50
62
  ...cfg,
51
63
  parseQuery(req) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newhomestar/sdk",
3
- "version": "0.8.9",
3
+ "version": "0.8.10",
4
4
  "description": "Type-safe SDK for building Nova pipelines (workers & functions)",
5
5
  "homepage": "https://github.com/newhomestar/nova-node-sdk#readme",
6
6
  "bugs": {