@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/events.d.ts
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/** Full request body for POST /events/queue */
|
|
2
|
+
export interface QueueEventPayload {
|
|
3
|
+
entity_type: string;
|
|
4
|
+
action: string;
|
|
5
|
+
/** Auto-stamped from NOVA_SERVICE_SLUG if not provided */
|
|
6
|
+
source_service?: string;
|
|
7
|
+
/** ISO 8601 — when the event actually happened (defaults to now()) */
|
|
8
|
+
occurred_at?: string;
|
|
9
|
+
/** UUID linking a chain of related events across services */
|
|
10
|
+
correlation_id?: string;
|
|
11
|
+
/** The event_id that directly caused this event */
|
|
12
|
+
causation_id?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Deduplication key from the outbox row.
|
|
15
|
+
* Auto-set by withEventOutbox(). Prevents double-posting on retries.
|
|
16
|
+
*/
|
|
17
|
+
idempotency_key?: string;
|
|
18
|
+
/** Payload schema version (default: 1) */
|
|
19
|
+
event_version?: number;
|
|
20
|
+
entity_id?: string;
|
|
21
|
+
integration_id?: string;
|
|
22
|
+
diff?: {
|
|
23
|
+
before: Record<string, unknown>;
|
|
24
|
+
after: Record<string, unknown>;
|
|
25
|
+
changed_fields: string[];
|
|
26
|
+
};
|
|
27
|
+
attributes?: Record<string, unknown>;
|
|
28
|
+
metadata?: Record<string, unknown>;
|
|
29
|
+
performed_by_id?: string;
|
|
30
|
+
affected_user_id?: string;
|
|
31
|
+
}
|
|
32
|
+
/** Request body for POST /events (audit-only, no fan-out) */
|
|
33
|
+
export interface LogEventPayload extends QueueEventPayload {
|
|
34
|
+
}
|
|
35
|
+
/** Options for startOutboxRelay() */
|
|
36
|
+
export interface OutboxRelayOptions {
|
|
37
|
+
/** How often to poll for undelivered outbox rows (default: 60_000ms) */
|
|
38
|
+
intervalMs?: number;
|
|
39
|
+
/** Rows with attempts >= maxAttempts are left for manual reconciliation (default: 5) */
|
|
40
|
+
maxAttempts?: number;
|
|
41
|
+
/** Events service URL — defaults to NOVA_EVENTS_SERVICE_URL env var */
|
|
42
|
+
eventsUrl?: string;
|
|
43
|
+
/** Service JWT — defaults to NOVA_SERVICE_TOKEN env var */
|
|
44
|
+
serviceToken?: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Returns `true` when a request originated from an integration sync or worker,
|
|
48
|
+
* indicated by the `x-source: integration_sync` header.
|
|
49
|
+
*
|
|
50
|
+
* **Producer side** — pass `req` to `withServiceEventOutbox()` and this check is
|
|
51
|
+
* done for you automatically. Use this helper directly only when you need
|
|
52
|
+
* conditional logic beyond event emission (e.g. skipping a side-effect).
|
|
53
|
+
*
|
|
54
|
+
* **Consumer side** — integration workers MUST check this before writing back to
|
|
55
|
+
* the source system to avoid infinite sync loops:
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* // In a worker handler — don't write back to BambooHR if BambooHR caused the event
|
|
59
|
+
* if (isIntegrationSync(event)) return { status: 'skipped' };
|
|
60
|
+
* await ctx.fetch(`https://api.bamboohr.com/...`, { method: 'PUT', ... });
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* // In a Next.js route handler (producer side) — pass req instead of calling this
|
|
64
|
+
* const employee = await withServiceEventOutbox(db, req, async (tx, emit) => { ... });
|
|
65
|
+
*/
|
|
66
|
+
export declare function isIntegrationSync(reqOrEvent: Request | Record<string, any>): boolean;
|
|
67
|
+
/** Emit function passed to the withServiceEventOutbox callback */
|
|
68
|
+
export type ServiceEmitFn = (topic: string, payload: Record<string, unknown>, idempotencyKey?: string) => void;
|
|
69
|
+
/**
|
|
70
|
+
* Run a Prisma transaction that atomically writes business data + outbox rows,
|
|
71
|
+
* then immediately relays events to the Nova Events Service.
|
|
72
|
+
*
|
|
73
|
+
* This is the **preferred** high-level API for emitting events from service
|
|
74
|
+
* route handlers. It is source-aware: if the incoming request carries
|
|
75
|
+
* `x-source: integration_sync` or `x-integration-id: <slug>`, those values are
|
|
76
|
+
* automatically stamped on every event's `metadata` field. This lets downstream
|
|
77
|
+
* consumers self-filter to prevent sync loops without suppressing events for
|
|
78
|
+
* other subscribers.
|
|
79
|
+
*
|
|
80
|
+
* Events **always fan out** to all subscriber queues — no events are silently
|
|
81
|
+
* dropped. An integration worker that receives an event it caused should check
|
|
82
|
+
* `isIntegrationSync(event)` and skip processing if true.
|
|
83
|
+
*
|
|
84
|
+
* @param db - Prisma client instance (must have `eventOutbox` model)
|
|
85
|
+
* @param reqOrCallback - Either the inbound `Request` (for source tagging) OR
|
|
86
|
+
* the callback directly (if source context is not needed)
|
|
87
|
+
* @param maybeCallback - The callback when `req` is provided as the second arg
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* // Route handler — source metadata auto-stamped from request headers
|
|
91
|
+
* export async function POST(req: Request) {
|
|
92
|
+
* const employee = await withServiceEventOutbox(db, req, async (tx, emit) => {
|
|
93
|
+
* const row = await tx.hrisEmployee.create({ data });
|
|
94
|
+
* emit('employee.created', { id: row.id, firstName: row.firstName });
|
|
95
|
+
* return row;
|
|
96
|
+
* });
|
|
97
|
+
* return NextResponse.json(employee);
|
|
98
|
+
* }
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* // Without a request (background job, scheduled task)
|
|
102
|
+
* const result = await withServiceEventOutbox(db, async (tx, emit) => {
|
|
103
|
+
* const row = await tx.hrisEmployee.update({ where: { id }, data });
|
|
104
|
+
* emit('employee.updated', { id, ...changes });
|
|
105
|
+
* return row;
|
|
106
|
+
* });
|
|
107
|
+
*/
|
|
108
|
+
export declare function withServiceEventOutbox<R>(db: any, reqOrCallback: Request | ((tx: any, emit: ServiceEmitFn) => Promise<R>), maybeCallback?: (tx: any, emit: ServiceEmitFn) => Promise<R>): Promise<R>;
|
|
109
|
+
/**
|
|
110
|
+
* Run a Prisma transaction that atomically writes business data + an outbox row,
|
|
111
|
+
* then immediately relays the event to the Nova Events Service.
|
|
112
|
+
*
|
|
113
|
+
* If the relay fails (network error or non-2xx), the outbox row is preserved and
|
|
114
|
+
* startOutboxRelay() will retry it with exponential backoff.
|
|
115
|
+
*
|
|
116
|
+
* @param db - Prisma client instance (must have `eventOutbox` model)
|
|
117
|
+
* @param callback - Receives the transaction `tx`; must return `{ events, result }`
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* const employee = await withEventOutbox(db, async (tx) => {
|
|
121
|
+
* const row = await tx.hrisEmployee.update({ where: { id }, data: updates });
|
|
122
|
+
* return {
|
|
123
|
+
* events: [{
|
|
124
|
+
* entity_type: 'employee',
|
|
125
|
+
* action: 'updated',
|
|
126
|
+
* entity_id: id,
|
|
127
|
+
* diff: { before: old, after: mapRow(row), changed_fields },
|
|
128
|
+
* }],
|
|
129
|
+
* result: row,
|
|
130
|
+
* };
|
|
131
|
+
* });
|
|
132
|
+
*/
|
|
133
|
+
export declare function withEventOutbox<R>(db: any, callback: (tx: any) => Promise<{
|
|
134
|
+
events: QueueEventPayload[];
|
|
135
|
+
result: R;
|
|
136
|
+
}>): Promise<R>;
|
|
137
|
+
/**
|
|
138
|
+
* POST /events — Record an event in the audit log WITHOUT queue fan-out.
|
|
139
|
+
*
|
|
140
|
+
* Use for:
|
|
141
|
+
* • Inbound sync audits (metadata.source = "integration_sync")
|
|
142
|
+
* • Read events ("employee.viewed")
|
|
143
|
+
* • System events that don't need integration write-back
|
|
144
|
+
*
|
|
145
|
+
* For events that need downstream processing, use queueEvent() or withEventOutbox().
|
|
146
|
+
*/
|
|
147
|
+
export declare function logEvent(payload: LogEventPayload): Promise<{
|
|
148
|
+
event_id: string;
|
|
149
|
+
status: string;
|
|
150
|
+
}>;
|
|
151
|
+
/**
|
|
152
|
+
* POST /events/queue — Log an event AND fan-out to all active subscriber queues.
|
|
153
|
+
*
|
|
154
|
+
* Use when there is NO database transaction context (e.g. a scheduled job,
|
|
155
|
+
* a webhook handler that doesn't update its own DB first).
|
|
156
|
+
*
|
|
157
|
+
* When you have a DB transaction, prefer withEventOutbox() for atomicity.
|
|
158
|
+
*
|
|
159
|
+
* @throws if the events service returns non-2xx (no retry logic)
|
|
160
|
+
*/
|
|
161
|
+
export declare function queueEvent(payload: QueueEventPayload): Promise<{
|
|
162
|
+
event_id: string;
|
|
163
|
+
subscriber_count: number;
|
|
164
|
+
status: string;
|
|
165
|
+
}>;
|
|
166
|
+
/**
|
|
167
|
+
* Start a background interval that retries undelivered outbox rows.
|
|
168
|
+
*
|
|
169
|
+
* Call ONCE at service startup (e.g. in layout.tsx or the service entry point).
|
|
170
|
+
* Returns the interval handle so you can clear it in tests or graceful shutdown.
|
|
171
|
+
*
|
|
172
|
+
* Retry schedule (exponential backoff): 2^attempts * 5s
|
|
173
|
+
* attempt 0 → 5s, attempt 1 → 10s, attempt 2 → 20s,
|
|
174
|
+
* attempt 3 → 40s, attempt 4 → 80s
|
|
175
|
+
*
|
|
176
|
+
* Rows with attempts >= maxAttempts (default 5) are skipped and left for
|
|
177
|
+
* a manual reconciler — they won't be retried automatically.
|
|
178
|
+
*
|
|
179
|
+
* @param db - Prisma client instance (must have `eventOutbox` model)
|
|
180
|
+
* @param options - Relay configuration
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* // src/app/layout.tsx
|
|
184
|
+
* import { startOutboxRelay } from '@newhomestar/sdk/events';
|
|
185
|
+
* import { db } from '@/lib/db';
|
|
186
|
+
*
|
|
187
|
+
* startOutboxRelay(db);
|
|
188
|
+
*/
|
|
189
|
+
export declare function startOutboxRelay(db: any, options?: OutboxRelayOptions): ReturnType<typeof setInterval>;
|
|
190
|
+
/**
|
|
191
|
+
* Class-based client when you need more control than the top-level functions.
|
|
192
|
+
* Useful when you want to pass configuration explicitly rather than relying on env vars.
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* const client = new NovaEventsClient({ serviceSlug: 'my-service' });
|
|
196
|
+
* await client.queueEvent({ entity_type: 'employee', action: 'updated', ... });
|
|
197
|
+
* client.startOutboxRelay(db);
|
|
198
|
+
*/
|
|
199
|
+
export declare class NovaEventsClient {
|
|
200
|
+
private readonly eventsUrl;
|
|
201
|
+
private readonly serviceToken;
|
|
202
|
+
private readonly serviceSlug?;
|
|
203
|
+
constructor(options?: {
|
|
204
|
+
eventsUrl?: string;
|
|
205
|
+
serviceToken?: string;
|
|
206
|
+
serviceSlug?: string;
|
|
207
|
+
});
|
|
208
|
+
/** POST /events — audit log only */
|
|
209
|
+
logEvent(payload: LogEventPayload): Promise<{
|
|
210
|
+
event_id: string;
|
|
211
|
+
status: string;
|
|
212
|
+
}>;
|
|
213
|
+
/** POST /events/queue — log + fan-out to all subscriber queues */
|
|
214
|
+
queueEvent(payload: QueueEventPayload): Promise<{
|
|
215
|
+
event_id: string;
|
|
216
|
+
subscriber_count: number;
|
|
217
|
+
status: string;
|
|
218
|
+
}>;
|
|
219
|
+
/** Transactional outbox helper (use in services with a Prisma DB) */
|
|
220
|
+
withOutbox<R>(db: any, callback: (tx: any) => Promise<{
|
|
221
|
+
events: QueueEventPayload[];
|
|
222
|
+
result: R;
|
|
223
|
+
}>): Promise<R>;
|
|
224
|
+
/** Start the background outbox retry relay */
|
|
225
|
+
startOutboxRelay(db: any, options?: Omit<OutboxRelayOptions, 'eventsUrl' | 'serviceToken'>): NodeJS.Timeout;
|
|
226
|
+
}
|