@myapihq/sdk 2.9.0 → 2.11.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/dist/client.d.ts CHANGED
@@ -34,6 +34,7 @@ export interface RequestOptions {
34
34
  */
35
35
  onReplay?: () => void;
36
36
  }
37
+ export declare function toError(err: any, status: number): MyApiError;
37
38
  export declare function request<T>(method: string, url: string, apiKey?: string, body?: unknown, extraHeaders?: Record<string, string>, opts?: RequestOptions): Promise<T>;
38
39
  export interface Page<T> {
39
40
  data: T[];
package/dist/client.js CHANGED
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MyApiError = void 0;
4
4
  exports.setUserAgent = setUserAgent;
5
+ exports.toError = toError;
5
6
  exports.request = request;
6
7
  exports.requestPage = requestPage;
7
8
  const version_1 = require("./version");
@@ -267,11 +268,31 @@ async function parseResponse(method, url, response) {
267
268
  }
268
269
  return apiResponse;
269
270
  }
271
+ // Build a MyApiError from a backend error object.
272
+ //
273
+ // The backend sends BOTH `message` and `detail`, and they carry different
274
+ // weight: `message` is the short generic line, `detail` is the specific
275
+ // reason. This used to read `message` only, so the specific reason was parsed,
276
+ // stored on `body`, and never shown. A customer lost a diagnosis to exactly
277
+ // that — outbound email answered
278
+ //
279
+ // { code: "SEND_FAILED", message: "relay failed",
280
+ // detail: "this account is not provisioned for outbound email" }
281
+ //
282
+ // and the CLI printed "relay failed", which sends you to DNS, SPF and the
283
+ // mailbox. The six words that named an account-level state they could not fix
284
+ // themselves were in the response the whole time.
285
+ //
286
+ // So: prefer `detail`, fall back to `message`. `body` still carries the full
287
+ // object for callers that want both. Exported because three call sites
288
+ // (here, function.uploadBundle, storage.uploadAsset) each hand-rolled this
289
+ // and each had the same bug.
270
290
  function toError(err, status) {
271
- const code = typeof err === 'object' && err !== null ? (err?.code || 'unknown_error') : (err || 'unknown_error');
272
- const detail = typeof err === 'object' && err !== null ? (err?.message || undefined) : undefined;
273
- const errBody = typeof err === 'object' && err !== null ? err : undefined;
274
- return new MyApiError(code, status, detail, errBody);
291
+ const obj = typeof err === 'object' && err !== null ? err : undefined;
292
+ const code = obj ? (obj.code || 'unknown_error') : (err || 'unknown_error');
293
+ const pick = (v) => (typeof v === 'string' && v.trim() !== '' ? v : undefined);
294
+ const detail = obj ? (pick(obj.detail) ?? pick(obj.message)) : undefined;
295
+ return new MyApiError(code, status, detail, obj);
275
296
  }
276
297
  // Returns the unwrapped `data` payload — the common case.
