ankka 0.12.0 → 0.13.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.
Files changed (55) hide show
  1. package/dist/_proto/ankka/protocol/v1/client_pb.d.ts +594 -0
  2. package/dist/_proto/ankka/protocol/v1/client_pb.js +172 -30
  3. package/dist/_proto/ankka/protocol/v1/discovery_pb.d.ts +134 -0
  4. package/dist/_proto/ankka/protocol/v1/discovery_pb.js +51 -36
  5. package/dist/_proto/ankka/protocol/v1/mcp_pb.d.ts +120 -0
  6. package/dist/_proto/ankka/protocol/v1/mcp_pb.js +29 -0
  7. package/dist/agent.d.ts +10 -0
  8. package/dist/agent.js +20 -0
  9. package/dist/autonomous.d.ts +5 -0
  10. package/dist/autonomous.js +10 -0
  11. package/dist/client-keys.d.ts +89 -0
  12. package/dist/client-keys.js +182 -0
  13. package/dist/consumer.d.ts +5 -0
  14. package/dist/consumer.js +10 -0
  15. package/dist/endpoint.d.ts +10 -0
  16. package/dist/endpoint.js +20 -0
  17. package/dist/graph.d.ts +5 -0
  18. package/dist/graph.js +10 -0
  19. package/dist/index.d.ts +6 -1
  20. package/dist/index.js +5 -0
  21. package/dist/mcp-endpoint.d.ts +144 -0
  22. package/dist/mcp-endpoint.js +256 -0
  23. package/dist/routes.d.ts +25 -2
  24. package/dist/routes.js +29 -2
  25. package/dist/server/discovery.d.ts +7 -0
  26. package/dist/server/discovery.js +26 -2
  27. package/dist/server/http.d.ts +8 -0
  28. package/dist/server/http.js +10 -4
  29. package/dist/server/mcp.d.ts +17 -0
  30. package/dist/server/mcp.js +157 -0
  31. package/dist/server/server.d.ts +11 -1
  32. package/dist/server/server.js +19 -0
  33. package/dist/server/storageEndpoint.d.ts +14 -0
  34. package/dist/server/storageEndpoint.js +82 -0
  35. package/dist/service.d.ts +6 -1
  36. package/dist/service.js +47 -4
  37. package/dist/sigv4.d.ts +47 -0
  38. package/dist/sigv4.js +149 -0
  39. package/dist/spec.d.ts +8 -2
  40. package/dist/spec.js +53 -3
  41. package/dist/storage.d.ts +288 -0
  42. package/dist/storage.js +1081 -0
  43. package/dist/testkit/integration.d.ts +9 -0
  44. package/dist/testkit/integration.js +5 -1
  45. package/dist/testkit/kinds.d.ts +16 -4
  46. package/dist/testkit/kinds.js +36 -14
  47. package/dist/testkit/unit.d.ts +4 -1
  48. package/dist/testkit/unit.js +8 -4
  49. package/dist/timedAction.d.ts +10 -0
  50. package/dist/timedAction.js +20 -0
  51. package/dist/version.d.ts +1 -1
  52. package/dist/version.js +1 -1
  53. package/dist/workflow.d.ts +8 -0
  54. package/dist/workflow.js +15 -0
  55. package/package.json +1 -1
