@newhomestar/sdk 0.7.21 → 0.7.23

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/next.d.ts CHANGED
@@ -50,6 +50,183 @@ export interface ParamMeta {
50
50
  order?: number;
51
51
  group?: string;
52
52
  }
53
+ /**
54
+ * A single FGA relation definition for an object type.
55
+ * Declared in `defineService().fga.relations[]` and auto-registered in the
56
+ * IAM service's `entity_relation_definitions` table at `nova services push` time.
57
+ */
58
+ export interface NovaFgaRelationDef {
59
+ /** Object type this relation applies to (e.g. 'community', 'trnVideo') */
60
+ object_type: string;
61
+ /** The relation name (e.g. 'admin', 'editor', 'member', 'viewer') */
62
+ relation: string;
63
+ /**
64
+ * Relations this relation implies (e.g. admin implies ['editor', 'member']).
65
+ * The IAM DB trigger pre-expands implied relations into entity_computed_access
66
+ * automatically — no application code needed for propagation.
67
+ */
68
+ implies?: string[];
69
+ /** Priority for ordering (default: 0). Higher = evaluated first. */
70
+ priority?: number;
71
+ }
72
+ /**
73
+ * IAM resource declaration for `defineService().iam.resources[]`.
74
+ * Declares how the service's permission namespace maps to the Odyssey UI resource tree.
75
+ * Used by `nova services push` to set resource types, hierarchy levels, and parent-child
76
+ * relationships between resources.
77
+ *
78
+ * @example
79
+ * ```ts
80
+ * iam: {
81
+ * resources: [
82
+ * { name: 'HRIS Admin', resource_path: 'hris_admin', resource_type: 'module', level: 50,
83
+ * description: 'HRIS module — unified HR and payroll data' },
84
+ * { name: 'Employees', resource_path: 'hris_employees', resource_type: 'block', level: 60,
85
+ * parent_resource_path: 'hris_admin', description: 'Employee records' },
86
+ * ],
87
+ * }
88
+ * ```
89
+ */
90
+ export interface NovaIamResourceDef {
91
+ /** Human-readable display name shown in Odyssey UI (e.g. 'Employees', 'HRIS Admin') */
92
+ name: string;
93
+ /** Unique resource path — must match the permission slug prefix (e.g. 'hris_employees') */
94
+ resource_path: string;
95
+ /** Position in the resource hierarchy */
96
+ resource_type: 'system' | 'module' | 'block' | 'resource';
97
+ /** Hierarchy level: 50=module, 60=block, 80=resource */
98
+ level: number;
99
+ /** Optional description shown in Odyssey UI */
100
+ description?: string;
101
+ /**
102
+ * resource_path of the parent resource.
103
+ * Resolved to parentResourceId at `nova services push` time by the IAM service.
104
+ * e.g. set to 'hris_admin' to nest this resource under the HRIS Admin module.
105
+ */
106
+ parent_resource_path?: string;
107
+ /** Arbitrary metadata stored on the resource record */
108
+ metadata?: Record<string, unknown>;
109
+ }
110
+ /**
111
+ * FGA check declared on a novaEndpoint() — runtime-only.
112
+ * Describes which relation on which object a request's subject must hold.
113
+ * The schema (relation hierarchy) is declared in `defineService().fga.relations`.
114
+ *
115
+ * Used by `nova.assertFga(req, context)` at runtime (Phase 2).
116
+ * Stored on the endpoint record in the platform DB for Odyssey UI display.
117
+ */
118
+ export interface NovaFgaCheckDef {
119
+ /** Object type to check (e.g. 'community', 'trnVideo') */
120
+ object_type: string;
121
+ /** Relation the subject must hold (e.g. 'member', 'viewer', 'editor') */
122
+ relation: string;
123
+ /**
124
+ * Where to resolve object_id from in the request:
125
+ * 'path' → Next.js route params (e.g. params.id for /communities/:id)
126
+ * 'query' → URL query string param
127
+ */
128
+ object_id_from: 'path' | 'query';
129
+ /** The param name to extract object_id from (e.g. 'id', 'community_id') */
130
+ object_id_param: string;
131
+ /** Optional tenant_id param name for multi-tenant scoped checks */
132
+ tenant_id_param?: string;
133
+ }
134
+ /**
135
+ * Service manifest definition — the code-first single source of truth for a
136
+ * Nova service. `nova services push` reads this to generate nova-service.yaml
137
+ * and sync all service metadata to the platform.
138
+ *
139
+ * Place this in `src/service.ts` (or `src/app/service.ts`) and export as `service`.
140
+ *
141
+ * @example
142
+ * ```ts
143
+ * // src/service.ts
144
+ * import { defineService } from '@newhomestar/sdk/next';
145
+ *
146
+ * export const service = defineService({
147
+ * slug: 'community',
148
+ * name: 'Community Service',
149
+ * category: 'social',
150
+ * runtime: 'nextjs',
151
+ * apiDir: './src/app',
152
+ *
153
+ * fga: {
154
+ * relations: [
155
+ * { object_type: 'community', relation: 'admin', implies: ['editor', 'member'], priority: 100 },
156
+ * { object_type: 'community', relation: 'editor', implies: ['viewer'], priority: 50 },
157
+ * { object_type: 'community', relation: 'member', implies: ['viewer'], priority: 30 },
158
+ * { object_type: 'community', relation: 'viewer', implies: [], priority: 10 },
159
+ * ],
160
+ * },
161
+ * });
162
+ * ```
163
+ */
164
+ export interface NovaServiceDef {
165
+ /** Unique service slug (e.g. 'community', 'hris', 'iam') */
166
+ slug: string;
167
+ /** Human-readable service name */
168
+ name: string;
169
+ /** Service description */
170
+ description?: string;
171
+ /** Category for grouping in Odyssey UI (e.g. 'platform', 'social', 'hris') */
172
+ category?: string;
173
+ /** Service version (default: '1.0.0') */
174
+ version?: string;
175
+ /** Runtime mode (default: 'nextjs') */
176
+ runtime?: 'nextjs' | 'sdk';
177
+ /** Path to route files directory (default: './src/app') */
178
+ apiDir?: string;
179
+ /** Public base URL for Odyssey UI API tester */
180
+ baseUrl?: string;
181
+ /** Shared Zod schemas registered in platform DB on push */
182
+ schemas?: Array<{
183
+ slug: string;
184
+ name: string;
185
+ file: string;
186
+ description?: string;
187
+ version?: string;
188
+ schemaType?: string;
189
+ }>;
190
+ /** Environment variable declarations */
191
+ envSpec?: Array<{
192
+ name: string;
193
+ secret?: boolean;
194
+ default?: string;
195
+ }>;
196
+ /** Container resource requests */
197
+ resources?: {
198
+ cpu?: string;
199
+ memory?: string;
200
+ };
201
+ /**
202
+ * FGA schema — defines relation hierarchies for all object types owned by
203
+ * this service. Registered in IAM `entity_relation_definitions` at push time.
204
+ *
205
+ * This MUST be pushed before any `entity_grants` are assigned, as the
206
+ * trigger-based expansion relies on these definitions to propagate implied
207
+ * relations through `entity_computed_access`.
208
+ */
209
+ fga?: {
210
+ relations: NovaFgaRelationDef[];
211
+ };
212
+ /**
213
+ * IAM resource hierarchy — declares how this service's permission namespace
214
+ * maps to the Odyssey UI resource tree (module → block → resource).
215
+ *
216
+ * Resources declared here override the auto-derived defaults (which default
217
+ * to resource_type: 'module', level: 50, no parent). Use this to:
218
+ * - Declare a parent module resource (e.g. 'hris_admin')
219
+ * - Nest entity resources as blocks under that module
220
+ * - Control display names, descriptions, and hierarchy levels
221
+ *
222
+ * At `nova services push` time the CLI reads this, merges with auto-derived
223
+ * resources from permission slugs, and sends `parent_resource_path` to the
224
+ * IAM service which resolves it to `parentResourceId` in the DB.
225
+ */
226
+ iam?: {
227
+ resources: NovaIamResourceDef[];
228
+ };
229
+ }
53
230
  /**
54
231
  * An event type emitted by a service endpoint.
55
232
  * Defined inline in `novaEndpoint()` and auto-registered in the platform
@@ -218,6 +395,47 @@ export interface NovaEndpointDef<I extends ZodTypeAny = ZodTypeAny, O extends Zo
218
395
  * ```
219
396
  */
