@newhomestar/sdk 0.7.15 → 0.7.17

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/index.d.ts CHANGED
@@ -85,6 +85,45 @@ export interface ActionDef<I extends ZodTypeAny, O extends ZodTypeAny> {
85
85
  eventTypes?: string[];
86
86
  consumerGroup?: string;
87
87
  }>;
88
+ /**
89
+ * Sync capability metadata — declares this action as a data sync endpoint.
90
+ * Read by `nova integrations build` to include sync config in nova-integration.yaml.
91
+ * Surfaced in the Odyssey UI DataSyncTab for each integration.
92
+ *
93
+ * @example
94
+ * ```ts
95
+ * sync: {
96
+ * entityType: "issue",
97
+ * direction: "to_nova",
98
+ * label: "Jira Issues",
99
+ * description: "Import all Jira issues into Nova",
100
+ * }
101
+ * ```
102
+ */
103
+ sync?: {
104
+ /** Entity type identifier (e.g., "issue", "contact", "employee") */
105
+ entityType: string;
106
+ /** Direction of sync: to_nova = import from provider; from_nova = export to provider */
107
+ direction: 'to_nova' | 'from_nova' | 'bidirectional';
108
+ /** Human-readable label shown in the UI (e.g., "Jira Issues") */
109
+ label: string;
110
+ /** Optional description shown below the label in the DataSyncTab */
111
+ description?: string;
112
+ };
113
+ /**
114
+ * Trigger-based routing declarations.
115
+ * Used by the SSE worker mode to route incoming messages to this action's handler.
116
+ * Also read by `nova integrations build` to register event_subscriptions on push.
117
+ */
118
+ triggers?: Array<{
119
+ type: 'event';
120
+ events: string[];
121
+ } | {
122
+ type: 'schedule';
123
+ cron: string;
124
+ timezone?: string;
125
+ description?: string;
126
+ }>;
88
127
  }
89
128
  /** Validated JWT claims from express-oauth2-jwt-bearer */
90
129
  export interface JWTPayload {
@@ -208,6 +247,30 @@ export declare function action<I extends ZodTypeAny, O extends ZodTypeAny>(cfg:
208
247
  eventTypes?: string[];
209
248
  consumerGroup?: string;
210
249
  }>;
250
+ /**
251
+ * Sync capability metadata — declares this action as a data sync endpoint.
252
+ * Surfaced in the Odyssey UI DataSyncTab and included in nova-integration.yaml
253
+ * by `nova integrations build`.
254
+ */
255
+ sync?: {
256
+ entityType: string;
257
+ direction: 'to_nova' | 'from_nova' | 'bidirectional';
258
+ label: string;
259
+ description?: string;
260
+ };
261
+ /**
262
+ * Trigger-based routing declarations for event/schedule triggers.
263
+ * Used by the SSE worker and CLI build pipeline.
264
+ */
265
+ triggers?: Array<{
266
+ type: 'event';
267
+ events: string[];
268
+ } | {
269
+ type: 'schedule';
270
+ cron: string;
271
+ timezone?: string;
272
+ description?: string;
273
+ }>;
211
274
  handler: (input: z.infer<I>, ctx: ActionCtx) => Promise<z.infer<O>>;
212
275
  }): ActionDef<I, O>;
213
276
  import type { NovaSpec } from './parseSpec.js';
package/dist/index.js CHANGED
@@ -767,7 +767,10 @@ export function runHttpServer(def, opts = {}) {
767
767
  rawInput[key] = val === 'true' || val === '1';
768
768
  }
769
769
  // Auto-detect from Zod schema shape if no explicit uiType
