@sprigr/apps-app-sdk 0.10.1 → 0.12.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
  *
@@ -1852,6 +1966,23 @@ interface ManifestDispatchBlock {
1852
1966
  required?: string[];
1853
1967
  }>;
1854
1968
  }
1969
+ /**
1970
+ * A manifest tool's DECLARED side-effect class, the `tools[].effects` field of
1971
+ * `sprigr-app.json`. Mirrors the platform's `AppToolEffectsDeclaration`.
1972
+ *
1973
+ * Deliberately the single literal `'write'`, not a `'read' | 'write'` union.
1974
+ * The platform decides a marketplace tool call's dispatch tier from a name
1975
+ * heuristic (a read verb prefix such as `list_` / `get_` / `find_`, anything
1976
+ * else is a write), and the tier controls the dispatch budget, whether a
1977
+ * timeout or a Durable Object disconnect is blind-retried, and whether an
1978
+ * idempotency token is forwarded.
1979
+ *
1980
+ * Declaring `'write'` only ever NARROWS what the app gets: the shorter budget,
1981
+ * no automatic retry, idempotency token forwarded. `'read'` would be the
1982
+ * inverse and is NOT honoured by the platform, so it is not expressible here.
1983
+ * See the platform's decision note 0057.
1984
+ */
1985
+ type AppToolEffectsDeclaration = 'write';
1855
1986
  interface ManifestToolLike {
1856
1987
  name: string;
1857
1988
  input_schema?: {
@@ -1862,6 +1993,17 @@ interface ManifestToolLike {
1862
1993
  confirmation?: unknown;
1863
1994
  confirmation_required?: boolean;
1864
1995
  dispatch?: unknown;
1996
+ /**
1997
+ * Force the platform's WRITE dispatch tier for this tool regardless of what
1998
+ * it is named. Declare it when the action mutates and the name does not
1999
+ * obviously say so: `*_or_create_*` composites, `sync_*`, `reconcile_*`, or
2000
+ * a genuine create hiding behind a read verb (`list_and_stage_batch`).
2001
+ *
2002
+ * Omitting it keeps today's behaviour exactly: the name heuristic decides.
2003
+ * A value the platform does not recognise is ignored with a publish-time
2004
+ * warning, never a rejection.
2005
+ */
2006
+ effects?: AppToolEffectsDeclaration;
1865
2007
  [k: string]: unknown;
1866
2008
  }
1867
2009
  interface ManifestLike {
@@ -2094,6 +2236,34 @@ interface SprigrDataIndexOpts {
2094
2236
  /** LOGICAL index name (a `data_indexes` key). See {@link SprigrDataSearchOpts.index}. */
2095
2237
  index?: string;
2096
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
+ }
2097
2267
  /**
2098
2268
  * The marketplace install's per-company datastore on Sprigr search,
2099
2269
  * injected on `env.SPRIGR` by the platform wrapper for /__sprigr/*
@@ -2155,6 +2325,32 @@ interface SprigrDataApi {
2155
2325
  object: Record<string, unknown> | null;
2156
2326
  index: string;
2157
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>;
2158
2354
  /**
2159
2355
  * Delete objects by objectID (mirror maintenance for source-side
2160
2356
  * deletions). Idempotent: unknown ids and never-imported indexes are a
@@ -2343,4 +2539,4 @@ interface ExecutionContextLike {
2343
2539
  }
2344
2540
  type HandlerFn<TArgs, TResult> = (args: TArgs, env: Record<string, unknown>, ctx?: ExecutionContextLike) => Promise<TResult>;
2345
2541
 
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 };
2542
+ 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 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.10.1",
3
+ "version": "0.12.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",