@sprigr/apps-app-sdk 0.11.0 → 0.13.0

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 CHANGED
@@ -15,6 +15,7 @@ The runtime injects the `env.SPRIGR` host object **only on `/__sprigr/*` dispatc
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
+ - `partialUpdateData(env, objects, opts?)`: merge fields into objects already in the app's data index without reading them first (`env.SPRIGR.data.partialUpdate` on dispatch, `POST ${SPRIGR_PLATFORM_BASE}/internal/wfp/data/partial-update` from an inline route). Deep merge: nested objects recurse, arrays and scalars replace wholesale, absent keys survive, `null` overwrites. `createIfNotExists` defaults to `false`; on a sharded logical index every patch must carry the `shard_field` value or the batch fails with `shard_field_invalid`; 1000 objects per call like `import`. Throws on a bad batch before sending (`SprigrDataValidationError`) and on a platform rejection (`.status`, `.error`, `.detail` on the `Error`), the same contract as `import`. `canPartialUpdate(env)` gates a patch step on either transport; the `SprigrDataPartialUpdateOpts` / `SprigrDataPartialUpdateResult` types let your env interface declare the host member.
18
19
  - `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.
19
20
  - `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`.
20
21
 
package/dist/index.d.ts CHANGED
@@ -649,6 +649,120 @@ declare function logToPlatform(env: WfpBridgeEnv, input: SprigrLogEntry | Sprigr
649
649
  */
650
650
  declare function withSprigrLogFallback<E extends WfpBridgeEnv & object>(env: E, opts?: LogToPlatformOptions): E;
651
651
 
652
+ /**
653
+ * `env.SPRIGR.data.partialUpdate` from BOTH execution contexts.
654
+ *
655
+ * The platform exposes `env.SPRIGR.data.partialUpdate(objects, opts)` on
656
+ * every `/__sprigr/*` dispatch path, backed by
657
+ * `POST /internal/wfp/data/partial-update` on the provisioning worker. It
658
+ * merges fields into objects the app already imported without the app
659
+ * reading them first, which is what turns a read-then-`import` round trip
660
+ * (two platform calls, a race window, and a whole-object rewrite) into one
661
+ * call that touches only the fields named.
662
+ *
663
+ * Merge semantics, identical on both transports because the platform does
664
+ * the merge: nested objects recurse, arrays and scalars replace wholesale,
665
+ * keys the patch does not carry survive, and `null` is a value that
666
+ * overwrites. `createIfNotExists` defaults to `false` so a patch never
667
+ * creates half an object unless asked. On a hash- or date-sharded logical
668
+ * index every patch MUST carry the `shard_field` value or the whole batch
669
+ * fails with `shard_field_invalid`: a patch cannot be routed to its shard
670
+ * otherwise. The per-call cap is the same 1000 objects as `import`.
671
+ *
672
+ * This module carries two things:
673
+ *
674
+ * 1. `buildPartialUpdateBody`, the wire body both transports send, with
675
+ * the client-side checks that reject a batch BEFORE anything is sent
676
+ * (too many objects, a patch without a string `objectID`).
677
+ * 2. The inline-route fallback (`partialUpdateData`, `canPartialUpdate`).
678
+ * Inline Next.js route handlers never receive `env.SPRIGR`, so an app
679
+ * whose provider webhook lands on one has no `data` namespace there.
680
+ * Same escape hatch as `emitMarketplaceEvent` and `logToPlatform`:
681
+ * `POST ${SPRIGR_PLATFORM_BASE}/internal/wfp/data/partial-update`
682
+ * with the `SPRIGR_INSTALL_TOKEN` bearer.
683
+ *
684
+ * Unlike `emit` and `log`, this THROWS on failure, matching the host
685
+ * member and `data.import`: a data write the caller wants the result of
686
+ * must not be reported as a soft `{ ok: false }` a webhook ack path could
687
+ * swallow.
688
+ *
689
+ * Wire contract (workers/provisioning/src/wfp-data.ts in sprigr-team):
690
+ * POST { index?, objects, createIfNotExists? }
691
+ * 200 { ok: true, updated, skippedMissing, index, physical_indexes? }
692
+ * 400 { error, detail, index? } with error one of too_many_objects,
693
+ * invalid_object, unknown_data_index, shard_field_invalid, ...
694
+ * 401 / 404 bad token / inactive install
695
+ */
696
+
697
+ /** Route the host member and the fallback both call. */
698
+ declare const DATA_PARTIAL_UPDATE_PATH = "/internal/wfp/data/partial-update";
699
+ /** Same per-call cap as `data.import` (`MAX_OBJECTS_PER_CALL` in wfp-data.ts). */
700
+ declare const SPRIGR_DATA_MAX_OBJECTS_PER_CALL = 1000;
701
+ /** One patch: the target `objectID` plus only the fields to merge. */
702
+ type SprigrDataPatch = {
703
+ objectID: string;
704
+ [key: string]: unknown;
705
+ };
706
+ /** The body `POST /internal/wfp/data/partial-update` receives. */
707
+ interface SprigrDataPartialUpdateBody {
708
+ index?: string;
709
+ objects: SprigrDataPatch[];
710
+ createIfNotExists: boolean;
711
+ }
712
+ interface PartialUpdateDataOptions extends SprigrDataPartialUpdateOpts {
713
+ /** Ceiling on the HTTP fallback. No default: a data write is awaited for its result. */
714
+ timeoutMs?: number;
715
+ }
716
+ /**
717
+ * Thrown by `buildPartialUpdateBody` (and therefore by `partialUpdateData`)
718
+ * before anything is sent. `error` is the same code the platform would
719
+ * answer with, so a caller can branch on one vocabulary.
720
+ */
721
+ declare class SprigrDataValidationError extends Error {
722
+ readonly error: string;
723
+ readonly index?: number;
724
+ constructor(message: string, fields: {
725
+ error: string;
726
+ index?: number;
727
+ });
728
+ }
729
+ /**
730
+ * Build the wire body, rejecting what the platform would reject anyway so
731
+ * the failure is at the call site rather than a 400 in a tail log. Pure.
732
+ * `createIfNotExists` is always explicit in the output so the default is
733
+ * visible on the wire and in tests, never inferred server-side.
734
+ */
735
+ declare function buildPartialUpdateBody(objects: unknown, opts?: SprigrDataPartialUpdateOpts): SprigrDataPartialUpdateBody;
736
+ /**
737
+ * Whether a partial update could reach the platform at all, by either
738
+ * transport. Gate a "patch the mirror" step on this rather than on
739
+ * `env.SPRIGR?.data?.partialUpdate`, which is absent on every inline route
740
+ * and on wrapper builds older than the platform route.
741
+ */
742
+ declare function canPartialUpdate(env: WfpBridgeEnv): boolean;
743
+ /**
744
+ * Merge fields into stored objects from any execution context.
745
+ *
746
+ * Prefers the injected `env.SPRIGR.data.partialUpdate`; falls back to the
747
+ * install-token bridge when it is absent (an inline Next route, or a
748
+ * wrapper build that predates the member). Validation runs first and
749
+ * THROWS `SprigrDataValidationError` on a bad batch, before anything is
750
+ * sent. A platform rejection also throws: the `Error` from
751
+ * `installTokenPost` carries `.status`, `.error` (the platform's code, e.g.
752
+ * `shard_field_invalid`) and `.detail`. Nothing here is swallowed.
753
+ *
754
+ * // before: two calls and a whole-object rewrite
755
+ * const { object } = await env.SPRIGR.data.get(orderId, { index: 'orders' });
756
+ * await env.SPRIGR.data.import([{ ...object, status: 'shipped' }], { index: 'orders' });
757
+ *
758
+ * // after: one call, only the named field changes
759
+ * await partialUpdateData(env, [{ objectID: orderId, created_at, status: 'shipped' }], { index: 'orders' });
760
+ *
761
+ * (`created_at` rides along because `orders` is date-sharded on it; an
762
+ * unsharded index needs only `objectID` and the fields to change.)
763
+ */
764
+ declare function partialUpdateData(env: WfpBridgeEnv, objects: SprigrDataPatch[], opts?: PartialUpdateDataOptions): Promise<SprigrDataPartialUpdateResult>;
765
+
652
766
  /**
653
767
  * Caller identity stamped by the platform on agent-initiated WFP dispatches.
654
768
  *
@@ -2122,6 +2236,34 @@ interface SprigrDataIndexOpts {
2122
2236
  /** LOGICAL index name (a `data_indexes` key). See {@link SprigrDataSearchOpts.index}. */
