@k9o/core 1.1.1 → 1.3.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/CHANGELOG.md +49 -0
- package/README.md +169 -16
- package/dist/{chunk-O2ZTV6YN.js → chunk-KC4EDMBF.js} +61 -3
- package/dist/chunk-KC4EDMBF.js.map +1 -0
- package/dist/{chunk-2NDFTHUB.js → chunk-UL53GNYM.js} +23 -5
- package/dist/chunk-UL53GNYM.js.map +1 -0
- package/dist/http.d.ts +2 -0
- package/dist/http.js +2 -1
- package/dist/index.d.ts +563 -517
- package/dist/index.js +170 -421
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +3 -3
- package/dist/url-grammar.d.ts +32 -16
- package/dist/url-grammar.js +5 -1
- package/package.json +4 -4
- package/src/audit.ts +16 -26
- package/src/compute/llms.ts +55 -33
- package/src/functions.ts +60 -27
- package/src/http.ts +5 -1
- package/src/index.ts +16 -17
- package/src/me.ts +69 -85
- package/src/migrations.ts +61 -11
- package/src/org.ts +138 -140
- package/src/orgs.ts +16 -3
- package/src/policies.ts +63 -24
- package/src/projects.ts +58 -22
- package/src/realtime.ts +21 -200
- package/src/region.ts +83 -3
- package/src/storage-buckets.ts +17 -5
- package/src/studio/workflows.ts +8 -8
- package/src/types.ts +3 -3
- package/src/url-grammar.ts +52 -25
- package/dist/chunk-2NDFTHUB.js.map +0 -1
- package/dist/chunk-O2ZTV6YN.js.map +0 -1
- package/src/metadata.ts +0 -130
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,54 @@
|
|
|
1
1
|
# Changelog — @k9o/core
|
|
2
2
|
|
|
3
|
+
## 1.3.0 — 2026-10-26
|
|
4
|
+
|
|
5
|
+
The SDK now matches the gateway. It passes the gateway's field names through, so some inputs and results change.
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Inputs: `projects.create` takes `displayName`. `functions.deploy` takes `artifactKey`, `version` and `tier`.
|
|
10
|
+
- Inputs: policies and `policies.validate` take a `policy` document.
|
|
11
|
+
- Inputs: audit filters are `eventType`, `actorId`, `after` and `before`.
|
|
12
|
+
- Inputs: `policies.publish` takes an optional `acknowledgeSelfLockout`. `me.profile.update` takes `locale` and `timezone`.
|
|
13
|
+
- Inputs: `me.apiTokens.create` takes `label`, `scopeManifest` and `ttlSeconds`. `org.smtp.set` takes `passwordRef`, not a password.
|
|
14
|
+
- Inputs: `org.auditLog.query` takes `type` and `limit` only. `migrations.listPending` takes an optional `status`.
|
|
15
|
+
- Inputs: `org.members.list` filters by `userId`, `role`, `acceptanceStatus`, `status` and `state`.
|
|
16
|
+
- Inputs: `functions.list` also filters by `tier` and `source`.
|
|
17
|
+
- Project results: `displayName` replaces `name`. `custodyMode` is `PLATFORM_MANAGED`, `SKR`, `BYOK` or `HYOK`.
|
|
18
|
+
- Project results also carry `region`, `status`, `role` and `residencySet`.
|
|
19
|
+
- Policy results: bundles carry `policy`, `projectId` and a `status` of `draft`, `published` or `superseded`. `validate` answers `{ valid: true }`.
|
|
20
|
+
- `policies.publish` answers `{ pendingChangeId, bundleId, status }`: the publish waits for approval. `policies.delete` returns no data.
|
|
21
|
+
- Migration results: `apply` and `applyPending` answer `{ applied, skipped }` without `appliedAt`. Pending rows use `id`, `queuedAt` and `queuedBy`.
|
|
22
|
+
- Function results: records add `version`, `entryPoint`, `retiredAt` and `errorDetail`. The failed status is `error`.
|
|
23
|
+
- `functions.delete` returns the retired record.
|
|
24
|
+
- Org results: `org.get()` and the `orgs` methods return `displayName`. Members add `invitedAt`, `acceptedAt` and `state`.
|
|
25
|
+
- `org.members.invite` answers `{ invitation, delivery }` when the email has no account. `org.members.remove` returns no data.
|
|
26
|
+
- Org groups carry `source` and no `memberCount`. Org audit events carry `createdAt`. `org.smtp.set` and `org.smtp.unset` return the org.
|
|
27
|
+
- `org.smtp.test` answers `{ accepted, resolvedTier, to, dryRun }`.
|
|
28
|
+
- `me` results: profiles add `mfaEnabled` and `updatedAt`. Sessions use `issuedAt`, `expiresAt` and `isCurrentSession`.
|
|
29
|
+
- `compute.llms.invoke` results use `content` and `usage`. Inference parameters are sent flat in the request.
|
|
30
|
+
- Signed URLs: the gateway signs for 900 seconds at most. The answer's `expiresIn` is the lifetime it used.
|
|
31
|
+
- Host handling: the API host comes from the base URL. `orgSlug` builds `<orgSlug>.api.<apex>` on any platform host, not only the default one.
|
|
32
|
+
- The README is rewritten and checked against the source.
|
|
33
|
+
|
|
34
|
+
### Removed
|
|
35
|
+
|
|
36
|
+
- `client.metadata`.
|
|
37
|
+
- `client.realtime.replay`, the WebSocket transport and polling. `subscribe` (server-sent events) remains.
|
|
38
|
+
- `org.members.get` and `org.smtp.get`.
|
|
39
|
+
- `org.auditLog.tail`. The org audit log cannot filter by time.
|
|
40
|
+
- `org.groups.create` and `org.groups.delete`. Groups come from your identity provider, so only `list` and `get` remain.
|
|
41
|
+
- `me.notifications`, `me.auditLog` and `me.dataExport`.
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
|
|
45
|
+
- `functions.uploadArtifact(bytes, kind)` uploads a function's bytes and returns its `artifactKey`.
|
|
46
|
+
- A contract test checks 100 SDK calls against the gateway's published API description.
|
|
47
|
+
|
|
48
|
+
## 1.2.0 — 2026-10-25
|
|
49
|
+
|
|
50
|
+
- The README now introduces the Kilimanjaro products (Keep, Pulse, Gate, Kibo). No change to the public API.
|
|
51
|
+
|
|
3
52
|
## 1.1.1 — 2026-10-25
|
|
4
53
|
|
|
5
54
|
- Documentation links corrected: the documentation hub is https://docs.kilimanjaro.dev, with the website, platform API and console listed in a Links section. Package metadata points at the same places.
|
package/README.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
The Kilimanjaro platform SDK for TypeScript and JavaScript. One client for your data, projects, functions, audit log and more.
|
|
4
4
|
|
|
5
|
+
Kilimanjaro is the AI-native foundation for building critical applications.
|
|
6
|
+
Its products are Keep (AI Security and Governance), Pulse (Data Intelligence),
|
|
7
|
+
Gate (Customer Identity and Access Management) and Kibo (Keys and Secrets Management).
|
|
8
|
+
|
|
5
9
|
## Contents
|
|
6
10
|
|
|
7
11
|
- [Links](#links)
|
|
@@ -9,6 +13,7 @@ The Kilimanjaro platform SDK for TypeScript and JavaScript. One client for your
|
|
|
9
13
|
- [Quick start](#quick-start)
|
|
10
14
|
- [Authentication and project context](#authentication-and-project-context)
|
|
11
15
|
- [Running queries](#running-queries)
|
|
16
|
+
- [Projects, functions and policies](#projects-functions-and-policies)
|
|
12
17
|
- [Handling errors](#handling-errors)
|
|
13
18
|
- [Regional hosts](#regional-hosts)
|
|
14
19
|
- [Appendix](#appendix)
|
|
@@ -63,6 +68,9 @@ Write the README
|
|
|
63
68
|
|
|
64
69
|
Every method resolves to `{ data, meta }`. `data` is the typed result and `meta` holds response metadata.
|
|
65
70
|
|
|
71
|
+
The SDK passes the gateway's field names through unchanged. Inputs and results use the names the gateway uses.
|
|
72
|
+
For example, a project has a `displayName`, not a `name`, and its `custodyMode` is `PLATFORM_MANAGED`, `SKR`, `BYOK` or `HYOK`.
|
|
73
|
+
|
|
66
74
|
## Authentication and project context
|
|
67
75
|
|
|
68
76
|
Three options select who you are and where you work. See [Client options](#client-options) for the full list.
|
|
@@ -73,17 +81,22 @@ Three options select who you are and where you work. See [Client options](#clien
|
|
|
73
81
|
| `orgSlug` | Selects your organization. |
|
|
74
82
|
| `projectSlug` | Selects the project. Required by project-scoped calls. |
|
|
75
83
|
|
|
76
|
-
-
|
|
77
|
-
-
|
|
84
|
+
- Pass `orgSlug` alone. The SDK builds the organization host `<orgSlug>.api.<apex>` from the base URL.
|
|
85
|
+
- With no `baseUrl`, the apex is the default public host, `api.k9o.io`. That host is only a default.
|
|
86
|
+
- Any other deployment passes its own `baseUrl` and `orgSlug`. The API host always comes from the base URL.
|
|
87
|
+
- A `baseUrl` that is already an organization host, such as `https://acme.api.example.com`, is used as given.
|
|
78
88
|
|
|
79
89
|
```ts
|
|
80
90
|
const client = createClient({
|
|
81
|
-
baseUrl: "https://
|
|
91
|
+
baseUrl: "https://api.example.com",
|
|
82
92
|
apiKey: process.env.K9O_API_KEY,
|
|
93
|
+
orgSlug: "acme",
|
|
83
94
|
projectSlug: "member-portal",
|
|
84
95
|
});
|
|
85
96
|
```
|
|
86
97
|
|
|
98
|
+
This client sends its requests to `https://acme.api.example.com`. Any apex works, including an internal one or `localhost`.
|
|
99
|
+
|
|
87
100
|
To retry transient failures, add `retryConfig`. See [Retry settings](#retry-settings).
|
|
88
101
|
|
|
89
102
|
## Running queries
|
|
@@ -98,6 +111,10 @@ for (const openTask of response.data) {
|
|
|
98
111
|
}
|
|
99
112
|
```
|
|
100
113
|
|
|
114
|
+
```text
|
|
115
|
+
Write the README
|
|
116
|
+
```
|
|
117
|
+
|
|
101
118
|
Update or delete one record by id:
|
|
102
119
|
|
|
103
120
|
```ts
|
|
@@ -112,7 +129,85 @@ true
|
|
|
112
129
|
|
|
113
130
|
- `update` and `delete` need an `eq("id", ...)` filter. They never change many rows at once.
|
|
114
131
|
- See [Data methods](#data-methods) for filters and paging.
|
|
115
|
-
- Other namespaces
|
|
132
|
+
- Other namespaces are listed in [Methods by namespace](#methods-by-namespace).
|
|
133
|
+
|
|
134
|
+
## Projects, functions and policies
|
|
135
|
+
|
|
136
|
+
These namespaces use the gateway's field names. See [Method reference](#method-reference) for every signature.
|
|
137
|
+
|
|
138
|
+
### Create a project
|
|
139
|
+
|
|
140
|
+
`projects.create` takes a `displayName`. The gateway derives the slug from it when you leave `slug` out.
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
const project = await client.projects.create({ displayName: "Member Portal" });
|
|
144
|
+
|
|
145
|
+
console.log(project.data.slug, project.data.status);
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
```text
|
|
149
|
+
member-portal active
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Deploy a function
|
|
153
|
+
|
|
154
|
+
Upload the function's bytes first. Then deploy the returned `artifactKey` with a `version` and a `tier`.
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
import { readFile } from "node:fs/promises";
|
|
158
|
+
|
|
159
|
+
const bytes = await readFile("score.wasm");
|
|
160
|
+
const artifact = await client.functions.uploadArtifact(bytes, "wasm");
|
|
161
|
+
|
|
162
|
+
const deployed = await client.functions.deploy({
|
|
163
|
+
name: "score",
|
|
164
|
+
runtime: "rust",
|
|
165
|
+
tier: "gateway",
|
|
166
|
+
artifactKey: artifact.data.artifactKey,
|
|
167
|
+
version: "1",
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
console.log(deployed.data.status);
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
```text
|
|
174
|
+
deploying
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
- Use `"wasm"` for the `gateway` tier and `"tar"` (an OCI image archive) for the `sandbox` tier.
|
|
178
|
+
- The status changes to `active` when the function is ready.
|
|
179
|
+
- `functions.delete` retires the function and returns the retired record.
|
|
180
|
+
|
|
181
|
+
### Publish a policy
|
|
182
|
+
|
|
183
|
+
A policy bundle holds a `policy` document. Publishing queues the bundle for approval.
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
const queued = await client.policies.publish("pol_9x2k", { acknowledgeSelfLockout: true });
|
|
187
|
+
|
|
188
|
+
console.log(queued.data.status);
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
```text
|
|
192
|
+
pending_approval
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
- The answer is `{ pendingChangeId, bundleId, status }`. The bundle becomes active once it is approved.
|
|
196
|
+
- Pass `acknowledgeSelfLockout: true` only if the bundle may remove your own read access.
|
|
197
|
+
|
|
198
|
+
### Audit log
|
|
199
|
+
|
|
200
|
+
Filter with `eventType`, `actorId`, `after` and `before`. Dates are ISO 8601.
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
const events = await client.audit.query({ eventType: "data.write.access.insert", limit: 5 });
|
|
204
|
+
|
|
205
|
+
console.log(events.data[0]?.type);
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
```text
|
|
209
|
+
data.write.access.insert
|
|
210
|
+
```
|
|
116
211
|
|
|
117
212
|
## Handling errors
|
|
118
213
|
|
|
@@ -125,25 +220,41 @@ try {
|
|
|
125
220
|
await client.from("tasks").insert<Task>({ title: "One more task", done: false });
|
|
126
221
|
} catch (error) {
|
|
127
222
|
if (error instanceof KError && error.code === "K9O_TIER_CAP_HIT") {
|
|
128
|
-
console.
|
|
223
|
+
console.log(error.code);
|
|
129
224
|
} else {
|
|
130
225
|
throw error;
|
|
131
226
|
}
|
|
132
227
|
}
|
|
133
228
|
```
|
|
134
229
|
|
|
230
|
+
```text
|
|
231
|
+
K9O_TIER_CAP_HIT
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
The output appears only when the plan's cap is reached.
|
|
235
|
+
|
|
135
236
|
- `KError` also carries `context`, `status` and `requestId`. See [Error fields](#error-fields).
|
|
136
237
|
- The gateway can send many codes. See [Error codes](#error-codes) for the ones the SDK raises itself.
|
|
137
238
|
|
|
138
239
|
## Regional hosts
|
|
139
240
|
|
|
140
|
-
- On an organization host such as `https://acme.api.
|
|
241
|
+
- On an organization host such as `https://acme.api.example.com`, the client sends one `HEAD` request first.
|
|
141
242
|
- The request carries no credentials. It finds the regional host for your organization.
|
|
142
243
|
- All later requests go to that host. The answer is cached for the life of the client.
|
|
143
244
|
- Any other `baseUrl` is used as given. If the lookup fails, the configured host is used.
|
|
144
245
|
|
|
145
246
|
## Appendix
|
|
146
247
|
|
|
248
|
+
- [Requirements](#requirements)
|
|
249
|
+
- [Client options](#client-options)
|
|
250
|
+
- [Retry settings](#retry-settings)
|
|
251
|
+
- [Methods by namespace](#methods-by-namespace)
|
|
252
|
+
- [Method reference](#method-reference)
|
|
253
|
+
- [Data methods](#data-methods)
|
|
254
|
+
- [Error fields](#error-fields)
|
|
255
|
+
- [Error codes](#error-codes)
|
|
256
|
+
- [Related packages](#related-packages)
|
|
257
|
+
|
|
147
258
|
### Requirements
|
|
148
259
|
|
|
149
260
|
| Requirement | Version |
|
|
@@ -158,7 +269,7 @@ try {
|
|
|
158
269
|
| Option | What it does |
|
|
159
270
|
| --- | --- |
|
|
160
271
|
| `apiKey` | Bearer token sent on every request. |
|
|
161
|
-
| `orgSlug` | Selects your organization. On
|
|
272
|
+
| `orgSlug` | Selects your organization. On a platform host (`api.<apex>`) it builds `https://<orgSlug>.api.<apex>`. |
|
|
162
273
|
| `projectSlug` | Selects the project. Required by project-scoped calls. |
|
|
163
274
|
| `baseUrl` | Gateway host, no path. Defaults to `https://api.k9o.io`. |
|
|
164
275
|
| `fetch` | Custom `fetch` implementation. |
|
|
@@ -167,6 +278,7 @@ try {
|
|
|
167
278
|
|
|
168
279
|
- A `baseUrl` with a path, query or fragment throws `K9O_INVALID_BASE_URL`.
|
|
169
280
|
- Slugs use 2 to 63 lowercase letters, digits and hyphens. They start with a letter and do not end with a hyphen.
|
|
281
|
+
- Do not pass both an organization-host `baseUrl` and `orgSlug`. One of them is enough.
|
|
170
282
|
|
|
171
283
|
### Retry settings
|
|
172
284
|
|
|
@@ -187,22 +299,63 @@ try {
|
|
|
187
299
|
| `client.from(collection)` | See [Data methods](#data-methods). |
|
|
188
300
|
| `client.projects` | `list`, `create`, `get`, `update` |
|
|
189
301
|
| `client.migrations` | `apply`, `list`, `get`, `queue`, `listPending`, `applyPending`, `rejectPending`, `deletePending` |
|
|
190
|
-
| `client.functions` | `deploy`, `list`, `get`, `delete` |
|
|
302
|
+
| `client.functions` | `uploadArtifact`, `deploy`, `list`, `get`, `delete`, `invoke` |
|
|
191
303
|
| `client.audit` | `query`, `tail` (needs `projectSlug`) |
|
|
192
|
-
| `client.policies` |
|
|
193
|
-
| `client.storage` |
|
|
194
|
-
| `client.
|
|
304
|
+
| `client.policies` | `current`, `list`, `create`, `validate`, `get`, `update`, `delete`, `publish` |
|
|
305
|
+
| `client.storage` | `listBuckets`, `getBucket`, `createBucket`, `updateBucket`, `deleteBucket`, `from(bucket)`, `locations` |
|
|
306
|
+
| `client.storage.from(bucket)` | `upload`, `createSignedUrl`, `list`, `remove` |
|
|
307
|
+
| `client.storage.locations` | `list`, `create`, `get`, `update`, `retire`, `test` |
|
|
195
308
|
| `client.orgs` | `list`, `get`, `restore`, `current` |
|
|
196
|
-
| `client.org` |
|
|
197
|
-
| `client.
|
|
198
|
-
| `client.
|
|
199
|
-
| `client.
|
|
309
|
+
| `client.org` | `get`, plus the sub-namespaces below |
|
|
310
|
+
| `client.org.members` | `list`, `invite`, `remove` |
|
|
311
|
+
| `client.org.groups` | `list`, `get` |
|
|
312
|
+
| `client.org.auditLog` | `query` |
|
|
313
|
+
| `client.org.gate.roles` | `list`, `grant`, `get`, `revoke` |
|
|
314
|
+
| `client.org.gate.idpProviders` | `list`, `create`, `get`, `configure`, `delete` |
|
|
315
|
+
| `client.org.smtp` | `set`, `test`, `unset` |
|
|
316
|
+
| `client.org.defaultLLM` | `get`, `set` |
|
|
317
|
+
| `client.me.profile` | `get`, `update` |
|
|
318
|
+
| `client.me.sessions` | `list`, `revokeAll` |
|
|
319
|
+
| `client.me.apiTokens` | `list`, `create`, `delete` |
|
|
320
|
+
| `client.studio.workflows` | `list`, `create`, `read`, `update`, `delete`, `trigger`, `runs` |
|
|
321
|
+
| `client.studio.workflows.runs` | `list`, `read`, `cancel` |
|
|
322
|
+
| `client.studio.dashboards` | `list`, `create`, `read`, `update`, `delete`, `pin`, `unpin` |
|
|
323
|
+
| `client.compute.llms` | `list`, `read`, `invoke`, `invokeStream` |
|
|
200
324
|
| `client.ai` | `embed` |
|
|
201
|
-
| `client.realtime` | `subscribe
|
|
325
|
+
| `client.realtime` | `subscribe` (server-sent events) |
|
|
202
326
|
| `client.gate` | End-user sign-in from `@k9o/gate` (needs `projectSlug`) |
|
|
203
327
|
| `client.kibo` | Vault and key operations from `@k9o/kibo` |
|
|
204
328
|
| `client.extensions` | `extension(slug)` from `@k9o/extensions` |
|
|
205
329
|
|
|
330
|
+
### Method reference
|
|
331
|
+
|
|
332
|
+
Signatures of the methods whose inputs or results are easy to get wrong. Types are exported from `@k9o/core`.
|
|
333
|
+
|
|
334
|
+
| Call | Input and result |
|
|
335
|
+
| --- | --- |
|
|
336
|
+
| `projects.create(input)` | `displayName` required. Optional `slug`, `region`, `custodyMode`, `productScope`, `processingContexts`, `idempotencyKey`. |
|
|
337
|
+
| `projects.update(slug, patch)` | `displayName`, `description`, `softDeleteRetentionDays`, `processingContexts`. A slug rename must be sent alone. |
|
|
338
|
+
| `functions.uploadArtifact(bytes, kind)` | `kind` is `"wasm"` (up to 16 MiB) or `"tar"` (up to 256 MiB). Returns `{ artifactKey }`. |
|
|
339
|
+
| `functions.deploy(input)` | `name`, `runtime`, `tier`, `artifactKey`, `version`. Optional `description`. |
|
|
340
|
+
| `functions.list(opts)` | Optional `limit`, `cursor`, `status`, `tier`, `source`. |
|
|
341
|
+
| `functions.invoke(name, payload, options)` | Optional `background`, `delegation`, `onBehalfOf`, `confinedTo`. |
|
|
342
|
+
| `policies.create(input)` | `policy` document. Optional `rules`, `editorMode`. |
|
|
343
|
+
| `policies.publish(bundleId, options)` | Optional `acknowledgeSelfLockout`. Returns `{ pendingChangeId, bundleId, status }`. |
|
|
344
|
+
| `policies.validate(input)` | `policy` document. Returns `{ valid: true }`, or throws. |
|
|
345
|
+
| `migrations.apply(input)` | Returns `{ applied, skipped }`. Applied rows have `version`, `slug` and `checksum`. |
|
|
346
|
+
| `migrations.queue(input)` | Returns one row per migration with `id`, `status` and `approvalId`. |
|
|
347
|
+
| `migrations.applyPending(id)` | Returns `{ pendingId, applied, skipped }`. |
|
|
348
|
+
| `audit.query(opts)` | Optional `eventType`, `actorId`, `after`, `before`, `limit`, `cursor`. |
|
|
349
|
+
| `org.members.invite(input)` | `email` and `role`. Returns the member, or `{ invitation, delivery }` for an unknown email. |
|
|
350
|
+
| `org.auditLog.query(opts)` | Optional `type` and `limit` (1 to 500). Events carry `createdAt`. |
|
|
351
|
+
| `org.smtp.set(config)` | Returns the org. The password is sent as `passwordRef`, never in clear. |
|
|
352
|
+
| `org.smtp.test(input)` | Returns `{ accepted, resolvedTier, to, dryRun }`. |
|
|
353
|
+
| `me.profile.update(patch)` | Optional `displayName`, `locale`, `timezone`. |
|
|
354
|
+
| `storage.createBucket(name, options)` | Optional `fileSizeLimit`, `allowedMimeTypes`, `byosBindingId`. `public`, `versioning` and `deleteAfterDays` are stored but not enforced yet. |
|
|
355
|
+
| `storage.from(bucket).createSignedUrl(path, options)` | `expiresIn` required. The gateway signs for 900 seconds at most. |
|
|
356
|
+
| `compute.llms.invoke(model, input)` | `prompt` and optional `params`. Result fields are `content` and `usage`. |
|
|
357
|
+
| `compute.llms.invokeStream(model, input)` | Async iterable of chunks. Read text from `choices[0].delta.content`. |
|
|
358
|
+
|
|
206
359
|
### Data methods
|
|
207
360
|
|
|
208
361
|
| Method | What it does |
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseApiHost
|
|
3
|
+
} from "./chunk-UL53GNYM.js";
|
|
1
4
|
import {
|
|
2
5
|
KError,
|
|
3
6
|
kerrorFromWire
|
|
@@ -53,7 +56,60 @@ async function withRetry(fn, method, config, sleepImpl = sleep) {
|
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
// src/region.ts
|
|
56
|
-
|
|
59
|
+
var cache = /* @__PURE__ */ new WeakMap();
|
|
60
|
+
function regionalOriginFromLocation(host, location, base) {
|
|
61
|
+
const from = parseApiHost(host);
|
|
62
|
+
if (from === null || from.orgSlug === null || from.region !== null) return null;
|
|
63
|
+
let u;
|
|
64
|
+
try {
|
|
65
|
+
u = new URL(location, base);
|
|
66
|
+
} catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
if (u.protocol !== "https:" || u.username !== "" || u.password !== "" || u.port !== "") return null;
|
|
70
|
+
const to = parseApiHost(u.hostname);
|
|
71
|
+
if (to === null || to.region === null) return null;
|
|
72
|
+
if (to.orgSlug !== from.orgSlug || to.family !== from.family || to.apex !== from.apex) return null;
|
|
73
|
+
return `https://${u.hostname}`;
|
|
74
|
+
}
|
|
75
|
+
async function probe(config) {
|
|
76
|
+
const fetchImpl = config.fetch ?? globalThis.fetch;
|
|
77
|
+
const configured = config.baseUrl;
|
|
78
|
+
if (typeof fetchImpl !== "function") return { origin: configured, cacheable: false };
|
|
79
|
+
let host;
|
|
80
|
+
try {
|
|
81
|
+
host = new URL(configured).hostname;
|
|
82
|
+
} catch {
|
|
83
|
+
return { origin: configured, cacheable: true };
|
|
84
|
+
}
|
|
85
|
+
const parsedHost = parseApiHost(host);
|
|
86
|
+
if (parsedHost === null || parsedHost.orgSlug === null || parsedHost.region !== null) return { origin: configured, cacheable: true };
|
|
87
|
+
try {
|
|
88
|
+
const res = await fetchImpl(`${configured.replace(/\/+$/, "")}/`, {
|
|
89
|
+
method: "HEAD",
|
|
90
|
+
redirect: "manual",
|
|
91
|
+
headers: { accept: "*/*" }
|
|
92
|
+
});
|
|
93
|
+
if (res.status === 308) {
|
|
94
|
+
const loc = res.headers.get("location");
|
|
95
|
+
const origin = loc === null ? null : regionalOriginFromLocation(host, loc, configured);
|
|
96
|
+
if (origin !== null) return { origin, cacheable: true };
|
|
97
|
+
}
|
|
98
|
+
return { origin: configured, cacheable: true };
|
|
99
|
+
} catch {
|
|
100
|
+
return { origin: configured, cacheable: false };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function resolveRegionalOrigin(config) {
|
|
104
|
+
const hit = cache.get(config);
|
|
105
|
+
if (hit !== void 0) return hit;
|
|
106
|
+
const p = probe(config).then((r) => {
|
|
107
|
+
if (!r.cacheable) cache.delete(config);
|
|
108
|
+
return r.origin;
|
|
109
|
+
});
|
|
110
|
+
cache.set(config, p);
|
|
111
|
+
return p;
|
|
112
|
+
}
|
|
57
113
|
|
|
58
114
|
// src/http.ts
|
|
59
115
|
async function request(config, init) {
|
|
@@ -85,7 +141,9 @@ async function requestOnce(config, init) {
|
|
|
85
141
|
method: init.method,
|
|
86
142
|
headers
|
|
87
143
|
};
|
|
88
|
-
if (init.
|
|
144
|
+
if (init.rawBody !== void 0) {
|
|
145
|
+
requestInit["body"] = init.rawBody;
|
|
146
|
+
} else if (init.body !== void 0) {
|
|
89
147
|
if (typeof FormData !== "undefined" && init.body instanceof FormData) {
|
|
90
148
|
requestInit["body"] = init.body;
|
|
91
149
|
} else {
|
|
@@ -231,4 +289,4 @@ export {
|
|
|
231
289
|
requestOnce,
|
|
232
290
|
requestStream
|
|
233
291
|
};
|
|
234
|
-
//# sourceMappingURL=chunk-
|
|
292
|
+
//# sourceMappingURL=chunk-KC4EDMBF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/retry.ts","../src/region.ts","../src/http.ts"],"sourcesContent":["/**\n * Retry + backoff for the HTTP client.\n *\n * Configured via `retryConfig: { maxRetries, backoff }`. Retries are\n * scoped to transient failures only — never retry on KError-carrying\n * non-2xx responses (those are semantic; the caller must handle them).\n *\n * Retryable conditions (transient; safe to retry idempotently):\n * - Transport error (`K9O_UPSTREAM_UNAVAILABLE` from `request()`) — network down,\n * DNS fail, connection refused, etc.\n * - HTTP 502 / 503 / 504 (gateway-up but downstream unavailable)\n * - HTTP 429 with `Retry-After` header (rate-limit; backoff by header value)\n *\n * Non-retryable (semantic; caller must handle):\n * - HTTP 4xx other than 429 (caller bug; retry won't help)\n * - HTTP 5xx other than 502/503/504 (gateway-side bug; surface immediately)\n * - KError on success-shape (rare; malformed envelope)\n *\n * Retry method discipline: only GET / HEAD / OPTIONS / DELETE are\n * retried by default — these are RFC-7231 idempotent. POST / PATCH /\n * PUT are NOT retried (caller can opt in via `idempotencyKey` header\n * when they assert idempotency).\n *\n * Backoff:\n * - `exponential` (default) — `initialDelayMs * 2^attempt`, capped at maxDelayMs; jittered ±25%\n * - `linear` — `initialDelayMs * (attempt + 1)`, capped at maxDelayMs; jittered ±25%\n * - `none` — no delay between attempts\n */\n\nimport { KError } from \"./kerror.ts\";\nimport type { RetryConfig } from \"./types.ts\";\n\nconst RETRYABLE_METHODS = new Set([\"GET\", \"HEAD\", \"OPTIONS\", \"DELETE\"]);\nconst RETRYABLE_STATUS = new Set([502, 503, 504]);\n\nexport const DEFAULT_RETRY_CONFIG: Required<RetryConfig> = {\n maxRetries: 3,\n backoff: \"exponential\",\n initialDelayMs: 100,\n maxDelayMs: 5000,\n};\n\n/**\n * Decide if a thrown error is retryable.\n * Only KError instances are inspected; other thrown values propagate\n * (programmer error / unrecoverable state).\n */\nexport function isRetryable(error: unknown, method: string): boolean {\n if (!RETRYABLE_METHODS.has(method.toUpperCase())) return false;\n if (!(error instanceof KError)) return false;\n if (error.code === \"K9O_UPSTREAM_UNAVAILABLE\") return true;\n if (error.status !== null && RETRYABLE_STATUS.has(error.status)) return true;\n if (error.status === 429) return true; // rate-limit; respect Retry-After if surfaced in context\n return false;\n}\n\n/**\n * Compute the backoff delay for a given attempt index (0-based).\n */\nexport function computeDelay(config: Required<RetryConfig>, attempt: number): number {\n let base: number;\n if (config.backoff === \"none\") return 0;\n if (config.backoff === \"linear\") base = config.initialDelayMs * (attempt + 1);\n else base = config.initialDelayMs * Math.pow(2, attempt);\n const capped = Math.min(base, config.maxDelayMs);\n // ±25% jitter\n const jitter = capped * 0.25 * (Math.random() * 2 - 1);\n return Math.max(0, Math.floor(capped + jitter));\n}\n\n/**\n * Sleep helper. Pulled out so tests can stub it via injection.\n */\nexport function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Execute a function with retry. The function is called up to\n * `maxRetries + 1` times; non-retryable failures throw immediately.\n *\n * @param fn - The function to execute (typically the HTTP request)\n * @param method - HTTP method (used to gate retry per RETRYABLE_METHODS)\n * @param config - Retry config; defaults applied if partial\n * @param sleepImpl - Sleep implementation (tests inject a no-op or fast variant)\n */\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n method: string,\n config: RetryConfig | undefined,\n sleepImpl: (ms: number) => Promise<void> = sleep,\n): Promise<T> {\n const c: Required<RetryConfig> = {\n ...DEFAULT_RETRY_CONFIG,\n ...(config ?? {}),\n };\n let lastError: unknown;\n for (let attempt = 0; attempt <= c.maxRetries; attempt++) {\n try {\n return await fn();\n } catch (e) {\n lastError = e;\n if (attempt === c.maxRetries) break;\n if (!isRetryable(e, method)) break;\n const delay = computeDelay(c, attempt);\n if (delay > 0) await sleepImpl(delay);\n }\n }\n throw lastError;\n}\n","/**\n * Regional host resolution.\n *\n * A region-free org host `<org>.api.<rest>` (or `<org>.api-staging.<rest>`) redirects GET/HEAD only and REFUSES a\n * request that carries a body, an `Authorization` header or a cookie (`400 K9O_INVALID_REQUEST_SHAPE`,\n * `region_free_host_takes_no_credentials`), so credentials must go to `<org>.api-<rgn>.<rest>`. The client learns the\n * region with ONE unauthenticated `HEAD` to the configured host, takes the `308` `Location`, and caches the regional\n * origin.\n *\n * Nothing here knows an apex. The host comes from the configured baseUrl; `<rest>` is whatever follows the second\n * label, so a public-cloud, internal-apex, air-gapped or localhost deployment all work. Properties:\n * - Only a host shaped `<org>.api.<rest>` / `<org>.api-staging.<rest>` is probed. A regional, single-label, IP,\n * localhost or any other baseUrl is used exactly as given.\n * - The probe carries NO credential and none of the configured default headers (they may hold one).\n * - The `Location` is trusted only if it is `https`, has no userinfo/port, and its host is exactly the SAME org and\n * SAME remainder with a 4-character region code appended to the second label. Anything else is ignored and the\n * configured baseUrl stays, so a hostile redirect can never receive a credential.\n * - Not a redirect, or any failure: the configured baseUrl stays and the server's own refusal, if any, is what the\n * caller sees. A definite answer is cached for the life of the client's config; a network failure is not cached.\n */\n\nimport { parseApiHost } from \"./url-grammar.ts\";\n\n/** The slice of a client config this module needs. */\nexport type RegionConfig = { baseUrl: string; fetch?: typeof globalThis.fetch | undefined };\n\nconst cache = new WeakMap<RegionConfig, Promise<string>>();\n\n/** The regional origin a `Location` may name for `host`, or null if it is not exactly that. Pure. */\nexport function regionalOriginFromLocation(host: string, location: string, base: string): string | null {\n const from = parseApiHost(host);\n if (from === null || from.orgSlug === null || from.region !== null) return null;\n let u: URL;\n try {\n u = new URL(location, base);\n } catch {\n return null;\n }\n if (u.protocol !== \"https:\" || u.username !== \"\" || u.password !== \"\" || u.port !== \"\") return null;\n const to = parseApiHost(u.hostname);\n if (to === null || to.region === null) return null;\n if (to.orgSlug !== from.orgSlug || to.family !== from.family || to.apex !== from.apex) return null;\n return `https://${u.hostname}`;\n}\n\nasync function probe(config: RegionConfig): Promise<{ origin: string; cacheable: boolean }> {\n const fetchImpl = config.fetch ?? globalThis.fetch;\n const configured = config.baseUrl;\n if (typeof fetchImpl !== \"function\") return { origin: configured, cacheable: false };\n let host: string;\n try {\n host = new URL(configured).hostname;\n } catch {\n return { origin: configured, cacheable: true };\n }\n const parsedHost = parseApiHost(host);\n if (parsedHost === null || parsedHost.orgSlug === null || parsedHost.region !== null) return { origin: configured, cacheable: true };\n try {\n const res = await fetchImpl(`${configured.replace(/\\/+$/, \"\")}/`, {\n method: \"HEAD\",\n redirect: \"manual\",\n headers: { accept: \"*/*\" },\n } as Parameters<typeof fetchImpl>[1]);\n if (res.status === 308) {\n const loc = res.headers.get(\"location\");\n const origin = loc === null ? null : regionalOriginFromLocation(host, loc, configured);\n if (origin !== null) return { origin, cacheable: true };\n }\n return { origin: configured, cacheable: true };\n } catch {\n return { origin: configured, cacheable: false };\n }\n}\n\n/** The origin requests for this client go to: the cached regional origin once learned, else the configured baseUrl. */\nexport function resolveRegionalOrigin(config: RegionConfig): Promise<string> {\n const hit = cache.get(config);\n if (hit !== undefined) return hit;\n const p = probe(config).then((r) => {\n if (!r.cacheable) cache.delete(config);\n return r.origin;\n });\n cache.set(config, p);\n return p;\n}\n","/**\n * HTTP client abstraction for `@k9o/core`.\n *\n * Lifted + generalised from `@k9o/gate/client.ts` (the auth-pillar SDK's\n * proven pattern). This client is per-instance — `createClient` constructs\n * one bound to its config; per-call options merge into the bound defaults.\n *\n * Responsibilities:\n * - URL construction against bound `baseUrl` + per-call `path` + query\n * - Envelope unwrap on 2xx (rejects malformed envelopes as INTERNAL_ERROR)\n * - KError rehydration on non-2xx\n * - Bearer token injection (from per-call override OR bound default)\n * - Project context header (`x-k9o-project-slug`) injection when bound\n * - Retry with exponential backoff per RetryConfig (see retry.ts) — not available\n * yet; requests are currently a single attempt\n *\n * The client is intentionally minimal — per-resource SDK methods compose\n * the path + body + bearer themselves and hand to `request()`. This keeps\n * the HTTP layer dumb and the per-resource logic auditable.\n */\n\nimport { KError, kerrorFromWire, type KErrorWire } from \"./kerror.ts\";\nimport type { Envelope } from \"./envelope.ts\";\nimport { withRetry } from \"./retry.ts\";\nimport type { RetryConfig } from \"./types.ts\";\nimport { resolveRegionalOrigin } from \"./region.ts\";\n\n/**\n * Per-instance HTTP client config. Constructed by `createClient`; passed\n * to `request()` per call.\n */\nexport type HttpClientConfig = {\n /** Normalised baseUrl (scheme + host); set via createClient. */\n baseUrl: string;\n /** Default bearer token (per-call override wins). */\n apiKey?: string | undefined;\n /** Active project slug — injected as `x-k9o-project-slug` header on every request. */\n projectSlug?: string | undefined;\n /** Active org slug — injected as `x-k9o-org-slug` header on every request. */\n orgSlug?: string | undefined;\n /** Custom fetch impl (defaults to globalThis.fetch); use for tests + non-standard runtimes. */\n fetch?: typeof globalThis.fetch | undefined;\n /** Default headers merged into every request. */\n defaultHeaders?: Record<string, string> | undefined;\n /** Retry config; idempotent methods only (GET/HEAD/OPTIONS/DELETE) — applied when transport fails or upstream returns 502/503/504/429. */\n retryConfig?: RetryConfig | undefined;\n};\n\n/**\n * Per-call request options.\n */\nexport type RequestInit = {\n method: \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\";\n path: string;\n body?: unknown;\n /** Send these bytes as the body, unchanged, with the `content-type` given in `headers` (for binary routes). Wins over `body`. */\n rawBody?: Uint8Array | ArrayBuffer | Blob | undefined;\n /** Per-call bearer override (wins over config.apiKey). */\n bearerToken?: string | undefined;\n /** Per-call additional headers (merged with config.defaultHeaders + auth). */\n headers?: Record<string, string> | undefined;\n /** Per-call query params. */\n query?: Record<string, string | number | boolean | undefined> | undefined;\n};\n\n/**\n * Issue a request against the gateway. Returns the unwrapped Envelope\n * on 2xx; throws KError on non-2xx, transport failures, or malformed\n * responses.\n */\nexport async function request<T>(\n config: HttpClientConfig,\n init: RequestInit,\n): Promise<Envelope<T>> {\n return withRetry(() => requestOnce<T>(config, init), init.method, config.retryConfig);\n}\n\n/**\n * Single-attempt request implementation. `request` wraps this in\n * retry-with-backoff per `config.retryConfig`. Exported for tests that\n * want to bypass retry semantics.\n */\nexport async function requestOnce<T>(\n config: HttpClientConfig,\n init: RequestInit,\n): Promise<Envelope<T>> {\n const fetchImpl = config.fetch ?? globalThis.fetch;\n if (typeof fetchImpl !== \"function\") {\n throw new KError({\n code: \"K9O_INTERNAL_ERROR\",\n message:\n \"No fetch implementation available — pass {fetch} in createClient options for runtimes without a global fetch\",\n context: {},\n recommendation: \"Use Node 18+, Bun 1.1+, or a modern browser; otherwise supply a fetch polyfill via the fetch option.\",\n });\n }\n\n const url = buildUrl(await resolveRegionalOrigin(config), init.path, init.query);\n const headers: Record<string, string> = {\n accept: \"application/json\",\n ...(config.defaultHeaders ?? {}),\n ...(config.projectSlug ? { \"x-k9o-project-slug\": config.projectSlug } : {}),\n ...(config.orgSlug ? { \"x-k9o-org-slug\": config.orgSlug } : {}),\n ...(init.headers ?? {}),\n };\n const bearer = init.bearerToken ?? config.apiKey;\n if (typeof bearer === \"string\" && bearer.length > 0) {\n headers[\"authorization\"] = `Bearer ${bearer}`;\n }\n const requestInit: Record<string, unknown> = {\n method: init.method,\n headers,\n };\n if (init.rawBody !== undefined) {\n requestInit[\"body\"] = init.rawBody;\n } else if (init.body !== undefined) {\n if (typeof FormData !== \"undefined\" && init.body instanceof FormData) {\n // Multipart: the runtime sets the content type (with its boundary); never JSON-encode it.\n requestInit[\"body\"] = init.body;\n } else {\n headers[\"content-type\"] = \"application/json\";\n requestInit[\"body\"] = JSON.stringify(init.body);\n }\n }\n\n let response: Response;\n try {\n response = await fetchImpl(url, requestInit as Parameters<typeof fetchImpl>[1]);\n } catch (e) {\n throw new KError({\n code: \"K9O_UPSTREAM_UNAVAILABLE\",\n message: `Network error reaching ${url}: ${e instanceof Error ? e.message : String(e)}`,\n context: { url },\n recommendation: \"Check connectivity to the gateway; retry with backoff if transient.\",\n });\n }\n\n // 204 No Content — synthesize an empty envelope (some DELETE / PUT endpoints).\n if (response.status === 204) {\n return { data: undefined as unknown as T, meta: {} };\n }\n\n let parsed: unknown;\n try {\n parsed = await response.json();\n } catch (e) {\n throw new KError({\n code: \"K9O_INTERNAL_ERROR\",\n message: `Failed to parse JSON response from ${url}: ${e instanceof Error ? e.message : String(e)}`,\n context: { url, status: response.status },\n status: response.status,\n });\n }\n\n if (response.status >= 200 && response.status < 300) {\n if (!isEnvelope<T>(parsed)) {\n throw new KError({\n code: \"K9O_INTERNAL_ERROR\",\n message: `Malformed envelope from ${url}: expected {data, meta}`,\n context: { url, status: response.status },\n status: response.status,\n });\n }\n return parsed;\n }\n\n if (isKErrorWire(parsed)) {\n throw kerrorFromWire(parsed, response.status);\n }\n throw new KError({\n code: \"K9O_INTERNAL_ERROR\",\n message: `Upstream returned ${response.status} with non-KError body`,\n context: { url, status: response.status, body: parsed },\n status: response.status,\n });\n}\n\n/**\n * Open a streaming request against the gateway. Returns the raw\n * `Response` on 2xx (caller consumes `response.body` as an SSE / chunked\n * stream); throws `KError` on non-2xx, transport failures, or a missing\n * body. Used by streaming surfaces — `client.compute.llms.invokeStream`\n * + `client.realtime.subscribe` SSE transport.\n *\n * Unlike `request()`, this does NOT unwrap the JSON envelope or apply\n * retry — streaming responses are consumed incrementally and retry on a\n * half-consumed stream is unsafe.\n */\nexport async function requestStream(\n config: HttpClientConfig,\n init: RequestInit & { accept?: string },\n): Promise<Response> {\n const fetchImpl = config.fetch ?? globalThis.fetch;\n if (typeof fetchImpl !== \"function\") {\n throw new KError({\n code: \"K9O_INTERNAL_ERROR\",\n message: \"No fetch implementation available — pass {fetch} in createClient options\",\n context: {},\n recommendation: \"Use Node 18+, Bun 1.1+, or a modern browser; otherwise supply a fetch polyfill via the fetch option.\",\n });\n }\n\n const url = buildUrl(await resolveRegionalOrigin(config), init.path, init.query);\n const headers: Record<string, string> = {\n accept: init.accept ?? \"text/event-stream\",\n ...(config.defaultHeaders ?? {}),\n ...(config.projectSlug ? { \"x-k9o-project-slug\": config.projectSlug } : {}),\n ...(config.orgSlug ? { \"x-k9o-org-slug\": config.orgSlug } : {}),\n ...(init.headers ?? {}),\n };\n const bearer = init.bearerToken ?? config.apiKey;\n if (typeof bearer === \"string\" && bearer.length > 0) {\n headers[\"authorization\"] = `Bearer ${bearer}`;\n }\n const requestInit: Record<string, unknown> = { method: init.method, headers };\n if (init.body !== undefined) {\n headers[\"content-type\"] = \"application/json\";\n requestInit[\"body\"] = JSON.stringify(init.body);\n }\n\n let response: Response;\n try {\n response = await fetchImpl(url, requestInit as Parameters<typeof fetchImpl>[1]);\n } catch (e) {\n throw new KError({\n code: \"K9O_UPSTREAM_UNAVAILABLE\",\n message: `Network error reaching ${url}: ${e instanceof Error ? e.message : String(e)}`,\n context: { url },\n recommendation: \"Check connectivity to the gateway; retry with backoff if transient.\",\n });\n }\n\n if (response.status < 200 || response.status >= 300) {\n let parsed: unknown = null;\n try {\n parsed = await response.json();\n } catch {\n /* non-JSON error body */\n }\n if (isKErrorWire(parsed)) throw kerrorFromWire(parsed, response.status);\n throw new KError({\n code: \"K9O_INTERNAL_ERROR\",\n message: `Upstream returned ${response.status} opening stream`,\n context: { url, status: response.status, body: parsed },\n status: response.status,\n });\n }\n\n if (response.body === null) {\n throw new KError({\n code: \"K9O_INTERNAL_ERROR\",\n message: `Stream response from ${url} has no body`,\n context: { url, status: response.status },\n status: response.status,\n });\n }\n\n return response;\n}\n\nfunction buildUrl(\n baseUrl: string,\n path: string,\n query: RequestInit[\"query\"],\n): string {\n const trimmedBase = baseUrl.endsWith(\"/\") ? baseUrl.slice(0, -1) : baseUrl;\n const trimmedPath = path.startsWith(\"/\") ? path : `/${path}`;\n let url = `${trimmedBase}${trimmedPath}`;\n if (query !== undefined) {\n const params = new URLSearchParams();\n for (const [k, v] of Object.entries(query)) {\n if (v !== undefined) params.set(k, String(v));\n }\n const qs = params.toString();\n if (qs.length > 0) url += `?${qs}`;\n }\n return url;\n}\n\nfunction isEnvelope<T>(value: unknown): value is Envelope<T> {\n if (value === null || typeof value !== \"object\") return false;\n const v = value as { data?: unknown; meta?: unknown };\n return \"data\" in v && \"meta\" in v && typeof v.meta === \"object\" && v.meta !== null;\n}\n\nfunction isKErrorWire(value: unknown): value is KErrorWire {\n if (value === null || typeof value !== \"object\") return false;\n const v = value as { code?: unknown; message?: unknown };\n return typeof v.code === \"string\" && typeof v.message === \"string\";\n}\n"],"mappings":"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","names":[]}
|
|
@@ -46,13 +46,29 @@ function validateBaseUrl(baseUrl) {
|
|
|
46
46
|
const normalised = `${parsed.protocol}//${parsed.host}`;
|
|
47
47
|
return normalised;
|
|
48
48
|
}
|
|
49
|
-
var
|
|
49
|
+
var DEFAULT_BASE_URL = "https://api.k9o.io";
|
|
50
|
+
var API_LABEL = /^(api|api-staging)(?:-([a-z]{2}\d{2}))?$/;
|
|
51
|
+
function parseApiHost(host) {
|
|
52
|
+
const labels = host.toLowerCase().split(".");
|
|
53
|
+
if (labels.some((l) => l.length === 0)) return null;
|
|
54
|
+
const mk = (gatewayLabel, orgSlug, rest) => {
|
|
55
|
+
const m = API_LABEL.exec(gatewayLabel);
|
|
56
|
+
if (m === null || rest.length === 0) return null;
|
|
57
|
+
return { orgSlug, family: m[1], region: m[2] ?? null, apex: rest.join(".") };
|
|
58
|
+
};
|
|
59
|
+
if (labels.length >= 3 && SLUG_RE.test(labels[0]) && !labels[0].endsWith("-") && API_LABEL.test(labels[1])) {
|
|
60
|
+
return mk(labels[1], labels[0], labels.slice(2));
|
|
61
|
+
}
|
|
62
|
+
if (labels.length >= 2 && API_LABEL.test(labels[0])) return mk(labels[0], null, labels.slice(1));
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
50
65
|
function resolveBaseUrl(baseUrl, orgSlug) {
|
|
51
|
-
const validated = validateBaseUrl(baseUrl ??
|
|
66
|
+
const validated = validateBaseUrl(baseUrl ?? DEFAULT_BASE_URL);
|
|
52
67
|
const parsed = new URL(validated);
|
|
53
|
-
|
|
68
|
+
const host = parseApiHost(parsed.hostname);
|
|
69
|
+
if (host !== null && host.orgSlug === null && host.region === null && orgSlug) {
|
|
54
70
|
validateSlug(orgSlug, "org");
|
|
55
|
-
return `${parsed.protocol}//${orgSlug}.${parsed.
|
|
71
|
+
return `${parsed.protocol}//${orgSlug}.${parsed.host}`;
|
|
56
72
|
}
|
|
57
73
|
return validated;
|
|
58
74
|
}
|
|
@@ -84,10 +100,12 @@ function platformPath(resource) {
|
|
|
84
100
|
|
|
85
101
|
export {
|
|
86
102
|
validateBaseUrl,
|
|
103
|
+
DEFAULT_BASE_URL,
|
|
104
|
+
parseApiHost,
|
|
87
105
|
resolveBaseUrl,
|
|
88
106
|
validateSlug,
|
|
89
107
|
projectPath,
|
|
90
108
|
orgPath,
|
|
91
109
|
platformPath
|
|
92
110
|
};
|
|
93
|
-
//# sourceMappingURL=chunk-
|
|
111
|
+
//# sourceMappingURL=chunk-UL53GNYM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/url-grammar.ts"],"sourcesContent":["/**\n * URL grammar.\n *\n * Canonical URL shapes:\n * - Member-plane (project-scoped): `<orgSlug>.api.<apex>/@<projectSlug>/<sub-resource>/...`\n * - Org-plane (org-scoped): `<orgSlug>.api.<apex>/<resource>/...`\n * - Platform-plane (cross-org): `api.<apex>/discovery/...` + `api.<apex>/auth/...` etc.\n *\n * `<apex>` is whatever the deployment uses (public cloud, internal, air-gapped, localhost);\n * the SDK takes the host from the baseUrl it is given and never matches it against a list.\n *\n * The `baseUrl` passed to `createClient` is the gateway host root —\n * scheme + host (+ optional port) Path-bearing baseUrls\n * are rejected with `K9O_INVALID_BASE_URL`.\n *\n * The SDK constructs URLs by composing `baseUrl + path-from-builder`.\n * Per-resource SDK methods use the builders below to assemble their\n * `path` argument before handing to the HTTP client.\n */\n\nimport { KError } from \"./kerror.ts\";\n\n/**\n * Validate a `baseUrl`. Returns the normalised form\n * (trailing-slash stripped) on success; throws `K9O_INVALID_BASE_URL`\n * on any violation.\n *\n * Accepted shapes:\n * - `https://api.k9o.io`\n * - `https://acme.api.k9o.io`\n * - `http://localhost:8080`\n * - `https://api.k9o.io:443`\n *\n * Rejected shapes:\n * - path segments (`https://api.k9o.io/v2`)\n * - missing scheme (`api.k9o.io`)\n * - non-http(s) scheme (`wss://api.k9o.io`)\n * - query / fragment (`https://api.k9o.io?x=1`)\n */\nexport function validateBaseUrl(baseUrl: string): string {\n let parsed: URL;\n try {\n parsed = new URL(baseUrl);\n } catch {\n throw new KError({\n code: \"K9O_INVALID_BASE_URL\",\n message: `baseUrl \"${baseUrl}\" is not a valid URL`,\n context: { baseUrl },\n recommendation: \"Pass a scheme + host like `https://api.k9o.io` or `https://<orgSlug>.api.k9o.io`\",\n status: null,\n });\n }\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n throw new KError({\n code: \"K9O_INVALID_BASE_URL\",\n message: `baseUrl \"${baseUrl}\" uses unsupported scheme \"${parsed.protocol}\"; expected http: or https:`,\n context: { baseUrl, scheme: parsed.protocol },\n recommendation: \"Use http:// or https://. WebSocket subscriptions are wired internally over SSE on the same host.\",\n status: null,\n });\n }\n if (parsed.pathname !== \"/\" && parsed.pathname !== \"\") {\n throw new KError({\n code: \"K9O_INVALID_BASE_URL\",\n message: `baseUrl \"${baseUrl}\" must not contain a path segment; got \"${parsed.pathname}\"`,\n context: { baseUrl, path: parsed.pathname },\n recommendation: \"Strip the path segment — pass host only. URL paths are composed by the SDK.\",\n status: null,\n });\n }\n if (parsed.search.length > 0 || parsed.hash.length > 0) {\n throw new KError({\n code: \"K9O_INVALID_BASE_URL\",\n message: `baseUrl \"${baseUrl}\" must not contain query or fragment`,\n context: { baseUrl, query: parsed.search, fragment: parsed.hash },\n recommendation: \"Pass host only.\",\n status: null,\n });\n }\n // Normalise — strip trailing slash so concatenation is predictable.\n const normalised = `${parsed.protocol}//${parsed.host}`;\n return normalised;\n}\n\n/** The convenience default when the caller passes no baseUrl. A default value only: never used to parse a host. */\nexport const DEFAULT_BASE_URL = \"https://api.k9o.io\";\n\n/** The structure of a gateway API host, read from the host itself. */\nexport type ApiHost = {\n /** First label of an org host (`acme` in `acme.api.corp.internal`); null for a platform host (`api.corp.internal`). */\n orgSlug: string | null;\n /** `api` or `api-staging`: the label that marks a gateway host. */\n family: \"api\" | \"api-staging\";\n /** 4-character region code when the host is regional (`api-de01`), else null (region-free). */\n region: string | null;\n /** Everything after the gateway label (`corp.internal`, `k9o.io`): the apex, never matched against a list. */\n apex: string;\n};\n\nconst API_LABEL = /^(api|api-staging)(?:-([a-z]{2}\\d{2}))?$/;\n\n/**\n * Read the org slug and region out of a gateway host without knowing its apex. The gateway label is `api` or\n * `api-staging`, optionally followed by `-<rgn>`; the label before it is the org slug and whatever follows it is the\n * apex. Returns null for any other shape (localhost, an IP, a single-label host, a host with no gateway label), which\n * callers use exactly as given.\n */\nexport function parseApiHost(host: string): ApiHost | null {\n const labels = host.toLowerCase().split(\".\");\n if (labels.some((l) => l.length === 0)) return null;\n const mk = (gatewayLabel: string, orgSlug: string | null, rest: string[]): ApiHost | null => {\n const m = API_LABEL.exec(gatewayLabel);\n if (m === null || rest.length === 0) return null;\n return { orgSlug, family: m[1] as \"api\" | \"api-staging\", region: m[2] ?? null, apex: rest.join(\".\") };\n };\n // Org host first: `<org>.<api[-rgn]>.<apex>`.\n if (labels.length >= 3 && SLUG_RE.test(labels[0]!) && !labels[0]!.endsWith(\"-\") && API_LABEL.test(labels[1]!)) {\n return mk(labels[1]!, labels[0]!, labels.slice(2));\n }\n // Platform host: `api.<apex>`.\n if (labels.length >= 2 && API_LABEL.test(labels[0]!)) return mk(labels[0]!, null, labels.slice(1));\n return null;\n}\n\n/**\n * Resolve the effective baseUrl. `baseUrl` is OPTIONAL: omitted, it defaults to {@link DEFAULT_BASE_URL} (the public\n * cloud, a convenience only). When the (validated) host is a platform host (`api.<apex>` or `api-staging.<apex>`, any\n * apex) and `orgSlug` is supplied, the canonical org host `<orgSlug>.<host>` is composed. Any other host (an org host\n * already, an internal or air-gapped host, localhost) passes through unchanged; `orgSlug` is then only sent as a header.\n */\nexport function resolveBaseUrl(baseUrl: string | undefined, orgSlug?: string): string {\n const validated = validateBaseUrl(baseUrl ?? DEFAULT_BASE_URL);\n const parsed = new URL(validated);\n const host = parseApiHost(parsed.hostname);\n if (host !== null && host.orgSlug === null && host.region === null && orgSlug) {\n validateSlug(orgSlug, \"org\");\n return `${parsed.protocol}//${orgSlug}.${parsed.host}`;\n }\n return validated;\n}\n\n/**\n * Slug format validator:\n * `[a-z][a-z0-9-]{1,62}` and must not end with `-`.\n */\nconst SLUG_RE = /^[a-z][a-z0-9-]{1,62}$/;\n\nexport function validateSlug(slug: string, kind: \"org\" | \"project\"): void {\n if (!SLUG_RE.test(slug) || slug.endsWith(\"-\")) {\n throw new KError({\n code: \"K9O_INVALID_FIELD_VALUE\",\n message: `${kind}Slug \"${slug}\" does not match required format ^[a-z][a-z0-9-]{1,62}$ (must not end with '-')`,\n context: { slug, kind },\n recommendation: `Pass a slug matching the format — lowercase letters / digits / hyphens; 2-63 chars; starts with a letter; does not end with hyphen.`,\n status: null,\n });\n }\n}\n\n/**\n * Build a project-scoped (member-plane) URL path per the URL grammar\n * E.g. `projectPath('member-portal', 'collections/posts/documents')`\n * → `/@member-portal/collections/posts/documents`.\n *\n * The caller is responsible for normalising sub-resource components\n * (URL-encoding goes through the caller's path construction; this\n * helper handles only the prefix + slug shape).\n */\nexport function projectPath(projectSlug: string, subResource: string): string {\n validateSlug(projectSlug, \"project\");\n const cleaned = subResource.startsWith(\"/\") ? subResource.slice(1) : subResource;\n return `/@${projectSlug}/${cleaned}`;\n}\n\n/**\n * Build an org-plane path. E.g. `orgPath('org')` → `/org`,\n * `orgPath('projects')` → `/projects`.\n */\nexport function orgPath(resource: string): string {\n const cleaned = resource.startsWith(\"/\") ? resource.slice(1) : resource;\n return `/${cleaned}`;\n}\n\n/**\n * Build a platform-plane path (cross-org). E.g.\n * `platformPath('discovery/projects')` → `/discovery/projects`,\n * `platformPath('auth/sign-in')` → `/auth/sign-in`.\n *\n * Composes with the platform baseUrl (`https://api.k9o.io`) — not the\n * org-subdomain baseUrl.\n */\nexport function platformPath(resource: string): string {\n const cleaned = resource.startsWith(\"/\") ? resource.slice(1) : resource;\n return `/${cleaned}`;\n}\n"],"mappings":"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","names":[]}
|
package/dist/http.d.ts
CHANGED
|
@@ -49,6 +49,8 @@ type RequestInit = {
|
|
|
49
49
|
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
50
50
|
path: string;
|
|
51
51
|
body?: unknown;
|
|
52
|
+
/** Send these bytes as the body, unchanged, with the `content-type` given in `headers` (for binary routes). Wins over `body`. */
|
|
53
|
+
rawBody?: Uint8Array | ArrayBuffer | Blob | undefined;
|
|
52
54
|
/** Per-call bearer override (wins over config.apiKey). */
|
|
53
55
|
bearerToken?: string | undefined;
|
|
54
56
|
/** Per-call additional headers (merged with config.defaultHeaders + auth). */
|