@homespunapps/core 1.6.64 → 1.6.65

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.d.ts CHANGED
@@ -652,6 +652,34 @@ export declare class HomespunClient {
652
652
  }>;
653
653
  /** DELETE /v1/apps/:id/connections/:name: idempotent. */
654
654
  deleteConnection(appId: string, name: string): Promise<void>;
655
+ /**
656
+ * GET /v1/apps/:id/webhooks/deliveries: the outbound delivery journal, newest
657
+ * first. This is a ROLLING window (the retention sweeper prunes terminal rows
658
+ * past WEBHOOKS_DELIVERY_RETENTION_DAYS), not the app's whole history.
659
+ *
660
+ * `payload` is the request body as it was rendered and sent, truncated
661
+ * server-side; `payloadTruncated` says whether it was cut. Together with
662
+ * `responseStatus`/`responseBody` that is what answers "why did the target
663
+ * reject this", which is the reason to reach for this call at all.
664
+ */
665
+ listWebhookDeliveries(appId: string, opts?: {
666
+ status?: string;
667
+ collection?: string;
668
+ limit?: number;
669
+ }): Promise<{
670
+ deliveries: Record<string, unknown>[];
671
+ }>;
672
+ /**
673
+ * POST /v1/apps/:id/webhooks/deliveries/:deliveryId/replay: re-send a stored
674
+ * delivery's own rule/url/payload as a fresh row, now, without waiting out
675
+ * its remaining backoff. Nothing caller-supplied enters the new row.
676
+ *
677
+ * NOT idempotent in effect: the target receives the same request again, so a
678
+ * target without its own dedupe ends up with a duplicate record.
679
+ */
680
+ replayWebhookDelivery(appId: string, deliveryId: string): Promise<{
681
+ delivery: Record<string, unknown>;
682
+ }>;
655
683
  /**
656
684
  * The browser URL that completes an oauth2 connection's consent. Build
657
685
  * only, never fetch this yourself: it 302s the caller to the third-party
@@ -667,8 +695,10 @@ export declare class HomespunClient {
667
695
  * conditions) and/or `sort` (`{field, dir}` list) to filter/order DB-side. The
668
696
  * relay applies read permission + author scoping FIRST, then the filter, so a
669
697
  * filtered read is always a subset of what the caller could already read. The
670
- * query is serialized into the `q` param as URL-encoded JSON. Note: a custom
671
- * `sort` cannot be combined with cursor pagination (`since`) in this version.
698
+ * query is serialized into the `q` param as URL-encoded JSON. Cursor
699
+ * pagination (`since`) works with a custom `sort` too (issue #1057): pass
700
+ * back the same `sort` a page's `next_cursor` came from, since a cursor is
701
+ * only valid for the exact sort it was issued under.
672
702
  */