770
- else if (!uiType && act.input?._def?.shape) {
770
+ // Guard: _def.shape must be a function (Zod v3 thunk) before calling it.
771
+ // Some Zod internals expose _def.shape as a plain object; calling a non-function
772
+ // throws "act.input._def.shape is not a function" at runtime.
773
+ else if (!uiType && typeof act.input?._def?.shape === 'function') {
771
774
  const fieldDef = act.input._def.shape()?.[key];
772
775
  const innerType = fieldDef?._def?.innerType?._def?.typeName ?? fieldDef?._def?.typeName;
773
776
  if (innerType === 'ZodNumber') {
package/dist/next.d.ts CHANGED
@@ -102,6 +102,50 @@ export interface NovaEndpointEventDef {
102
102
  /** Whether this event should trigger a push notification. Defaults to false. */
103
103
  triggers_notification?: boolean;
104
104
  }
105
+ /**
106
+ * Human-readable metadata for a single permission slug.
107
+ * Defined inline in `novaEndpoint()` alongside `requiredPermissions` and
108
+ * auto-embedded into the IAM permission registry when `nova services push` runs.
109
+ *
110
+ * This drives hierarchical/grouped rendering in the Odyssey UI permission
111
+ * assignment views — permissions in the same `group` are displayed together
112
+ * under a shared parent label with friendly titles and descriptions.
113
+ *
114
+ * @example
115
+ * ```ts
116
+ * requiredPermissions: 'iam_admin:roles_view',
117
+ * permissionMeta: {
118
+ * slug: 'iam_admin:roles_view',
119
+ * title: 'View Roles',
120
+ * description: 'Read access to role definitions and listings',
121
+ * group: 'Roles',
122
+ * },
123
+ * ```
124
+ */
125
+ export interface PermissionMeta {
126
+ /**
127
+ * The exact permission slug this metadata describes.
128
+ * Must match a value in `requiredPermissions`.
129
+ * e.g. `'iam_admin:roles_view'`
130
+ */
131
+ slug: string;
132
+ /**
133
+ * Human-friendly label shown in the Odyssey UI.
134
+ * e.g. `'View Roles'`
135
+ */
136
+ title: string;
137
+ /**
138
+ * Optional description of what this permission grants.
139
+ * e.g. `'Read access to role definitions and listings'`
140
+ */
141
+ description?: string;
142
+ /**
143
+ * Parent group label used for hierarchical rendering in the UI.
144
+ * All permissions with the same `group` value are displayed together.
145
+ * e.g. `'Roles'`
146
+ */
147
+ group: string;
148
+ }
105
149
  export interface NovaEndpointDef<I extends ZodTypeAny = ZodTypeAny, O extends ZodTypeAny = ZodTypeAny> {
106
150
  /** HTTP method */
107
151
  method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
@@ -139,6 +183,29 @@ export interface NovaEndpointDef<I extends ZodTypeAny = ZodTypeAny, O extends Zo
139
183
  * DELETE → `'hris_admin:manage'`
140
184
  */
141
185
  requiredPermissions?: string | string[];
186
+ /**
187
+ * Human-readable metadata for the permission slug(s) in `requiredPermissions`.
188
+ * Used to generate hierarchical, grouped permission views in the Odyssey UI.
189
+ *
190
+ * Provide one entry per unique permission slug used on this endpoint.
191
+ * Metadata is deduplicated by slug — defining the same slug on multiple
192
+ * endpoints is fine; the first definition wins.
193
+ *
194
+ * `nova services push` reads this field and embeds title, description,
195
+ * and group into the IAM permission registry automatically.
196
+ *
197
+ * @example
198
+ * ```ts
199
+ * requiredPermissions: 'iam_admin:roles_view',
200
+ * permissionMeta: {
201
+ * slug: 'iam_admin:roles_view',
202
+ * title: 'View Roles',
203
+ * description: 'Read access to role definitions and listings',
204
+ * group: 'Roles',
205
+ * },
206
+ * ```
207
+ */
208
+ permissionMeta?: PermissionMeta | PermissionMeta[];
142
209
  /**
143
210
  * Event types emitted by this endpoint.
144
211
  * Defined here and auto-registered in the platform event_types registry
@@ -303,23 +370,9 @@ export declare function buildPrismaPage(input: CursorPageInputType): {
303
370
  id: 'desc';
304
371
  }>;
305
372
  };
306
- /**
307
- * Convert a snake_case string to camelCase.
308
- * Used internally to normalise sort column names from API query params
309
- * (snake_case) to Prisma model field names (camelCase).
310
- *
311
- * @example
312
- * ```ts
313
- * snakeToCamel('modified_at') // → 'modifiedAt'
314
- * snakeToCamel('createdAt') // → 'createdAt' (no-op)
315
- * ```
316
- */
317
- export declare function snakeToCamel(s: string): string;
318
373
  /**
319
374
  * Build Prisma `skip`, `take`, and `orderBy` from an `OffsetPageInput`.
320
375
  * Handles offset pagination and sort direction.
321
- * Sort column names are automatically converted from snake_case (API convention)
322
- * to camelCase (Prisma convention) via `snakeToCamel()`.
323
376
  * Does NOT handle `search` or `filters` — apply those to the `where` clause manually.
324
377
  *
325
378
  * @example
@@ -442,6 +495,51 @@ export declare const CURSOR_PAGE_PARAMS: Record<string, ParamMeta>;
442
495
  * Spread into `novaEndpoint({ params: { ...OFFSET_PAGE_PARAMS, ...yourParams } })`.
443
496
  */
444
497
  export declare const OFFSET_PAGE_PARAMS: Record<string, ParamMeta>;
498
+ /**
499
+ * Metadata for a Nova service — passed to `defineService()`.
500
+ */
501
+ export interface ServiceDef {
502
+ /** Unique service slug (snake_case). e.g. "nova_ticketing_service" */
503
+ slug: string;
504
+ /** Human-readable service name */
505
+ name: string;
506
+ /** Short service description */
507
+ description?: string;
508
+ /** Service category for Odyssey UI grouping (e.g. "support", "hris") */
509
+ category?: string;
510
+ /** Semver version string — defaults to "1.0.0" */
511
+ version?: string;
512
+ }
513
+ /**
514
+ * defineService() — declare Nova service identity and auto-set NOVA_SERVICE_SLUG.
515
+ *
516
+ * Call this once in a shared module (e.g. `src/lib/service.ts`) and import it
517
+ * early in your app entrypoint. At import time it sets `process.env.NOVA_SERVICE_SLUG`
518
+ * so `withServiceEventOutbox()` can stamp `source_service` on all outbound events.
519
+ *
520
+ * `nova services push` also detects this export (the `__novaService` marker) to
521
+ * cross-validate the slug against `nova-service.yaml` during the build scan.
522
+ *
523
+ * **Usage:**
524
+ * ```ts
525
+ * // src/lib/service.ts
526
+ * import { defineService } from '@newhomestar/sdk/next';
527
+ *
528
+ * export const service = defineService({
529
+ * slug: 'nova_ticketing_service',
530
+ * name: 'Nova Ticketing Service',
531
+ * category: 'support',
532
+ * });
533
+ * ```
534
+ *
535
+ * Then import it early — e.g. in `src/instrumentation.ts` (Next.js) or your app entry:
536
+ * ```ts
537
+ * import '@/lib/service'; // ensures NOVA_SERVICE_SLUG is set before any route runs
538
+ * ```
539
+ */
540
+ export declare function defineService<T extends ServiceDef>(def: T): T & {
541
+ readonly __novaService: true;
542
+ };
445
543
  /** @deprecated Use `buildPageResponse` instead */
446
544
  export declare function buildPaginatedResponse<T extends {
447
545
  id: string;
package/dist/next.js CHANGED
@@ -191,25 +191,9 @@ export function buildPrismaPage(input) {
191
191
  orderBy: [{ createdAt: 'desc' }, { id: 'desc' }],
192
192
  };
193
193
  }
194
- /**
195
- * Convert a snake_case string to camelCase.
196
- * Used internally to normalise sort column names from API query params
197
- * (snake_case) to Prisma model field names (camelCase).
198
- *
199
- * @example
200
- * ```ts
201
- * snakeToCamel('modified_at') // → 'modifiedAt'
202
- * snakeToCamel('createdAt') // → 'createdAt' (no-op)
203
- * ```
204
- */
205
- export function snakeToCamel(s) {
206
- return s.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
207
- }
208
194
  /**
209
195
  * Build Prisma `skip`, `take`, and `orderBy` from an `OffsetPageInput`.
210
196
  * Handles offset pagination and sort direction.
211
- * Sort column names are automatically converted from snake_case (API convention)
212
- * to camelCase (Prisma convention) via `snakeToCamel()`.
213
197
  * Does NOT handle `search` or `filters` — apply those to the `where` clause manually.
214
198
  *
215
199
  * @example
@@ -219,7 +203,7 @@ export function snakeToCamel(s) {
219
203
  * ```
220
204
  */
221
205
  export function buildPrismaOffsetPage(input, defaultSort = 'createdAt') {
222
- const col = snakeToCamel(input.sort ?? defaultSort);
206
+ const col = input.sort ?? defaultSort;
223
207
  return {
224
208
  skip: input.offset,
225
209
  take: input.limit,
@@ -339,6 +323,42 @@ export const OFFSET_PAGE_PARAMS = {
339
323
  sort_dir: { in: 'query', uiType: 'select', label: 'Sort Dir', description: 'asc or desc', options: [{ label: 'Ascending', value: 'asc' }, { label: 'Descending', value: 'desc' }] },
340
324
  filters: { in: 'query', uiType: 'json', label: 'Filters', description: 'JSON array of FilterCondition objects' },
341
325
  };
326
+ /**
327
+ * defineService() — declare Nova service identity and auto-set NOVA_SERVICE_SLUG.
328
+ *
329
+ * Call this once in a shared module (e.g. `src/lib/service.ts`) and import it
330
+ * early in your app entrypoint. At import time it sets `process.env.NOVA_SERVICE_SLUG`
331
+ * so `withServiceEventOutbox()` can stamp `source_service` on all outbound events.
332
+ *
333
+ * `nova services push` also detects this export (the `__novaService` marker) to
334
+ * cross-validate the slug against `nova-service.yaml` during the build scan.
335
+ *
336
+ * **Usage:**
337
+ * ```ts
338
+ * // src/lib/service.ts
339
+ * import { defineService } from '@newhomestar/sdk/next';
340
+ *
341
+ * export const service = defineService({
342
+ * slug: 'nova_ticketing_service',
343
+ * name: 'Nova Ticketing Service',
344
+ * category: 'support',
345
+ * });
346
+ * ```
347
+ *
348
+ * Then import it early — e.g. in `src/instrumentation.ts` (Next.js) or your app entry:
349
+ * ```ts
350
+ * import '@/lib/service'; // ensures NOVA_SERVICE_SLUG is set before any route runs
351
+ * ```
352
+ */
353
+ export function defineService(def) {
354
+ // Set NOVA_SERVICE_SLUG at import time so withServiceEventOutbox() always has
355
+ // access to source_service without requiring a separate env var. Only sets it
356
+ // if not already set — explicit env var overrides (e.g. in tests) still work.
357
+ if (!process.env.NOVA_SERVICE_SLUG) {
358
+ process.env.NOVA_SERVICE_SLUG = def.slug;
359
+ }
360
+ return { ...def, __novaService: true };
361
+ }
342
362
  // ─── Legacy re-exports (backward compat) ─────────────────────────────────────
343
363
  /** @deprecated Use `buildPageResponse` instead */
344
364
  export function buildPaginatedResponse(data, count, pageSize) {
package/package.json CHANGED
@@ -1,58 +1,58 @@
1
- {
2
- "name": "@newhomestar/sdk",
3
- "version": "0.7.15",
4
- "description": "Type-safe SDK for building Nova pipelines (workers & functions)",
5
- "homepage": "https://github.com/newhomestar/nova-node-sdk#readme",
6
- "bugs": {
7
- "url": "https://github.com/newhomestar/nova-node-sdk/issues"
8
- },
9
- "repository": {
10
- "type": "git",
11
- "url": "git+https://github.com/newhomestar/nova-node-sdk.git"
12
- },
13
- "license": "ISC",
14
- "author": "Christian Gomez",
15
- "type": "module",
16
- "main": "dist/index.js",
17
- "types": "dist/index.d.ts",
18
- "exports": {
19
- ".": {
20
- "import": "./dist/index.js",
21
- "types": "./dist/index.d.ts"
22
- },
23
- "./next": {
24
- "import": "./dist/next.js",
25
- "types": "./dist/next.d.ts"
26
- },
27
- "./events": {
28
- "import": "./dist/events.js",
29
- "types": "./dist/events.d.ts"
30
- }
31
- },
32
- "files": [
33
- "dist"
34
- ],
35
- "scripts": {
36
- "build": "tsc"
37
- },
38
- "dependencies": {
39
- "@openfga/sdk": "^0.9.0",
40
- "@orpc/openapi": "1.7.4",
41
- "@orpc/server": "1.7.4",
42
- "@supabase/supabase-js": "^2.39.0",
43
- "body-parser": "^1.20.2",
44
- "dotenv": "^16.4.3",
45
- "express": "^4.18.2",
46
- "express-oauth2-jwt-bearer": "^1.7.4",
47
- "undici": "^7.24.4",
48
- "yaml": "^2.7.1"
49
- },
50
- "peerDependencies": {
51
- "zod": ">=4.0.0"
52
- },
53
- "devDependencies": {
54
- "@types/node": "^20.11.17",
55
- "typescript": "^5.4.4",
56
- "zod": "^4.3.0"
57
- }
58
- }
1
+ {
2
+ "name": "@newhomestar/sdk",
3
+ "version": "0.7.17",
4
+ "description": "Type-safe SDK for building Nova pipelines (workers & functions)",
5
+ "homepage": "https://github.com/newhomestar/nova-node-sdk#readme",
6
+ "bugs": {
7
+ "url": "https://github.com/newhomestar/nova-node-sdk/issues"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/newhomestar/nova-node-sdk.git"
12
+ },
13
+ "license": "ISC",
14
+ "author": "Christian Gomez",
15
+ "type": "module",
16
+ "main": "dist/index.js",
17
+ "types": "dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "import": "./dist/index.js",
21
+ "types": "./dist/index.d.ts"
22
+ },
23
+ "./next": {
24
+ "import": "./dist/next.js",
25
+ "types": "./dist/next.d.ts"
26
+ },
27
+ "./events": {
28
+ "import": "./dist/events.js",
29
+ "types": "./dist/events.d.ts"
30
+ }
31
+ },
32
+ "files": [
33
+ "dist"
34
+ ],
35
+ "scripts": {
36
+ "build": "tsc"
37
+ },
38
+ "dependencies": {
39
+ "@openfga/sdk": "^0.9.0",
40
+ "@orpc/openapi": "1.7.4",
41
+ "@orpc/server": "1.7.4",
42
+ "@supabase/supabase-js": "^2.39.0",
43
+ "body-parser": "^1.20.2",
44
+ "dotenv": "^16.4.3",
45
+ "express": "^4.18.2",
46
+ "express-oauth2-jwt-bearer": "^1.7.4",
47
+ "undici": "^7.24.4",
48
+ "yaml": "^2.7.1"
49
+ },
50
+ "peerDependencies": {
51
+ "zod": ">=4.0.0"
52
+ },
53
+ "devDependencies": {
54
+ "@types/node": "^20.11.17",
55
+ "typescript": "^5.4.4",
56
+ "zod": "^4.3.0"
57
+ }
58
+ }