@sprigr/apps-app-sdk 0.10.0 → 0.10.1
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/README.md +2 -1
- package/dist/index.d.ts +185 -1
- package/dist/index.js +209 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,7 +14,8 @@ The runtime injects the `env.SPRIGR` host object **only on `/__sprigr/*` dispatc
|
|
|
14
14
|
- `createMarketplaceEmitter(integrationType, defaults?)` — pre-bind an app's integration type so call sites pass only what varies; `sourceIntegration` is built per call from `env.INSTALL_ID`.
|
|
15
15
|
- `canEmit(env)` — whether an emit could reach the platform by either transport. Gate work that only exists to feed an emit on this, not on `env.SPRIGR?.emit`.
|
|
16
16
|
- `withSprigrEmitFallback(env)` — repair `env.SPRIGR.emit` once and leave existing call sites untouched. Matches the host object's contract (resolves `{ ok, eventId, queued }`, throws on non-2xx).
|
|
17
|
-
- `
|
|
17
|
+
- `logToPlatform(env, entry | entry[], opts?)`: durable app logging from either context (`env.SPRIGR.log` on dispatch, `POST ${SPRIGR_PLATFORM_BASE}/internal/wfp/log` from an inline route). Rows land in the platform's `system_logs` Analytics Engine dataset (90-day retention, scoped to the install's company, category prefixed with the app slug), so use it instead of a per-webhook or per-tick audit row in your D1: D1 bills every row written, AE does not. Validation throws synchronously on a cap violation (summary 256, detail 4096, metadata JSON 3840, category 64 matching `[A-Za-z0-9._:-]`, 50 entries per call; nothing is ever truncated); after that the promise never rejects and resolves `{ ok: true, written }` or `{ ok: false, error, status?, detail? }`. Pass `{ waitUntil: ctx.waitUntil.bind(ctx) }` on an inline route so the send outlives the response. `withSprigrLogFallback(env, opts?)` installs `env.SPRIGR.log` once for many call sites; `canLog(env)` gates work whose only purpose is a log row; the `SprigrLogEntry` / `SprigrLogFn` types let your env interface declare the host member.
|
|
18
|
+
- `resolveInstallBridge(env)` / `installTokenPost(bridge, path, body, opts?)`: build your own `/internal/wfp/*` fallback (collections, files, inbox) with the auth and error extraction handled. A non-2xx throws an `Error` whose `.status`, `.error` (the platform's code) and `.detail` are set alongside the message.
|
|
18
19
|
- `overlaySprigr(env, sprigr)` — overlay a patched `SPRIGR` via `Object.create`. Never rebuild a dispatch-path env by spread: the real bindings live on the prototype and `SPRIGR` is non-enumerable, so `{ ...env }` yields an env whose `DB` is `undefined`.
|
|
19
20
|
|
|
20
21
|
## Per-actor tool wrappers
|
package/dist/index.d.ts
CHANGED
|
@@ -465,6 +465,190 @@ declare function createMarketplaceEmitter<E extends WfpBridgeEnv = WfpBridgeEnv>
|
|
|
465
465
|
*/
|
|
466
466
|
declare function withSprigrEmitFallback<E extends WfpBridgeEnv & object>(env: E): E;
|
|
467
467
|
|
|
468
|
+
/**
|
|
469
|
+
* `env.SPRIGR.log`: durable app logging without a D1 audit table.
|
|
470
|
+
*
|
|
471
|
+
* The platform (sprigr-team#7154) exposes `env.SPRIGR.log(entry | entry[])`
|
|
472
|
+
* on every `/__sprigr/*` dispatch path. Each entry becomes one row in the
|
|
473
|
+
* platform's `system_logs` Analytics Engine dataset: 90-day retention,
|
|
474
|
+
* scoped to the install's company, `source = 'install'`, category prefixed
|
|
475
|
+
* with the app slug, metadata stamped with `app_slug` + `install_id`.
|
|
476
|
+
*
|
|
477
|
+
* Use it for every "we saw X" line a webhook, schedule tick, poll or tool
|
|
478
|
+
* call used to write into the app's own D1 as an audit row. D1 bills each
|
|
479
|
+
* row written (plus one per index), so a row per delivery is the largest
|
|
480
|
+
* D1 line item an app can produce (sprigr-apps#1519: one Shopify install
|
|
481
|
+
* wrote ~1.7M audit + dedup rows a day). AE has no per-row write bill.
|
|
482
|
+
*
|
|
483
|
+
* This module carries three things:
|
|
484
|
+
*
|
|
485
|
+
* 1. The TYPES of the host member (`SprigrLogEntry`, `SprigrLogFn`), so an
|
|
486
|
+
* app's env interface can declare `log` on its `SPRIGR` type.
|
|
487
|
+
* 2. `validateLogEntries`, a byte-for-byte mirror of the caps the host
|
|
488
|
+
* member and the platform route enforce. Caps are REJECTIONS, never
|
|
489
|
+
* truncation: an oversize entry throws before anything is sent.
|
|
490
|
+
* 3. The inline-route fallback (`logToPlatform`, `withSprigrLogFallback`).
|
|
491
|
+
* Inline Next.js route handlers never receive `env.SPRIGR`, so an app
|
|
492
|
+
* whose provider webhook lands on one has no `log` there. Same escape
|
|
493
|
+
* hatch as `emitMarketplaceEvent`: `POST ${SPRIGR_PLATFORM_BASE}/internal/wfp/log`
|
|
494
|
+
* with the `SPRIGR_INSTALL_TOKEN` bearer and `{ entries: [...] }`.
|
|
495
|
+
*
|
|
496
|
+
* Wire contract (workers/provisioning/src/wfp-log.ts in sprigr-team):
|
|
497
|
+
* 200 { ok: true, written }
|
|
498
|
+
* 400 { error, detail, index, field, max?, length? } with error one of
|
|
499
|
+
* summary_too_long, detail_too_long, metadata_too_large,
|
|
500
|
+
* too_many_entries, invalid_category, invalid_level, ...
|
|
501
|
+
* 413 body_too_large (over 512 KiB)
|
|
502
|
+
* 401 / 404 bad token / inactive install
|
|
503
|
+
*/
|
|
504
|
+
|
|
505
|
+
/** Severity of one log row. Maps onto `system_logs.level`. */
|
|
506
|
+
type SprigrLogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
507
|
+
declare const SPRIGR_LOG_LEVELS: readonly SprigrLogLevel[];
|
|
508
|
+
/**
|
|
509
|
+
* One row for `env.SPRIGR.log`. Every string cap is enforced client-side
|
|
510
|
+
* (throw) AND platform-side (400); nothing is ever truncated.
|
|
511
|
+
*/
|
|
512
|
+
interface SprigrLogEntry {
|
|
513
|
+
/** Required. */
|
|
514
|
+
level: SprigrLogLevel;
|
|
515
|
+
/**
|
|
516
|
+
* Required. 1-64 chars of `[A-Za-z0-9._:-]`, first char alphanumeric. The
|
|
517
|
+
* platform stores it as `<app_slug>.<category>` and it is the exact-match
|
|
518
|
+
* filter key when reading rows back, so make it a stable outcome name
|
|
519
|
+
* (`webhook.ok`, `webhook.duplicate`, `sync.tick`) and put the varying
|
|
520
|
+
* parts (topic, ids) in `metadata`.
|
|
521
|
+
*/
|
|
522
|
+
category: string;
|
|
523
|
+
/** Required. 1-256 chars. One line a human can scan in a log viewer. */
|
|
524
|
+
summary: string;
|
|
525
|
+
/** Optional, at most 4096 chars. Reserve it for error text; bulk goes in `metadata`. */
|
|
526
|
+
detail?: string;
|
|
527
|
+
/** Optional plain object whose `JSON.stringify` is at most 3840 chars.
|
|
528
|
+
* The platform merges `{ app_slug, install_id }` over it (platform keys win). */
|
|
529
|
+
metadata?: Record<string, unknown>;
|
|
530
|
+
/** Optional, at most 128 chars. Defaults on the dispatch path to the
|
|
531
|
+
* dispatching actor's agent id. */
|
|
532
|
+
agent_id?: string;
|
|
533
|
+
/** Optional, at most 128 chars. Defaults on the platform to the ambient request trace. */
|
|
534
|
+
trace_id?: string;
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* What `env.SPRIGR.log` resolves to. The promise NEVER rejects: transport
|
|
538
|
+
* and platform failures come back as `{ ok: false }` (and a console.warn).
|
|
539
|
+
* Only a cap or shape violation throws, synchronously, before sending.
|
|
540
|
+
*/
|
|
541
|
+
type SprigrLogResult = {
|
|
542
|
+
ok: true;
|
|
543
|
+
written: number;
|
|
544
|
+
} | {
|
|
545
|
+
ok: false;
|
|
546
|
+
error: string;
|
|
547
|
+
status?: number;
|
|
548
|
+
detail?: string;
|
|
549
|
+
};
|
|
550
|
+
/**
|
|
551
|
+
* The host member's signature. Declare it on your app's `SPRIGR` env type:
|
|
552
|
+
*
|
|
553
|
+
* interface MyEnv { SPRIGR?: { emit: ...; log?: SprigrLogFn } }
|
|
554
|
+
*
|
|
555
|
+
* Mark it optional: wrapper builds older than sprigr-team#7154 do not carry
|
|
556
|
+
* it, and `logToPlatform` degrades cleanly when it is absent.
|
|
557
|
+
*/
|
|
558
|
+
type SprigrLogFn = (input: SprigrLogEntry | SprigrLogEntry[]) => Promise<SprigrLogResult>;
|
|
559
|
+
/**
|
|
560
|
+
* The caps, verbatim from the platform route (`WFP_LOG_*` in
|
|
561
|
+
* workers/provisioning/src/wfp-log.ts) and the wrapper's `log` member. The
|
|
562
|
+
* platform's 400 body echoes its live cap, so a mismatch here is
|
|
563
|
+
* self-describing rather than silent.
|
|
564
|
+
*/
|
|
565
|
+
declare const SPRIGR_LOG_LIMITS: Readonly<{
|
|
566
|
+
maxEntriesPerCall: 50;
|
|
567
|
+
maxCategoryChars: 64;
|
|
568
|
+
categoryPattern: RegExp;
|
|
569
|
+
maxSummaryChars: 256;
|
|
570
|
+
maxDetailChars: 4096;
|
|
571
|
+
/** 4096-char metadata blob minus the platform's 256-char reserve for
|
|
572
|
+
* `app_slug` + `install_id`. */
|
|
573
|
+
maxMetadataChars: 3840;
|
|
574
|
+
maxIdChars: 128;
|
|
575
|
+
}>;
|
|
576
|
+
/** Same 5s ceiling as the emit fallback: a webhook ack matters more than a log row. */
|
|
577
|
+
declare const DEFAULT_LOG_TIMEOUT_MS = 5000;
|
|
578
|
+
/**
|
|
579
|
+
* Thrown by `validateLogEntries` (and therefore by `logToPlatform` and the
|
|
580
|
+
* fallback `log`) before anything is sent. Carries the same fields as the
|
|
581
|
+
* platform's 400 body so a caller can log or branch on them.
|
|
582
|
+
*/
|
|
583
|
+
declare class SprigrLogValidationError extends Error {
|
|
584
|
+
readonly index: number;
|
|
585
|
+
readonly field: string;
|
|
586
|
+
readonly max?: number;
|
|
587
|
+
readonly length?: number;
|
|
588
|
+
constructor(message: string, fields: {
|
|
589
|
+
index: number;
|
|
590
|
+
field: string;
|
|
591
|
+
max?: number;
|
|
592
|
+
length?: number;
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Validate one entry against the platform caps. Returns a fresh, normalised
|
|
597
|
+
* copy (unknown keys dropped, `undefined` optionals omitted) or throws
|
|
598
|
+
* `SprigrLogValidationError`. Pure; `index` only labels the error.
|
|
599
|
+
*/
|
|
600
|
+
declare function validateLogEntry(raw: unknown, index?: number): SprigrLogEntry;
|
|
601
|
+
/**
|
|
602
|
+
* Normalise `entry | entry[]` to a validated array, or throw. One bad entry
|
|
603
|
+
* rejects the whole batch, matching the platform's all-or-nothing write.
|
|
604
|
+
*/
|
|
605
|
+
declare function validateLogEntries(input: unknown): SprigrLogEntry[];
|
|
606
|
+
interface LogToPlatformOptions {
|
|
607
|
+
/** Override the 5s ceiling on the HTTP fallback. */
|
|
608
|
+
timeoutMs?: number;
|
|
609
|
+
/**
|
|
610
|
+
* `ctx.waitUntil` from the route handler. On the HTTP fallback the row
|
|
611
|
+
* is only guaranteed to land if the fetch outlives the response, which
|
|
612
|
+
* on an inline route means registering it here. The injected host member
|
|
613
|
+
* does this on its own; the fallback cannot see `ctx`, so hand it over.
|
|
614
|
+
*/
|
|
615
|
+
waitUntil?: (p: Promise<unknown>) => void;
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Whether a log row could reach the platform at all, by either transport.
|
|
619
|
+
* Gate work whose only purpose is to build a log entry on this, not on
|
|
620
|
+
* `env.SPRIGR?.log`, which is absent on every inline route.
|
|
621
|
+
*/
|
|
622
|
+
declare function canLog(env: WfpBridgeEnv): boolean;
|
|
623
|
+
/**
|
|
624
|
+
* Write log rows from any execution context.
|
|
625
|
+
*
|
|
626
|
+
* Prefers the injected `env.SPRIGR.log`; falls back to the install-token
|
|
627
|
+
* bridge when it is absent (an inline Next route). Validation runs first
|
|
628
|
+
* and THROWS on a cap or shape violation, exactly like the host member, so
|
|
629
|
+
* a bad entry is caught at the call site. Everything after validation is
|
|
630
|
+
* reported in the result, never raised: a webhook ack is never at risk.
|
|
631
|
+
*
|
|
632
|
+
* await logToPlatform(env, {
|
|
633
|
+
* level: 'info', category: 'webhook.ok', summary: 'orders/create 6699',
|
|
634
|
+
* metadata: { topic: 'orders/create', resource_id: '6699' },
|
|
635
|
+
* }, { waitUntil: ctx.waitUntil.bind(ctx) });
|
|
636
|
+
*/
|
|
637
|
+
declare function logToPlatform(env: WfpBridgeEnv, input: SprigrLogEntry | SprigrLogEntry[], opts?: LogToPlatformOptions): Promise<SprigrLogResult>;
|
|
638
|
+
/**
|
|
639
|
+
* Return an env whose `SPRIGR.log` works even on an inline route, leaving
|
|
640
|
+
* existing `env.SPRIGR.log(...)` call sites untouched. Same shape as
|
|
641
|
+
* `withSprigrEmitFallback`, and composable with it:
|
|
642
|
+
*
|
|
643
|
+
* const env = withSprigrLogFallback(withSprigrEmitFallback(rawEnv), { waitUntil });
|
|
644
|
+
*
|
|
645
|
+
* The installed `log` matches the host member's contract: it throws
|
|
646
|
+
* synchronously on a cap or shape violation and its promise never rejects.
|
|
647
|
+
* When the bindings are absent the env is returned as-is (`SPRIGR.log`
|
|
648
|
+
* stays undefined) so callers fail the same way they already do.
|
|
649
|
+
*/
|
|
650
|
+
declare function withSprigrLogFallback<E extends WfpBridgeEnv & object>(env: E, opts?: LogToPlatformOptions): E;
|
|
651
|
+
|
|
468
652
|
/**
|
|
469
653
|
* Caller identity stamped by the platform on agent-initiated WFP dispatches.
|
|
470
654
|
*
|
|
@@ -2159,4 +2343,4 @@ interface ExecutionContextLike {
|
|
|
2159
2343
|
}
|
|
2160
2344
|
type HandlerFn<TArgs, TResult> = (args: TArgs, env: Record<string, unknown>, ctx?: ExecutionContextLike) => Promise<TResult>;
|
|
2161
2345
|
|
|
2162
|
-
export { APPROVAL_GRANTED_KEY, type Actor, type AppApprovalEnvelope, type AppFileListItem, type AppFileUrlArgs, type AppFileUrlResult, type AppFilesEnv, type AppUndoEnvelope, type ApplyPoliciesOptions, type ApprovalGateOptions, type ApprovalSpec, type ArchiveOfferOptions, type ArchiveOfferRefusal, type CaptureJournal, type ConfirmCondition, type ConfirmRule, type ConfirmationPolicy, type D1Like, type D1PreparedStatementLike, DEFAULT_EMIT_TIMEOUT_MS, DEFAULT_MAX_FILE_BYTES, DEFAULT_MONEY_FIELDS, DEFAULT_WRITE_PREFIXES, type DerivePolicyOptions, type DerivedPolicy, type DispatcherApprovalGate, type DispatcherApprovalOptions, type EmitMarketplaceEventOptions, type EmitResult, type EmitTransport, type EventArgs, type ExecutionContextLike, type FetchFileResult, type FetchWithRetryOptions, type GetAppFileResult, type HandlerFn, type InboxOwner, type InstallBridge, InvalidUndoEnvelopeError, type JournalRowLike, type ManifestDispatchBlock, type ManifestLike, type ManifestToolLike, type MarketplaceEventEmitOpts, type MarketplaceEventSourceIntegration, NotConnectedError, type OAuthBouncerSignals, type OAuthState, type OfferUndoArgs, type PlatformHostEnv, type PolicyCheckInput, type PolicySource, type PutAppFileArgs, type PutAppFileResult, type PutAppFileStreamArgs, type RequireApprovalOptions, type ResolveViewerOptions, type RestoreResult, type RestoreSpec, type ScheduleArgs, type SprigrDataAggregationCell, type SprigrDataAggregationResult, type SprigrDataAggregationSpec, type SprigrDataApi, type SprigrDataIndexOpts, type SprigrDataSearchOpts, type SprigrDataSearchResult, type SprigrFilesApi, type SprigrFilesApiWithEdit, type SprigrFilesCreateInput, type SprigrFilesCreateResult, type SprigrFilesEditInput, type SprigrFilesEditResult, type SprigrFilesJobResult, type ToolArgs, type ToolHandler, type ToolResult, type ToolWrapperOptions, UNIT_SEP, type UndoApplyOptions, type UndoApplyResult, type UndoCaptureSpec, type UndoFidelity, VIEWER_ANONYMOUS, VIEWER_CONTEXT_HEADER, VIEWER_PLATFORM_USER_ID_HEADER, VIEWER_ROLE_HEADER, VIEWER_USER_ID_HEADER, type VerbRule, type ViewerContext, type ViewerContextFailure, type ViewerContextVerification, type ViewerEnv, type WebhookArgs, type WfpBridgeEnv, type WfpEmitReply, actorKey, actorTool, appFileUrl, applyConfirmationPolicies, approvalHash, archiveOfferRefusal, base64ToBytes, buildConfirmationPolicy, buildMarketplaceWebhookUrl, bytesToBase64, canEmit, checkConfirmationPolicy, commonDestructiveVerbs, constantTimeEqual, createMarketplaceEmitter, createToolWrappers, decodeState, deleteAppFile, deriveConfirmationPolicy, describeMissingBridge, dispatcherApproval, emitMarketplaceEvent, encodeState, fetchFileAsBase64, fetchFileBytes, fetchWithRetry, fullWarning, getAppFile, hmacSha256Hex, installTokenPost, isNotConnectedError, isStagingHost, listAppFiles, manifestIsFresh, offerUndo, overlaySprigr, ownerFromActorKey, parseActor, putAppFile, putAppFileStream, randomHex, recreatedWarning, refuseWithoutForce, requireApproval, resolveInstallBridge, resolveOAuthBouncerBase, resolveOAuthBouncerCallbackUrl, resolvePlatformWebhookBase, resolveViewerContext, resolveViewerUserId, runUndoApply, safeCapture, seq, serializeManifest, set, tool, undoEnvelope, verifyViewerToken, withSprigrEmitFallback };
|
|
2346
|
+
export { APPROVAL_GRANTED_KEY, type Actor, type AppApprovalEnvelope, type AppFileListItem, type AppFileUrlArgs, type AppFileUrlResult, type AppFilesEnv, type AppUndoEnvelope, type ApplyPoliciesOptions, type ApprovalGateOptions, type ApprovalSpec, type ArchiveOfferOptions, type ArchiveOfferRefusal, type CaptureJournal, type ConfirmCondition, type ConfirmRule, type ConfirmationPolicy, type D1Like, type D1PreparedStatementLike, DEFAULT_EMIT_TIMEOUT_MS, DEFAULT_LOG_TIMEOUT_MS, DEFAULT_MAX_FILE_BYTES, DEFAULT_MONEY_FIELDS, DEFAULT_WRITE_PREFIXES, type DerivePolicyOptions, type DerivedPolicy, type DispatcherApprovalGate, type DispatcherApprovalOptions, type EmitMarketplaceEventOptions, type EmitResult, type EmitTransport, type EventArgs, type ExecutionContextLike, type FetchFileResult, type FetchWithRetryOptions, type GetAppFileResult, type HandlerFn, type InboxOwner, type InstallBridge, InvalidUndoEnvelopeError, type JournalRowLike, type LogToPlatformOptions, type ManifestDispatchBlock, type ManifestLike, type ManifestToolLike, type MarketplaceEventEmitOpts, type MarketplaceEventSourceIntegration, NotConnectedError, type OAuthBouncerSignals, type OAuthState, type OfferUndoArgs, type PlatformHostEnv, type PolicyCheckInput, type PolicySource, type PutAppFileArgs, type PutAppFileResult, type PutAppFileStreamArgs, type RequireApprovalOptions, type ResolveViewerOptions, type RestoreResult, type RestoreSpec, SPRIGR_LOG_LEVELS, SPRIGR_LOG_LIMITS, type ScheduleArgs, type SprigrDataAggregationCell, type SprigrDataAggregationResult, type SprigrDataAggregationSpec, type SprigrDataApi, type SprigrDataIndexOpts, type SprigrDataSearchOpts, type SprigrDataSearchResult, type SprigrFilesApi, type SprigrFilesApiWithEdit, type SprigrFilesCreateInput, type SprigrFilesCreateResult, type SprigrFilesEditInput, type SprigrFilesEditResult, type SprigrFilesJobResult, type SprigrLogEntry, type SprigrLogFn, type SprigrLogLevel, type SprigrLogResult, SprigrLogValidationError, type ToolArgs, type ToolHandler, type ToolResult, type ToolWrapperOptions, UNIT_SEP, type UndoApplyOptions, type UndoApplyResult, type UndoCaptureSpec, type UndoFidelity, VIEWER_ANONYMOUS, VIEWER_CONTEXT_HEADER, VIEWER_PLATFORM_USER_ID_HEADER, VIEWER_ROLE_HEADER, VIEWER_USER_ID_HEADER, type VerbRule, type ViewerContext, type ViewerContextFailure, type ViewerContextVerification, type ViewerEnv, type WebhookArgs, type WfpBridgeEnv, type WfpEmitReply, actorKey, actorTool, appFileUrl, applyConfirmationPolicies, approvalHash, archiveOfferRefusal, base64ToBytes, buildConfirmationPolicy, buildMarketplaceWebhookUrl, bytesToBase64, canEmit, canLog, checkConfirmationPolicy, commonDestructiveVerbs, constantTimeEqual, createMarketplaceEmitter, createToolWrappers, decodeState, deleteAppFile, deriveConfirmationPolicy, describeMissingBridge, dispatcherApproval, emitMarketplaceEvent, encodeState, fetchFileAsBase64, fetchFileBytes, fetchWithRetry, fullWarning, getAppFile, hmacSha256Hex, installTokenPost, isNotConnectedError, isStagingHost, listAppFiles, logToPlatform, manifestIsFresh, offerUndo, overlaySprigr, ownerFromActorKey, parseActor, putAppFile, putAppFileStream, randomHex, recreatedWarning, refuseWithoutForce, requireApproval, resolveInstallBridge, resolveOAuthBouncerBase, resolveOAuthBouncerCallbackUrl, resolvePlatformWebhookBase, resolveViewerContext, resolveViewerUserId, runUndoApply, safeCapture, seq, serializeManifest, set, tool, undoEnvelope, validateLogEntries, validateLogEntry, verifyViewerToken, withSprigrEmitFallback, withSprigrLogFallback };
|
package/dist/index.js
CHANGED
|
@@ -245,8 +245,13 @@ async function installTokenPost(bridge, path, body, opts) {
|
|
|
245
245
|
parsed = null;
|
|
246
246
|
}
|
|
247
247
|
if (!resp.ok) {
|
|
248
|
-
const
|
|
249
|
-
|
|
248
|
+
const obj = parsed && typeof parsed === "object" ? parsed : null;
|
|
249
|
+
const code = obj && "error" in obj ? String(obj.error) : text.slice(0, 200);
|
|
250
|
+
const err = new Error(`${label} failed: ${resp.status} ${code}`);
|
|
251
|
+
err.status = resp.status;
|
|
252
|
+
err.error = code;
|
|
253
|
+
if (obj && typeof obj.detail === "string") err.detail = obj.detail;
|
|
254
|
+
throw err;
|
|
250
255
|
}
|
|
251
256
|
return parsed ?? {};
|
|
252
257
|
} finally {
|
|
@@ -312,6 +317,198 @@ function withSprigrEmitFallback(env) {
|
|
|
312
317
|
return overlaySprigr(env, { ...existing, emit });
|
|
313
318
|
}
|
|
314
319
|
|
|
320
|
+
// src/platform-log.ts
|
|
321
|
+
var SPRIGR_LOG_LEVELS = ["debug", "info", "warn", "error"];
|
|
322
|
+
var SPRIGR_LOG_LIMITS = Object.freeze({
|
|
323
|
+
maxEntriesPerCall: 50,
|
|
324
|
+
maxCategoryChars: 64,
|
|
325
|
+
categoryPattern: /^[A-Za-z0-9][A-Za-z0-9._:-]*$/,
|
|
326
|
+
maxSummaryChars: 256,
|
|
327
|
+
maxDetailChars: 4096,
|
|
328
|
+
/** 4096-char metadata blob minus the platform's 256-char reserve for
|
|
329
|
+
* `app_slug` + `install_id`. */
|
|
330
|
+
maxMetadataChars: 3840,
|
|
331
|
+
maxIdChars: 128
|
|
332
|
+
});
|
|
333
|
+
var DEFAULT_LOG_TIMEOUT_MS = 5e3;
|
|
334
|
+
var SprigrLogValidationError = class extends Error {
|
|
335
|
+
index;
|
|
336
|
+
field;
|
|
337
|
+
max;
|
|
338
|
+
length;
|
|
339
|
+
constructor(message, fields) {
|
|
340
|
+
super(message);
|
|
341
|
+
this.name = "SprigrLogValidationError";
|
|
342
|
+
this.index = fields.index;
|
|
343
|
+
this.field = fields.field;
|
|
344
|
+
if (fields.max !== void 0) this.max = fields.max;
|
|
345
|
+
if (fields.length !== void 0) this.length = fields.length;
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
function isPlainObject(v) {
|
|
349
|
+
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
350
|
+
}
|
|
351
|
+
function tooLong(index, field, length, max) {
|
|
352
|
+
return new SprigrLogValidationError(
|
|
353
|
+
`env.SPRIGR.log: entries[${index}].${field} is ${length} chars; max ${max}. Nothing was sent; the platform never truncates a log field. Move the long part into detail or metadata, or split it across rows.`,
|
|
354
|
+
{ index, field, max, length }
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
function validateLogEntry(raw, index = 0) {
|
|
358
|
+
const at = `env.SPRIGR.log: entries[${index}]`;
|
|
359
|
+
if (!isPlainObject(raw)) {
|
|
360
|
+
throw new SprigrLogValidationError(`${at} must be an object`, { index, field: "entry" });
|
|
361
|
+
}
|
|
362
|
+
const L = SPRIGR_LOG_LIMITS;
|
|
363
|
+
const level = raw.level;
|
|
364
|
+
if (typeof level !== "string" || !SPRIGR_LOG_LEVELS.includes(level)) {
|
|
365
|
+
throw new SprigrLogValidationError(`${at}.level must be one of ${SPRIGR_LOG_LEVELS.join(" | ")}`, {
|
|
366
|
+
index,
|
|
367
|
+
field: "level"
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
const category = raw.category;
|
|
371
|
+
if (typeof category !== "string" || category.length === 0) {
|
|
372
|
+
throw new SprigrLogValidationError(`${at}.category required (string)`, { index, field: "category" });
|
|
373
|
+
}
|
|
374
|
+
if (category.length > L.maxCategoryChars) throw tooLong(index, "category", category.length, L.maxCategoryChars);
|
|
375
|
+
if (!L.categoryPattern.test(category)) {
|
|
376
|
+
throw new SprigrLogValidationError(
|
|
377
|
+
`${at}.category must match ${L.categoryPattern} (letters, digits, . _ : -; no spaces)`,
|
|
378
|
+
{ index, field: "category" }
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
const summary = raw.summary;
|
|
382
|
+
if (typeof summary !== "string" || summary.length === 0) {
|
|
383
|
+
throw new SprigrLogValidationError(`${at}.summary required (string)`, { index, field: "summary" });
|
|
384
|
+
}
|
|
385
|
+
if (summary.length > L.maxSummaryChars) throw tooLong(index, "summary", summary.length, L.maxSummaryChars);
|
|
386
|
+
const out = { level, category, summary };
|
|
387
|
+
if (raw.detail !== void 0) {
|
|
388
|
+
if (typeof raw.detail !== "string") {
|
|
389
|
+
throw new SprigrLogValidationError(`${at}.detail must be a string`, { index, field: "detail" });
|
|
390
|
+
}
|
|
391
|
+
if (raw.detail.length > L.maxDetailChars) throw tooLong(index, "detail", raw.detail.length, L.maxDetailChars);
|
|
392
|
+
out.detail = raw.detail;
|
|
393
|
+
}
|
|
394
|
+
if (raw.metadata !== void 0) {
|
|
395
|
+
if (!isPlainObject(raw.metadata)) {
|
|
396
|
+
throw new SprigrLogValidationError(`${at}.metadata must be a plain object`, { index, field: "metadata" });
|
|
397
|
+
}
|
|
398
|
+
const serialised = JSON.stringify(raw.metadata).length;
|
|
399
|
+
if (serialised > L.maxMetadataChars) {
|
|
400
|
+
throw new SprigrLogValidationError(
|
|
401
|
+
`${at}.metadata JSON is ${serialised} chars; max ${L.maxMetadataChars}. Nothing was sent; drop or split fields.`,
|
|
402
|
+
{ index, field: "metadata", max: L.maxMetadataChars, length: serialised }
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
out.metadata = raw.metadata;
|
|
406
|
+
}
|
|
407
|
+
for (const field of ["agent_id", "trace_id"]) {
|
|
408
|
+
const v = raw[field];
|
|
409
|
+
if (v === void 0) continue;
|
|
410
|
+
if (typeof v !== "string" || v.length === 0 || v.length > L.maxIdChars) {
|
|
411
|
+
throw new SprigrLogValidationError(
|
|
412
|
+
`${at}.${field} must be a non-empty string of at most ${L.maxIdChars} chars`,
|
|
413
|
+
{ index, field, max: L.maxIdChars, length: typeof v === "string" ? v.length : void 0 }
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
out[field] = v;
|
|
417
|
+
}
|
|
418
|
+
return out;
|
|
419
|
+
}
|
|
420
|
+
function validateLogEntries(input) {
|
|
421
|
+
const list = Array.isArray(input) ? input : [input];
|
|
422
|
+
if (list.length === 0) {
|
|
423
|
+
throw new SprigrLogValidationError("env.SPRIGR.log: at least one entry required", {
|
|
424
|
+
index: 0,
|
|
425
|
+
field: "entries"
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
if (list.length > SPRIGR_LOG_LIMITS.maxEntriesPerCall) {
|
|
429
|
+
throw new SprigrLogValidationError(
|
|
430
|
+
`env.SPRIGR.log: at most ${SPRIGR_LOG_LIMITS.maxEntriesPerCall} entries per call (got ${list.length}); nothing was sent`,
|
|
431
|
+
{
|
|
432
|
+
index: 0,
|
|
433
|
+
field: "entries",
|
|
434
|
+
max: SPRIGR_LOG_LIMITS.maxEntriesPerCall,
|
|
435
|
+
length: list.length
|
|
436
|
+
}
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
return list.map((e, i) => validateLogEntry(e, i));
|
|
440
|
+
}
|
|
441
|
+
function bindingLog(env) {
|
|
442
|
+
const sprigr = env.SPRIGR;
|
|
443
|
+
return typeof sprigr?.log === "function" ? sprigr.log.bind(sprigr) : null;
|
|
444
|
+
}
|
|
445
|
+
function canLog(env) {
|
|
446
|
+
return bindingLog(env) !== null || resolveInstallBridge(env) !== null;
|
|
447
|
+
}
|
|
448
|
+
async function postLog(env, entries, timeoutMs) {
|
|
449
|
+
const bridge = resolveInstallBridge(env);
|
|
450
|
+
if (!bridge) {
|
|
451
|
+
return { ok: false, error: "no_log_path", detail: describeMissingBridge(env) };
|
|
452
|
+
}
|
|
453
|
+
try {
|
|
454
|
+
const reply = await installTokenPost(bridge, "/internal/wfp/log", { entries }, {
|
|
455
|
+
label: "log",
|
|
456
|
+
timeoutMs
|
|
457
|
+
});
|
|
458
|
+
if (reply.ok === false) {
|
|
459
|
+
return { ok: false, error: reply.error ?? "log_failed", detail: reply.detail };
|
|
460
|
+
}
|
|
461
|
+
return { ok: true, written: typeof reply.written === "number" ? reply.written : entries.length };
|
|
462
|
+
} catch (err) {
|
|
463
|
+
const e = err;
|
|
464
|
+
const message = e instanceof Error ? e.message : String(err);
|
|
465
|
+
console.warn(`[sprigr-log] log fallback failed: ${message}`);
|
|
466
|
+
return {
|
|
467
|
+
ok: false,
|
|
468
|
+
error: e.error ?? (e.status !== void 0 ? "log_failed" : "log_transport_failed"),
|
|
469
|
+
...e.status !== void 0 ? { status: e.status } : {},
|
|
470
|
+
detail: e.detail ?? message
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
function logToPlatform(env, input, opts) {
|
|
475
|
+
const entries = validateLogEntries(input);
|
|
476
|
+
const injected = bindingLog(env);
|
|
477
|
+
let result;
|
|
478
|
+
if (injected) {
|
|
479
|
+
try {
|
|
480
|
+
result = Promise.resolve(injected(entries)).catch((err) => ({
|
|
481
|
+
ok: false,
|
|
482
|
+
error: "log_failed",
|
|
483
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
484
|
+
}));
|
|
485
|
+
} catch (err) {
|
|
486
|
+
result = Promise.resolve({
|
|
487
|
+
ok: false,
|
|
488
|
+
error: "log_failed",
|
|
489
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
} else {
|
|
493
|
+
result = postLog(env, entries, opts?.timeoutMs ?? DEFAULT_LOG_TIMEOUT_MS);
|
|
494
|
+
}
|
|
495
|
+
if (opts?.waitUntil) opts.waitUntil(result);
|
|
496
|
+
return result;
|
|
497
|
+
}
|
|
498
|
+
function withSprigrLogFallback(env, opts) {
|
|
499
|
+
if (bindingLog(env)) return env;
|
|
500
|
+
const bridge = resolveInstallBridge(env);
|
|
501
|
+
if (!bridge) return env;
|
|
502
|
+
const log = (input) => {
|
|
503
|
+
const entries = validateLogEntries(input);
|
|
504
|
+
const p = postLog(env, entries, opts?.timeoutMs ?? DEFAULT_LOG_TIMEOUT_MS);
|
|
505
|
+
if (opts?.waitUntil) opts.waitUntil(p);
|
|
506
|
+
return p;
|
|
507
|
+
};
|
|
508
|
+
const existing = env.SPRIGR ?? {};
|
|
509
|
+
return overlaySprigr(env, { ...existing, log });
|
|
510
|
+
}
|
|
511
|
+
|
|
315
512
|
// src/actor.ts
|
|
316
513
|
function parseActor(args) {
|
|
317
514
|
if (!args || typeof args !== "object") return void 0;
|
|
@@ -1167,11 +1364,15 @@ function commonDestructiveVerbs(subject) {
|
|
|
1167
1364
|
export {
|
|
1168
1365
|
APPROVAL_GRANTED_KEY,
|
|
1169
1366
|
DEFAULT_EMIT_TIMEOUT_MS,
|
|
1367
|
+
DEFAULT_LOG_TIMEOUT_MS,
|
|
1170
1368
|
DEFAULT_MAX_FILE_BYTES,
|
|
1171
1369
|
DEFAULT_MONEY_FIELDS,
|
|
1172
1370
|
DEFAULT_WRITE_PREFIXES,
|
|
1173
1371
|
InvalidUndoEnvelopeError,
|
|
1174
1372
|
NotConnectedError,
|
|
1373
|
+
SPRIGR_LOG_LEVELS,
|
|
1374
|
+
SPRIGR_LOG_LIMITS,
|
|
1375
|
+
SprigrLogValidationError,
|
|
1175
1376
|
UNIT_SEP,
|
|
1176
1377
|
VIEWER_ANONYMOUS,
|
|
1177
1378
|
VIEWER_CONTEXT_HEADER,
|
|
@@ -1189,6 +1390,7 @@ export {
|
|
|
1189
1390
|
buildMarketplaceWebhookUrl,
|
|
1190
1391
|
bytesToBase64,
|
|
1191
1392
|
canEmit,
|
|
1393
|
+
canLog,
|
|
1192
1394
|
checkConfirmationPolicy,
|
|
1193
1395
|
commonDestructiveVerbs,
|
|
1194
1396
|
constantTimeEqual,
|
|
@@ -1211,6 +1413,7 @@ export {
|
|
|
1211
1413
|
isNotConnectedError,
|
|
1212
1414
|
isStagingHost,
|
|
1213
1415
|
listAppFiles,
|
|
1416
|
+
logToPlatform,
|
|
1214
1417
|
manifestIsFresh,
|
|
1215
1418
|
offerUndo,
|
|
1216
1419
|
overlaySprigr,
|
|
@@ -1235,6 +1438,9 @@ export {
|
|
|
1235
1438
|
set,
|
|
1236
1439
|
tool,
|
|
1237
1440
|
undoEnvelope,
|
|
1441
|
+
validateLogEntries,
|
|
1442
|
+
validateLogEntry,
|
|
1238
1443
|
verifyViewerToken,
|
|
1239
|
-
withSprigrEmitFallback
|
|
1444
|
+
withSprigrEmitFallback,
|
|
1445
|
+
withSprigrLogFallback
|
|
1240
1446
|
};
|