673
703
  listAppRows(appId: string, collection: string, opts?: {
674
704
  since?: string;
@@ -887,6 +917,24 @@ export declare class HomespunClient {
887
917
  /** POST /v1/reviews/:id/unhold (operator): publish a held review. */
888
918
  unholdReview(reviewId: string): Promise<CommunityReview>;
889
919
  }
920
+ /**
921
+ * PUT raw bytes to a presigned upload URL returned by `presignBlob()`.
922
+ * Deliberately a bare function, not a `HomespunClient` method: the URL is
923
+ * ALREADY pre-authorized (a SAS query string on Azure, scoped to this one
924
+ * key), so sending `Authorization: Bearer <apiKey>` alongside it would hand
925
+ * the agent's relay credential to a storage host that never asked for it and
926
+ * has no use for it. Routing this through `this.call` / `this.fetchImpl`
927
+ * would attach that header unconditionally, so this stays outside the
928
+ * authenticated client machinery on purpose.
929
+ *
930
+ * Azure Blob's single-shot PUT additionally requires the
931
+ * `x-ms-blob-type: BlockBlob` header; every presign backend today is Azure
932
+ * SAS, and a PUT without it 400s with `MissingRequiredHeader`, so it is
933
+ * always sent. There is no relay error envelope to parse on failure here:
934
+ * this response is storage's own, so a non-2xx throws a plain `Error`
935
+ * carrying the status and whatever body storage returned.
936
+ */
937
+ export declare function putPresigned(uploadUrl: string, bytes: Uint8Array | Buffer, mime: string, fetchImpl?: typeof fetch): Promise<void>;
890
938
  /**
891
939
  * One multi-file-deploy asset carrying its bytes inline as base64. The deployed
892
940
  * page references it by a stable, app-relative, same-origin path (e.g.
package/dist/client.js CHANGED
@@ -1104,6 +1104,44 @@ export class HomespunClient {
1104
1104
  if (!r.ok)
1105
1105
  this.fail(r);
1106
1106
  }
1107
+ /**
1108
+ * GET /v1/apps/:id/webhooks/deliveries: the outbound delivery journal, newest
1109
+ * first. This is a ROLLING window (the retention sweeper prunes terminal rows
1110
+ * past WEBHOOKS_DELIVERY_RETENTION_DAYS), not the app's whole history.
1111
+ *
1112
+ * `payload` is the request body as it was rendered and sent, truncated
1113
+ * server-side; `payloadTruncated` says whether it was cut. Together with
1114
+ * `responseStatus`/`responseBody` that is what answers "why did the target
1115
+ * reject this", which is the reason to reach for this call at all.
1116
+ */
1117
+ async listWebhookDeliveries(appId, opts = {}) {
1118
+ const q = new URLSearchParams();
1119
+ if (opts.status !== undefined)
1120
+ q.set("status", opts.status);
1121
+ if (opts.collection !== undefined)
1122
+ q.set("collection", opts.collection);
1123
+ if (opts.limit !== undefined)
1124
+ q.set("limit", String(opts.limit));
1125
+ const qs = q.toString();
1126
+ const r = await this.call("GET", `/v1/apps/${encodeURIComponent(appId)}/webhooks/deliveries${qs ? `?${qs}` : ""}`);
1127
+ if (!r.ok)
1128
+ this.fail(r);
1129
+ return this.asObject(r);
1130
+ }
1131
+ /**
1132
+ * POST /v1/apps/:id/webhooks/deliveries/:deliveryId/replay: re-send a stored
1133
+ * delivery's own rule/url/payload as a fresh row, now, without waiting out
1134
+ * its remaining backoff. Nothing caller-supplied enters the new row.
1135
+ *
1136
+ * NOT idempotent in effect: the target receives the same request again, so a
1137
+ * target without its own dedupe ends up with a duplicate record.
1138
+ */
1139
+ async replayWebhookDelivery(appId, deliveryId) {
1140
+ const r = await this.call("POST", `/v1/apps/${encodeURIComponent(appId)}/webhooks/deliveries/${encodeURIComponent(deliveryId)}/replay`);
1141
+ if (!r.ok)
1142
+ this.fail(r);
1143
+ return this.asObject(r);
1144
+ }
1107
1145
  /**
1108
1146
  * The browser URL that completes an oauth2 connection's consent. Build
1109
1147
  * only, never fetch this yourself: it 302s the caller to the third-party
@@ -1126,8 +1164,10 @@ export class HomespunClient {
1126
1164
  * conditions) and/or `sort` (`{field, dir}` list) to filter/order DB-side. The
1127
1165
  * relay applies read permission + author scoping FIRST, then the filter, so a
1128
1166
  * filtered read is always a subset of what the caller could already read. The
1129
- * query is serialized into the `q` param as URL-encoded JSON. Note: a custom
1130
- * `sort` cannot be combined with cursor pagination (`since`) in this version.
1167
+ * query is serialized into the `q` param as URL-encoded JSON. Cursor
1168
+ * pagination (`since`) works with a custom `sort` too (issue #1057): pass
1169
+ * back the same `sort` a page's `next_cursor` came from, since a cursor is
1170
+ * only valid for the exact sort it was issued under.
1131
1171
  */
1132
1172
  async listAppRows(appId, collection, opts = {}) {
1133
1173
  const q = new URLSearchParams();
@@ -1554,6 +1594,45 @@ export class HomespunClient {
1554
1594
  return this.asObject(r);
1555
1595
  }
1556
1596
  }
1597
+ /**
1598
+ * PUT raw bytes to a presigned upload URL returned by `presignBlob()`.
1599
+ * Deliberately a bare function, not a `HomespunClient` method: the URL is
1600
+ * ALREADY pre-authorized (a SAS query string on Azure, scoped to this one
1601
+ * key), so sending `Authorization: Bearer <apiKey>` alongside it would hand
1602
+ * the agent's relay credential to a storage host that never asked for it and
1603
+ * has no use for it. Routing this through `this.call` / `this.fetchImpl`
1604
+ * would attach that header unconditionally, so this stays outside the
1605
+ * authenticated client machinery on purpose.
1606
+ *
1607
+ * Azure Blob's single-shot PUT additionally requires the
1608
+ * `x-ms-blob-type: BlockBlob` header; every presign backend today is Azure
1609
+ * SAS, and a PUT without it 400s with `MissingRequiredHeader`, so it is
1610
+ * always sent. There is no relay error envelope to parse on failure here:
1611
+ * this response is storage's own, so a non-2xx throws a plain `Error`
1612
+ * carrying the status and whatever body storage returned.
1613
+ */
1614
+ export async function putPresigned(uploadUrl, bytes, mime, fetchImpl = fetch) {
1615
+ const body = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
1616
+ let res;
1617
+ try {
1618
+ res = await fetchImpl(uploadUrl, {
1619
+ method: "PUT",
1620
+ headers: {
1621
+ "content-type": mime,
1622
+ "x-ms-blob-type": "BlockBlob",
1623
+ },
1624
+ body,
1625
+ });
1626
+ }
1627
+ catch (e) {
1628
+ const msg = e instanceof Error ? e.message : String(e);
1629
+ throw new Error(`presigned upload failed: ${msg}`, { cause: e });
1630
+ }
1631
+ if (!res.ok) {
1632
+ const text = await res.text().catch(() => "");
1633
+ throw new Error(`presigned upload failed: HTTP ${res.status}${text ? ` (${text.slice(0, 500)})` : ""}`);
1634
+ }
1635
+ }
1557
1636
  /**
1558
1637
  * Path for a community-template ref action. A namespaced `<handle>/<slug>` ref
1559
1638
  * becomes a TWO-segment path (`/templates/<handle>/<slug>/<suffix>`) so it
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { HomespunClient, HomespunApiError } from "./client.js";
1
+ export { HomespunClient, HomespunApiError, putPresigned } from "./client.js";
2
2
  export type { ClientOptions, RelayResponse, AttachmentRef, UploadBlobOptions, PresignBlobOptions, AttachmentTokenMintResponse, ListBlobsOptions, AttachmentTokenAuditEntry, AttachmentTokenListResponse, QueryResponse, AppAsset, DeployAppRequest, DeployAppResponse, RedeployAppRequest, RedeployAppResponse, DeployCheckRequest, DeployCheckResult, KeyMintResult, AppSummary, AppDetail, AppsPage, AppDomain, AppDomainDnsRecord, AppMember, AddAppMemberResult, AppRow, AppRowsPage, ListScalar, ListWhereCondition, ListSortSpec, BatchRowInput, BatchRowResult, BatchResult, BatchWriteOptions, AppFeedEntry, AppFeedPage, CommunitySetupStep, RetentionAxes, CollectionRetentionView, ServiceCredentialGrant, MintAppCredentialResult, AppCredentialSummary, RotateAppCredentialResult, ConnectionSummary, } from "./client.js";
3
3
  export { openStream } from "./stream.js";
4
4
  export type { OpenStreamOptions, StreamHandlers, StreamHandle, } from "./stream.js";
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // @homespunapps/core — typed client for the Homespun relay HTTP + WebSocket API.
2
2
  // Pure and framework-free: no argv, no MCP, no server deps.
3
- export { HomespunClient, HomespunApiError } from "./client.js";
3
+ export { HomespunClient, HomespunApiError, putPresigned } from "./client.js";
4
4
  export { openStream } from "./stream.js";
5
5
  export { openAppStream, appWsUrlFromAppUrl } from "./app-stream.js";
6
6
  export { openWorkerStream, workerWsUrlFromBase } from "./app-stream.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homespunapps/core",
3
- "version": "1.6.64",
3
+ "version": "1.6.65",
4
4
  "description": "Homespun relay client: typed HTTP + WebSocket operations against a Homespun relay. Framework-free.",
5
5
  "license": "MIT",
6
6
  "type": "module",