package/dist/sigv4.js ADDED
@@ -0,0 +1,149 @@
1
+ // AWS Signature Version 4, as S3 and every store that speaks it reads it: a request signed in its
2
+ // headers, and a URL signed in its query string (feature 055). The runtime's `SigV4.scala` made the
3
+ // same choices, and the same vectors hold both: paths are encoded once, segment by segment, as S3
4
+ // expects; a put's payload is `UNSIGNED-PAYLOAD`, so a stream is sent once and never hashed.
5
+ //
6
+ // The signer works on a request's parts — its method, its endpoint, its path and query *before*
7
+ // encoding, the headers it signs and the hash of its payload — so the header form and the query form
8
+ // share one canonical request, and AWS's published examples test it with no client at all.
9
+ import { createHash, createHmac } from "node:crypto";
10
+ /** A key pair. Never printed whole: `toString` hides the secret. */
11
+ export class Credentials {
12
+ accessKey;
13
+ #secretKey;
14
+ constructor(accessKey, secretKey) {
15
+ this.accessKey = accessKey;
16
+ this.#secretKey = secretKey;
17
+ Object.freeze(this);
18
+ }
19
+ /** @internal */
20
+ get _secretKey() {
21
+ return this.#secretKey;
22
+ }
23
+ toString() {
24
+ return `Credentials(${this.accessKey}, ***)`;
25
+ }
26
+ toJSON() {
27
+ return this.toString();
28
+ }
29
+ }
30
+ export const ALGORITHM = "AWS4-HMAC-SHA256";
31
+ /** The payload hash a put sends so its body is streamed once and never hashed. */
32
+ export const UNSIGNED_PAYLOAD = "UNSIGNED-PAYLOAD";
33
+ const utf8 = new TextEncoder();
34
+ export function sha256Hex(data) {
35
+ return createHash("sha256").update(typeof data === "string" ? utf8.encode(data) : data).digest("hex");
36
+ }
37
+ export function hmac(key, data) {
38
+ return createHmac("sha256", key).update(data, "utf8").digest();
39
+ }
40
+ /** The hash of an empty payload. */
41
+ export const EMPTY_PAYLOAD = sha256Hex(new Uint8Array());
42
+ /** RFC 3986 encoding as SigV4 states it: unreserved characters kept, everything else `%XX` of its UTF-8 bytes. */
43
+ export function encode(text, encodeSlash) {
44
+ let out = "";
45
+ for (const b of utf8.encode(text)) {
46
+ const c = String.fromCharCode(b);
47
+ if ((b >= 0x41 && b <= 0x5a) || (b >= 0x61 && b <= 0x7a) || (b >= 0x30 && b <= 0x39) || c === "-" || c === "_" || c === "." || c === "~" || (c === "/" && !encodeSlash)) {
48
+ out += c;
49
+ }
50
+ else {
51
+ out += "%" + b.toString(16).toUpperCase().padStart(2, "0");
52
+ }
53
+ }
54
+ return out;
55
+ }
56
+ /** One request: the endpoint's scheme and authority, a decoded path and decoded query pairs. */
57
+ export class SignedRequest {
58
+ method;
59
+ endpoint;
60
+ path;
61
+ query;
62
+ constructor(method, endpoint, path, query = []) {
63
+ this.method = method;
64
+ this.endpoint = typeof endpoint === "string" ? new URL(endpoint) : endpoint;
65
+ this.path = path;
66
+ this.query = query;
67
+ Object.freeze(this);
68
+ }
69
+ /** The `host` header's value: the host, with the port when the endpoint names one. */
70
+ get host() {
71
+ return this.endpoint.port === "" ? this.endpoint.hostname : `${this.endpoint.hostname}:${this.endpoint.port}`;
72
+ }
73
+ get canonicalPath() {
74
+ const raw = this.endpoint.pathname === "/" ? "" : this.endpoint.pathname;
75
+ const base = raw.endsWith("/") ? raw.slice(0, -1) : raw;
76
+ const p = this.path.startsWith("/") ? this.path : "/" + this.path;
77
+ return base + encode(p, false);
78
+ }
79
+ get canonicalQuery() {
80
+ return this.query
81
+ .map(([k, v]) => [encode(k, true), encode(v, true)])
82
+ .sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0))
83
+ .map(([k, v]) => `${k}=${v}`)
84
+ .join("&");
85
+ }
86
+ /** The URL the request is sent to: the same encoding as the one signed. */
87
+ get url() {
88
+ const q = this.canonicalQuery;
89
+ return `${this.endpoint.protocol}//${this.host}${this.canonicalPath}${q === "" ? "" : "?" + q}`;
90
+ }
91
+ withQuery(more) {
92
+ return new SignedRequest(this.method, this.endpoint, this.path, [...this.query, ...more]);
93
+ }
94
+ }
95
+ function pad(n, width = 2) {
96
+ return String(n).padStart(width, "0");
97
+ }
98
+ /** `20150830T123600Z`. */
99
+ export function amzDate(now) {
100
+ return `${shortDate(now)}T${pad(now.getUTCHours())}${pad(now.getUTCMinutes())}${pad(now.getUTCSeconds())}Z`;
101
+ }
102
+ function shortDate(now) {
103
+ return `${pad(now.getUTCFullYear(), 4)}${pad(now.getUTCMonth() + 1)}${pad(now.getUTCDate())}`;
104
+ }
105
+ function signingKey(secret, now, region, service) {
106
+ const date = hmac(utf8.encode("AWS4" + secret), shortDate(now));
107
+ return hmac(hmac(hmac(date, region), service), "aws4_request");
108
+ }
109
+ function stringToSign(stamp, scope, canonicalRequest) {
110
+ return `${ALGORITHM}\n${stamp}\n${scope}\n${sha256Hex(canonicalRequest)}`;
111
+ }
112
+ /**
113
+ * The headers to send with `request` so the store believes it: `x-amz-date` and `Authorization`.
114
+ * `headers` are the ones to sign beside `host` and `x-amz-date` — an S3 request includes
115
+ * `x-amz-content-sha256` among them — and are sent as given.
116
+ */
117
+ export function sign(request, headers, payloadHash, credentials, region, service, now) {
118
+ const stamp = amzDate(now);
119
+ const signed = new Map();
120
+ for (const [k, v] of Object.entries(headers))
121
+ signed.set(k.toLowerCase(), v);
122
+ signed.set("host", request.host);
123
+ signed.set("x-amz-date", stamp);
124
+ const sorted = [...signed.entries()].map(([k, v]) => [k, v.trim().replace(/ +/g, " ")]).sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
125
+ const names = sorted.map(([k]) => k).join(";");
126
+ const canonical = sorted.map(([k, v]) => `${k}:${v}\n`).join("");
127
+ const scope = `${shortDate(now)}/${region}/${service}/aws4_request`;
128
+ const creq = [request.method, request.canonicalPath, request.canonicalQuery, canonical, names, payloadHash].join("\n");
129
+ const signature = hmac(signingKey(credentials._secretKey, now, region, service), stringToSign(stamp, scope, creq)).toString("hex");
130
+ return {
131
+ "x-amz-date": stamp,
132
+ Authorization: `${ALGORITHM} Credential=${credentials.accessKey}/${scope}, SignedHeaders=${names}, Signature=${signature}`,
133
+ };
134
+ }
135
+ /** A URL anyone may use, until it expires, for `request`'s method on its path. Signs `host` alone. */
136
+ export function presign(request, credentials, region, service, expiresSeconds, now) {
137
+ const stamp = amzDate(now);
138
+ const scope = `${shortDate(now)}/${region}/${service}/aws4_request`;
139
+ const withAuth = request.withQuery([
140
+ ["X-Amz-Algorithm", ALGORITHM],
141
+ ["X-Amz-Credential", `${credentials.accessKey}/${scope}`],
142
+ ["X-Amz-Date", stamp],
143
+ ["X-Amz-Expires", String(expiresSeconds)],
144
+ ["X-Amz-SignedHeaders", "host"],
145
+ ]);
146
+ const creq = [request.method, withAuth.canonicalPath, withAuth.canonicalQuery, `host:${request.host}\n`, "host", UNSIGNED_PAYLOAD].join("\n");
147
+ const signature = hmac(signingKey(credentials._secretKey, now, region, service), stringToSign(stamp, scope, creq)).toString("hex");
148
+ return withAuth.withQuery([["X-Amz-Signature", signature]]).url;
149
+ }
package/dist/spec.d.ts CHANGED
@@ -1,10 +1,16 @@
1
1
  import { type MessageInitShape } from "@bufbuild/protobuf";