220
397
  events?: NovaEndpointEventDef[];
398
+ /**
399
+ * FGA (Fine-Grained Authorization) check(s) required to access this endpoint.
400
+ *
401
+ * The authenticated subject (user) must hold the declared `relation` on the
402
+ * specified `object_type:object_id` to be granted access.
403
+ *
404
+ * The FGA schema (relation hierarchy) is declared separately in
405
+ * `defineService().fga.relations` and registered at `nova services push` time.
406
+ * The fgaCheck here is **runtime-only** — it describes how to resolve the
407
+ * object_id from the current request and which relation to verify.
408
+ *
409
+ * Runtime enforcement is via `nova.assertFga(req, { path: params })` (Phase 2).
410
+ * At push time, the declarations are stored on the endpoint record in the
411
+ * platform DB for Odyssey UI display.
412
+ *
413
+ * @example
414
+ * ```ts
415
+ * // Single check: user must be a member of the community
416
+ * fgaCheck: {
417
+ * object_type: 'community',
418
+ * relation: 'member',
419
+ * object_id_from: 'path',
420
+ * object_id_param: 'id',
421
+ * }
422
+ *
423
+ * // Multiple checks: user must be member of community AND editor of the video
424
+ * fgaCheck: [
425
+ * { object_type: 'community', relation: 'member', object_id_from: 'path', object_id_param: 'community_id' },
426
+ * { object_type: 'trnVideo', relation: 'editor', object_id_from: 'path', object_id_param: 'video_id' },
427
+ * ],
428
+ * fgaMode: 'all',
429
+ * ```
430
+ */
431
+ fgaCheck?: NovaFgaCheckDef | NovaFgaCheckDef[];
432
+ /**
433
+ * When multiple `fgaCheck` entries are declared, controls evaluation logic:
434
+ * 'all' → every check must pass (default — AND logic, most restrictive)
435
+ * 'any' → at least one check must pass (OR logic)
436
+ * Ignored when fgaCheck is a single object.
437
+ */
438
+ fgaMode?: 'all' | 'any';
221
439
  }
