@newhomestar/sdk 0.5.2 → 0.6.7

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,186 @@
1
+ import { parse as parseYAML } from "yaml";
2
+ import { z } from "zod";
3
+ /**
4
+ * IntegrationSpec — the shape of nova-integration.yaml
5
+ * Generated by `nova integrations build` from a defineIntegration() call.
6
+ */
7
+ const IntegrationSchemaSpecSchema = z.object({
8
+ slug: z.string(),
9
+ name: z.string(),
10
+ type: z.enum(['request', 'response', 'entity', 'webhook_payload', 'configuration']),
11
+ description: z.string().optional(),
12
+ schema: z.union([z.string(), z.record(z.string(), z.unknown())]),
13
+ version: z.string().optional(),
14
+ fieldCount: z.number().optional(),
15
+ });
16
+ const IntegrationEventSpecSchema = z.object({
17
+ slug: z.string(),
18
+ name: z.string(),
19
+ direction: z.enum(['inbound', 'outbound', 'bidirectional']),
20
+ description: z.string().optional(),
21
+ category: z.string().optional(),
22
+ severity: z.enum(['info', 'warning', 'error', 'critical']).optional(),
23
+ payloadSchema: z.string().optional(),
24
+ });
25
+ const IntegrationFunctionSpecSchema = z.object({
26
+ slug: z.string(),
27
+ name: z.string(),
28
+ httpMethod: z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']),
29
+ endpointPath: z.string(),
30
+ description: z.string().optional(),
31
+ requestSchema: z.string().optional(),
32
+ responseSchema: z.string().optional(),
33
+ requiredScopes: z.array(z.string()).optional(),
34
+ category: z.string().optional(),
35
+ });
36
+ // Capability schemas (shared with worker spec)
37
+ const WebhookCapabilitySchema = z.object({
38
+ type: z.literal('webhook'),
39
+ eventTypes: z.array(z.string()),
40
+ source: z.string(),
41
+ endpoint: z.string().optional(),
42
+ headers: z.record(z.string(), z.string()).optional(),
43
+ authentication: z.object({
44
+ type: z.enum(['bearer', 'basic', 'api_key', 'signature']),
45
+ config: z.record(z.string(), z.unknown()).optional(),
46
+ }).optional(),
47
+ });
48
+ const ScheduledCapabilitySchema = z.object({
49
+ type: z.literal('scheduled'),
50
+ cron: z.string(),
51
+ timezone: z.string().optional(),
52
+ description: z.string().optional(),
53
+ });
54
+ const QueueCapabilitySchema = z.object({
55
+ type: z.literal('queue'),
56
+ topics: z.array(z.string()),
57
+ queueName: z.string().optional(),
58
+ consumerGroup: z.string().optional(),
59
+ });
60
+ const StreamCapabilitySchema = z.object({
61
+ type: z.literal('stream'),
62
+ streamName: z.string(),
63
+ eventTypes: z.array(z.string()).optional(),
64
+ consumerGroup: z.string().optional(),
65
+ });
66
+ const CapabilitySchema = z.discriminatedUnion('type', [
67
+ WebhookCapabilitySchema,
68
+ ScheduledCapabilitySchema,
69
+ QueueCapabilitySchema,
70
+ StreamCapabilitySchema,
71
+ ]);
72
+ export const IntegrationSpecSchema = z.object({
73
+ apiVersion: z.string(),
74
+ kind: z.literal('Integration'),
75
+ metadata: z.object({
76
+ slug: z.string(),
77
+ name: z.string(),
78
+ displayName: z.string().optional(),
79
+ description: z.string().optional(),
80
+ category: z.string().optional(),
81
+ icon: z.string().optional(),
82
+ tags: z.array(z.string()).optional(),
83
+ logoUrl: z.string().optional(),
84
+ color: z.string().optional(),
85
+ }),
86
+ spec: z.object({
87
+ type: z.enum(['oidc', 'oauth2', 'api_key']),
88
+ endpoints: z.object({
89
+ authorization: z.string().optional(),
90
+ token: z.string().optional(),
91
+ userinfo: z.string().optional(),
92
+ revocation: z.string().optional(),
93
+ jwks: z.string().optional(),
94
+ baseUrl: z.string().optional(),
95
+ }).optional(),
96
+ scopes: z.array(z.string()).optional(),
97
+ runtime: z.object({
98
+ type: z.string(),
99
+ image: z.string(),
100
+ queue: z.string(),
101
+ port: z.number().optional(),
102
+ command: z.array(z.string()),
103
+ resources: z.object({
104
+ cpu: z.string(),
105
+ memory: z.string(),
106
+ }),
107
+ envSpec: z.array(z.object({
108
+ name: z.string(),
109
+ value: z.string().optional(),
110
+ secret: z.boolean().optional(),
111
+ default: z.string().optional(),
112
+ })).optional(),
113
+ }),
114
+ actions: z.array(z.object({
115
+ name: z.string(),
116
+ displayName: z.string().optional(),
117
+ description: z.string().optional(),
118
+ async: z.boolean().optional(),
119
+ input: z.unknown().optional(),
120
+ output: z.unknown().optional(),
121
+ schema: z.object({
122
+ input: z.string(),
123
+ output: z.string(),
124
+ }).optional(),
125
+ fga: z.object({
126
+ resourceType: z.string(),
127
+ relation: z.string(),
128
+ }).optional(),
129
+ capabilities: z.array(CapabilitySchema).optional(),
130
+ })),
131
+ capabilities: z.array(CapabilitySchema).optional(),
132
+ // Integration-specific config sections
133
+ schemas: z.array(IntegrationSchemaSpecSchema).optional(),
134
+ events: z.array(IntegrationEventSpecSchema).optional(),
135
+ functions: z.array(IntegrationFunctionSpecSchema).optional(),
136
+ }),
137
+ build: z.object({
138
+ dockerfile: z.string(),
139
+ context: z.string(),
140
+ }).optional(),
141
+ ui: z.object({
142
+ category: z.string().optional(),
143
+ color: z.string().optional(),
144
+ }).optional(),
145
+ fga: z.object({
146
+ types: z.array(z.object({
147
+ name: z.string(),
148
+ relations: z.record(z.string(), z.union([
149
+ z.array(z.string()),
150
+ z.object({
151
+ computedUserset: z.object({
152
+ object: z.string(),
153
+ relation: z.string(),
154
+ }),
155
+ }),
156
+ ])),
157
+ })),
158
+ }).optional(),
159
+ });
160
+ /**
161
+ * Parse nova-integration.yaml content and validate against the IntegrationSpec schema.
162
+ * @param yamlContent String contents of a nova-integration.yaml specification
163
+ * @returns Parsed IntegrationSpec object
164
+ * @throws Error with validation details if parsing or validation fail
165
+ */
166
+ export function parseIntegrationSpec(yamlContent) {
167
+ let parsed;
168
+ try {
169
+ parsed = parseYAML(yamlContent);
170
+ }
171
+ catch (e) {
172
+ throw new Error(`Failed to parse YAML content: ${e.message}`);
173
+ }
174
+ try {
175
+ return IntegrationSpecSchema.parse(parsed);
176
+ }
177
+ catch (e) {
178
+ if (e instanceof z.ZodError) {
179
+ const details = e.issues
180
+ .map((issue) => `Path '${issue.path.join(".")}': ${issue.message}`)
181
+ .join("; ");
182
+ throw new Error(`nova-integration.yaml validation error(s): ${details}`);
183
+ }
184
+ throw e;
185
+ }
186
+ }
package/dist/next.d.ts ADDED
@@ -0,0 +1,105 @@
1
+ /**
2
+ * @newhomestar/sdk/next
3
+ *
4
+ * Utilities for building Nova service endpoints in Next.js App Router.
5
+ * Each route file exports a `nova` constant using `novaEndpoint()`.
6
+ *
7
+ * `nova services push` scans all route files for this export and
8
+ * auto-registers the endpoint in the platform DB (app_service_endpoints).
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * // src/app/api/hris/employees/route.ts
13
+ * import { novaEndpoint } from '@newhomestar/sdk/next';
14
+ * import { z } from 'zod';
15
+ *
16
+ * export const nova = novaEndpoint({
17
+ * method: 'GET',
18
+ * path: '/hris/employees',
19
+ * category: 'employees',
20
+ * input: z.object({ page_size: z.coerce.number().default(25) }),
21
+ * output: z.object({ results: z.array(z.any()), next: z.string().nullable() }),
22
+ * });
23
+ *
24
+ * export async function GET(req: Request) {
25
+ * const input = nova.parseQuery(req);
26
+ * return nova.respond({ results: [], next: null });
27
+ * }
28
+ * ```
29
+ */
30
+ import type { ZodTypeAny } from 'zod';
31
+ export type ParamIn = 'path' | 'query' | 'body' | 'header';
32
+ export type ParamUiType = 'text' | 'textarea' | 'number' | 'integer' | 'boolean' | 'date' | 'datetime' | 'select' | 'multiselect' | 'password' | 'email' | 'url' | 'uuid' | 'json' | 'hidden';
33
+ export interface ParamMeta {
34
+ in: ParamIn;
35
+ uiType?: ParamUiType;
36
+ label?: string;
37
+ description?: string;
38
+ placeholder?: string;
39
+ required?: boolean;
40
+ defaultValue?: unknown;
41
+ options?: Array<{
42
+ label: string;
43
+ value: string | number | boolean;
44
+ }>;
45
+ min?: number;
46
+ max?: number;
47
+ step?: number;
48
+ pattern?: string;
49
+ order?: number;
50
+ group?: string;
51
+ }
52
+ export interface NovaEndpointDef<I extends ZodTypeAny = ZodTypeAny, O extends ZodTypeAny = ZodTypeAny> {
53
+ /** HTTP method */
54
+ method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
55
+ /** REST path template. Use :param for path params. e.g. "/hris/employees/:id" */
56
+ path: string;
57
+ /** Human-readable description shown in Odyssey UI endpoint tester */
58
+ description?: string;
59
+ /** Endpoint group for Odyssey UI organization */
60
+ category?: string;
61
+ /** Reference to a service schema slug for the response */
62
+ responseSchema?: string;
63
+ /** Zod schema for query/path params — converted to JSON Schema on push */
64
+ input: I;
65
+ /** Zod schema for the response body — converted to JSON Schema on push */
66
+ output: O;
67
+ /** Per-field parameter metadata */
68
+ params?: Record<string, ParamMeta>;
69
+ /** Whether this endpoint requires a valid JWT (default: true) */
70
+ requiresAuth?: boolean;
71
+ /** Whether this endpoint returns sensitive data (SSN, DOB, etc.) */
72
+ sensitiveFields?: boolean;
73
+ }
74
+ export type NovaEndpoint<I extends ZodTypeAny = ZodTypeAny, O extends ZodTypeAny = ZodTypeAny> = NovaEndpointDef<I, O> & {
75
+ /**
76
+ * Parse and validate URL query string params against the input schema.
77
+ * Uses z.coerce for string→number/boolean conversion automatically.
78
+ */
79
+ parseQuery(req: Request): ReturnType<I['parse']>;
80
+ /**
81
+ * Parse and validate JSON request body against the input schema.
82
+ */
83
+ parseBody(req: Request): Promise<ReturnType<I['parse']>>;
84
+ /**
85
+ * Validate output data and serialize to a Response.
86
+ */
87
+ respond(data: ReturnType<O['parse']>, status?: number): Response;
88
+ };
89
+ /**
90
+ * novaEndpoint() — define a Nova service endpoint co-located with its route.
91
+ *
92
+ * Export the result as `nova` and `nova services push` will auto-discover
93
+ * and register this endpoint in the platform DB.
94
+ *
95
+ * @example
96
+ * ```ts
97
+ * export const nova = novaEndpoint({
98
+ * method: 'GET',
99
+ * path: '/hris/employees',
100
+ * input: z.object({ page_size: z.coerce.number().default(25) }),
101
+ * output: z.object({ results: z.array(EmployeeZod), next: z.string().nullable() }),
102
+ * });
103
+ * ```
104
+ */
105
+ export declare function novaEndpoint<I extends ZodTypeAny, O extends ZodTypeAny>(cfg: NovaEndpointDef<I, O>): NovaEndpoint<I, O>;
package/dist/next.js ADDED
@@ -0,0 +1,67 @@
1
+ /**
2
+ * @newhomestar/sdk/next
3
+ *
4
+ * Utilities for building Nova service endpoints in Next.js App Router.
5
+ * Each route file exports a `nova` constant using `novaEndpoint()`.
6
+ *
7
+ * `nova services push` scans all route files for this export and
8
+ * auto-registers the endpoint in the platform DB (app_service_endpoints).
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * // src/app/api/hris/employees/route.ts
13
+ * import { novaEndpoint } from '@newhomestar/sdk/next';
14
+ * import { z } from 'zod';
15
+ *
16
+ * export const nova = novaEndpoint({
17
+ * method: 'GET',
18
+ * path: '/hris/employees',
19
+ * category: 'employees',
20
+ * input: z.object({ page_size: z.coerce.number().default(25) }),
21
+ * output: z.object({ results: z.array(z.any()), next: z.string().nullable() }),
22
+ * });
23
+ *
24
+ * export async function GET(req: Request) {
25
+ * const input = nova.parseQuery(req);
26
+ * return nova.respond({ results: [], next: null });
27
+ * }
28
+ * ```
29
+ */
30
+ // ─── Factory function ─────────────────────────────────────────────────────────
31
+ /**
32
+ * novaEndpoint() — define a Nova service endpoint co-located with its route.
33
+ *
34
+ * Export the result as `nova` and `nova services push` will auto-discover
35
+ * and register this endpoint in the platform DB.
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * export const nova = novaEndpoint({
40
+ * method: 'GET',
41
+ * path: '/hris/employees',
42
+ * input: z.object({ page_size: z.coerce.number().default(25) }),
43
+ * output: z.object({ results: z.array(EmployeeZod), next: z.string().nullable() }),
44
+ * });
45
+ * ```
46
+ */
47
+ export function novaEndpoint(cfg) {
48
+ return {
49
+ ...cfg,
50
+ parseQuery(req) {
51
+ const url = new URL(req.url);
52
+ const raw = {};
53
+ url.searchParams.forEach((value, key) => {
54
+ raw[key] = value;
55
+ });
56
+ return cfg.input.parse(raw);
57
+ },
58
+ async parseBody(req) {
59
+ const body = await req.json();
60
+ return cfg.input.parse(body);
61
+ },
62
+ respond(data, status = 200) {
63
+ const validated = cfg.output.parse(data);
64
+ return Response.json(validated, { status });
65
+ },
66
+ };
67
+ }
@@ -29,11 +29,54 @@ export declare const WorkerDefSchema: z.ZodObject<{
29
29
  method: z.ZodOptional<z.ZodEnum<{
30
30
  GET: "GET";
31
31
  POST: "POST";
32
- PUT: "PUT";
33
- DELETE: "DELETE";
34
32
  PATCH: "PATCH";
33
+ DELETE: "DELETE";
34
+ PUT: "PUT";
35
35
  }>>;
36
36
  path: z.ZodOptional<z.ZodString>;
37
+ scopes: z.ZodOptional<z.ZodArray<z.ZodString>>;
38
+ category: z.ZodOptional<z.ZodString>;
39
+ description: z.ZodOptional<z.ZodString>;
40
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
41
+ in: z.ZodEnum<{
42
+ body: "body";
43
+ path: "path";
44
+ query: "query";
45
+ header: "header";
46
+ }>;
47
+ uiType: z.ZodOptional<z.ZodEnum<{
48
+ number: "number";
49
+ boolean: "boolean";
50
+ date: "date";
51
+ text: "text";
52
+ uuid: "uuid";
53
+ json: "json";
54
+ textarea: "textarea";
55
+ integer: "integer";
56
+ datetime: "datetime";
57
+ select: "select";
58
+ multiselect: "multiselect";
59
+ password: "password";
60
+ email: "email";
61
+ url: "url";
62
+ hidden: "hidden";
63
+ }>>;
64
+ label: z.ZodOptional<z.ZodString>;
65
+ description: z.ZodOptional<z.ZodString>;
66
+ placeholder: z.ZodOptional<z.ZodString>;
67
+ required: z.ZodOptional<z.ZodBoolean>;
68
+ defaultValue: z.ZodOptional<z.ZodAny>;
69
+ options: z.ZodOptional<z.ZodArray<z.ZodObject<{
70
+ label: z.ZodString;
71
+ value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>;
72
+ }, z.core.$strip>>>;
73
+ min: z.ZodOptional<z.ZodNumber>;
74
+ max: z.ZodOptional<z.ZodNumber>;
75
+ step: z.ZodOptional<z.ZodNumber>;
76
+ pattern: z.ZodOptional<z.ZodString>;
77
+ order: z.ZodOptional<z.ZodNumber>;
78
+ group: z.ZodOptional<z.ZodString>;
79
+ }, z.core.$strip>>>;
37
80
  fga: z.ZodOptional<z.ZodObject<{
38
81
  resourceType: z.ZodString;
39
82
  relation: z.ZodString;
@@ -26,6 +26,34 @@ export const WorkerDefSchema = z.object({
26
26
  // NEW: HTTP routing support for oRPC
27
27
  method: z.enum(['GET', 'POST', 'PUT', 'DELETE', 'PATCH']).optional(),
28
28
  path: z.string().optional(),
29
+ // Integration function metadata
30
+ scopes: z.array(z.string()).optional(),
31
+ category: z.string().optional(),
32
+ description: z.string().optional(),
33
+ // Per-field parameter metadata (path/query/body/header + UI type hints)
34
+ params: z.record(z.string(), z.object({
35
+ in: z.enum(['path', 'query', 'body', 'header']),
36
+ uiType: z.enum([
37
+ 'text', 'textarea', 'number', 'integer', 'boolean',
38
+ 'date', 'datetime', 'select', 'multiselect',
39
+ 'password', 'email', 'url', 'uuid', 'json', 'hidden',
40
+ ]).optional(),
41
+ label: z.string().optional(),
42
+ description: z.string().optional(),
43
+ placeholder: z.string().optional(),
44
+ required: z.boolean().optional(),
45
+ defaultValue: z.any().optional(),
46
+ options: z.array(z.object({
47
+ label: z.string(),
48
+ value: z.union([z.string(), z.number(), z.boolean()]),
49
+ })).optional(),
50
+ min: z.number().optional(),
51
+ max: z.number().optional(),
52
+ step: z.number().optional(),
53
+ pattern: z.string().optional(),
54
+ order: z.number().optional(),
55
+ group: z.string().optional(),
56
+ })).optional(),
29
57
  // Optional per-action OpenFGA hints: resource type, relation, ID key, and optional policy caveat
30
58
  fga: z.object({
31
59
  resourceType: z.string(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newhomestar/sdk",
3
- "version": "0.5.2",
3
+ "version": "0.6.7",
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": {
@@ -19,6 +19,10 @@
19
19
  ".": {
20
20
  "import": "./dist/index.js",
21
21
  "types": "./dist/index.d.ts"
22
+ },
23
+ "./next": {
24
+ "import": "./dist/next.js",
25
+ "types": "./dist/next.d.ts"
22
26
  }
23
27
  },
24
28
  "files": [
@@ -35,6 +39,7 @@
35
39
  "body-parser": "^1.20.2",
36
40
  "dotenv": "^16.4.3",
37
41
  "express": "^4.18.2",
42
+ "express-oauth2-jwt-bearer": "^1.7.4",
38
43
  "yaml": "^2.7.1",
39
44
  "zod": "^4.0.5"
40
45
  },