2
- import { Endpoint_Acl, type CallerMatcherSchema, type Spec } from "./_proto/ankka/protocol/v1/discovery_pb.ts";
2
+ import { Endpoint_Acl, type McpAclSchema, type CallerMatcherSchema, type Spec } from "./_proto/ankka/protocol/v1/discovery_pb.ts";
3
3
  import type { Registry } from "./service.ts";
4
4
  import type { Acl } from "./routes.ts";
5
- export declare const PROTOCOL_VERSION = "1.19";
5
+ export declare const PROTOCOL_VERSION = "1.21";
6
6
  export declare const SDK_NAME = "ankka-typescript";
7
+ /** An HTTP endpoint's rule; client keys and `anyOf` are refused for one at registration, so they deny here. */
7
8
  export declare function aclToProto(acl: Acl): Endpoint_Acl;
8
9
  /** The callers a CALLERS acl names, as discovery carries them; empty for every other rule. */
9
10
  export declare function callersToProto(acl: Acl): MessageInitShape<typeof CallerMatcherSchema>[];
11
+ /**
12
+ * An MCP endpoint's or tool's rule as discovery carries it (1.20): each member of an `anyOf` sets its
13
+ * own field, so `anyOf(clientKeys, authenticated)` is `client_keys` and `tokens`.
14
+ */
15
+ export declare function mcpAclToProto(acl: Acl): MessageInitShape<typeof McpAclSchema>;
10
16
  export declare function renderSpec(registry: Registry): Spec;