222
440
  export type NovaEndpoint<I extends ZodTypeAny = ZodTypeAny, O extends ZodTypeAny = ZodTypeAny> = NovaEndpointDef<I, O> & {
223
441
  /**
@@ -370,9 +588,23 @@ export declare function buildPrismaPage(input: CursorPageInputType): {
370
588
  id: 'desc';
371
589
  }>;
372
590
  };
591
+ /**
592
+ * Convert a snake_case string to camelCase.
593
+ * Used internally to normalise sort column names from API query params
594
+ * (snake_case) to Prisma model field names (camelCase).
595
+ *
596
+ * @example
597
+ * ```ts
598
+ * snakeToCamel('modified_at') // → 'modifiedAt'
599
+ * snakeToCamel('createdAt') // → 'createdAt' (no-op)
600
+ * ```
601
+ */
602
+ export declare function snakeToCamel(s: string): string;
373
603
  /**
374
604
  * Build Prisma `skip`, `take`, and `orderBy` from an `OffsetPageInput`.
375
605
  * Handles offset pagination and sort direction.
606
+ * Sort column names are automatically converted from snake_case (API convention)
607
+ * to camelCase (Prisma convention) via `snakeToCamel()`.
376
608
  * Does NOT handle `search` or `filters` — apply those to the `where` clause manually.
377
609
  *
378
610
  * @example
@@ -497,6 +729,8 @@ export declare const CURSOR_PAGE_PARAMS: Record<string, ParamMeta>;
497
729
  export declare const OFFSET_PAGE_PARAMS: Record<string, ParamMeta>;
498
730
  /**
499
731
  * Metadata for a Nova service — passed to `defineService()`.
732
+ * Extends `NovaServiceDef` to include FGA relation schema and IAM resource
733
+ * hierarchy declarations consumed by `nova services push`.
500
734
  */
501
735
  export interface ServiceDef {
502
736
  /** Unique service slug (snake_case). e.g. "nova_ticketing_service" */
@@ -509,6 +743,21 @@ export interface ServiceDef {
509
743
  category?: string;
510
744
  /** Semver version string — defaults to "1.0.0" */
511
745
  version?: string;
746
+ /**
747
+ * FGA schema — relation hierarchies for object types owned by this service.
748
+ * Registered in IAM `entity_relation_definitions` at `nova services push` time.
749
+ */
750
+ fga?: {
751
+ relations: NovaFgaRelationDef[];
752
+ };
753
+ /**
754
+ * IAM resource hierarchy — maps permission namespace to the Odyssey UI tree.
755
+ * Overrides auto-derived resource names/types/levels and sets parent-child links.
756
+ * See `NovaIamResourceDef` for field documentation.
757
+ */
758
+ iam?: {
759
+ resources: NovaIamResourceDef[];
760
+ };
512
761
  }
513
762
  /**
514
763
  * defineService() — declare Nova service identity and auto-set NOVA_SERVICE_SLUG.
package/dist/next.js CHANGED
@@ -191,9 +191,25 @@ 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
+ }
194
208
  /**
195
209
  * Build Prisma `skip`, `take`, and `orderBy` from an `OffsetPageInput`.
196
210
  * 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()`.
197
213
  * Does NOT handle `search` or `filters` — apply those to the `where` clause manually.
198
214
  *
199
215
  * @example
@@ -203,7 +219,7 @@ export function buildPrismaPage(input) {
203
219
  * ```
204
220
  */
205
221
  export function buildPrismaOffsetPage(input, defaultSort = 'createdAt') {
206
- const col = input.sort ?? defaultSort;
222
+ const col = snakeToCamel(input.sort ?? defaultSort);
207
223
  return {
208
224
  skip: input.offset,
209
225
  take: input.limit,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newhomestar/sdk",
3
- "version": "0.7.21",
3
+ "version": "0.7.23",
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": {