@w6w/sdk 0.1.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/LICENSE +21 -0
- package/README.md +131 -0
- package/dist/mod.d.ts +37 -0
- package/dist/mod.d.ts.map +1 -0
- package/dist/mod.js +51 -0
- package/dist/mod.js.map +1 -0
- package/dist/src/client.d.ts +134 -0
- package/dist/src/client.d.ts.map +1 -0
- package/dist/src/client.js +165 -0
- package/dist/src/client.js.map +1 -0
- package/dist/src/config.d.ts +151 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +161 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/connections.d.ts +58 -0
- package/dist/src/connections.d.ts.map +1 -0
- package/dist/src/connections.js +52 -0
- package/dist/src/connections.js.map +1 -0
- package/dist/src/documents.d.ts +168 -0
- package/dist/src/documents.d.ts.map +1 -0
- package/dist/src/documents.js +178 -0
- package/dist/src/documents.js.map +1 -0
- package/dist/src/env.d.ts +61 -0
- package/dist/src/env.d.ts.map +1 -0
- package/dist/src/env.js +73 -0
- package/dist/src/env.js.map +1 -0
- package/dist/src/errors.d.ts +79 -0
- package/dist/src/errors.d.ts.map +1 -0
- package/dist/src/errors.js +88 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/http.d.ts +130 -0
- package/dist/src/http.d.ts.map +1 -0
- package/dist/src/http.js +150 -0
- package/dist/src/http.js.map +1 -0
- package/dist/src/me.d.ts +53 -0
- package/dist/src/me.d.ts.map +1 -0
- package/dist/src/me.js +77 -0
- package/dist/src/me.js.map +1 -0
- package/dist/src/run.d.ts +92 -0
- package/dist/src/run.d.ts.map +1 -0
- package/dist/src/run.js +83 -0
- package/dist/src/run.js.map +1 -0
- package/dist/src/types.d.ts +362 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +109 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/vars.d.ts +149 -0
- package/dist/src/vars.d.ts.map +1 -0
- package/dist/src/vars.js +149 -0
- package/dist/src/vars.js.map +1 -0
- package/dist/src/version.d.ts +14 -0
- package/dist/src/version.d.ts.map +1 -0
- package/dist/src/version.js +14 -0
- package/dist/src/version.js.map +1 -0
- package/dist/src/workflows.d.ts +180 -0
- package/dist/src/workflows.d.ts.map +1 -0
- package/dist/src/workflows.js +160 -0
- package/dist/src/workflows.js.map +1 -0
- package/package.json +36 -0
package/dist/src/http.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The transport: one `request()`, three failure modes, no policy.
|
|
3
|
+
*
|
|
4
|
+
* Everything the operation modules do goes through {@linkcode request}. It
|
|
5
|
+
* attaches the bearer, serialises a JSON body, reads the response exactly once,
|
|
6
|
+
* parses it guardedly, and raises one of the three failure modes pinned in
|
|
7
|
+
* `docs/implementation.md` §3 — and nothing else. In particular it does **not**
|
|
8
|
+
* retry, does **not** refresh a token, does **not** poll, and does **not**
|
|
9
|
+
* inspect an error code to decide on a side effect. A `401` raises and stops.
|
|
10
|
+
*
|
|
11
|
+
* It also returns the **HTTP status** alongside the parsed body, because a
|
|
12
|
+
* `202` is a normal outcome on this API (a queued workflow run), not an error —
|
|
13
|
+
* and a signature that dropped the status would leave the run operations unable
|
|
14
|
+
* to tell "finished" from "queued" (§4).
|
|
15
|
+
*
|
|
16
|
+
* @module
|
|
17
|
+
*/
|
|
18
|
+
import { requireToken } from "./config.js";
|
|
19
|
+
import { ApiError } from "./errors.js";
|
|
20
|
+
/**
|
|
21
|
+
* Build a base-relative path, percent-encoding every interpolated value.
|
|
22
|
+
*
|
|
23
|
+
* ```ts
|
|
24
|
+
* path`/documents/by-key/${key}` // key "a/b" → "/documents/by-key/a%2Fb"
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* **Use this tag for every path that contains a caller-supplied value**, per the
|
|
28
|
+
* encoding pin in `docs/implementation.md` §2. It exists as a tag rather than a
|
|
29
|
+
* helper function because the encoding then happens at the point of
|
|
30
|
+
* interpolation and cannot be forgotten one call site at a time: there is no way
|
|
31
|
+
* to write the template and skip it.
|
|
32
|
+
*
|
|
33
|
+
* Why it matters, in one line: the server accepts any non-empty key up to 128
|
|
34
|
+
* characters, so `".."` is a key a user can legitimately create — and
|
|
35
|
+
* `` `/documents/by-key/${".."}` `` naively concatenated resolves to the
|
|
36
|
+
* **list** route, returning every document with a `200` and no error anywhere.
|
|
37
|
+
*
|
|
38
|
+
* This is encoding, never validation. A key the server accepts is sent as-is
|
|
39
|
+
* (encoded); the wrapper does not reject, canonicalise or rewrite it. A
|
|
40
|
+
* character policy for keys is business logic, and it lives in the server.
|
|
41
|
+
*
|
|
42
|
+
* The one case encoding cannot fix is a dot-only segment: `.` is unreserved in
|
|
43
|
+
* RFC 3986, and the URL parser behind `fetch` removes dot segments regardless of
|
|
44
|
+
* how they are spelled. `"."` and `".."` are therefore not addressable through a
|
|
45
|
+
* path segment from this runtime — encode and send anyway, and let the server
|
|
46
|
+
* decide what it means.
|
|
47
|
+
*
|
|
48
|
+
* @param strings - Template literal fragments (author-controlled, not encoded).
|
|
49
|
+
* @param values - Interpolated values (caller-controlled, each percent-encoded).
|
|
50
|
+
* @returns The assembled path.
|
|
51
|
+
*/
|
|
52
|
+
export function path(strings, ...values) {
|
|
53
|
+
return strings.reduce((acc, fragment, i) => i === 0 ? fragment : `${acc}${encodeURIComponent(values[i - 1])}${fragment}`, "");
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Assemble the full request URL from a resolved base, a path and query params.
|
|
57
|
+
*
|
|
58
|
+
* The base is already joined with the API base path by `src/config.ts`; the
|
|
59
|
+
* path is appended verbatim (it is expected to be encoded already — see
|
|
60
|
+
* {@linkcode path}), and the query string is built with `URLSearchParams`,
|
|
61
|
+
* which encodes both names and values.
|
|
62
|
+
*
|
|
63
|
+
* @param config - The resolved configuration.
|
|
64
|
+
* @param options - The request being built.
|
|
65
|
+
* @returns The absolute URL to fetch.
|
|
66
|
+
*/
|
|
67
|
+
export function buildUrl(config, options) {
|
|
68
|
+
const params = new URLSearchParams();
|
|
69
|
+
for (const [name, value] of Object.entries(options.query ?? {})) {
|
|
70
|
+
if (value !== undefined)
|
|
71
|
+
params.set(name, String(value));
|
|
72
|
+
}
|
|
73
|
+
const query = params.toString();
|
|
74
|
+
return `${config.baseUrl}${options.path}${query ? `?${query}` : ""}`;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Perform one request and map its outcome.
|
|
78
|
+
*
|
|
79
|
+
* Returns `{status, body}` on any `2xx`. Raises {@linkcode ApiError} in exactly
|
|
80
|
+
* three cases, transcribed from the studio's client, which has these three and
|
|
81
|
+
* no others:
|
|
82
|
+
*
|
|
83
|
+
* 1. **Transport failure** — `fetch` itself rejected: server down, DNS, TLS,
|
|
84
|
+
* connection refused. `status` `0`, code `network_error`, and the message
|
|
85
|
+
* names the **method and full URL** plus the underlying message, because a
|
|
86
|
+
* bare `TypeError: Failed to fetch` says nothing a user can act on.
|
|
87
|
+
* 2. **Non-JSON body on a non-OK status** — a proxy or Cloudflare HTML error
|
|
88
|
+
* page. `code` `bad_response`, message carrying a ≤200-character snippet;
|
|
89
|
+
* reporting it as an opaque `SyntaxError` would throw away the only
|
|
90
|
+
* diagnostic present. A non-JSON body on an **OK** status is not an error.
|
|
91
|
+
* 3. **Envelope error** — a non-OK status with a JSON body. `code` from
|
|
92
|
+
* `body.error.code` (else `"error"`), `message` from `body.error.message`
|
|
93
|
+
* (else the status text), and `raw` = the parsed body, which carries the
|
|
94
|
+
* fields an invoke failure rides alongside (`logs`, `apiCalls`).
|
|
95
|
+
*
|
|
96
|
+
* A `424` lands in case 3 like any other non-OK JSON status: it means the
|
|
97
|
+
* target app or its upstream vendor failed during execute, and it is
|
|
98
|
+
* deliberately a 4xx so Cloudflare cannot swallow it. It is never a transport
|
|
99
|
+
* error and is never normalised into a 5xx.
|
|
100
|
+
*
|
|
101
|
+
* @param config - Resolved configuration (base URL and credential).
|
|
102
|
+
* @param fetchImpl - The transport to use; injected, never read from a module global.
|
|
103
|
+
* @param options - The request.
|
|
104
|
+
* @returns The status and parsed body.
|
|
105
|
+
* @throws {ConfigError} When no token is configured.
|
|
106
|
+
* @throws {ApiError} On any of the three failure modes above.
|
|
107
|
+
*/
|
|
108
|
+
export async function request(config, fetchImpl, options) {
|
|
109
|
+
const url = buildUrl(config, options);
|
|
110
|
+
const headers = new Headers();
|
|
111
|
+
// Resolved per request rather than at construction: a client with no token is
|
|
112
|
+
// constructible (so a CLI's --help works offline) and fails here instead.
|
|
113
|
+
headers.set("authorization", `Bearer ${requireToken(config)}`);
|
|
114
|
+
const hasBody = options.body !== undefined;
|
|
115
|
+
if (hasBody)
|
|
116
|
+
headers.set("content-type", "application/json");
|
|
117
|
+
let res;
|
|
118
|
+
try {
|
|
119
|
+
res = await fetchImpl(url, {
|
|
120
|
+
method: options.method,
|
|
121
|
+
headers,
|
|
122
|
+
body: hasBody ? JSON.stringify(options.body) : undefined,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
throw new ApiError(0, "network_error", `Could not reach the w6w server (${options.method} ${url}). ` +
|
|
127
|
+
`It may be down or unreachable. (${err instanceof Error ? err.message : String(err)})`);
|
|
128
|
+
}
|
|
129
|
+
// Read the body exactly once, as text, then parse it guardedly — a response
|
|
130
|
+
// body is a single-use stream, and `res.json()` on an HTML error page throws
|
|
131
|
+
// an opaque SyntaxError that loses the page.
|
|
132
|
+
const text = await res.text();
|
|
133
|
+
let data = null;
|
|
134
|
+
if (text) {
|
|
135
|
+
try {
|
|
136
|
+
data = JSON.parse(text);
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
if (!res.ok) {
|
|
140
|
+
throw new ApiError(res.status, "bad_response", `Server returned a non-JSON ${res.status} response: ${text.slice(0, 200)}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (!res.ok) {
|
|
145
|
+
const envelope = data?.error ?? {};
|
|
146
|
+
throw new ApiError(res.status, envelope.code ?? "error", envelope.message ?? res.statusText, data);
|
|
147
|
+
}
|
|
148
|
+
return { status: res.status, body: data };
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/http.ts"],"names":[],"mappings":"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"}
|
package/dist/src/me.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `client.me()` — who the caller is, and what answered.
|
|
3
|
+
*
|
|
4
|
+
* One GET to `/auth/me` — the server's real identity route (verified live).
|
|
5
|
+
* The response body is **flat** — `{tenant, subject, account, role,
|
|
6
|
+
* versions?}` — so there is nothing for `unwrap` to do here, and this is the
|
|
7
|
+
* one read operation in the package that does not call it.
|
|
8
|
+
*
|
|
9
|
+
* The one thing this module adds to the body is `versions.wrapper`, which the
|
|
10
|
+
* server cannot know — it is this package's own published version. It is a
|
|
11
|
+
* **fill, never an overwrite**: a key the server supplied wins, including
|
|
12
|
+
* `wrapper` itself, so a future host that has a better answer is not silently
|
|
13
|
+
* overruled by the client. The server does not send a `versions` block today,
|
|
14
|
+
* so every call currently returns just `{wrapper: VERSION}` — expected, not
|
|
15
|
+
* an error.
|
|
16
|
+
*
|
|
17
|
+
* It is deliberately not a namespace class. `endpoints.json` names this
|
|
18
|
+
* operation `client.me()`, a method on the client itself, so the module exports
|
|
19
|
+
* a function over a narrow host interface and `W6wClient` delegates to it —
|
|
20
|
+
* same layering as the namespaces, without inventing a `client.me.me()`.
|
|
21
|
+
*
|
|
22
|
+
* @module
|
|
23
|
+
*/
|
|
24
|
+
import type { HttpResponse, RequestOptions } from "./http.ts";
|
|
25
|
+
import type { Me } from "./types.ts";
|
|
26
|
+
/**
|
|
27
|
+
* The slice of `W6wClient` this operation needs: the transport, and nothing
|
|
28
|
+
* else. Structural rather than a concrete client type, so it stays
|
|
29
|
+
* independently callable in a test and this module never imports the client
|
|
30
|
+
* back.
|
|
31
|
+
*/
|
|
32
|
+
export interface MeHost {
|
|
33
|
+
/** Perform one request. */
|
|
34
|
+
request<T>(options: RequestOptions): Promise<HttpResponse<T>>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Fetch the caller's identity and the component versions behind it.
|
|
38
|
+
*
|
|
39
|
+
* The identity fields are returned exactly as they arrived — unknown ones
|
|
40
|
+
* included, since the return is an interface and not a closed schema.
|
|
41
|
+
* `versions` is normalised to always exist and to always carry a `wrapper`
|
|
42
|
+
* entry, so a caller printing a version banner never has to branch on whether
|
|
43
|
+
* the server sent the block at all.
|
|
44
|
+
*
|
|
45
|
+
* @param host - The client this operation issues its request through.
|
|
46
|
+
* @returns The caller's identity, with `versions.wrapper` filled in.
|
|
47
|
+
* @throws {ApiError} On any non-2xx, e.g. `401` when the token is not accepted.
|
|
48
|
+
* @throws {ApiError} `bad_response` when a 2xx body is not a plain object — an
|
|
49
|
+
* array included, since `typeof [] === "object"` and a spread array yields
|
|
50
|
+
* character-indexed keys rather than identity fields.
|
|
51
|
+
*/
|
|
52
|
+
export declare function fetchMe(host: MeHost): Promise<Me>;
|
|
53
|
+
//# sourceMappingURL=me.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"me.d.ts","sourceRoot":"","sources":["../../src/me.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC9D,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,YAAY,CAAC;AAGrC;;;;;GAKG;AACH,MAAM,WAAW,MAAM;IACrB,2BAA2B;IAC3B,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;CAC/D;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,EAAE,CAAC,CAuCvD"}
|
package/dist/src/me.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `client.me()` — who the caller is, and what answered.
|
|
3
|
+
*
|
|
4
|
+
* One GET to `/auth/me` — the server's real identity route (verified live).
|
|
5
|
+
* The response body is **flat** — `{tenant, subject, account, role,
|
|
6
|
+
* versions?}` — so there is nothing for `unwrap` to do here, and this is the
|
|
7
|
+
* one read operation in the package that does not call it.
|
|
8
|
+
*
|
|
9
|
+
* The one thing this module adds to the body is `versions.wrapper`, which the
|
|
10
|
+
* server cannot know — it is this package's own published version. It is a
|
|
11
|
+
* **fill, never an overwrite**: a key the server supplied wins, including
|
|
12
|
+
* `wrapper` itself, so a future host that has a better answer is not silently
|
|
13
|
+
* overruled by the client. The server does not send a `versions` block today,
|
|
14
|
+
* so every call currently returns just `{wrapper: VERSION}` — expected, not
|
|
15
|
+
* an error.
|
|
16
|
+
*
|
|
17
|
+
* It is deliberately not a namespace class. `endpoints.json` names this
|
|
18
|
+
* operation `client.me()`, a method on the client itself, so the module exports
|
|
19
|
+
* a function over a narrow host interface and `W6wClient` delegates to it —
|
|
20
|
+
* same layering as the namespaces, without inventing a `client.me.me()`.
|
|
21
|
+
*
|
|
22
|
+
* @module
|
|
23
|
+
*/
|
|
24
|
+
import { ApiError } from "./errors.js";
|
|
25
|
+
import { VERSION } from "./version.js";
|
|
26
|
+
/**
|
|
27
|
+
* Fetch the caller's identity and the component versions behind it.
|
|
28
|
+
*
|
|
29
|
+
* The identity fields are returned exactly as they arrived — unknown ones
|
|
30
|
+
* included, since the return is an interface and not a closed schema.
|
|
31
|
+
* `versions` is normalised to always exist and to always carry a `wrapper`
|
|
32
|
+
* entry, so a caller printing a version banner never has to branch on whether
|
|
33
|
+
* the server sent the block at all.
|
|
34
|
+
*
|
|
35
|
+
* @param host - The client this operation issues its request through.
|
|
36
|
+
* @returns The caller's identity, with `versions.wrapper` filled in.
|
|
37
|
+
* @throws {ApiError} On any non-2xx, e.g. `401` when the token is not accepted.
|
|
38
|
+
* @throws {ApiError} `bad_response` when a 2xx body is not a plain object — an
|
|
39
|
+
* array included, since `typeof [] === "object"` and a spread array yields
|
|
40
|
+
* character-indexed keys rather than identity fields.
|
|
41
|
+
*/
|
|
42
|
+
export async function fetchMe(host) {
|
|
43
|
+
const res = await host.request({ method: "GET", path: "/auth/me" });
|
|
44
|
+
const body = res.body;
|
|
45
|
+
// A body that is not an object cannot be identity data. Raise rather than
|
|
46
|
+
// spread it — the same `bad_response` class the envelope reader uses for the
|
|
47
|
+
// keyed routes — so a proxy answering `200 "ok"` fails here instead of
|
|
48
|
+
// returning a `Me` whose only populated field is the one this client just
|
|
49
|
+
// added.
|
|
50
|
+
//
|
|
51
|
+
// `Array.isArray` is load-bearing, not belt-and-braces: `typeof [] === "object"`
|
|
52
|
+
// and `[] !== null`, so without it a `200 []` spread into exactly the degenerate
|
|
53
|
+
// value described above — measured, `{"versions":{"wrapper":"0.1.0"}}` — and
|
|
54
|
+
// `200 [{...}]` produced character-indexed keys. It also keeps this lane in step
|
|
55
|
+
// with python, whose natural `isinstance(body, dict)` excludes a list already.
|
|
56
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)) {
|
|
57
|
+
throw new ApiError(res.status, "bad_response", `Server returned a ${res.status} whose body is not an identity object.`, body);
|
|
58
|
+
}
|
|
59
|
+
// Same reasoning one level down: `versions` is typed `Record<string, string>`,
|
|
60
|
+
// but the wire can carry anything, and spreading a string produces
|
|
61
|
+
// character-indexed junk (`"1.2.3"` → `{"0":"1","1":".",…}`) that satisfies the
|
|
62
|
+
// type and so is invisible to the compiler. A `versions` that is not a plain
|
|
63
|
+
// object is dropped in favour of the client's own fill rather than merged —
|
|
64
|
+
// this operation reports identity, and inventing keys out of a malformed block
|
|
65
|
+
// would be worse than reporting only what this client actually knows.
|
|
66
|
+
const versions = body.versions;
|
|
67
|
+
const merged = typeof versions === "object" && versions !== null && !Array.isArray(versions)
|
|
68
|
+
? versions
|
|
69
|
+
: undefined;
|
|
70
|
+
return {
|
|
71
|
+
...body,
|
|
72
|
+
// Spread order is the policy: the wrapper's own version is the DEFAULT and
|
|
73
|
+
// every key the server sent lands on top of it, `wrapper` included.
|
|
74
|
+
versions: { wrapper: VERSION, ...merged },
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=me.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"me.js","sourceRoot":"","sources":["../../src/me.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAGvC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAavC;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,IAAY;IACxC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAK,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;IACxE,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC;IACtB,0EAA0E;IAC1E,6EAA6E;IAC7E,uEAAuE;IACvE,0EAA0E;IAC1E,SAAS;IACT,EAAE;IACF,iFAAiF;IACjF,iFAAiF;IACjF,6EAA6E;IAC7E,iFAAiF;IACjF,+EAA+E;IAC/E,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACrE,MAAM,IAAI,QAAQ,CAChB,GAAG,CAAC,MAAM,EACV,cAAc,EACd,qBAAqB,GAAG,CAAC,MAAM,wCAAwC,EACvE,IAAI,CACL,CAAC;IACJ,CAAC;IACD,+EAA+E;IAC/E,mEAAmE;IACnE,gFAAgF;IAChF,6EAA6E;IAC7E,4EAA4E;IAC5E,+EAA+E;IAC/E,sEAAsE;IACtE,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;IAC/B,MAAM,MAAM,GAAG,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC;QAC1F,CAAC,CAAC,QAAQ;QACV,CAAC,CAAC,SAAS,CAAC;IACd,OAAO;QACL,GAAG,IAAI;QACP,2EAA2E;QAC3E,oEAAoE;QACpE,QAAQ,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,MAAM,EAAE;KAC1C,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `client.run({urn, action, payload})` — run anything a URN addresses.
|
|
3
|
+
*
|
|
4
|
+
* One POST, one kind-tagged body, no envelope key. The URN resolves over the
|
|
5
|
+
* four runnable arms — `conn_…`, `wf_…`, `fn_…`, `ep_…` (D16) — and the answer
|
|
6
|
+
* says which one it hit:
|
|
7
|
+
*
|
|
8
|
+
* | `kind` | Field | HTTP |
|
|
9
|
+
* |---|---|---|
|
|
10
|
+
* | `"action"` | `value` | `200` |
|
|
11
|
+
* | `"function"` | `output` | `200` |
|
|
12
|
+
* | `"workflow"` | `runId` + `status` | `202` |
|
|
13
|
+
*
|
|
14
|
+
* `value` and `output` are **deliberately different names** and are never
|
|
15
|
+
* normalised into one field: the discrimination is the whole point of the
|
|
16
|
+
* operation (D3). `202` on the workflow arm is **success** — the run is queued
|
|
17
|
+
* and `runId` is how the caller follows it.
|
|
18
|
+
*
|
|
19
|
+
* ── The unknown fourth kind ──
|
|
20
|
+
* A `kind` this release has never heard of is **returned verbatim**, never
|
|
21
|
+
* raised (`docs/implementation.md` §5). `run` dispatches on whatever a URN
|
|
22
|
+
* resolves to, and the server can grow a new kind before the wrappers do — a
|
|
23
|
+
* purely additive change. Raising would turn that into a hard breakage for every
|
|
24
|
+
* installed client on the one operation whose entire job is dispatch, and would
|
|
25
|
+
* leave the caller with an exception instead of a payload this module had
|
|
26
|
+
* already parsed and held. Callers discriminate with `isActionRun`,
|
|
27
|
+
* `isFunctionRun` and `isWorkflowRun` (`src/types.ts`), which narrow properly —
|
|
28
|
+
* a bare `env.kind === "action"` check does not, because the union is open.
|
|
29
|
+
*
|
|
30
|
+
* `POST /api/run` (verified live 2026-07-28): the URN resolver dispatches to
|
|
31
|
+
* the same runner each dedicated route already uses — no new execution path.
|
|
32
|
+
*
|
|
33
|
+
* ── Why this is not folded into `workflows.run` ──
|
|
34
|
+
* D4: `?wait=`, `variables` and `trigger` have no slot in the three-field
|
|
35
|
+
* `{urn, action, payload}` shape, and a workflow has no `action`. The two
|
|
36
|
+
* operations ship side by side — this one dispatches, `workflows.run` is the
|
|
37
|
+
* typed path.
|
|
38
|
+
*
|
|
39
|
+
* Like `me`, this is a function over a narrow host interface rather than a
|
|
40
|
+
* namespace class, because `endpoints.json` names the symbol `client.run(input)`
|
|
41
|
+
* — a method on the client itself, not a `client.run.run()`.
|
|
42
|
+
*
|
|
43
|
+
* @module
|
|
44
|
+
*/
|
|
45
|
+
import type { HttpResponse, RequestOptions } from "./http.ts";
|
|
46
|
+
import type { RunEnvelope } from "./types.ts";
|
|
47
|
+
/**
|
|
48
|
+
* The slice of `W6wClient` this operation needs: the transport, and nothing
|
|
49
|
+
* else. Structural rather than a concrete client type, so it stays
|
|
50
|
+
* independently callable in a test and this module never imports the client
|
|
51
|
+
* back.
|
|
52
|
+
*/
|
|
53
|
+
export interface RunHost {
|
|
54
|
+
/** Perform one request. */
|
|
55
|
+
request<T>(options: RequestOptions): Promise<HttpResponse<T>>;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The body of `client.run()`.
|
|
59
|
+
*
|
|
60
|
+
* All three fields travel in the **request body**, so none of them is
|
|
61
|
+
* percent-encoded: `/run` is a fixed path with no interpolation, and a URN in
|
|
62
|
+
* JSON needs no escaping. (The `path` tag is for path segments — `workflows.run`
|
|
63
|
+
* uses it for its `wf_…` id.)
|
|
64
|
+
*/
|
|
65
|
+
export interface RunInput {
|
|
66
|
+
/** What to run: `conn_…`, `wf_…`, `fn_…` or `ep_…`. */
|
|
67
|
+
urn: string;
|
|
68
|
+
/**
|
|
69
|
+
* Which action to invoke. Optional, because a workflow, function or endpoint
|
|
70
|
+
* URN has no action; required in practice for a `conn_…` URN, and the server
|
|
71
|
+
* is what says so — this client does not second-guess the URN's arm.
|
|
72
|
+
*/
|
|
73
|
+
action?: string;
|
|
74
|
+
/** Input to the run. Defaults to `{}`. Opaque pass-through. */
|
|
75
|
+
payload?: Record<string, unknown>;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Run whatever a URN addresses.
|
|
79
|
+
*
|
|
80
|
+
* @param host - The client this operation issues its request through.
|
|
81
|
+
* @param input - The URN, the optional action, and the payload.
|
|
82
|
+
* @returns The kind-tagged envelope, with `kind` and the arm's field verbatim.
|
|
83
|
+
* @throws {ApiError} On any non-2xx — `404` for an unresolvable URN, `424` when
|
|
84
|
+
* the app or its upstream vendor failed during execute (a 4xx on purpose, so
|
|
85
|
+
* Cloudflare cannot swallow the message; never normalised into a transport
|
|
86
|
+
* error).
|
|
87
|
+
* @throws {ApiError} `bad_response` when a success body carries no `kind` at all
|
|
88
|
+
* — that is a malformed dispatch response, not a new arm, and it is the one
|
|
89
|
+
* shape a caller cannot do anything with.
|
|
90
|
+
*/
|
|
91
|
+
export declare function runUrn(host: RunHost, input: RunInput): Promise<RunEnvelope>;
|
|
92
|
+
//# sourceMappingURL=run.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/run.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;GAKG;AACH,MAAM,WAAW,OAAO;IACtB,2BAA2B;IAC3B,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;CAC/D;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,QAAQ;IACvB,uDAAuD;IACvD,GAAG,EAAE,MAAM,CAAC;IACZ;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,WAAW,CAAC,CA8BjF"}
|
package/dist/src/run.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `client.run({urn, action, payload})` — run anything a URN addresses.
|
|
3
|
+
*
|
|
4
|
+
* One POST, one kind-tagged body, no envelope key. The URN resolves over the
|
|
5
|
+
* four runnable arms — `conn_…`, `wf_…`, `fn_…`, `ep_…` (D16) — and the answer
|
|
6
|
+
* says which one it hit:
|
|
7
|
+
*
|
|
8
|
+
* | `kind` | Field | HTTP |
|
|
9
|
+
* |---|---|---|
|
|
10
|
+
* | `"action"` | `value` | `200` |
|
|
11
|
+
* | `"function"` | `output` | `200` |
|
|
12
|
+
* | `"workflow"` | `runId` + `status` | `202` |
|
|
13
|
+
*
|
|
14
|
+
* `value` and `output` are **deliberately different names** and are never
|
|
15
|
+
* normalised into one field: the discrimination is the whole point of the
|
|
16
|
+
* operation (D3). `202` on the workflow arm is **success** — the run is queued
|
|
17
|
+
* and `runId` is how the caller follows it.
|
|
18
|
+
*
|
|
19
|
+
* ── The unknown fourth kind ──
|
|
20
|
+
* A `kind` this release has never heard of is **returned verbatim**, never
|
|
21
|
+
* raised (`docs/implementation.md` §5). `run` dispatches on whatever a URN
|
|
22
|
+
* resolves to, and the server can grow a new kind before the wrappers do — a
|
|
23
|
+
* purely additive change. Raising would turn that into a hard breakage for every
|
|
24
|
+
* installed client on the one operation whose entire job is dispatch, and would
|
|
25
|
+
* leave the caller with an exception instead of a payload this module had
|
|
26
|
+
* already parsed and held. Callers discriminate with `isActionRun`,
|
|
27
|
+
* `isFunctionRun` and `isWorkflowRun` (`src/types.ts`), which narrow properly —
|
|
28
|
+
* a bare `env.kind === "action"` check does not, because the union is open.
|
|
29
|
+
*
|
|
30
|
+
* `POST /api/run` (verified live 2026-07-28): the URN resolver dispatches to
|
|
31
|
+
* the same runner each dedicated route already uses — no new execution path.
|
|
32
|
+
*
|
|
33
|
+
* ── Why this is not folded into `workflows.run` ──
|
|
34
|
+
* D4: `?wait=`, `variables` and `trigger` have no slot in the three-field
|
|
35
|
+
* `{urn, action, payload}` shape, and a workflow has no `action`. The two
|
|
36
|
+
* operations ship side by side — this one dispatches, `workflows.run` is the
|
|
37
|
+
* typed path.
|
|
38
|
+
*
|
|
39
|
+
* Like `me`, this is a function over a narrow host interface rather than a
|
|
40
|
+
* namespace class, because `endpoints.json` names the symbol `client.run(input)`
|
|
41
|
+
* — a method on the client itself, not a `client.run.run()`.
|
|
42
|
+
*
|
|
43
|
+
* @module
|
|
44
|
+
*/
|
|
45
|
+
import { ApiError } from "./errors.js";
|
|
46
|
+
/**
|
|
47
|
+
* Run whatever a URN addresses.
|
|
48
|
+
*
|
|
49
|
+
* @param host - The client this operation issues its request through.
|
|
50
|
+
* @param input - The URN, the optional action, and the payload.
|
|
51
|
+
* @returns The kind-tagged envelope, with `kind` and the arm's field verbatim.
|
|
52
|
+
* @throws {ApiError} On any non-2xx — `404` for an unresolvable URN, `424` when
|
|
53
|
+
* the app or its upstream vendor failed during execute (a 4xx on purpose, so
|
|
54
|
+
* Cloudflare cannot swallow the message; never normalised into a transport
|
|
55
|
+
* error).
|
|
56
|
+
* @throws {ApiError} `bad_response` when a success body carries no `kind` at all
|
|
57
|
+
* — that is a malformed dispatch response, not a new arm, and it is the one
|
|
58
|
+
* shape a caller cannot do anything with.
|
|
59
|
+
*/
|
|
60
|
+
export async function runUrn(host, input) {
|
|
61
|
+
const res = await host.request({
|
|
62
|
+
method: "POST",
|
|
63
|
+
path: "/run",
|
|
64
|
+
body: {
|
|
65
|
+
urn: input.urn,
|
|
66
|
+
action: input.action,
|
|
67
|
+
// Defaulted rather than omitted: the server's parameter schemas are
|
|
68
|
+
// written against an object, and `{}` says "no input" where an absent key
|
|
69
|
+
// says "I forgot".
|
|
70
|
+
payload: input.payload ?? {},
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
const body = res.body;
|
|
74
|
+
if (typeof body !== "object" || body === null ||
|
|
75
|
+
typeof body.kind !== "string") {
|
|
76
|
+
throw new ApiError(res.status, "bad_response", `Server returned a ${res.status} with no "kind" in the response body.`, body);
|
|
77
|
+
}
|
|
78
|
+
// Returned as it arrived — no rebuild, no field renaming, no per-arm
|
|
79
|
+
// destructure. An unknown `kind` therefore reaches the caller with every
|
|
80
|
+
// sibling field intact.
|
|
81
|
+
return body;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/run.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAoCvC;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,IAAa,EAAE,KAAe;IACzD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,CAAU;QACtC,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE;YACJ,GAAG,EAAE,KAAK,CAAC,GAAG;YACd,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,oEAAoE;YACpE,0EAA0E;YAC1E,mBAAmB;YACnB,OAAO,EAAE,KAAK,CAAC,OAAO,IAAI,EAAE;SAC7B;KACF,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC;IACtB,IACE,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI;QACzC,OAAQ,IAA2B,CAAC,IAAI,KAAK,QAAQ,EACrD,CAAC;QACD,MAAM,IAAI,QAAQ,CAChB,GAAG,CAAC,MAAM,EACV,cAAc,EACd,qBAAqB,GAAG,CAAC,MAAM,uCAAuC,EACtE,IAAI,CACL,CAAC;IACJ,CAAC;IACD,qEAAqE;IACrE,yEAAyE;IACzE,wBAAwB;IACxB,OAAO,IAAmB,CAAC;AAC7B,CAAC"}
|