@nightfuel-ai/app-sdk 0.1.0 → 0.1.2

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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 NightFUEL
3
+ Copyright (c) 2026 Nightfuel
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # @nightfuel-ai/app-sdk
2
2
 
3
- `@nightfuel-ai/app-sdk` is for the **server process of a NightFUEL Cloud App**.
4
- It uses the App's Cloud Run workload identity to reach the private NightFUEL
3
+ `@nightfuel-ai/app-sdk` is for the **server process of a Nightfuel Cloud App**.
4
+ It uses the App's Cloud Run workload identity to reach the private Nightfuel
5
5
  App gateway. It has no browser build and must never be placed in client code.
6
6
 
7
7
  ```bash
@@ -49,6 +49,23 @@ for await (const event of nightfuel.workflows.stream('notify-low-stock', run.run
49
49
  }
50
50
  ```
51
51
 
52
+ ## Workflow input
53
+
54
+ `workflows.run(bindingId, input, { idempotencyKey, customFields })` sends the
55
+ same run input as `workflow_run` in the Nightfuel SDK. `input` is a JSON object
56
+ that reaches the bound workflow's entry node:
57
+
58
+ - a **Webhook** entry receives it as its request body (`payload.payload.body`),
59
+ checked against the entry's JSON Schema;
60
+ - a **Request Harness** entry receives it as its input: an App sends `text`
61
+ only, and `customFields` fills the custom fields the entry declares.
62
+
63
+ Input that does not match the entry is refused with `400` before the run is
64
+ accepted (`WEBHOOK_BODY_SCHEMA_MISMATCH`, `HARNESS_CUSTOM_FIELDS_INVALID`,
65
+ `WORKFLOW_INPUT_INVALID`). A run accepted with `202` that cannot start (no
66
+ credits, or a workflow that runs with its own published identity) ends its
67
+ stream with `run.failed` and `payload.errorCode`.
68
+
52
69
  ## Runtime environment
53
70
 
54
71
  A Cloud App receives these variables from the platform; the SDK reads them
@@ -78,7 +95,7 @@ its machine-readable stream code and `afterSeq`. Persist `data.seq` after the
78
95
  App has committed its own effect when process-restart recovery matters; SSE is
79
96
  at-least-once, not exactly-once.
80
97
 
81
- ## Calls from NightFUEL into the App
98
+ ## Calls from Nightfuel into the App
82
99
 
83
100
  The Apphook workflow node signs each request it sends to the App in the
84
101
  `X-NF-App-Call` header. `verifyNightfuelCall` checks the signature, expiry,
package/index.d.ts CHANGED
@@ -161,10 +161,17 @@ export interface NightfuelDatabaseMutationInput {
161
161
  }
162
162
 
