@homespunapps/core 1.6.1 → 1.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.ts +94 -15
- package/dist/client.js +90 -13
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -192,6 +192,25 @@ export declare class HomespunClient {
|
|
|
192
192
|
* @param contentBase64 standard base64 (e.g. `Buffer.from(bytes).toString("base64")`).
|
|
193
193
|
*/
|
|
194
194
|
uploadBlobInline(contentBase64: string, opts?: UploadBlobOptions): Promise<AttachmentRef>;
|
|
195
|
+
/**
|
|
196
|
+
* POST /v1/attachments/fetch — server-side URL ingestion. The RELAY downloads
|
|
197
|
+
* the bytes from `sourceUrl` itself (SSRF-guarded: https only, no private /
|
|
198
|
+
* loopback / link-local / metadata hosts, DNS resolved-and-pinned against
|
|
199
|
+
* rebinding, redirects refused, body size-capped, request timed out), then
|
|
200
|
+
* runs the IDENTICAL validation pipeline every upload runs (magic-byte MIME
|
|
201
|
+
* sniff, allowlist, per-type size cap, sha256, scan hook, per-agent / per-app /
|
|
202
|
+
* per-account quota) before returning the ready `AttachmentRef`.
|
|
203
|
+
*
|
|
204
|
+
* The caller sends only the URL string, so the bytes never enter the model
|
|
205
|
+
* context and cost NO tokens — the highest-leverage zero-context path for an
|
|
206
|
+
* image/media source that already has a URL. `mime` is advisory (the relay
|
|
207
|
+
* sniffs the real type). Works on any storage backend.
|
|
208
|
+
*/
|
|
209
|
+
fetchBlob(sourceUrl: string, opts?: {
|
|
210
|
+
scope?: "agent" | "app";
|
|
211
|
+
appId?: string;
|
|
212
|
+
mime?: string;
|
|
213
|
+
}): Promise<AttachmentRef>;
|
|
195
214
|
/** GET /v1/attachments/:id — download bytes as an ArrayBuffer. */
|
|
196
215
|
downloadBlob(attachmentId: string): Promise<ArrayBuffer>;
|
|
197
216
|
/**
|
|
@@ -295,6 +314,14 @@ export declare class HomespunClient {
|
|
|
295
314
|
}): Promise<AppsPage>;
|
|
296
315
|
/** GET /v1/apps/:id — full app detail (manifest, current_version, quota). */
|
|
297
316
|
getApp(appId: string): Promise<AppDetail>;
|
|
317
|
+
/**
|
|
318
|
+
* GET /v1/apps/:id/document: the app's AUTHORED HTML source (its current
|
|
319
|
+
* version), before the served page's injected prelude (SDK script, favicon,
|
|
320
|
+
* OG meta, trial banner). Use this to recover the exact HTML you deployed
|
|
321
|
+
* instead of scraping the public page and stripping the prelude. 404 if the
|
|
322
|
+
* app has no deployed version.
|
|
323
|
+
*/
|
|
324
|
+
getAppDocument(appId: string): Promise<AppDocument>;
|
|
298
325
|
/**
|
|
299
326
|
* PATCH /v1/apps/:id updates the app's mutable settings. Both fields are
|
|
300
327
|
* optional but at least one must be given: `visibility` (private|link|public)
|
|
@@ -465,6 +492,28 @@ export declare class HomespunClient {
|
|
|
465
492
|
clearIngestSigningSecret(appId: string, name: string): Promise<{
|
|
466
493
|
ok: boolean;
|
|
467
494
|
}>;
|
|
495
|
+
/**
|
|
496
|
+
* POST /v1/apps/:id/ingest-hooks/:name/backfill bulk-loads an array of raw
|
|
497
|
+
* provider bodies through a hook's CURRENT manifest mapping, so a backfilled
|
|
498
|
+
* row is byte-identical to a live delivery (issue #966). Each body is an
|
|
499
|
+
* already-parsed JSON value (whatever the provider would have POSTed). It runs
|
|
500
|
+
* the SAME receive pipeline as the public URL, so map/dedupeKey/upsertOn/schema
|
|
501
|
+
* validation and the collection quota all apply, but it SKIPS the public-URL
|
|
502
|
+
* brakes (per-IP rate limit, per-app hourly cap) and never verifies a signature
|
|
503
|
+
* (owner/agent-authed). Wake is suppressed so a historical load never wakes a
|
|
504
|
+
* dormant app. Dedupe is ON: re-running an interrupted backfill is idempotent
|
|
505
|
+
* for a body-path dedupeKey (a header:<name> dedupeKey cannot resolve with no
|
|
506
|
+
* request headers, so it does not dedupe). Chunk arrays over the relay's
|
|
507
|
+
* INGEST_BACKFILL_MAX_BODIES (default 500), which return 400. Returns aggregate
|
|
508
|
+
* { accepted, dropped_duplicate, failed } counts plus a per-body `outcomes`
|
|
509
|
+
* array in input order.
|
|
510
|
+
*/
|
|
511
|
+
backfillIngestHook(appId: string, name: string, bodies: unknown[]): Promise<{
|
|
512
|
+
accepted: number;
|
|
513
|
+
dropped_duplicate: number;
|
|
514
|
+
failed: number;
|
|
515
|
+
outcomes: string[];
|
|
516
|
+
}>;
|
|
468
517
|
/**
|
|
469
518
|
* POST /v1/apps/:id/grants: mint a grant link. `role` must be a declared
|
|
470
519
|
* custom role for the app. `mode` is "once" (one-time, first-browser-claims)
|
|
@@ -598,19 +647,19 @@ export declare class HomespunClient {
|
|
|
598
647
|
note?: string;
|
|
599
648
|
}): Promise<CommunitySubmissionDetail>;
|
|
600
649
|
/**
|
|
601
|
-
* GET
|
|
602
|
-
*
|
|
603
|
-
*
|
|
604
|
-
*
|
|
650
|
+
* GET the install-time config contract for an installable template (its
|
|
651
|
+
* settings collection + the ordered keyed config/upload steps). Use it to
|
|
652
|
+
* discover what a template needs and to pre-upload files for `upload` steps
|
|
653
|
+
* (POST /v1/attachments) before install.
|
|
605
654
|
*/
|
|
606
655
|
getCommunityConfigContract(ref: string): Promise<CommunityConfigContract>;
|
|
607
656
|
/**
|
|
608
|
-
* POST
|
|
609
|
-
*
|
|
610
|
-
* `
|
|
611
|
-
*
|
|
612
|
-
*
|
|
613
|
-
*
|
|
657
|
+
* POST to install a community template for the calling agent's owning human.
|
|
658
|
+
* `config` is `{ [stepKey]: value }` where a `config` step's value is a string
|
|
659
|
+
* and an `upload` step's value is an attachment id the agent pre-uploaded
|
|
660
|
+
* (agent-scoped) via `uploadBlob()` / `uploadBlobInline()`; the relay
|
|
661
|
+
* re-points it to the new app on install. Installs always create; returns the
|
|
662
|
+
* new app's id, slug, and url.
|
|
614
663
|
*/
|
|
615
664
|
installCommunityTemplate(ref: string, config?: Record<string, unknown>): Promise<CommunityInstallResult>;
|
|
616
665
|
/** GET /v1/publisher: the caller's own publisher profile. */
|
|
@@ -641,16 +690,29 @@ export declare class HomespunClient {
|
|
|
641
690
|
unholdReview(reviewId: string): Promise<CommunityReview>;
|
|
642
691
|
}
|
|
643
692
|
/**
|
|
644
|
-
* One multi-file-deploy asset
|
|
645
|
-
* app-relative, same-origin path (e.g.
|
|
646
|
-
* standard base64 of the raw bytes;
|
|
647
|
-
* real type from the leading bytes).
|
|
693
|
+
* One multi-file-deploy asset carrying its bytes inline as base64. The deployed
|
|
694
|
+
* page references it by a stable, app-relative, same-origin path (e.g.
|
|
695
|
+
* `frames/000.jpg`). `content_base64` is the standard base64 of the raw bytes;
|
|
696
|
+
* `mime` is advisory (the relay sniffs the real type from the leading bytes).
|
|
648
697
|
*/
|
|
649
|
-
export interface
|
|
698
|
+
export interface AppAssetInline {
|
|
650
699
|
path: string;
|
|
651
700
|
content_base64: string;
|
|
652
701
|
mime?: string;
|
|
653
702
|
}
|
|
703
|
+
/**
|
|
704
|
+
* One multi-file-deploy asset referencing an ALREADY-uploaded attachment (via
|
|
705
|
+
* `attachments fetch` or presign + finalize) instead of carrying its bytes. The
|
|
706
|
+
* referenced attachment must be owned by the deploying agent, app-scoped to THIS
|
|
707
|
+
* app, and `ready`. Lets an image reach the app with zero base64 in the deploy
|
|
708
|
+
* body (and, with fetch, zero bytes in the model context at all).
|
|
709
|
+
*/
|
|
710
|
+
export interface AppAssetRef {
|
|
711
|
+
path: string;
|
|
712
|
+
attachment_id: string;
|
|
713
|
+
}
|
|
714
|
+
/** A multi-file-deploy asset: inline bytes OR a by-reference attachment id. */
|
|
715
|
+
export type AppAsset = AppAssetInline | AppAssetRef;
|
|
654
716
|
/** Request body for `POST /v1/apps` — create (deploy) a new App. */
|
|
655
717
|
export interface DeployAppRequest {
|
|
656
718
|
html: string;
|
|
@@ -804,6 +866,15 @@ export interface AppsPage {
|
|
|
804
866
|
items: AppSummary[];
|
|
805
867
|
next_cursor: string | null;
|
|
806
868
|
}
|
|
869
|
+
/** `GET /v1/apps/:id/document`: an app's authored HTML source (current version). */
|
|
870
|
+
export interface AppDocument {
|
|
871
|
+
/** The authored HTML, exactly as deployed (no served-page prelude injected). */
|
|
872
|
+
html: string;
|
|
873
|
+
/** The current version number these bytes belong to. */
|
|
874
|
+
version: number;
|
|
875
|
+
/** Content hash of `html` (the app's `sourceHash`). */
|
|
876
|
+
source_hash: string;
|
|
877
|
+
}
|
|
807
878
|
/** One DNS record the domain owner must publish (custom domains). */
|
|
808
879
|
export interface AppDomainDnsRecord {
|
|
809
880
|
type: string;
|
|
@@ -1001,6 +1072,14 @@ export interface ListSortSpec {
|
|
|
1001
1072
|
field: string;
|
|
1002
1073
|
dir?: "asc" | "desc";
|
|
1003
1074
|
}
|
|
1075
|
+
/**
|
|
1076
|
+
* Path for a community-template ref action. A namespaced `<handle>/<slug>` ref
|
|
1077
|
+
* becomes a TWO-segment path (`/templates/<handle>/<slug>/<suffix>`) so it
|
|
1078
|
+
* survives a proxy/edge that normalizes a percent-encoded slash to `/` before
|
|
1079
|
+
* routing (#921); a snapshot id (no slash) stays single-segment. Mirrors the
|
|
1080
|
+
* server's `parseNamespacedId`: exactly two non-empty slash-separated parts.
|
|
1081
|
+
*/
|
|
1082
|
+
export declare function communityTemplatePath(ref: string, suffix: string): string;
|
|
1004
1083
|
/** One row of a batch write (Wave B). `key` absent => create; present => upsert. */
|
|
1005
1084
|
export interface BatchRowInput {
|
|
1006
1085
|
key?: string;
|
package/dist/client.js
CHANGED
|
@@ -357,6 +357,33 @@ export class HomespunClient {
|
|
|
357
357
|
this.fail(r);
|
|
358
358
|
return this.asObject(r);
|
|
359
359
|
}
|
|
360
|
+
/**
|
|
361
|
+
* POST /v1/attachments/fetch — server-side URL ingestion. The RELAY downloads
|
|
362
|
+
* the bytes from `sourceUrl` itself (SSRF-guarded: https only, no private /
|
|
363
|
+
* loopback / link-local / metadata hosts, DNS resolved-and-pinned against
|
|
364
|
+
* rebinding, redirects refused, body size-capped, request timed out), then
|
|
365
|
+
* runs the IDENTICAL validation pipeline every upload runs (magic-byte MIME
|
|
366
|
+
* sniff, allowlist, per-type size cap, sha256, scan hook, per-agent / per-app /
|
|
367
|
+
* per-account quota) before returning the ready `AttachmentRef`.
|
|
368
|
+
*
|
|
369
|
+
* The caller sends only the URL string, so the bytes never enter the model
|
|
370
|
+
* context and cost NO tokens — the highest-leverage zero-context path for an
|
|
371
|
+
* image/media source that already has a URL. `mime` is advisory (the relay
|
|
372
|
+
* sniffs the real type). Works on any storage backend.
|
|
373
|
+
*/
|
|
374
|
+
async fetchBlob(sourceUrl, opts = {}) {
|
|
375
|
+
const body = { source_url: sourceUrl };
|
|
376
|
+
if (opts.scope)
|
|
377
|
+
body.scope = opts.scope;
|
|
378
|
+
if (opts.appId)
|
|
379
|
+
body.app_id = opts.appId;
|
|
380
|
+
if (opts.mime)
|
|
381
|
+
body.mime = opts.mime;
|
|
382
|
+
const r = await this.call("POST", "/v1/attachments/fetch", body);
|
|
383
|
+
if (!r.ok)
|
|
384
|
+
this.fail(r);
|
|
385
|
+
return this.asObject(r);
|
|
386
|
+
}
|
|
360
387
|
/** GET /v1/attachments/:id — download bytes as an ArrayBuffer. */
|
|
361
388
|
async downloadBlob(attachmentId) {
|
|
362
389
|
const url = this.base + "/v1/attachments/" + encodeURIComponent(attachmentId);
|
|
@@ -584,6 +611,19 @@ export class HomespunClient {
|
|
|
584
611
|
this.fail(r);
|
|
585
612
|
return this.asObject(r);
|
|
586
613
|
}
|
|
614
|
+
/**
|
|
615
|
+
* GET /v1/apps/:id/document: the app's AUTHORED HTML source (its current
|
|
616
|
+
* version), before the served page's injected prelude (SDK script, favicon,
|
|
617
|
+
* OG meta, trial banner). Use this to recover the exact HTML you deployed
|
|
618
|
+
* instead of scraping the public page and stripping the prelude. 404 if the
|
|
619
|
+
* app has no deployed version.
|
|
620
|
+
*/
|
|
621
|
+
async getAppDocument(appId) {
|
|
622
|
+
const r = await this.call("GET", `/v1/apps/${encodeURIComponent(appId)}/document`);
|
|
623
|
+
if (!r.ok)
|
|
624
|
+
this.fail(r);
|
|
625
|
+
return this.asObject(r);
|
|
626
|
+
}
|
|
587
627
|
/**
|
|
588
628
|
* PATCH /v1/apps/:id updates the app's mutable settings. Both fields are
|
|
589
629
|
* optional but at least one must be given: `visibility` (private|link|public)
|
|
@@ -841,6 +881,28 @@ export class HomespunClient {
|
|
|
841
881
|
this.fail(r);
|
|
842
882
|
return this.asObject(r);
|
|
843
883
|
}
|
|
884
|
+
/**
|
|
885
|
+
* POST /v1/apps/:id/ingest-hooks/:name/backfill bulk-loads an array of raw
|
|
886
|
+
* provider bodies through a hook's CURRENT manifest mapping, so a backfilled
|
|
887
|
+
* row is byte-identical to a live delivery (issue #966). Each body is an
|
|
888
|
+
* already-parsed JSON value (whatever the provider would have POSTed). It runs
|
|
889
|
+
* the SAME receive pipeline as the public URL, so map/dedupeKey/upsertOn/schema
|
|
890
|
+
* validation and the collection quota all apply, but it SKIPS the public-URL
|
|
891
|
+
* brakes (per-IP rate limit, per-app hourly cap) and never verifies a signature
|
|
892
|
+
* (owner/agent-authed). Wake is suppressed so a historical load never wakes a
|
|
893
|
+
* dormant app. Dedupe is ON: re-running an interrupted backfill is idempotent
|
|
894
|
+
* for a body-path dedupeKey (a header:<name> dedupeKey cannot resolve with no
|
|
895
|
+
* request headers, so it does not dedupe). Chunk arrays over the relay's
|
|
896
|
+
* INGEST_BACKFILL_MAX_BODIES (default 500), which return 400. Returns aggregate
|
|
897
|
+
* { accepted, dropped_duplicate, failed } counts plus a per-body `outcomes`
|
|
898
|
+
* array in input order.
|
|
899
|
+
*/
|
|
900
|
+
async backfillIngestHook(appId, name, bodies) {
|
|
901
|
+
const r = await this.call("POST", `/v1/apps/${encodeURIComponent(appId)}/ingest-hooks/${encodeURIComponent(name)}/backfill`, { bodies });
|
|
902
|
+
if (!r.ok)
|
|
903
|
+
this.fail(r);
|
|
904
|
+
return this.asObject(r);
|
|
905
|
+
}
|
|
844
906
|
// -------------------------------------------------------------------------
|
|
845
907
|
// v2 grant links (M5). An owner/agent mints a capability URL carrying a
|
|
846
908
|
// DECLARED custom role (x-homespun-manifest.roles) plus an optional row/filter
|
|
@@ -1083,30 +1145,31 @@ export class HomespunClient {
|
|
|
1083
1145
|
// Agent-key community install (install-config programme, PR 4). Both act AS
|
|
1084
1146
|
// the calling agent's owning human, who becomes the installed app's owner. A
|
|
1085
1147
|
// `ref` is a namespaced `<handle>/<slug>` OR a community snapshot id;
|
|
1086
|
-
//
|
|
1148
|
+
// `communityTemplatePath` sends a namespaced ref as two path segments so it
|
|
1149
|
+
// survives an edge that decodes `%2F` to `/` before routing (#921).
|
|
1087
1150
|
// -------------------------------------------------------------------------
|
|
1088
1151
|
/**
|
|
1089
|
-
* GET
|
|
1090
|
-
*
|
|
1091
|
-
*
|
|
1092
|
-
*
|
|
1152
|
+
* GET the install-time config contract for an installable template (its
|
|
1153
|
+
* settings collection + the ordered keyed config/upload steps). Use it to
|
|
1154
|
+
* discover what a template needs and to pre-upload files for `upload` steps
|
|
1155
|
+
* (POST /v1/attachments) before install.
|
|
1093
1156
|
*/
|
|
1094
1157
|
async getCommunityConfigContract(ref) {
|
|
1095
|
-
const r = await this.call("GET",
|
|
1158
|
+
const r = await this.call("GET", communityTemplatePath(ref, "config-contract"));
|
|
1096
1159
|
if (!r.ok)
|
|
1097
1160
|
this.fail(r);
|
|
1098
1161
|
return this.asObject(r);
|
|
1099
1162
|
}
|
|
1100
1163
|
/**
|
|
1101
|
-
* POST
|
|
1102
|
-
*
|
|
1103
|
-
* `
|
|
1104
|
-
*
|
|
1105
|
-
*
|
|
1106
|
-
*
|
|
1164
|
+
* POST to install a community template for the calling agent's owning human.
|
|
1165
|
+
* `config` is `{ [stepKey]: value }` where a `config` step's value is a string
|
|
1166
|
+
* and an `upload` step's value is an attachment id the agent pre-uploaded
|
|
1167
|
+
* (agent-scoped) via `uploadBlob()` / `uploadBlobInline()`; the relay
|
|
1168
|
+
* re-points it to the new app on install. Installs always create; returns the
|
|
1169
|
+
* new app's id, slug, and url.
|
|
1107
1170
|
*/
|
|
1108
1171
|
async installCommunityTemplate(ref, config) {
|
|
1109
|
-
const r = await this.call("POST",
|
|
1172
|
+
const r = await this.call("POST", communityTemplatePath(ref, "install"), config !== undefined ? { config } : {});
|
|
1110
1173
|
if (!r.ok)
|
|
1111
1174
|
this.fail(r);
|
|
1112
1175
|
return this.asObject(r);
|
|
@@ -1203,6 +1266,20 @@ export class HomespunClient {
|
|
|
1203
1266
|
return this.asObject(r);
|
|
1204
1267
|
}
|
|
1205
1268
|
}
|
|
1269
|
+
/**
|
|
1270
|
+
* Path for a community-template ref action. A namespaced `<handle>/<slug>` ref
|
|
1271
|
+
* becomes a TWO-segment path (`/templates/<handle>/<slug>/<suffix>`) so it
|
|
1272
|
+
* survives a proxy/edge that normalizes a percent-encoded slash to `/` before
|
|
1273
|
+
* routing (#921); a snapshot id (no slash) stays single-segment. Mirrors the
|
|
1274
|
+
* server's `parseNamespacedId`: exactly two non-empty slash-separated parts.
|
|
1275
|
+
*/
|
|
1276
|
+
export function communityTemplatePath(ref, suffix) {
|
|
1277
|
+
const parts = ref.split("/");
|
|
1278
|
+
if (parts.length === 2 && parts[0] && parts[1]) {
|
|
1279
|
+
return `/v1/community/templates/${encodeURIComponent(parts[0])}/${encodeURIComponent(parts[1])}/${suffix}`;
|
|
1280
|
+
}
|
|
1281
|
+
return `/v1/community/templates/${encodeURIComponent(ref)}/${suffix}`;
|
|
1282
|
+
}
|
|
1206
1283
|
// Serialize `where`/`sort` into the `q` query param (URL-encoded JSON), or
|
|
1207
1284
|
// undefined when neither is given. URLSearchParams handles the percent-encoding.
|
|
1208
1285
|
function buildListQueryParam(where, sort) {
|