package/dist/spec.js CHANGED
@@ -17,11 +17,12 @@ function toolInit(t) {
17
17
  ...(approval !== undefined ? { approval } : {}),
18
18
  };
19
19
  }
20
- export const PROTOCOL_VERSION = "1.19";
20
+ export const PROTOCOL_VERSION = "1.21";
21
21
  export const SDK_NAME = "ankka-typescript";
22
+ /** An HTTP endpoint's rule; client keys and `anyOf` are refused for one at registration, so they deny here. */
22
23
  export function aclToProto(acl) {
23
24
  if (typeof acl === "object")
24
- return Endpoint_Acl.CALLERS;
25
+ return acl.kind === "callers" ? Endpoint_Acl.CALLERS : Endpoint_Acl.DENY_ALL;
25
26
  switch (acl) {
26
27
  case "allow-all":
27
28
  return Endpoint_Acl.ALLOW_ALL;
@@ -29,11 +30,13 @@ export function aclToProto(acl) {
29
30
  return Endpoint_Acl.DENY_ALL;
30
31
  case "authenticated":
31
32
  return Endpoint_Acl.AUTHENTICATED;
33
+ case "client-keys":
34
+ return Endpoint_Acl.DENY_ALL;
32
35
  }
33
36
  }
34
37
  /** The callers a CALLERS acl names, as discovery carries them; empty for every other rule. */
35
38
  export function callersToProto(acl) {
36
- if (typeof acl !== "object")
39
+ if (typeof acl !== "object" || acl.kind !== "callers")
37
40
  return [];
38
41
  return acl.callers.map((m) => {
39
42
  switch (m.kind) {
@@ -50,6 +53,38 @@ export function callersToProto(acl) {
50
53
  }
51
54
  });
52
55
  }
56
+ /**
57
+ * An MCP endpoint's or tool's rule as discovery carries it (1.20): each member of an `anyOf` sets its
58
+ * own field, so `anyOf(clientKeys, authenticated)` is `client_keys` and `tokens`.
59
+ */
60
+ export function mcpAclToProto(acl) {
61
+ const out = { allowAll: false, denyAll: false, tokens: false, clientKeys: false, callers: [] };
62
+ const add = (rule) => {
63
+ if (typeof rule === "object") {
64
+ if (rule.kind === "anyOf")
65
+ rule.acls.forEach(add);
66
+ else
67
+ out.callers.push(...callersToProto(rule));
68
+ return;
69
+ }
70
+ switch (rule) {
71
+ case "allow-all":
72
+ out.allowAll = true;
73
+ break;
74
+ case "deny-all":
75
+ out.denyAll = true;
76
+ break;
77
+ case "authenticated":
78
+ out.tokens = true;
79
+ break;
80
+ case "client-keys":
81
+ out.clientKeys = true;
82
+ break;
83
+ }
84
+ };
85
+ add(acl);
86
+ return out;
87
+ }
53
88
  function handlerInits(handlers) {
54
89
  return [...handlers.values()]
55
90
  .map((h) => ({ name: h.name, readOnly: h.readOnly, streaming: h.streaming }))
@@ -232,10 +267,25 @@ export function renderSpec(registry) {
232
267
  ...(r.acl !== undefined ? { acl: aclToProto(r.acl), allowCallers: callersToProto(r.acl) } : {}),
233
268
  })),
234
269
  }));
270
+ const mcpEndpoints = [...registry.mcpEndpoints.values()].map((e) => ({
271
+ id: e.id,
272
+ path: e.path,
273
+ ...(e.serverName !== undefined ? { name: e.serverName } : {}),
274
+ ...(e.serverVersion !== undefined ? { version: e.serverVersion } : {}),
275
+ acl: mcpAclToProto(e.acl),
276
+ tools: e.tools.map((t) => ({
277
+ name: t.name,
278
+ description: t.description,
279
+ inputSchemaJson: JSON.stringify(t.inputSchema),
280
+ ...(t.acl !== undefined ? { acl: mcpAclToProto(t.acl) } : {}),
281
+ })),
282
+ brokered: e.brokered,
283
+ }));
235
284
  return create(SpecSchema, {
236
285
  protocolVersion: PROTOCOL_VERSION,
237
286
  sdk: { name: SDK_NAME, version: VERSION },
238
287
  components,
239
288
  endpoints,
289
+ mcpEndpoints,
240
290
  });