163
163
  export interface NightfuelWorkflowClient {
164
+ /**
165
+ * Starts the bound workflow. `input` reaches its entry node: a Request
166
+ * Harness entry reads it as its input (an App sends `text` only), a Webhook
167
+ * entry as its request body, checked against the entry's JSON Schema.
168
+ * `customFields` fills the Request Harness entry's declared custom fields.
169
+ * Input outside that contract is refused with 400 before the run starts.
170
+ */
164
171
  run(
165
172
  bindingId: string,
166
173
  input: Record<string, unknown>,
167
- options: { idempotencyKey: string }
174
+ options: { idempotencyKey: string; customFields?: Record<string, unknown> }
168
175
  ): Promise<WorkflowRunAccepted>;
169
176
  stream<T = NightfuelKnownWorkflowEventData | NightfuelWorkflowEventData>(
170
177
  bindingId: string,
@@ -182,7 +189,7 @@ export interface NightfuelAppSdk {
182
189
 
183
190
  export function createNightfuelAppSdk(options?: NightfuelAppSdkOptions): NightfuelAppSdk;
184
191
 
185
- /** The claims NightFUEL signs into an Apphook request. */
192
+ /** The claims Nightfuel signs into an Apphook request. */
186
193
  export interface NightfuelCallClaims {
187
194
  v: 1;
188
195
  appId: string;
@@ -199,7 +206,7 @@ export interface NightfuelCallClaims {
199
206
  }
200
207
 
201
208
  /**
202
- * Verifies a call NightFUEL made to this App and returns its claims.
209
+ * Verifies a call Nightfuel made to this App and returns its claims.
203
210
  * `rawBody` is the JSON body, or the multipart `payload` field, exactly as
204
211
  * received -- parse it only after this returns. Pure: no network, no platform
205
212
  * identity, so an App can prove it rejects a forged call in a unit test.
package/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  //
5
5
  // This package intentionally has no browser build. It authenticates with the
6
6
  // Google metadata server available to the App's Cloud Run service account and
7
- // calls only pre-bound NightFUEL App runtime endpoints.
7
+ // calls only pre-bound Nightfuel App runtime endpoints.
8
8
 
9
9
  const crypto = require('crypto');
10
10
  const { parseSseFrame, recoverableSseStream, terminalRunStatus } = require('./stream');
@@ -168,7 +168,7 @@ function createNightfuelAppSdk(options = {}) {
168
168
  if (!response.ok && !allowErrorResponse) {
169
169
  const payload = await responsePayload(response);
170
170
  throw new NightfuelAppSdkError(
171
- payload?.error || `NightFUEL App gateway request failed (${response.status})`,
171
+ payload?.error || `Nightfuel App gateway request failed (${response.status})`,
172
172
  { status: response.status, code: payload?.code || 'NIGHTFUEL_APP_SDK_GATEWAY_ERROR', payload }
173
173
  );
174
174
  }
@@ -207,7 +207,7 @@ function createNightfuelAppSdk(options = {}) {
207
207
  const key = idempotencyKey(options.idempotencyKey);
208
208
  return postJson(
209
209
  `/internal/hosting/apps/${appId}/workflows/${encodeURIComponent(resolvedBindingId)}/runs`,
210
- { input },
210
+ { input, ...(options.customFields === undefined ? {} : { customFields: options.customFields }) },
211
211
  { 'Idempotency-Key': key }
212
212
  );
213
213
  },
@@ -249,7 +249,7 @@ function createNightfuelAppSdk(options = {}) {
249
249
  });
250
250
  }
251
251
 
252
- // ── Calls FROM NightFUEL (the Call App workflow node) ─────────────────────
252
+ // ── Calls FROM Nightfuel (the Call App workflow node) ─────────────────────
253
253
  //
254
254
  // The platform signs every call with the App's NF_APP_CALL_SECRET:
255
255
  // X-NF-App-Call: <base64url(claims)>.<base64url(HMAC-SHA256(secret, claims))>
@@ -278,7 +278,7 @@ function headerValue(headers, name) {
278
278
  }
279
279
 
280
280
  /**
281
- * Verifies a call made by NightFUEL to this App. `rawBody` is the JSON body
281
+ * Verifies a call made by Nightfuel to this App. `rawBody` is the JSON body
282
282
  * (or the multipart `payload` field) exactly as received. Returns the claims.
283
283
  */
284
284
  function verifyNightfuelCall({ headers, method, path, rawBody, secret, appId, now = Date.now() } = {}) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nightfuel-ai/app-sdk",
3
- "version": "0.1.0",
4
- "description": "Server-only NightFUEL SDK for a hosted App workload identity.",
3
+ "version": "0.1.2",
4
+ "description": "Server-only Nightfuel SDK for a hosted App workload identity.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
7
7
  "exports": {
package/stream.js CHANGED
@@ -115,7 +115,7 @@ function responseError(createError, response, payload, afterSeq, reconnects) {
115
115
  const auth = status === 401 || status === 403;
116
116
  const error = makeError(
117
117
  createError,
118
- payload?.error || `NightFUEL event stream request failed (${status || 'unknown status'})`,
118
+ payload?.error || `Nightfuel event stream request failed (${status || 'unknown status'})`,
119
119
  {
120
120
  status,
121
121
  code: payload?.code || (auth ? 'NIGHTFUEL_STREAM_AUTH_FAILED' : 'NIGHTFUEL_STREAM_FAILED'),
@@ -268,7 +268,7 @@ async function* recoverableSseStream(options = {}) {
268
268
  idleTimeoutMs,
269
269
  onIdle: () => {
270
270
  const idle = recoverableError(createError, 'NIGHTFUEL_STREAM_IDLE_TIMEOUT',
271
- `NightFUEL event stream was idle for ${idleTimeoutMs}ms`, confirmedSeq, reconnects);
271
+ `Nightfuel event stream was idle for ${idleTimeoutMs}ms`, confirmedSeq, reconnects);
272
272
  attempt.controller.abort(idle);
273
273
  return idle;
274
274
  },
@@ -314,7 +314,7 @@ async function* recoverableSseStream(options = {}) {
314
314
  throw recoverableError(
315
315
  createError,
316
316
  'NIGHTFUEL_STREAM_TRUNCATED',
317
- 'NightFUEL event stream ended in the middle of an SSE frame',
317
+ 'Nightfuel event stream ended in the middle of an SSE frame',
318
318
  confirmedSeq,
319
319
  reconnects
320
320
  );
@@ -322,7 +322,7 @@ async function* recoverableSseStream(options = {}) {
322
322
  throw recoverableError(
323
323
  createError,
324
324
  'NIGHTFUEL_STREAM_DROPPED',
325
- 'NightFUEL event stream closed before a terminal run event',
325
+ 'Nightfuel event stream closed before a terminal run event',
326
326
  confirmedSeq,
327
327
  reconnects
328
328
  );
@@ -337,7 +337,7 @@ async function* recoverableSseStream(options = {}) {
337
337
  const rawFrame = buffer.slice(0, boundary.index);
338
338
  buffer = buffer.slice(boundary.index + boundary[0].length);
339
339
  if (Buffer.byteLength(rawFrame, 'utf8') > MAX_FRAME_BYTES) {
340
- throw protocolError(createError, 'NightFUEL stream frame exceeds 8 MiB', confirmedSeq, reconnects);
340
+ throw protocolError(createError, 'Nightfuel stream frame exceeds 8 MiB', confirmedSeq, reconnects);
341
341
  }
342
342
  if (!rawFrame || rawFrame.split(/\r?\n/).every((line) => !line || line.startsWith(':'))) continue;
343
343
  const frame = parseSseFrame(rawFrame);
@@ -346,7 +346,7 @@ async function* recoverableSseStream(options = {}) {
346
346
  const error = recoverableError(
347
347
  createError,
348
348
  frame.data?.code || 'NIGHTFUEL_STREAM_SERVER_ERROR',
349
- frame.data?.error || 'NightFUEL event stream failed after opening',
349
+ frame.data?.error || 'Nightfuel event stream failed after opening',
350
350
  confirmedSeq,
351
351
  reconnects,
352
352
  null,
@@ -360,7 +360,7 @@ async function* recoverableSseStream(options = {}) {
360
360
  throw recoverableError(
361
361
  createError,
362
362
  'NIGHTFUEL_STREAM_DROPPED',
363
- 'NightFUEL dropped a slow event subscriber; resuming from the confirmed cursor',
363
+ 'Nightfuel dropped a slow event subscriber; resuming from the confirmed cursor',
364
364
  confirmedSeq,
365
365
  reconnects,
366
366
  null,
@@ -378,7 +378,7 @@ async function* recoverableSseStream(options = {}) {
378
378
  throw recoverableError(
379
379
  createError,
380
380
  'NIGHTFUEL_STREAM_GAP',
381
- `NightFUEL event stream expected seq ${confirmedSeq + 1} but received ${seq}`,
381
+ `Nightfuel event stream expected seq ${confirmedSeq + 1} but received ${seq}`,
382
382
  confirmedSeq,
383
383
  reconnects,
384
384
  null,
@@ -395,7 +395,7 @@ async function* recoverableSseStream(options = {}) {
395
395
  if (terminal) return;
396
396
  }
397
397
  if (Buffer.byteLength(buffer, 'utf8') > MAX_FRAME_BYTES) {
398
- throw protocolError(createError, 'NightFUEL stream frame exceeds 8 MiB', confirmedSeq, reconnects);
398
+ throw protocolError(createError, 'Nightfuel stream frame exceeds 8 MiB', confirmedSeq, reconnects);
399
399
  }
400
400
  }
401
401
  } catch (error) {
@@ -406,7 +406,7 @@ async function* recoverableSseStream(options = {}) {
406
406
  ? recoverableError(
407
407
  createError,
408
408
  'NIGHTFUEL_STREAM_DROPPED',
409
- 'NightFUEL event stream transport disconnected',
409
+ 'Nightfuel event stream transport disconnected',
410
410
  confirmedSeq,
411
411
  reconnects,
412
412
  error