@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 +1 -0
- package/dist/client.js +25 -4
- package/dist/container.js +5 -0
- package/dist/function.js +3 -4
- package/dist/services.js +16 -0
- package/dist/storage.d.ts +26 -2
- package/dist/storage.js +50 -10
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
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
|
|
272
|
-
const
|
|
273
|
-
const
|
|
274
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
|
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
|
-
//
|
|
41
|
-
//
|
|
42
|
-
// err.code an object, so
|
|
43
|
-
//
|
|
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
|
-
|
|
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.
|
|
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.
|
|
11
|
+
exports.SDK_VERSION = '2.11.0';
|