2123
2237
  index?: string;
2124
2238
  }
2239
+ /** Options for `env.SPRIGR.data.partialUpdate`. */
2240
+ interface SprigrDataPartialUpdateOpts extends SprigrDataIndexOpts {
2241
+ /**
2242
+ * Create an object that does not exist yet from the patch alone. Default
2243
+ * `false`: a patch never creates half an object unless asked, and a
2244
+ * missing target is counted in `skippedMissing` instead of being written.
2245
+ */
2246
+ createIfNotExists?: boolean;
2247
+ }
2248
+ /** Reply of `env.SPRIGR.data.partialUpdate` (`POST /internal/wfp/data/partial-update`). */
2249
+ interface SprigrDataPartialUpdateResult {
2250
+ ok: true;
2251
+ /** Objects whose stored copy changed. */
2252
+ updated: number;
2253
+ /** Objects that did not exist. Only non-zero when `createIfNotExists` is false. */
2254
+ skippedMissing: number;
2255
+ /**
2256
+ * The logical index name for multi-index apps, else the physical index
2257
+ * that was patched (informational).
2258
+ */
2259
+ index: string;
2260
+ /** Multi-index apps: what was patched per physical index/shard. */
2261
+ physical_indexes?: Array<{
2262
+ index: string;
2263
+ shard?: string;
2264
+ updated: number;
2265
+ }>;
2266
+ }
2125
2267
  /**
2126
2268
  * The marketplace install's per-company datastore on Sprigr search,
2127
2269
  * injected on `env.SPRIGR` by the platform wrapper for /__sprigr/*
@@ -2183,6 +2325,32 @@ interface SprigrDataApi {
2183
2325
  object: Record<string, unknown> | null;
2184
2326
  index: string;
2185
2327
  }>;
2328
+ /**
2329
+ * Merge fields into existing objects without reading them first. Each
2330
+ * patch MUST carry a string `objectID`. Max 1000 objects per call, the
2331
+ * same cap as `import`. The platform deep-merges each patch onto the
2332
+ * stored object: nested objects recurse, arrays and scalars replace
2333
+ * wholesale, keys you do not send survive. `null` is a value and
2334
+ * overwrites, so strip keys you do not mean to change rather than
2335
+ * sending them as `null` or `undefined`-then-serialised.
2336
+ *
2337
+ * `createIfNotExists` defaults to `false`: a patch never creates half an
2338
+ * object unless asked, and a missing target is counted in
2339
+ * `skippedMissing`. On a hash- or date-sharded logical index every patch
2340
+ * MUST carry the `shard_field` value, or the WHOLE batch fails with
2341
+ * `shard_field_invalid`: a patch cannot be routed to its shard otherwise,
2342
+ * and the platform will not probe every shard to find it. The response's
2343
+ * `physical_indexes` lists what was patched per shard.
2344
+ *
2345
+ * `?`-optional because wrapper builds older than the platform route
2346
+ * predate it. Feature-detect, or call this package's
2347
+ * `partialUpdateData(env, objects, opts)`, which uses this member when
2348
+ * present and the install-token bridge otherwise.
2349
+ */
2350
+ partialUpdate?(objects: Array<{
2351
+ objectID: string;
2352
+ [key: string]: unknown;
2353
+ }>, opts?: SprigrDataPartialUpdateOpts): Promise<SprigrDataPartialUpdateResult>;
2186
2354
  /**
2187
2355
  * Delete objects by objectID (mirror maintenance for source-side
2188
2356
  * deletions). Idempotent: unknown ids and never-imported indexes are a
@@ -2211,6 +2379,12 @@ interface SprigrDataApi {
2211
2379
  * URL the upstream deletes after a short retention window) so the URL your
2212
2380
  * app hands back keeps resolving.
2213
2381
  *
2382
+ * Per-user storage (sprigr-team#9621): keys under the manifest's
2383
+ * `actor_scoped_storage_prefixes` are stored and resolved per CALLER by the
2384
+ * platform, from the signed actor the wrapper forwards on every call. Keep
2385
+ * using app-relative keys; never add your own owner tag. A call with no actor
2386
+ * may only `list` / `delete` there (maintenance), never read.
2387
+ *
2214
2388
  * This is the in-isolate bridge surface, injected on `env.SPRIGR` for
2215
2389
  * `/__sprigr/*` tool / webhook / event handlers. The standalone
2216
2390
  * `putAppFileStream` / `appFileUrl` helpers in this package call the same
@@ -2250,6 +2424,75 @@ interface SprigrFilesApi {
2250
2424
  expires_at: number;
2251
2425
  key: string;
2252
2426
  }>;
2427
+ /**
2428
+ * Buffered put of base64 bytes (cap 10 MB); prefer `putStream` for anything
2429
+ * large. Optional: absent on wrapper builds before sprigr-team#9621, so
2430
+ * feature-detect. Throws with `err.code` / `err.status` on a refusal.
2431
+ */
2432
+ put?(key: string, base64: string, opts?: {
2433
+ contentType?: string;
2434
+ filename?: string;
2435
+ }): Promise<{
2436
+ ok: boolean;
2437
+ key: string;
2438
+ bytes: number;
2439
+ contentType: string;
2440
+ }>;
2441
+ /** Read a stored object back as base64. Throws `err.code === 'not_found'`
2442
+ * (404) when absent. Optional: see `put`. */
2443
+ get?(key: string): Promise<{
2444
+ ok: boolean;
2445
+ key: string;
2446
+ base64: string;
2447
+ contentType: string;
2448
+ filename?: string;
2449
+ bytes: number;
2450
+ }>;
2451
+ /**
2452
+ * List stored keys under an app-relative prefix, 1000 per page; pass the
2453
+ * returned `cursor` for the next page. On a call with no actor (a schedule,
2454
+ * webhook or event handler), a prefix under `actor_scoped_storage_prefixes`
2455
+ * lists EVERY user's copies, each entry carrying an opaque `owner_ref`
2456
+ * (`owner_scope: 'all_owners'`); with `ownerless: true` it lists the copies
2457
+ * written there before the prefix was declared (`owner_scope: 'ownerless'`).
2458
+ * Optional: see `put`.
2459
+ */
2460
+ list?(prefix?: string, opts?: {
2461
+ cursor?: string;
2462
+ ownerless?: boolean;
2463
+ }): Promise<SprigrFilesListResult>;
2464
+ /**
2465
+ * Delete a stored key. On a call with no actor, a per-user key needs the
2466
+ * `owner_ref` (or `ownerless: true`) a maintenance `list` returned; on a call
2467
+ * made for a user both are ignored and that user's own copy is deleted.
2468
+ * Optional: see `put`.
2469
+ */
2470
+ delete?(key: string, opts?: {
2471
+ owner_ref?: string;
2472
+ ownerless?: boolean;
2473
+ }): Promise<{
2474
+ ok: boolean;
2475
+ key: string;
2476
+ }>;
2477
+ }
2478
+ /** One entry of `env.SPRIGR.files.list`. `owner_ref` / `ownerless` appear only
2479
+ * on a maintenance listing of a per-user prefix (sprigr-team#9621). */
2480
+ interface SprigrFilesListItem {
2481
+ key: string;
2482
+ bytes: number;
2483
+ uploaded?: string;
2484
+ filename?: string;
2485
+ contentType?: string;
2486
+ owner_ref?: string;
2487
+ ownerless?: boolean;
2488
+ }
2489
+ /** Result of `env.SPRIGR.files.list`. */
2490
+ interface SprigrFilesListResult {
2491
+ ok: boolean;
2492
+ files: SprigrFilesListItem[];
2493
+ truncated: boolean;
2494
+ cursor?: string;
2495
+ owner_scope?: 'all_owners' | 'ownerless';
2253
2496
  }
