@newhomestar/sdk 0.6.7 → 0.6.9
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 +226 -0
- package/dist/events.js +503 -0
- package/dist/index.d.ts +37 -1
- package/dist/index.js +214 -0
- package/dist/integration.d.ts +81 -0
- package/dist/integration.js +13 -0
- package/dist/integrationSpec.d.ts +2 -2
- package/dist/next.d.ts +232 -1
- package/dist/next.js +174 -1
- package/dist/parseSpec.d.ts +2 -2
- package/dist/workerSchema.d.ts +9 -0
- package/dist/workerSchema.js +18 -1
- package/package.json +11 -4
package/dist/next.js
CHANGED
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
* }
|
|
28
28
|
* ```
|
|
29
29
|
*/
|
|
30
|
+
import { z } from 'zod';
|
|
30
31
|
// ─── Factory function ─────────────────────────────────────────────────────────
|
|
31
32
|
/**
|
|
32
33
|
* novaEndpoint() — define a Nova service endpoint co-located with its route.
|
|
@@ -59,9 +60,181 @@ export function novaEndpoint(cfg) {
|
|
|
59
60
|
const body = await req.json();
|
|
60
61
|
return cfg.input.parse(body);
|
|
61
62
|
},
|
|
62
|
-
respond(data,
|
|
63
|
+
respond(data, init) {
|
|
64
|
+
const status = typeof init === 'number' ? init : (init?.status ?? 200);
|
|
63
65
|
const validated = cfg.output.parse(data);
|
|
64
66
|
return Response.json(validated, { status });
|
|
65
67
|
},
|
|
66
68
|
};
|
|
67
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Encode a record into an opaque base64url cursor string.
|
|
72
|
+
* The cursor encodes `id` and `created_at` for stable keyset pagination.
|
|
73
|
+
*/
|
|
74
|
+
export function encodeCursor(record) {
|
|
75
|
+
const payload = { id: record.id, created_at: record.created_at };
|
|
76
|
+
return Buffer.from(JSON.stringify(payload)).toString('base64url');
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Decode a cursor string back into its `id` and `created_at` fields.
|
|
80
|
+
* Throws a descriptive error if the cursor is malformed.
|
|
81
|
+
*/
|
|
82
|
+
export function parseCursor(cursor) {
|
|
83
|
+
try {
|
|
84
|
+
const json = Buffer.from(cursor, 'base64url').toString('utf-8');
|
|
85
|
+
const parsed = JSON.parse(json);
|
|
86
|
+
if (!parsed.id || !parsed.created_at)
|
|
87
|
+
throw new Error('Missing required cursor fields');
|
|
88
|
+
return parsed;
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
throw new Error(`Invalid cursor: "${cursor}". Cursors must be generated by encodeCursor().`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Build the unified `PagedResponse` from a DB result set.
|
|
96
|
+
*
|
|
97
|
+
* @param results Mapped row objects for this page
|
|
98
|
+
* @param total Total count of ALL records (pre-filter)
|
|
99
|
+
* @param filteredTotal Count after search/filters applied (pass same as `total` if no filter active)
|
|
100
|
+
* @param pageSize The page_size / limit that was requested
|
|
101
|
+
*/
|
|
102
|
+
export function buildPageResponse(results, total, filteredTotal, pageSize) {
|
|
103
|
+
return {
|
|
104
|
+
results,
|
|
105
|
+
total,
|
|
106
|
+
filtered_total: filteredTotal,
|
|
107
|
+
next: results.length === pageSize ? encodeCursor(results[results.length - 1]) : null,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
// ─── Zod schemas ─────────────────────────────────────────────────────────────
|
|
111
|
+
/**
|
|
112
|
+
* Standard input schema for cursor-based list endpoints (programmatic APIs).
|
|
113
|
+
* Spread into `novaEndpoint({ input: CursorPageInput.extend({ ... }) })` to add resource filters.
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```ts
|
|
117
|
+
* input: CursorPageInput.extend({ employee_id: z.string().uuid().optional() })
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
export const CursorPageInput = z.object({
|
|
121
|
+
page_size: z.coerce.number().min(1).max(100).default(25),
|
|
122
|
+
cursor: z.string().optional(),
|
|
123
|
+
modified_after: z.string().datetime().optional(),
|
|
124
|
+
include_deleted: z.coerce.boolean().default(false),
|
|
125
|
+
});
|
|
126
|
+
/**
|
|
127
|
+
* Standard input schema for offset-based list endpoints (UI-facing browsing).
|
|
128
|
+
* Matches what `ParquetDataTable` / Odyssey UI data browsers send.
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```ts
|
|
132
|
+
* input: OffsetPageInput.extend({ status: StatusEnum.optional() })
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
export const OffsetPageInput = z.object({
|
|
136
|
+
limit: z.coerce.number().min(1).max(500).default(20),
|
|
137
|
+
offset: z.coerce.number().min(0).default(0),
|
|
138
|
+
search: z.string().optional(),
|
|
139
|
+
sort: z.string().optional(),
|
|
140
|
+
sort_dir: z.enum(['asc', 'desc']).default('desc'),
|
|
141
|
+
filters: z.string().optional(), // JSON-encoded FilterCondition[]
|
|
142
|
+
});
|
|
143
|
+
/**
|
|
144
|
+
* Build the Zod output schema for any paginated list endpoint.
|
|
145
|
+
* Always produces `{ results, total, filtered_total, next }`.
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```ts
|
|
149
|
+
* output: paginatedOutput(HrisEmployeeZod)
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
export function paginatedOutput(itemSchema) {
|
|
153
|
+
return z.object({
|
|
154
|
+
results: z.array(itemSchema),
|
|
155
|
+
total: z.number().describe('Total record count (unfiltered)'),
|
|
156
|
+
filtered_total: z.number().describe('Record count after active search/filters'),
|
|
157
|
+
next: z.string().nullable().describe('Cursor for next page; null if last page'),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
// ─── Prisma helpers ───────────────────────────────────────────────────────────
|
|
161
|
+
/**
|
|
162
|
+
* Build Prisma `where`, `take`, and `orderBy` from a `CursorPageInput`.
|
|
163
|
+
* Handles cursor keyset pagination and `modified_after` filtering.
|
|
164
|
+
* Merge with resource-specific filters before passing to Prisma.
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* ```ts
|
|
168
|
+
* const page = buildPrismaPage(input);
|
|
169
|
+
* const [data, total] = await Promise.all([
|
|
170
|
+
* db.hrisEmployee.findMany({ where: { remoteWasDeleted: input.include_deleted, ...page.where }, take: page.take, orderBy: page.orderBy }),
|
|
171
|
+
* db.hrisEmployee.count({ where: { remoteWasDeleted: input.include_deleted, ...page.where } }),
|
|
172
|
+
* ]);
|
|
173
|
+
* return nova.respond(buildPageResponse(data.map(mapRow), total, total, input.page_size));
|
|
174
|
+
* ```
|
|
175
|
+
*/
|
|
176
|
+
export function buildPrismaPage(input) {
|
|
177
|
+
const where = {};
|
|
178
|
+
if (input.modified_after) {
|
|
179
|
+
where.modifiedAt = { gte: new Date(input.modified_after) };
|
|
180
|
+
}
|
|
181
|
+
if (input.cursor) {
|
|
182
|
+
const { id, created_at } = parseCursor(input.cursor);
|
|
183
|
+
where.OR = [
|
|
184
|
+
{ createdAt: { lt: new Date(created_at) } },
|
|
185
|
+
{ createdAt: new Date(created_at), id: { lt: id } },
|
|
186
|
+
];
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
where,
|
|
190
|
+
take: input.page_size,
|
|
191
|
+
orderBy: [{ createdAt: 'desc' }, { id: 'desc' }],
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Build Prisma `skip`, `take`, and `orderBy` from an `OffsetPageInput`.
|
|
196
|
+
* Handles offset pagination and sort direction.
|
|
197
|
+
* Does NOT handle `search` or `filters` — apply those to the `where` clause manually.
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* ```ts
|
|
201
|
+
* const page = buildPrismaOffsetPage(input);
|
|
202
|
+
* const data = await db.example.findMany({ where, skip: page.skip, take: page.take, orderBy: page.orderBy });
|
|
203
|
+
* ```
|
|
204
|
+
*/
|
|
205
|
+
export function buildPrismaOffsetPage(input, defaultSort = 'createdAt') {
|
|
206
|
+
const col = input.sort ?? defaultSort;
|
|
207
|
+
return {
|
|
208
|
+
skip: input.offset,
|
|
209
|
+
take: input.limit,
|
|
210
|
+
orderBy: { [col]: input.sort_dir },
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
// ─── Standard param metadata ──────────────────────────────────────────────────
|
|
214
|
+
/**
|
|
215
|
+
* Standard `params` metadata for cursor-based pagination fields.
|
|
216
|
+
* Spread into `novaEndpoint({ params: { ...CURSOR_PAGE_PARAMS, ...yourParams } })`.
|
|
217
|
+
*/
|
|
218
|
+
export const CURSOR_PAGE_PARAMS = {
|
|
219
|
+
page_size: { in: 'query', uiType: 'number', label: 'Page Size', description: 'Records per page (1–100, default 25)' },
|
|
220
|
+
cursor: { in: 'query', uiType: 'text', label: 'Cursor', description: 'Opaque pagination cursor from previous response' },
|
|
221
|
+
modified_after: { in: 'query', uiType: 'datetime', label: 'Modified After', description: 'Return records modified after this ISO 8601 timestamp' },
|
|
222
|
+
include_deleted: { in: 'query', uiType: 'boolean', label: 'Include Deleted', description: 'Include soft-deleted records (default false)' },
|
|
223
|
+
};
|
|
224
|
+
/**
|
|
225
|
+
* Standard `params` metadata for offset-based pagination fields.
|
|
226
|
+
* Spread into `novaEndpoint({ params: { ...OFFSET_PAGE_PARAMS, ...yourParams } })`.
|
|
227
|
+
*/
|
|
228
|
+
export const OFFSET_PAGE_PARAMS = {
|
|
229
|
+
limit: { in: 'query', uiType: 'number', label: 'Limit', description: 'Records per page (1–500, default 20)' },
|
|
230
|
+
offset: { in: 'query', uiType: 'number', label: 'Offset', description: 'Number of records to skip' },
|
|
231
|
+
search: { in: 'query', uiType: 'text', label: 'Search', description: 'Free-text search across indexed fields' },
|
|
232
|
+
sort: { in: 'query', uiType: 'text', label: 'Sort By', description: 'Column name to sort by' },
|
|
233
|
+
sort_dir: { in: 'query', uiType: 'select', label: 'Sort Dir', description: 'asc or desc', options: [{ label: 'Ascending', value: 'asc' }, { label: 'Descending', value: 'desc' }] },
|
|
234
|
+
filters: { in: 'query', uiType: 'json', label: 'Filters', description: 'JSON array of FilterCondition objects' },
|
|
235
|
+
};
|
|
236
|
+
// ─── Legacy re-exports (backward compat) ─────────────────────────────────────
|
|
237
|
+
/** @deprecated Use `buildPageResponse` instead */
|
|
238
|
+
export function buildPaginatedResponse(data, count, pageSize) {
|
|
239
|
+
return buildPageResponse(data, count ?? 0, count ?? 0, pageSize);
|
|
240
|
+
}
|
package/dist/parseSpec.d.ts
CHANGED
|
@@ -73,7 +73,7 @@ export declare const NovaSpecSchema: z.ZodObject<{
|
|
|
73
73
|
streamName: z.ZodString;
|
|
74
74
|
eventTypes: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
75
75
|
consumerGroup: z.ZodOptional<z.ZodString>;
|
|
76
|
-
}, z.core.$strip>]>>>;
|
|
76
|
+
}, z.core.$strip>], "type">>>;
|
|
77
77
|
}, z.core.$strip>>;
|
|
78
78
|
capabilities: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
79
79
|
type: z.ZodLiteral<"webhook">;
|
|
@@ -105,7 +105,7 @@ export declare const NovaSpecSchema: z.ZodObject<{
|
|
|
105
105
|
streamName: z.ZodString;
|
|
106
106
|
eventTypes: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
107
107
|
consumerGroup: z.ZodOptional<z.ZodString>;
|
|
108
|
-
}, z.core.$strip>]>>>;
|
|
108
|
+
}, z.core.$strip>], "type">>>;
|
|
109
109
|
}, z.core.$strip>;
|
|
110
110
|
build: z.ZodOptional<z.ZodObject<{
|
|
111
111
|
dockerfile: z.ZodString;
|
package/dist/workerSchema.d.ts
CHANGED
|
@@ -83,6 +83,15 @@ export declare const WorkerDefSchema: z.ZodObject<{
|
|
|
83
83
|
resourceIdKey: z.ZodOptional<z.ZodString>;
|
|
84
84
|
policy: z.ZodOptional<z.ZodString>;
|
|
85
85
|
}, z.core.$strip>>;
|
|
86
|
+
triggers: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
87
|
+
type: z.ZodLiteral<"event">;
|
|
88
|
+
events: z.ZodArray<z.ZodString>;
|
|
89
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
90
|
+
type: z.ZodLiteral<"schedule">;
|
|
91
|
+
cron: z.ZodString;
|
|
92
|
+
timezone: z.ZodOptional<z.ZodString>;
|
|
93
|
+
description: z.ZodOptional<z.ZodString>;
|
|
94
|
+
}, z.core.$strip>]>>>;
|
|
86
95
|
capabilities: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
87
96
|
type: z.ZodLiteral<"webhook">;
|
|
88
97
|
eventTypes: z.ZodArray<z.ZodString>;
|
package/dist/workerSchema.js
CHANGED
|
@@ -61,7 +61,24 @@ export const WorkerDefSchema = z.object({
|
|
|
61
61
|
resourceIdKey: z.string().optional(),
|
|
62
62
|
policy: z.string().optional(),
|
|
63
63
|
}).optional(),
|
|
64
|
-
// NEW:
|
|
64
|
+
// NEW: Trigger-based routing (preferred over capabilities for SSE-based workers)
|
|
65
|
+
// Use triggers to declaratively map event topics and cron schedules to this action.
|
|
66
|
+
// `nova workers push` / `nova integrations push` reads these to register
|
|
67
|
+
// event_subscriptions and ensure PGMQ queues exist.
|
|
68
|
+
triggers: z.array(z.union([
|
|
69
|
+
z.object({
|
|
70
|
+
type: z.literal('event'),
|
|
71
|
+
events: z.array(z.string()), // e.g. ['employee.updated', 'employee.created']
|
|
72
|
+
}),
|
|
73
|
+
z.object({
|
|
74
|
+
type: z.literal('schedule'),
|
|
75
|
+
cron: z.string(), // standard 5-field cron e.g. '0 8 * * *'
|
|
76
|
+
timezone: z.string().optional(),
|
|
77
|
+
description: z.string().optional(),
|
|
78
|
+
}),
|
|
79
|
+
])).optional(),
|
|
80
|
+
// LEGACY: Capabilities array — still supported for backward compatibility.
|
|
81
|
+
// New code should use `triggers` instead.
|
|
65
82
|
capabilities: z.array(z.union([
|
|
66
83
|
z.object({
|
|
67
84
|
type: z.literal('webhook'),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@newhomestar/sdk",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.9",
|
|
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": {
|
|
@@ -23,6 +23,10 @@
|
|
|
23
23
|
"./next": {
|
|
24
24
|
"import": "./dist/next.js",
|
|
25
25
|
"types": "./dist/next.d.ts"
|
|
26
|
+
},
|
|
27
|
+
"./events": {
|
|
28
|
+
"import": "./dist/events.js",
|
|
29
|
+
"types": "./dist/events.d.ts"
|
|
26
30
|
}
|
|
27
31
|
},
|
|
28
32
|
"files": [
|
|
@@ -40,11 +44,14 @@
|
|
|
40
44
|
"dotenv": "^16.4.3",
|
|
41
45
|
"express": "^4.18.2",
|
|
42
46
|
"express-oauth2-jwt-bearer": "^1.7.4",
|
|
43
|
-
"yaml": "^2.7.1"
|
|
44
|
-
|
|
47
|
+
"yaml": "^2.7.1"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"zod": ">=4.0.0"
|
|
45
51
|
},
|
|
46
52
|
"devDependencies": {
|
|
47
53
|
"@types/node": "^20.11.17",
|
|
48
|
-
"typescript": "^5.4.4"
|
|
54
|
+
"typescript": "^5.4.4",
|
|
55
|
+
"zod": "^4.3.0"
|
|
49
56
|
}
|
|
50
57
|
}
|