277
298
  async function request(method, url, apiKey, body, extraHeaders, opts) {
package/dist/container.js CHANGED
@@ -78,6 +78,11 @@ async function deployContainerSource(apiKey, orgId, containerId, tarball, filena
78
78
  // dropped here: the options never reached this function at all.
79
79
  if (opts.promote === false)
80
80
  formData.append('promote', 'false');
81
+ // Multipart takes the assertion as a JSON string. Wired here as well as on
82
+ // the JSON path, because the outage this exists to prevent was a source
83
+ // build and the first version of this flag reached only the image path.
84
+ if (opts.smoke)
85
+ formData.append('smoke', JSON.stringify(opts.smoke));
81
86
  const response = await fetch(`${config_1.CONTAINER_BASE}/container/orgs/${encodeURIComponent(orgId)}/containers/${encodeURIComponent(containerId)}/deploy`, { method: 'POST', headers: { Authorization: `Bearer ${apiKey}` }, body: formData });
82
87
  let result;
83
88
  try {
package/dist/function.js CHANGED
@@ -50,10 +50,9 @@ async function uploadBundle(apiKey, orgId, fnId, bundle, filename = 'bundle.js')
50
50
  throw new client_1.MyApiError('invalid_json_response', response.status);
51
51
  }
52
52
  if (!response.ok || !result?.success) {
53
- const err = result?.error;
54
- const code = typeof err === 'object' ? (err?.code || 'unknown_error') : (err || 'unknown_error');
55
- const detail = typeof err === 'object' ? err?.message : undefined;
56
- throw new client_1.MyApiError(code, response.status, detail, typeof err === 'object' ? err : undefined);
53
+ // Shared with client.ts so this path cannot drift back to reading
54
+ // `message` and dropping the specific `detail` see toError.
55
+ throw (0, client_1.toError)(result?.error, response.status);
57
56
  }
58
57
  return result.data;
59
58
  }
package/dist/services.js CHANGED
@@ -118,6 +118,22 @@ exports.SERVICES = [
118
118
  status: 'preview',
119
119
  keywords: k('pixel', 'analytics', 'tracking', 'visit', 'identity'),
120
120
  },
121
+ {
122
+ // CLI top-level command + SDK namespace are both `feedback`; skill
123
+ // directory is `my-feedback-api`. Inbound like webhook/pixel, but the
124
+ // sender is a person rather than a system.
125
+ //
126
+ // Not ga: classification and duplicate-grouping are designed and not
127
+ // built, so `kind` is the reporter's own label rather than a processed
128
+ // signal. The CLI surface itself is stable.
129
+ module: 'feedback',
130
+ skill: 'my-feedback-api',
131
+ domain: 'myfeedbackapi.com',
132
+ description: 'Collect feedback from the people using what you built. A public widget key lets a page submit without a credential; you list, filter, and resolve the results.',
133
+ category: 'capture',
134
+ status: 'preview',
135
+ keywords: k('feedback', 'bug-report', 'feature-request', 'widget', 'support'),
136
+ },
121
137
  // ── data ────────────────────────────────────────────────────────────
122
138
  {
123
139
  // Goldfox-backed people search is gated pre-launch — 503 SERVICE_NOT_LAUNCHED.
package/dist/storage.d.ts CHANGED
@@ -1,14 +1,38 @@
1
1
  import type { Exposes } from './exposes';
2
2
  export declare const EXPOSES: Exposes;
3
+ /** `public` is the backend default and means readable by anyone holding the
4
+ * URL, forever, with no credential. */
5
+ export type Visibility = 'public' | 'private';
3
6
  export interface Asset {
4
7
  asset_id: string;
5
- url: string;
8
+ url?: string;
6
9
  name: string;
7
10
  created_at: string;
11
+ visibility?: Visibility;
8
12
  }
9
13
  export declare function ingestAsset(apiKey: string, orgId: string, url: string, name?: string): Promise<Asset>;
10
14
  export type UploadContentType = 'image/jpeg' | 'image/png' | 'image/gif' | 'image/webp' | 'image/svg+xml' | 'application/pdf' | 'video/mp4' | 'video/webm';
11
15
  export type UploadContentTypeInput = UploadContentType | (string & {});
12
- export declare function uploadAsset(apiKey: string, orgId: string, file: Blob | Buffer, contentType: UploadContentTypeInput, name?: string): Promise<Asset>;
16
+ export declare function uploadAsset(apiKey: string, orgId: string, file: Blob | Buffer, contentType: UploadContentTypeInput, name?: string, visibility?: Visibility): Promise<Asset>;
17
+ /** Flip an existing asset between public and private. Making it private takes
18
+ * effect at once — the plain URL stops serving and only signed links work. */
19
+ export declare function setAssetVisibility(apiKey: string, orgId: string, assetId: string, visibility: Visibility): Promise<Asset>;
20
+ /** A time-limited link to a private asset. The link carries its own
21
+ * authorisation, so it can go to someone with no MyAPI account.
22
+ * `expiresIn` is seconds — backend default 900, maximum 86400.
23
+ * Signing is billable; a 402 here means the wallet, not the asset. */
24
+ export declare function createSignedUrl(apiKey: string, orgId: string, assetId: string, expiresIn?: number): Promise<SignedUrl>;
25
+ /** Kill every signed link for an asset, including ones that had not expired.
26
+ * Does nothing for a public asset — anyone with the plain URL still reads it. */
27
+ export declare function revokeAssetLinks(apiKey: string, orgId: string, assetId: string): Promise<void>;
28
+ export interface SignedUrl {
29
+ signed_url?: string;
30
+ url?: string;
31
+ expires_at?: string;
32
+ expires_in?: number;
33
+ [key: string]: unknown;
34
+ }
35
+ /** The URL out of a signed-url response, whichever field carries it. */
36
+ export declare function signedUrlOf(res: SignedUrl): string | undefined;
13
37
  export declare function listAssets(apiKey: string, orgId: string): Promise<Asset[]>;
14
38
  export declare function deleteAsset(apiKey: string, orgId: string, assetId: string): Promise<void>;
package/dist/storage.js CHANGED
@@ -3,6 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.EXPOSES = void 0;
4
4
  exports.ingestAsset = ingestAsset;
5
5
  exports.uploadAsset = uploadAsset;
6
+ exports.setAssetVisibility = setAssetVisibility;
7
+ exports.createSignedUrl = createSignedUrl;
8
+ exports.revokeAssetLinks = revokeAssetLinks;
9
+ exports.signedUrlOf = signedUrlOf;
6
10
  exports.listAssets = listAssets;
7
11
  exports.deleteAsset = deleteAsset;
8
12
  const client_1 = require("./client");
@@ -12,11 +16,14 @@ exports.EXPOSES = [
12
16
  'POST /storage/orgs/{org_id}/assets/upload',
13
17
  'GET /storage/orgs/{org_id}/assets',
14
18
  'DELETE /storage/orgs/{org_id}/assets/{asset_id}',
19
+ 'PATCH /storage/orgs/{org_id}/assets/{asset_id}',
20
+ 'POST /storage/orgs/{org_id}/assets/{asset_id}/signed-url',
21
+ 'POST /storage/orgs/{org_id}/assets/{asset_id}/revoke-links',
15
22
  ];
16
23
  async function ingestAsset(apiKey, orgId, url, name) {
17
24
  return (0, client_1.request)('POST', `${config_1.STORAGE_BASE}/storage/orgs/${encodeURIComponent(orgId)}/assets/ingest`, apiKey, { url, name });
18
25
  }
19
- async function uploadAsset(apiKey, orgId, file, contentType, name) {
26
+ async function uploadAsset(apiKey, orgId, file, contentType, name, visibility) {
20
27
  const headers = {
21
28
  'Authorization': `Bearer ${apiKey}`
22
29
  };
@@ -25,6 +32,11 @@ async function uploadAsset(apiKey, orgId, file, contentType, name) {
25
32
  if (name) {
26
33
  formData.append('name', name);
27
34
  }
35
+ // Omitted entirely when not asked for, so the backend's own default applies
36
+ // rather than this client pinning one it would then have to track.
37
+ if (visibility) {
38
+ formData.append('visibility', visibility);
39
+ }
28
40
  const response = await fetch(`${config_1.STORAGE_BASE}/storage/orgs/${encodeURIComponent(orgId)}/assets/upload`, {
29
41
  method: 'POST',
30
42
  headers,
@@ -37,19 +49,47 @@ async function uploadAsset(apiKey, orgId, file, contentType, name) {
37
49
  catch {
38
50
  throw new client_1.MyApiError('invalid_json_response', response.status);
39
51
  }
40
- // Mirror client.ts error handling: the backend's modern error shape is an
41
- // object {code, message, ...}. The old string-only handling here made
42
- // err.code an object, so e.g. funds.isInsufficientFunds could never match
43
- // a 402 on a billable upload.
52
+ // Shared with client.ts so this path cannot drift back to reading `message`
53
+ // and dropping the specific `detail` see toError. (It previously also
54
+ // made err.code an object, so funds.isInsufficientFunds never matched a 402
55
+ // on a billable upload; the shared helper fixes both.)
44
56
  if (!response.ok || !result.success) {
45
- const err = result?.error;
46
- const code = typeof err === 'object' && err !== null ? (err?.code || 'unknown_error') : (err || 'unknown_error');
47
- const detail = typeof err === 'object' && err !== null ? (err?.message || undefined) : undefined;
48
- const errBody = typeof err === 'object' && err !== null ? err : undefined;
49
- throw new client_1.MyApiError(code, response.status, detail, errBody);
57
+ throw (0, client_1.toError)(result?.error, response.status);
50
58
  }
51
59
  return result.data;
52
60
  }
61
+ // ---------------------------------------------------------------------------
62
+ // Private assets
63
+ // ---------------------------------------------------------------------------
64
+ // These three endpoints shipped on the backend on 2026-07-28 and had no client
65
+ // surface until now. The cost of that gap is on record: a team storing signed
66
+ // Swiss leases built AES-256-GCM envelope encryption instead, and ended up
67
+ // holding a master key with no backup path — `fn env` encrypts it at rest and
68
+ // never returns it, so losing it destroys every document. They asked for
69
+ // exactly `storage upload --private` and `storage sign <id> --ttl 300`, which
70
+ // had existed for a week behind an undocumented HTTP route.
71
+ /** Flip an existing asset between public and private. Making it private takes
72
+ * effect at once — the plain URL stops serving and only signed links work. */
73
+ async function setAssetVisibility(apiKey, orgId, assetId, visibility) {
74
+ return (0, client_1.request)('PATCH', `${config_1.STORAGE_BASE}/storage/orgs/${encodeURIComponent(orgId)}/assets/${encodeURIComponent(assetId)}`, apiKey, { visibility });
75
+ }
76
+ /** A time-limited link to a private asset. The link carries its own
77
+ * authorisation, so it can go to someone with no MyAPI account.
78
+ * `expiresIn` is seconds — backend default 900, maximum 86400.
79
+ * Signing is billable; a 402 here means the wallet, not the asset. */
80
+ async function createSignedUrl(apiKey, orgId, assetId, expiresIn) {
81
+ const body = expiresIn === undefined ? {} : { expires_in: expiresIn };
82
+ return (0, client_1.request)('POST', `${config_1.STORAGE_BASE}/storage/orgs/${encodeURIComponent(orgId)}/assets/${encodeURIComponent(assetId)}/signed-url`, apiKey, body);
83
+ }
84
+ /** Kill every signed link for an asset, including ones that had not expired.
85
+ * Does nothing for a public asset — anyone with the plain URL still reads it. */
86
+ async function revokeAssetLinks(apiKey, orgId, assetId) {
87
+ return (0, client_1.request)('POST', `${config_1.STORAGE_BASE}/storage/orgs/${encodeURIComponent(orgId)}/assets/${encodeURIComponent(assetId)}/revoke-links`, apiKey, {});
88
+ }
89
+ /** The URL out of a signed-url response, whichever field carries it. */
90
+ function signedUrlOf(res) {
91
+ return res.signed_url ?? res.url;
92
+ }
53
93
  async function listAssets(apiKey, orgId) {
54
94
  return (0, client_1.request)('GET', `${config_1.STORAGE_BASE}/storage/orgs/${encodeURIComponent(orgId)}/assets`, apiKey);
55
95
  }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const SDK_VERSION = "2.9.0";
1
+ export declare const SDK_VERSION = "2.11.0";
package/dist/version.js CHANGED
@@ -8,4 +8,4 @@ exports.SDK_VERSION = void 0;
8
8
  // Why a constant and not a package.json read: the SDK runs inside edge
9
9
  // functions (Cloudflare Workers), so it must not import node:fs. A literal
10
10
  // is the only version source that works in every runtime we ship to.
11
- exports.SDK_VERSION = '2.9.0';
11
+ exports.SDK_VERSION = '2.11.0';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@myapihq/sdk",
3
3
  "license": "Apache-2.0",
4
- "version": "2.9.0",
4
+ "version": "2.11.0",
5
5
  "description": "TypeScript SDK for the MyAPI ecosystem",
6
6
  "repository": {
7
7
  "type": "git",