2254
2497
  /**
2255
2498
  * Platform document-engine bridge types (`env.SPRIGR.files.edit` / `.create`
@@ -2371,4 +2614,4 @@ interface ExecutionContextLike {
2371
2614
  }
2372
2615
  type HandlerFn<TArgs, TResult> = (args: TArgs, env: Record<string, unknown>, ctx?: ExecutionContextLike) => Promise<TResult>;
2373
2616
 
2374
- export { APPROVAL_GRANTED_KEY, type Actor, type AppApprovalEnvelope, type AppFileListItem, type AppFileUrlArgs, type AppFileUrlResult, type AppFilesEnv, type AppToolEffectsDeclaration, 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 };
2617
+ export { APPROVAL_GRANTED_KEY, type Actor, type AppApprovalEnvelope, type AppFileListItem, type AppFileUrlArgs, type AppFileUrlResult, type AppFilesEnv, type AppToolEffectsDeclaration, type AppUndoEnvelope, type ApplyPoliciesOptions, type ApprovalGateOptions, type ApprovalSpec, type ArchiveOfferOptions, type ArchiveOfferRefusal, type CaptureJournal, type ConfirmCondition, type ConfirmRule, type ConfirmationPolicy, type D1Like, type D1PreparedStatementLike, DATA_PARTIAL_UPDATE_PATH, 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 PartialUpdateDataOptions, type PlatformHostEnv, type PolicyCheckInput, type PolicySource, type PutAppFileArgs, type PutAppFileResult, type PutAppFileStreamArgs, type RequireApprovalOptions, type ResolveViewerOptions, type RestoreResult, type RestoreSpec, SPRIGR_DATA_MAX_OBJECTS_PER_CALL, SPRIGR_LOG_LEVELS, SPRIGR_LOG_LIMITS, type ScheduleArgs, type SprigrDataAggregationCell, type SprigrDataAggregationResult, type SprigrDataAggregationSpec, type SprigrDataApi, type SprigrDataIndexOpts, type SprigrDataPartialUpdateBody, type SprigrDataPartialUpdateOpts, type SprigrDataPartialUpdateResult, type SprigrDataPatch, type SprigrDataSearchOpts, type SprigrDataSearchResult, SprigrDataValidationError, type SprigrFilesApi, type SprigrFilesApiWithEdit, type SprigrFilesCreateInput, type SprigrFilesCreateResult, type SprigrFilesEditInput, type SprigrFilesEditResult, type SprigrFilesJobResult, type SprigrFilesListItem, type SprigrFilesListResult, 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, buildPartialUpdateBody, bytesToBase64, canEmit, canLog, canPartialUpdate, 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, partialUpdateData, 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
@@ -509,6 +509,91 @@ function withSprigrLogFallback(env, opts) {
509
509
  return overlaySprigr(env, { ...existing, log });
510
510
  }
511
511
 
512
+ // src/platform-data.ts
513
+ var DATA_PARTIAL_UPDATE_PATH = "/internal/wfp/data/partial-update";
514
+ var SPRIGR_DATA_MAX_OBJECTS_PER_CALL = 1e3;
515
+ var SprigrDataValidationError = class extends Error {
516
+ error;
517
+ index;
518
+ constructor(message, fields) {
519
+ super(message);
520
+ this.name = "SprigrDataValidationError";
521
+ this.error = fields.error;
522
+ if (fields.index !== void 0) this.index = fields.index;
523
+ }
524
+ };
525
+ function buildPartialUpdateBody(objects, opts) {
526
+ const at = "env.SPRIGR.data.partialUpdate";
527
+ if (!Array.isArray(objects) || objects.length === 0) {
528
+ throw new SprigrDataValidationError(`${at}: objects must be a non-empty array`, {
529
+ error: "invalid_object"
530
+ });
531
+ }
532
+ if (objects.length > SPRIGR_DATA_MAX_OBJECTS_PER_CALL) {
533
+ throw new SprigrDataValidationError(
534
+ `${at}: at most ${SPRIGR_DATA_MAX_OBJECTS_PER_CALL} objects per call (got ${objects.length}); nothing was sent. Split the batch.`,
535
+ { error: "too_many_objects" }
536
+ );
537
+ }
538
+ for (let i = 0; i < objects.length; i++) {
539
+ const o = objects[i];
540
+ if (!o || typeof o !== "object" || Array.isArray(o)) {
541
+ throw new SprigrDataValidationError(`${at}: objects[${i}] must be an object`, {
542
+ error: "invalid_object",
543
+ index: i
544
+ });
545
+ }
546
+ const id = o.objectID;
547
+ if (typeof id !== "string" || id.length === 0) {
548
+ throw new SprigrDataValidationError(
549
+ `${at}: objects[${i}].objectID must be a non-empty string (a patch has to name its target)`,
550
+ { error: "invalid_object", index: i }
551
+ );
552
+ }
553
+ }
554
+ if (opts?.index !== void 0 && (typeof opts.index !== "string" || opts.index.length === 0)) {
555
+ throw new SprigrDataValidationError(`${at}: index must be a non-empty string when given`, {
556
+ error: "unknown_data_index"
557
+ });
558
+ }
559
+ return {
560
+ ...opts?.index !== void 0 ? { index: opts.index } : {},
561
+ objects,
562
+ createIfNotExists: opts?.createIfNotExists === true
563
+ };
564
+ }
565
+ function bindingPartialUpdate(env) {
566
+ const data = env.SPRIGR?.data;
567
+ return typeof data?.partialUpdate === "function" ? data.partialUpdate.bind(data) : null;
568
+ }
569
+ function canPartialUpdate(env) {
570
+ return bindingPartialUpdate(env) !== null || resolveInstallBridge(env) !== null;
571
+ }
572
+ function describeMissingDataPath(env) {
573
+ const base = typeof env.SPRIGR_PLATFORM_BASE === "string" && env.SPRIGR_PLATFORM_BASE ? "set" : "unset";
574
+ const token = typeof env.SPRIGR_INSTALL_TOKEN === "string" && env.SPRIGR_INSTALL_TOKEN ? "set" : "unset";
575
+ return `no_data_path (SPRIGR.data.partialUpdate absent, SPRIGR_PLATFORM_BASE=${base}, SPRIGR_INSTALL_TOKEN=${token})`;
576
+ }
577
+ async function partialUpdateData(env, objects, opts) {
578
+ const { timeoutMs, ...dataOpts } = opts ?? {};
579
+ const body = buildPartialUpdateBody(objects, dataOpts);
580
+ const injected = bindingPartialUpdate(env);
581
+ if (injected) {
582
+ return injected(body.objects, {
583
+ ...body.index !== void 0 ? { index: body.index } : {},
584
+ createIfNotExists: body.createIfNotExists
585
+ });
586
+ }
587
+ const bridge = resolveInstallBridge(env);
588
+ if (!bridge) {
589
+ throw new Error(`env.SPRIGR.data.partialUpdate unavailable: ${describeMissingDataPath(env)}`);
590
+ }
591
+ return await installTokenPost(bridge, DATA_PARTIAL_UPDATE_PATH, body, {
592
+ label: "data.partialUpdate",
593
+ ...timeoutMs !== void 0 ? { timeoutMs } : {}
594
+ });
595
+ }
596
+
512
597
  // src/actor.ts
513
598
  function parseActor(args) {
514
599
  if (!args || typeof args !== "object") return void 0;
@@ -1363,6 +1448,7 @@ function commonDestructiveVerbs(subject) {
1363
1448
  }
1364
1449
  export {
1365
1450
  APPROVAL_GRANTED_KEY,
1451
+ DATA_PARTIAL_UPDATE_PATH,
1366
1452
  DEFAULT_EMIT_TIMEOUT_MS,
1367
1453
  DEFAULT_LOG_TIMEOUT_MS,
1368
1454
  DEFAULT_MAX_FILE_BYTES,
@@ -1370,8 +1456,10 @@ export {
1370
1456
  DEFAULT_WRITE_PREFIXES,
1371
1457
  InvalidUndoEnvelopeError,
1372
1458
  NotConnectedError,
1459
+ SPRIGR_DATA_MAX_OBJECTS_PER_CALL,
1373
1460
  SPRIGR_LOG_LEVELS,
1374
1461
  SPRIGR_LOG_LIMITS,
1462
+ SprigrDataValidationError,
1375
1463
  SprigrLogValidationError,
1376
1464
  UNIT_SEP,
1377
1465
  VIEWER_ANONYMOUS,
@@ -1388,9 +1476,11 @@ export {
1388
1476
  base64ToBytes,
1389
1477
  buildConfirmationPolicy,
1390
1478
  buildMarketplaceWebhookUrl,
1479
+ buildPartialUpdateBody,
1391
1480
  bytesToBase64,
1392
1481
  canEmit,
1393
1482
  canLog,
1483
+ canPartialUpdate,
1394
1484
  checkConfirmationPolicy,
1395
1485
  commonDestructiveVerbs,
1396
1486
  constantTimeEqual,
@@ -1419,6 +1509,7 @@ export {
1419
1509
  overlaySprigr,
1420
1510
  ownerFromActorKey,
1421
1511
  parseActor,
1512
+ partialUpdateData,
1422
1513
  putAppFile,
1423
1514
  putAppFileStream,
1424
1515
  randomHex,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sprigr/apps-app-sdk",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",