241
291
  }
@@ -0,0 +1,288 @@
1
+ import { CommandError } from "./effects/common.ts";
2
+ import { Credentials } from "./sigv4.ts";
3
+ import { Instant } from "./time.ts";
4
+ /** How a bucket is reached from outside the installation. */
5
+ export type BucketExposure = "private" | "signed" | "public" | "supplied";
6
+ /** What a signed URL lets its holder do with one object. */
7
+ export type SignedAccess = "read" | "keep";
8
+ /** An object read from a bucket. Read its `body` once, or call `bytes()`. */
9
+ export interface StoredObject {
10
+ readonly contentType: string;
11
+ readonly length: number;
12
+ readonly lastKept: Instant;
13
+ readonly body: ReadableStream<Uint8Array>;
14
+ /** The whole body, read from `body`. */
15
+ bytes(): Promise<Uint8Array>;
16
+ }
17
+ /** What a bucket keeps under a name, without the bytes. */
18
+ export interface ObjectInfo {
19
+ readonly name: string;
20
+ readonly contentType: string;
21
+ readonly length: number;
22
+ readonly lastKept: Instant;
23
+ }
24
+ /** One object in a listing. */
25
+ export interface ObjectEntry {
26
+ readonly name: string;
27
+ readonly length: number;
28
+ readonly lastKept: Instant;
29
+ }
30
+ /** How a put keeps its body: its content type, and its length, which a stream must declare. */
31
+ export interface PutOptions {
32
+ readonly contentType: string;
33
+ readonly length?: number;
34
+ }
35
+ /**
36
+ * One bucket. Its objects are named with `/` between segments, so a listing by prefix reads like a
37
+ * directory; it is not one. A put replaces the whole object or leaves the old one.
38
+ */
39
+ export interface Bucket {
40
+ /** The descriptor's name for it; `undefined` for the unnamed bucket. */
41
+ readonly name: string | undefined;
42
+ readonly exposure: BucketExposure;
43
+ /**
44
+ * Keeps `body` under `name`, replacing what was there. A stream declares its `length`; one that ends
45
+ * before it fails and keeps nothing, and the old object, if any, stays.
46
+ */
47
+ put(name: string, body: ReadableStream<Uint8Array> | Uint8Array, options: PutOptions): Promise<void>;
48
+ /** The object kept under `name`, or `undefined` when there is none. */
49
+ get(name: string): Promise<StoredObject | undefined>;
50
+ /** Removes what is kept under `name`. Removing nothing is not an error. */
51
+ delete(name: string): Promise<void>;
52
+ /** Every object whose name begins with `prefix`, in name order; pages are read as it is walked. */
53
+ list(prefix: string): AsyncIterable<ObjectEntry>;
54
+ /** What is kept under `name`, without its bytes, or `undefined`. */
55
+ stat(name: string): Promise<ObjectInfo | undefined>;
56
+ /**
57
+ * A URL a browser may use, until `spanSeconds` (15 minutes by default, at most seven days) have
58
+ * passed, to read or keep the object under `name`. Computed: no request reaches the store. A signed
59
+ * bucket's, or the unnamed bucket's when the service sets `exposeObjectStorage`.
60
+ */
61
+ signedUrl(name: string, access: SignedAccess, spanSeconds?: number): string;
62
+ /** The address a browser reads the object under `name` at, with no signature: a public bucket's only. */
63
+ url(name: string): string;
64
+ }
65
+ /** A service's buckets: `default`, the unnamed one, and the named ones by name. */
66
+ export interface Buckets {
67
+ /** The unnamed bucket. Refuses, naming `provisionObjectStorage`, when the service has none. */
68
+ readonly default: Bucket;
69
+ /** A bucket the descriptor's `buckets` list names; refused, naming the ones it does name, otherwise. */
70
+ bucket(name: string): Bucket;
71
+ /** Every named bucket, in no particular order. */
72
+ readonly buckets: Bucket[];
73
+ }
74
+ /**
75
+ * What an object's name, a prefix, a bucket's name and a signed URL's span may be. Every store and every
76
+ * test double applies these, and `protocol/fixtures/storage/rules.json` holds them to the other SDKs.
77
+ */
78
+ export declare const ObjectRules: Readonly<{
79
+ MAX_NAME_BYTES: 1024;
80
+ MAX_BUCKET_NAME: 32;
81
+ /** Seven days. */
82
+ MAX_SIGNED_URL_SECONDS: 604800;
83
+ /** Fifteen minutes. */
84
+ DEFAULT_SIGNED_URL_SECONDS: 900;
85
+ BUCKET_NAME_RULE: "lower-case letters, digits and '-', 1 to 32 characters";
86
+ /** What is wrong with an object's `name`, if anything. */
87
+ nameProblem(name: string): string | undefined;
88
+ /** What is wrong with a listing's `prefix`, if anything. Empty is everything. */
89
+ prefixProblem(prefix: string): string | undefined;
90
+ /** What is wrong with a bucket's name, if anything, in the descriptor's words. */
91
+ bucketNameProblem(name: string): string | undefined;
92
+ /** What is wrong with a signed URL's span, in seconds, if anything. */
93
+ spanProblem(spanSeconds: number): string | undefined;
94
+ checkName(name: string): void;
95
+ checkPrefix(prefix: string): void;
96
+ checkSpan(spanSeconds: number): void;
97
+ }>;
98
+ /** What the storage client says when it refuses: the runtime's words (`StorageRefusals` in the Scala SDK). */
99
+ export declare const StorageRefusals: Readonly<{
100
+ noBucket(): CommandError;
101
+ unknown(name: string, known: Iterable<string>): CommandError;
102
+ noCredential(): CommandError;
103
+ notExposed(): CommandError;
104
+ notSigned(bucket: string, exposure: BucketExposure): CommandError;
105
+ notPublic(bucket: string | undefined, exposure: BucketExposure): CommandError;
106
+ unreachable(endpoint: string, cause: string): CommandError;
107
+ refusedByStore(endpoint: string, status: number, detail: string): CommandError;
108
+ unreadable(endpoint: string, cause: string): CommandError;
109
+ shortBody(declared: number, read: number): CommandError;
110
+ negativeLength(length: number): CommandError;
111
+ /** TypeScript's own: a stream's length cannot be known before it is read, and S3 is told it first. */
112
+ undeclaredLength(): CommandError;
113
+ stepsOnly(): CommandError;
114
+ }>;
115
+ /** Buckets that refuse every call, naming `provisionObjectStorage`: a deployed service with no bucket. */
116
+ export declare const noStorage: Buckets;
117
+ export interface InMemoryBucketsOptions {
118
+ /** The named buckets and their exposure; more may be declared later with `declare`. */
119
+ readonly buckets?: Readonly<Record<string, BucketExposure>>;
120
+ /** Whether the service has the unnamed bucket; `true` by default. */
121
+ readonly unnamed?: boolean;
122
+ /** The unnamed bucket's exposure: `signed` by default, so a test may sign a URL for it. */
123
+ readonly unnamedExposure?: BucketExposure;
124
+ /** Where a public bucket's objects are read: `http://memory/public/<bucket>` by default, reaching nothing. */
125
+ readonly publicBase?: (bucket: string) => string;
126
+ }
127
+ /**
128
+ * A service's buckets in memory: the test kits' store, so a test of a handler that keeps or reads an
129
+ * object needs no object store and no Docker. It applies every rule the other stores apply, in the
130
+ * same words. A signed URL is computed (`http://memory/...`) and reaches nothing.
131
+ */
132
+ export declare class InMemoryBuckets implements Buckets {
133
+ #private;
134
+ constructor(options?: InMemoryBucketsOptions);
135
+ get default(): Bucket;
136
+ bucket(name: string): Bucket;
137
+ get buckets(): Bucket[];
138
+ /** Declares a named bucket, before or after the service starts. */
139
+ declare(name: string, exposure: BucketExposure): void;
140
+ /** Forgets every object; the declared buckets stay. */
141
+ clear(): void;
142
+ }
143
+ /** What a descriptor declares: whether the unnamed bucket is asked for, and each named one. */
144
+ export interface DeclaredBuckets {
145
+ readonly unnamed: boolean;
146
+ readonly buckets: Readonly<Record<string, BucketExposure>>;
147
+ }
148
+ /**
149
+ * The buckets `service.json` in `directory` declares, read where a service runs on a developer's machine
150
+ * and in a test, so the directory and memory stores refuse the names a deployed service would; `undefined`
151
+ * when there is no such file. Only the two fields that say which buckets there are.
152
+ */
153
+ export declare function declaredBuckets(directory?: string): DeclaredBuckets | undefined;
154
+ /** Where a service serves its directory store's objects. */
155
+ export declare const STORAGE_PATH = "/_ankka/storage";
156
+ /** The unnamed bucket's directory and URL segment: no bucket name begins with `_`. */
157
+ export declare const DEFAULT_SEGMENT = "_default";
158
+ export interface DirectoryBucketsOptions {
159
+ /** The buckets `service.json` declares; with none, the unnamed bucket and any name asked for, as private. */
160
+ readonly declared?: DeclaredBuckets;
161
+ /** The service's own address, where its URLs point: `http://<ANKKA_HTTP_INTERFACE or 127.0.0.1>:<ANKKA_HTTP_PORT or 9000>`. */
162
+ readonly addressBase?: () => string;
163
+ }
164
+ /** The address a local service's HTTP port answers at, as the sidecar beside it binds it. */
165
+ export declare function localAddress(env?: Readonly<Record<string, string | undefined>>): string;
166
+ /**
167
+ * A service's buckets in a directory on a developer's machine: what a service keeps when it runs with no
168
+ * variable of an object store. The runtime's layout exactly, so a Scala service and a TypeScript one read
169
+ * each other's: one directory per bucket under `root` (`_default` for the unnamed one), an object's name
170
+ * encoded into one file name under `objects/` (its hash when too long), and beside it under `meta/` its
171
+ * name, content type, length and when it was kept. A put writes a temporary file and moves it into place,
172
+ * so a stream that ends short leaves the old object.
173
+ *
174
+ * A signed URL and a public address point at the service's own HTTP port, under `/_ankka/storage`, which
175
+ * the SDK serves through the sidecar while this is the service's store.
176
+ */
177
+ export declare class DirectoryBuckets implements Buckets {
178
+ #private;
179
+ readonly root: string;
180
+ constructor(root: string, options?: DirectoryBucketsOptions);
181
+ get default(): Bucket;
182
+ bucket(name: string): Bucket;
183
+ get buckets(): Bucket[];
184
+ /** The bucket a URL's segment names, `_default` or a bucket's name, as this store would answer it. */
185
+ bySegment(segment: string): {
186
+ readonly bucket: Bucket;
187
+ readonly segment: string;
188
+ } | undefined;
189
+ /** `HMAC-SHA256(key, "<segment>/<name>|<access>|<expires>")` in hex, over the key kept in the root. */
190
+ token(segment: string, objectName: string, access: string, expires: number): string;
191
+ /** Whether `token` signs this access until `expires`, and `expires` is still to come. */
192
+ admits(segment: string, objectName: string, access: string, expires: number, token: string): boolean;
193
+ }
194
+ /**
195
+ * The deepest object name whose address keeps its slashes. The sidecar matches a process's route by
196
+ * whole segments, so the storage endpoint declares one route per depth up to this; a deeper name's
197
+ * slashes are encoded and it travels as one segment.
198
+ */
199
+ export declare const MAX_ADDRESS_SEGMENTS = 16;
200
+ /** An object's name in a directory store's address: as the runtime writes it, while the sidecar can route it. */
201
+ export declare function addressOf(objectName: string): string;
202
+ /** One S3 bucket and how to reach it. */
203
+ export interface S3Target {
204
+ readonly endpoint: URL;
205
+ readonly region: string;
206
+ readonly bucket: string;
207
+ readonly credentials: Credentials | undefined;
208
+ readonly publicEndpoint: URL | undefined;
209
+ }
210
+ /** A bucket the descriptor names: its exposure, where it is, and for a public one the base it is read at. */
211
+ export interface NamedTarget {
212
+ readonly name: string;
213
+ readonly exposure: BucketExposure;
214
+ readonly target: S3Target;
215
+ readonly publicUrl: string | undefined;
216
+ }
217
+ /** The S3 store's settings, read from the `ANKKA_S3_*` variables as the runtime's `StorageSettings` reads them. */
218
+ export interface S3Settings {
219
+ readonly unnamed: S3Target | undefined;
220
+ readonly named: readonly NamedTarget[];
221
+ /** How long one request waits for an answer: 30 seconds. */
222
+ readonly timeoutMs: number;
223
+ }
224
+ type Env = Readonly<Record<string, string | undefined>>;
225
+ /** The bucket a variable's `<NAME>` names: lower case, `_` as `-`. */
226
+ export declare function bucketNameOf(suffix: string): string;
227
+ /** The `<NAME>` of a bucket's variables: upper case, `-` as `_`. */
228
+ export declare function bucketVariableSuffix(bucket: string): string;
229
+ /**
230
+ * The S3 settings in `env`: the unnamed family `ANKKA_S3_{ENDPOINT,REGION,BUCKET,ACCESS_KEY,SECRET_KEY,PUBLIC_ENDPOINT}`;
231
+ * a named bucket the platform made, `ANKKA_S3_BUCKET_<NAME>`, `ANKKA_S3_EXPOSURE_<NAME>` and, for a public
232
+ * one, `ANKKA_S3_PUBLIC_URL_<NAME>`, sharing the unnamed family's endpoint, region and key; a supplied
233
+ * bucket, the five `ANKKA_S3_{ENDPOINT,REGION,BUCKET,ACCESS_KEY,SECRET_KEY}_<NAME>` of its own. Throws,
234
+ * naming the variable, on a family that cannot be read.
235
+ */
236
+ export declare function s3Settings(env?: Env, timeoutMs?: number): S3Settings;
237
+ /** A `ListObjectsV2` answer: its entries, and the token for the next page while it is truncated. */
238
+ export declare function parseListing(xml: string): {
239
+ readonly entries: ObjectEntry[];
240
+ readonly next: string | undefined;
241
+ };
242
+ /** One bucket in an S3 store: path-style requests, a put streamed once with an unsigned payload, a listing paged as it is walked. */
243
+ export declare class S3Bucket implements Bucket {
244
+ #private;
245
+ readonly name: string | undefined;
246
+ readonly exposure: BucketExposure;
247
+ constructor(target: S3Target, name: string | undefined, exposure: BucketExposure, publicUrl: string | undefined, timeoutMs?: number);
248
+ put(objectName: string, body: ReadableStream<Uint8Array> | Uint8Array, options: PutOptions): Promise<void>;
249
+ get(objectName: string): Promise<StoredObject | undefined>;
250
+ stat(objectName: string): Promise<ObjectInfo | undefined>;
251
+ delete(objectName: string): Promise<void>;
252
+ list(prefix: string): AsyncIterable<ObjectEntry>;
253
+ signedUrl(objectName: string, access: SignedAccess, spanSeconds?: number): string;
254
+ url(objectName: string): string;
255
+ }
256
+ /** The S3 store's buckets, by name, from `s3Settings`. */
257
+ export declare class S3Buckets implements Buckets {
258
+ #private;
259
+ constructor(settings?: S3Settings);
260
+ get default(): Bucket;
261
+ bucket(name: string): Bucket;
262
+ get buckets(): Bucket[];
263
+ }
264
+ /** What stands behind every bucket of a service. */
265
+ export type StorageBackend = "s3" | "directory" | "memory" | "unavailable";
266
+ export interface ChosenStorage {
267
+ readonly backend: StorageBackend;
268
+ readonly buckets: Buckets;
269
+ }
270
+ export interface StorageChoiceOptions {
271
+ /** Where `service.json` is read from, for the buckets it declares: the working directory by default. */
272
+ readonly serviceDirectory?: string;
273
+ /** The service's own address, for the directory store's URLs. */
274
+ readonly addressBase?: () => string;
275
+ }
276
+ /**
277
+ * The one place a process's store is chosen, as the runtime chooses its own: `ANKKA_STORAGE_BACKEND` when
278
+ * set (`s3`, `directory` or `memory`); otherwise S3 when any `ANKKA_S3_*` bucket is given, the directory
279
+ * store (`ANKKA_STORAGE_DIR`, default `.ankka/storage`) on a developer's machine — anywhere Kubernetes did
280
+ * not start the process — and no store at all on the platform. Throws, naming the variable, when the
281
+ * settings cannot be read: a service that cannot reach its buckets does not start.
282
+ */
283
+ export declare function chooseStorage(env?: Env, options?: StorageChoiceOptions): ChosenStorage;
284
+ /** The store the server chose for this process, which every component's `storage` answers unless a test gave it one. */
285
+ export declare function processStorage(): Buckets;
286
+ /** @internal The server's: the store this process was given. */
287
+ export declare function _setProcessStorage(buckets: Buckets): void;
288
+ export {};