@greatwait/contracts 0.1.0-alpha.1
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 +77 -0
- package/dist/authority.d.ts +217 -0
- package/dist/authority.js +261 -0
- package/dist/callback-ownership.d.ts +58 -0
- package/dist/callback-ownership.js +71 -0
- package/dist/callback-safety.d.ts +38 -0
- package/dist/callback-safety.js +403 -0
- package/dist/compatibility.d.ts +82 -0
- package/dist/compatibility.js +115 -0
- package/dist/delivery.d.ts +401 -0
- package/dist/delivery.js +311 -0
- package/dist/endpoints.d.ts +305 -0
- package/dist/endpoints.js +179 -0
- package/dist/events.d.ts +62 -0
- package/dist/events.js +38 -0
- package/dist/first-value-journey.d.ts +305 -0
- package/dist/first-value-journey.js +466 -0
- package/dist/forecast.d.ts +443 -0
- package/dist/forecast.js +729 -0
- package/dist/identifiers.d.ts +109 -0
- package/dist/identifiers.js +131 -0
- package/dist/identity.d.ts +196 -0
- package/dist/identity.js +182 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +27 -0
- package/dist/mcp.d.ts +51 -0
- package/dist/mcp.js +56 -0
- package/dist/oauth.d.ts +796 -0
- package/dist/oauth.js +708 -0
- package/dist/operations.d.ts +3985 -0
- package/dist/operations.js +1837 -0
- package/dist/partition.d.ts +771 -0
- package/dist/partition.js +206 -0
- package/dist/policy.d.ts +93 -0
- package/dist/policy.js +180 -0
- package/dist/problems.d.ts +158 -0
- package/dist/problems.js +104 -0
- package/dist/protocol.d.ts +308 -0
- package/dist/protocol.js +515 -0
- package/dist/scheduling-records.d.ts +395 -0
- package/dist/scheduling-records.js +284 -0
- package/dist/scheduling.d.ts +1098 -0
- package/dist/scheduling.js +1116 -0
- package/dist/secrets.d.ts +383 -0
- package/dist/secrets.js +272 -0
- package/dist/signatures.d.ts +704 -0
- package/dist/signatures.js +867 -0
- package/dist/tenancy.d.ts +178 -0
- package/dist/tenancy.js +145 -0
- package/dist/test-sink.d.ts +986 -0
- package/dist/test-sink.js +755 -0
- package/dist/time.d.ts +45 -0
- package/dist/time.js +128 -0
- package/dist/usage.d.ts +121 -0
- package/dist/usage.js +200 -0
- package/dist/webhooks.d.ts +116 -0
- package/dist/webhooks.js +161 -0
- package/dist/workspace.d.ts +92 -0
- package/dist/workspace.js +59 -0
- package/generated/v1/callback-delivery-signature.json +467 -0
- package/generated/v1/callback-endpoint-verification.json +59 -0
- package/generated/v1/callback-receiver.json +241 -0
- package/generated/v1/examples/attempts.list.response.json +24 -0
- package/generated/v1/examples/attempts.read.response.json +20 -0
- package/generated/v1/examples/credentials.create.request.json +5 -0
- package/generated/v1/examples/credentials.create.response.json +10 -0
- package/generated/v1/examples/credentials.list.response.json +17 -0
- package/generated/v1/examples/credentials.revoke.response.json +11 -0
- package/generated/v1/examples/credentials.rotate.request.json +3 -0
- package/generated/v1/examples/credentials.rotate.response.json +22 -0
- package/generated/v1/examples/endpoints.create.request.json +3 -0
- package/generated/v1/examples/endpoints.create.response.json +16 -0
- package/generated/v1/examples/endpoints.disable.response.json +15 -0
- package/generated/v1/examples/endpoints.list.response.json +21 -0
- package/generated/v1/examples/endpoints.read.response.json +14 -0
- package/generated/v1/examples/endpoints.rotateSigningKey.request.json +3 -0
- package/generated/v1/examples/endpoints.rotateSigningKey.response.json +23 -0
- package/generated/v1/examples/endpoints.update.request.json +3 -0
- package/generated/v1/examples/endpoints.update.response.json +13 -0
- package/generated/v1/examples/endpoints.verify.response.json +15 -0
- package/generated/v1/examples/forecast.read.response.json +28 -0
- package/generated/v1/examples/forecast.windows.response.json +21 -0
- package/generated/v1/examples/grants.list.response.json +17 -0
- package/generated/v1/examples/grants.revoke.response.json +16 -0
- package/generated/v1/examples/meta.scaffold.response.json +5 -0
- package/generated/v1/examples/meta.version.response.json +5 -0
- package/generated/v1/examples/occurrences.list.response.json +17 -0
- package/generated/v1/examples/occurrences.read.response.json +15 -0
- package/generated/v1/examples/occurrences.redeliver.response.json +16 -0
- package/generated/v1/examples/projects.create.request.json +3 -0
- package/generated/v1/examples/projects.create.response.json +5 -0
- package/generated/v1/examples/projects.list.response.json +9 -0
- package/generated/v1/examples/schedules.archive.response.json +25 -0
- package/generated/v1/examples/schedules.cancel.response.json +24 -0
- package/generated/v1/examples/schedules.create.request.json +9 -0
- package/generated/v1/examples/schedules.create.response.json +25 -0
- package/generated/v1/examples/schedules.list.response.json +23 -0
- package/generated/v1/examples/schedules.pause.response.json +23 -0
- package/generated/v1/examples/schedules.read.response.json +25 -0
- package/generated/v1/examples/schedules.replace.request.json +8 -0
- package/generated/v1/examples/schedules.replace.response.json +25 -0
- package/generated/v1/examples/schedules.resume.response.json +23 -0
- package/generated/v1/examples/sinkdeliveries.list.response.json +19 -0
- package/generated/v1/examples/sinkdeliveries.read.response.json +16 -0
- package/generated/v1/examples/testsinks.configure.request.json +3 -0
- package/generated/v1/examples/testsinks.configure.response.json +13 -0
- package/generated/v1/examples/testsinks.create.request.json +8 -0
- package/generated/v1/examples/testsinks.create.response.json +13 -0
- package/generated/v1/examples/testsinks.delete.response.json +13 -0
- package/generated/v1/examples/testsinks.list.response.json +20 -0
- package/generated/v1/examples/testsinks.read.response.json +14 -0
- package/generated/v1/examples/usage.read.response.json +20 -0
- package/generated/v1/first-value-journey.json +436 -0
- package/generated/v1/manifest.json +1037 -0
- package/generated/v1/openapi.json +23665 -0
- package/generated/v1/policy-matrix.json +305 -0
- package/generated/v1/schemas/attempts.list.query.json +16 -0
- package/generated/v1/schemas/attempts.list.response.json +175 -0
- package/generated/v1/schemas/attempts.read.response.json +168 -0
- package/generated/v1/schemas/credentials.create.request.json +39 -0
- package/generated/v1/schemas/credentials.create.response.json +88 -0
- package/generated/v1/schemas/credentials.list.query.json +16 -0
- package/generated/v1/schemas/credentials.list.response.json +124 -0
- package/generated/v1/schemas/credentials.revoke.response.json +84 -0
- package/generated/v1/schemas/credentials.rotate.request.json +11 -0
- package/generated/v1/schemas/credentials.rotate.response.json +171 -0
- package/generated/v1/schemas/endpoints.create.request.json +12 -0
- package/generated/v1/schemas/endpoints.create.response.json +115 -0
- package/generated/v1/schemas/endpoints.disable.response-headers.json +11 -0
- package/generated/v1/schemas/endpoints.disable.response.json +100 -0
- package/generated/v1/schemas/endpoints.list.query.json +16 -0
- package/generated/v1/schemas/endpoints.list.response.json +146 -0
- package/generated/v1/schemas/endpoints.read.response-headers.json +11 -0
- package/generated/v1/schemas/endpoints.read.response.json +100 -0
- package/generated/v1/schemas/endpoints.rotateSigningKey.request-headers.json +10 -0
- package/generated/v1/schemas/endpoints.rotateSigningKey.request.json +11 -0
- package/generated/v1/schemas/endpoints.rotateSigningKey.response-headers.json +11 -0
- package/generated/v1/schemas/endpoints.rotateSigningKey.response.json +118 -0
- package/generated/v1/schemas/endpoints.update.request-headers.json +10 -0
- package/generated/v1/schemas/endpoints.update.request.json +12 -0
- package/generated/v1/schemas/endpoints.update.response-headers.json +11 -0
- package/generated/v1/schemas/endpoints.update.response.json +100 -0
- package/generated/v1/schemas/endpoints.verify.request-headers.json +10 -0
- package/generated/v1/schemas/endpoints.verify.response-headers.json +11 -0
- package/generated/v1/schemas/endpoints.verify.response.json +100 -0
- package/generated/v1/schemas/forecast.read.query.json +26 -0
- package/generated/v1/schemas/forecast.read.response.json +100 -0
- package/generated/v1/schemas/forecast.windows.query.json +26 -0
- package/generated/v1/schemas/forecast.windows.response.json +94 -0
- package/generated/v1/schemas/grants.list.response.json +98 -0
- package/generated/v1/schemas/grants.revoke.response.json +95 -0
- package/generated/v1/schemas/meta.scaffold.response.json +36 -0
- package/generated/v1/schemas/meta.version.response.json +19 -0
- package/generated/v1/schemas/occurrences.list.query.json +16 -0
- package/generated/v1/schemas/occurrences.list.response.json +83 -0
- package/generated/v1/schemas/occurrences.read.response.json +76 -0
- package/generated/v1/schemas/occurrences.redeliver.request-headers.json +13 -0
- package/generated/v1/schemas/occurrences.redeliver.response-headers.json +10 -0
- package/generated/v1/schemas/occurrences.redeliver.response.json +168 -0
- package/generated/v1/schemas/projects.create.request.json +12 -0
- package/generated/v1/schemas/projects.create.response.json +25 -0
- package/generated/v1/schemas/projects.list.response.json +35 -0
- package/generated/v1/schemas/schedules.archive.request-headers.json +10 -0
- package/generated/v1/schemas/schedules.archive.response-headers.json +11 -0
- package/generated/v1/schemas/schedules.archive.response.json +189 -0
- package/generated/v1/schemas/schedules.cancel.request-headers.json +10 -0
- package/generated/v1/schemas/schedules.cancel.response-headers.json +11 -0
- package/generated/v1/schemas/schedules.cancel.response.json +189 -0
- package/generated/v1/schemas/schedules.create.request-headers.json +12 -0
- package/generated/v1/schemas/schedules.create.request.json +99 -0
- package/generated/v1/schemas/schedules.create.response-headers.json +15 -0
- package/generated/v1/schemas/schedules.create.response.json +189 -0
- package/generated/v1/schemas/schedules.list.query.json +16 -0
- package/generated/v1/schemas/schedules.list.response.json +170 -0
- package/generated/v1/schemas/schedules.pause.request-headers.json +10 -0
- package/generated/v1/schemas/schedules.pause.response-headers.json +11 -0
- package/generated/v1/schemas/schedules.pause.response.json +189 -0
- package/generated/v1/schemas/schedules.read.response-headers.json +11 -0
- package/generated/v1/schemas/schedules.read.response.json +189 -0
- package/generated/v1/schemas/schedules.replace.request-headers.json +10 -0
- package/generated/v1/schemas/schedules.replace.request.json +99 -0
- package/generated/v1/schemas/schedules.replace.response-headers.json +11 -0
- package/generated/v1/schemas/schedules.replace.response.json +189 -0
- package/generated/v1/schemas/schedules.resume.request-headers.json +10 -0
- package/generated/v1/schemas/schedules.resume.response-headers.json +11 -0
- package/generated/v1/schemas/schedules.resume.response.json +189 -0
- package/generated/v1/schemas/sinkdeliveries.list.query.json +16 -0
- package/generated/v1/schemas/sinkdeliveries.list.response.json +126 -0
- package/generated/v1/schemas/sinkdeliveries.read.response.json +98 -0
- package/generated/v1/schemas/testsinks.configure.request.json +66 -0
- package/generated/v1/schemas/testsinks.configure.response.json +115 -0
- package/generated/v1/schemas/testsinks.create.request.json +76 -0
- package/generated/v1/schemas/testsinks.create.response.json +115 -0
- package/generated/v1/schemas/testsinks.delete.response.json +115 -0
- package/generated/v1/schemas/testsinks.list.query.json +16 -0
- package/generated/v1/schemas/testsinks.list.response.json +161 -0
- package/generated/v1/schemas/testsinks.read.response.json +115 -0
- package/generated/v1/schemas/usage.read.query.json +15 -0
- package/generated/v1/schemas/usage.read.response.json +71 -0
- package/generated/v1/snippets/attempts.list.json +5 -0
- package/generated/v1/snippets/attempts.read.json +5 -0
- package/generated/v1/snippets/credentials.create.json +5 -0
- package/generated/v1/snippets/credentials.list.json +5 -0
- package/generated/v1/snippets/credentials.revoke.json +5 -0
- package/generated/v1/snippets/credentials.rotate.json +5 -0
- package/generated/v1/snippets/endpoints.create.json +5 -0
- package/generated/v1/snippets/endpoints.disable.json +5 -0
- package/generated/v1/snippets/endpoints.list.json +5 -0
- package/generated/v1/snippets/endpoints.read.json +5 -0
- package/generated/v1/snippets/endpoints.rotateSigningKey.json +5 -0
- package/generated/v1/snippets/endpoints.update.json +5 -0
- package/generated/v1/snippets/endpoints.verify.json +5 -0
- package/generated/v1/snippets/forecast.read.json +5 -0
- package/generated/v1/snippets/forecast.windows.json +5 -0
- package/generated/v1/snippets/grants.list.json +5 -0
- package/generated/v1/snippets/grants.revoke.json +5 -0
- package/generated/v1/snippets/meta.scaffold.json +5 -0
- package/generated/v1/snippets/meta.version.json +5 -0
- package/generated/v1/snippets/occurrences.list.json +5 -0
- package/generated/v1/snippets/occurrences.read.json +5 -0
- package/generated/v1/snippets/occurrences.redeliver.json +5 -0
- package/generated/v1/snippets/projects.create.json +5 -0
- package/generated/v1/snippets/projects.list.json +5 -0
- package/generated/v1/snippets/schedules.archive.json +5 -0
- package/generated/v1/snippets/schedules.cancel.json +5 -0
- package/generated/v1/snippets/schedules.create.json +5 -0
- package/generated/v1/snippets/schedules.list.json +5 -0
- package/generated/v1/snippets/schedules.pause.json +5 -0
- package/generated/v1/snippets/schedules.read.json +5 -0
- package/generated/v1/snippets/schedules.replace.json +5 -0
- package/generated/v1/snippets/schedules.resume.json +5 -0
- package/generated/v1/snippets/sinkdeliveries.list.json +5 -0
- package/generated/v1/snippets/sinkdeliveries.read.json +5 -0
- package/generated/v1/snippets/testsinks.configure.json +5 -0
- package/generated/v1/snippets/testsinks.create.json +5 -0
- package/generated/v1/snippets/testsinks.delete.json +5 -0
- package/generated/v1/snippets/testsinks.list.json +5 -0
- package/generated/v1/snippets/testsinks.read.json +5 -0
- package/generated/v1/snippets/usage.read.json +5 -0
- package/package.json +153 -0
package/dist/problems.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Refusals.
|
|
4
|
+
*
|
|
5
|
+
* Every non-2xx response is an RFC 9457 problem document. It carries a stable
|
|
6
|
+
* machine-readable `code`, a human `title`, and — where the caller can act on
|
|
7
|
+
* it — a `retry` hint and per-field `errors`. The legacy scheduler returned
|
|
8
|
+
* `{ "error": "invalid_cron" }` with no category, no correlation, and no way
|
|
9
|
+
* for a client to tell "retry this" from "never retry this"; every adapter and
|
|
10
|
+
* SDK invented its own interpretation.
|
|
11
|
+
*
|
|
12
|
+
* `code` is the contract. `title` and `detail` are prose and may change without
|
|
13
|
+
* being a breaking change; clients must never branch on them.
|
|
14
|
+
*/
|
|
15
|
+
export const PROBLEM_CATEGORIES = [
|
|
16
|
+
"authentication",
|
|
17
|
+
"authorization",
|
|
18
|
+
"conflict",
|
|
19
|
+
"invalid-request",
|
|
20
|
+
"not-found",
|
|
21
|
+
"rate-limited",
|
|
22
|
+
"unavailable",
|
|
23
|
+
];
|
|
24
|
+
export const problemCategorySchema = z.enum(PROBLEM_CATEGORIES);
|
|
25
|
+
/**
|
|
26
|
+
* The stable refusal catalog. Adding a code is additive; removing one, or
|
|
27
|
+
* changing the category or status of an existing one, is breaking.
|
|
28
|
+
*/
|
|
29
|
+
export const PROBLEM_CODES = {
|
|
30
|
+
authentication_required: { category: "authentication", status: 401 },
|
|
31
|
+
// Separate from `credential_revoked` because the two mean different things to
|
|
32
|
+
// whoever is reading them. An expiry is the deadline a rotation already told
|
|
33
|
+
// you about; a revocation is somebody deciding this credential stops working
|
|
34
|
+
// now. One code for both would make a missed rotation window indistinguishable
|
|
35
|
+
// from a security event, and the client's remedy differs — rotate again
|
|
36
|
+
// versus find out who revoked it.
|
|
37
|
+
credential_expired: { category: "authentication", status: 401 },
|
|
38
|
+
credential_revoked: { category: "authentication", status: 401 },
|
|
39
|
+
endpoint_unverified: { category: "invalid-request", status: 422 },
|
|
40
|
+
idempotency_key_reused: { category: "conflict", status: 409 },
|
|
41
|
+
invalid_request: { category: "invalid-request", status: 400 },
|
|
42
|
+
not_found: { category: "not-found", status: 404 },
|
|
43
|
+
payload_too_large: { category: "invalid-request", status: 413 },
|
|
44
|
+
plan_limit_reached: { category: "conflict", status: 409 },
|
|
45
|
+
rate_limited: { category: "rate-limited", status: 429 },
|
|
46
|
+
scope_missing: { category: "authorization", status: 403 },
|
|
47
|
+
service_unavailable: { category: "unavailable", status: 503 },
|
|
48
|
+
// The resource is in a state that does not permit what was asked — resuming a
|
|
49
|
+
// cancelled schedule, archiving an active one. A conflict rather than an
|
|
50
|
+
// invalid request: the request is well formed and would succeed against the
|
|
51
|
+
// same resource in another state, so telling the caller to fix their request
|
|
52
|
+
// would send them looking in the wrong place. Distinct from `version_conflict`,
|
|
53
|
+
// which says somebody else got there first; this one says nobody did and the
|
|
54
|
+
// transition is still not available.
|
|
55
|
+
state_conflict: { category: "conflict", status: 409 },
|
|
56
|
+
test_to_live_escalation: { category: "authorization", status: 403 },
|
|
57
|
+
version_conflict: { category: "conflict", status: 409 },
|
|
58
|
+
};
|
|
59
|
+
export const problemCodeSchema = z.enum(Object.keys(PROBLEM_CODES));
|
|
60
|
+
/** Whether repeating the identical request could succeed later. */
|
|
61
|
+
export function isRetryable(code) {
|
|
62
|
+
const category = PROBLEM_CODES[code].category;
|
|
63
|
+
return category === "rate-limited" || category === "unavailable";
|
|
64
|
+
}
|
|
65
|
+
export const fieldErrorSchema = z.object({
|
|
66
|
+
/** JSON Pointer into the request body, e.g. `/schedule/expression`. */
|
|
67
|
+
pointer: z.string().startsWith("/"),
|
|
68
|
+
reason: z.string().min(1),
|
|
69
|
+
});
|
|
70
|
+
export const problemSchema = z.object({
|
|
71
|
+
code: problemCodeSchema,
|
|
72
|
+
/** Prose. Never branch on it. */
|
|
73
|
+
detail: z.string().min(1).optional(),
|
|
74
|
+
errors: z.array(fieldErrorSchema).optional(),
|
|
75
|
+
/** Seconds to wait before repeating the request; only on retryable codes. */
|
|
76
|
+
retryAfterSeconds: z.number().int().positive().optional(),
|
|
77
|
+
status: z.number().int().min(400).max(599),
|
|
78
|
+
/** Prose. Never branch on it. */
|
|
79
|
+
title: z.string().min(1),
|
|
80
|
+
/** Echoes the request correlation id so a report can be traced. */
|
|
81
|
+
traceId: z.string().min(1).optional(),
|
|
82
|
+
type: z.literal("about:blank"),
|
|
83
|
+
});
|
|
84
|
+
/**
|
|
85
|
+
* Builds a problem whose status and category cannot contradict its code, and
|
|
86
|
+
* which cannot carry a retry hint on a code that is not retryable.
|
|
87
|
+
*/
|
|
88
|
+
export function problem(code, options) {
|
|
89
|
+
if (options.retryAfterSeconds !== undefined && !isRetryable(code)) {
|
|
90
|
+
throw new RangeError(`${code} is not retryable, so it must not carry retryAfterSeconds`);
|
|
91
|
+
}
|
|
92
|
+
return problemSchema.parse({
|
|
93
|
+
code,
|
|
94
|
+
status: PROBLEM_CODES[code].status,
|
|
95
|
+
type: "about:blank",
|
|
96
|
+
...(options.detail === undefined ? {} : { detail: options.detail }),
|
|
97
|
+
...(options.errors === undefined ? {} : { errors: [...options.errors] }),
|
|
98
|
+
...(options.retryAfterSeconds === undefined
|
|
99
|
+
? {}
|
|
100
|
+
: { retryAfterSeconds: options.retryAfterSeconds }),
|
|
101
|
+
title: options.title,
|
|
102
|
+
...(options.traceId === undefined ? {} : { traceId: options.traceId }),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { Authority } from "./authority.js";
|
|
3
|
+
import type { FieldError } from "./problems.js";
|
|
4
|
+
/**
|
|
5
|
+
* Request and collection mechanics: paging, optimistic concurrency, and
|
|
6
|
+
* idempotent writes.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Cursor paging only. Offsets drift when rows are inserted mid-scan, and a
|
|
10
|
+
* scheduling book changes constantly. The cursor is opaque: clients pass back
|
|
11
|
+
* exactly what they were given.
|
|
12
|
+
*/
|
|
13
|
+
export declare const PAGE_SIZE: {
|
|
14
|
+
readonly default: 25;
|
|
15
|
+
readonly max: 100;
|
|
16
|
+
readonly min: 1;
|
|
17
|
+
};
|
|
18
|
+
export declare const pageRequestSchema: z.ZodObject<{
|
|
19
|
+
cursor: z.ZodOptional<z.ZodString>;
|
|
20
|
+
limit: z.ZodDefault<z.ZodNumber>;
|
|
21
|
+
}, z.core.$strip>;
|
|
22
|
+
export type PageRequest = z.infer<typeof pageRequestSchema>;
|
|
23
|
+
/**
|
|
24
|
+
* A page of results.
|
|
25
|
+
*
|
|
26
|
+
* `freshness` is deliberately part of the contract rather than an
|
|
27
|
+
* implementation detail: cross-partition listings are served from a projection
|
|
28
|
+
* that may lag, and a client that cannot tell fresh from stale will present
|
|
29
|
+
* stale data as fact. `authoritative` means the answer came from the owning
|
|
30
|
+
* partition; `projected` means it may lag and carries the age.
|
|
31
|
+
*/
|
|
32
|
+
export declare const freshnessSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
33
|
+
kind: z.ZodLiteral<"authoritative">;
|
|
34
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
35
|
+
asOf: z.ZodString;
|
|
36
|
+
kind: z.ZodLiteral<"projected">;
|
|
37
|
+
lagSeconds: z.ZodNumber;
|
|
38
|
+
}, z.core.$strip>], "kind">;
|
|
39
|
+
export type Freshness = z.infer<typeof freshnessSchema>;
|
|
40
|
+
/**
|
|
41
|
+
* The `projected` arm alone, for a response that is never authoritative.
|
|
42
|
+
*
|
|
43
|
+
* **A comment saying "always projected" is a claim; this is a check.** The
|
|
44
|
+
* forecast's own schema documented that arm as the only one it ever sends and
|
|
45
|
+
* then admitted both, so every reader had to handle a case the service cannot
|
|
46
|
+
* produce — and the two most useful fields, the age and the instant it was
|
|
47
|
+
* taken, were unreachable without narrowing that no consumer could justify.
|
|
48
|
+
* Naming the narrower shape moves the claim into the type.
|
|
49
|
+
*/
|
|
50
|
+
export declare const projectedFreshnessSchema: z.ZodObject<{
|
|
51
|
+
asOf: z.ZodString;
|
|
52
|
+
kind: z.ZodLiteral<"projected">;
|
|
53
|
+
lagSeconds: z.ZodNumber;
|
|
54
|
+
}, z.core.$strip>;
|
|
55
|
+
export type ProjectedFreshness = z.infer<typeof projectedFreshnessSchema>;
|
|
56
|
+
export declare function pageSchema<TItem extends z.ZodTypeAny>(item: TItem): z.ZodObject<{
|
|
57
|
+
data: z.ZodArray<TItem>;
|
|
58
|
+
freshness: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
59
|
+
kind: z.ZodLiteral<"authoritative">;
|
|
60
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
61
|
+
asOf: z.ZodString;
|
|
62
|
+
kind: z.ZodLiteral<"projected">;
|
|
63
|
+
lagSeconds: z.ZodNumber;
|
|
64
|
+
}, z.core.$strip>], "kind">;
|
|
65
|
+
nextCursor: z.ZodOptional<z.ZodString>;
|
|
66
|
+
}, z.core.$strip>;
|
|
67
|
+
/**
|
|
68
|
+
* Where a page stopped.
|
|
69
|
+
*
|
|
70
|
+
* Every collection in this product is ordered by the instant a row was created
|
|
71
|
+
* and then by its identifier, so one position type serves all of them. The
|
|
72
|
+
* identifier is the tiebreak rather than decoration: two credentials issued in
|
|
73
|
+
* the same millisecond are ordinary, and an order that does not break the tie
|
|
74
|
+
* can show one of them twice and the other never.
|
|
75
|
+
*/
|
|
76
|
+
export interface PagePosition {
|
|
77
|
+
/** The creation instant of the last item on the page just served. */
|
|
78
|
+
readonly createdAtMs: number;
|
|
79
|
+
/** That item's identifier. */
|
|
80
|
+
readonly id: string;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The cursor a client is handed for the next page.
|
|
84
|
+
*
|
|
85
|
+
* Opaque by construction: it is a position and a tenancy fingerprint, and
|
|
86
|
+
* `docs/contracts/v1-conventions.md` says clients pass back exactly what they
|
|
87
|
+
* were given. It is deliberately *not* signed. A signature would imply the value
|
|
88
|
+
* carries authority, and it carries none — every listing is scoped by the
|
|
89
|
+
* principal before a cursor is even looked at.
|
|
90
|
+
*/
|
|
91
|
+
export declare function encodeCursor(authority: Authority, position: PagePosition): string;
|
|
92
|
+
/**
|
|
93
|
+
* The position a cursor names, or nothing.
|
|
94
|
+
*
|
|
95
|
+
* Nothing covers every way a cursor can fail to be one this listing issued:
|
|
96
|
+
* not base64url, not JSON, the wrong shape, or minted under a different
|
|
97
|
+
* authority. They collapse into one answer on purpose — telling a caller *how*
|
|
98
|
+
* their cursor is wrong tells them what a valid one looks like, and the caller
|
|
99
|
+
* never constructs one anyway.
|
|
100
|
+
*/
|
|
101
|
+
export declare function decodeCursor(authority: Authority, cursor: string): PagePosition | undefined;
|
|
102
|
+
export type PageRequestParse = {
|
|
103
|
+
readonly ok: true;
|
|
104
|
+
readonly page: PageRequest;
|
|
105
|
+
} | {
|
|
106
|
+
readonly ok: false;
|
|
107
|
+
readonly errors: readonly FieldError[];
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* A page request, read from a URL's query string.
|
|
111
|
+
*
|
|
112
|
+
* Lives here rather than in a route so REST and MCP cannot disagree about what
|
|
113
|
+
* `limit=0`, `limit=abc` or a repeated parameter mean — the bounds and the
|
|
114
|
+
* default come from `pageRequestSchema` and are stated once.
|
|
115
|
+
*
|
|
116
|
+
* A **repeated** parameter is refused rather than resolved by taking the first
|
|
117
|
+
* or the last. `?limit=5&limit=500` is a caller who does not agree with
|
|
118
|
+
* themselves, and silently picking one makes their bug look like ours.
|
|
119
|
+
*
|
|
120
|
+
* Unknown parameters are ignored. A client written against a later version of
|
|
121
|
+
* this contract must not start failing against an earlier deployment, and that
|
|
122
|
+
* is the direction compatibility has to hold in.
|
|
123
|
+
*/
|
|
124
|
+
export declare function parsePageRequest(query: URLSearchParams): PageRequestParse;
|
|
125
|
+
/**
|
|
126
|
+
* Every mutable resource carries a monotonically increasing `version`, surfaced
|
|
127
|
+
* as a strong ETag. A conditional write that loses the race is refused with
|
|
128
|
+
* `version_conflict` rather than silently overwriting — a cancel racing a
|
|
129
|
+
* replace must not resolve by luck.
|
|
130
|
+
*/
|
|
131
|
+
export declare const versionSchema: z.ZodNumber;
|
|
132
|
+
export type Version = z.infer<typeof versionSchema>;
|
|
133
|
+
export declare function toETag(version: Version): string;
|
|
134
|
+
/** Parses a strong ETag, returning undefined for anything malformed or weak. */
|
|
135
|
+
export declare function fromETag(etag: string): Version | undefined;
|
|
136
|
+
/**
|
|
137
|
+
* The same rule as a schema, and deliberately the *same* rule: the refinement
|
|
138
|
+
* calls the parser rather than restating what it accepts, so a value the
|
|
139
|
+
* document says is valid cannot be one the service then refuses.
|
|
140
|
+
*
|
|
141
|
+
* The `regex` is kept beside the refinement because only the regex survives
|
|
142
|
+
* JSON Schema conversion — the published document carries the pattern, and the
|
|
143
|
+
* refinement is the extra strictness the OpenAPI preamble already discloses
|
|
144
|
+
* that a described schema does not capture.
|
|
145
|
+
*/
|
|
146
|
+
export declare const etagSchema: z.ZodString;
|
|
147
|
+
/**
|
|
148
|
+
* Idempotency keys make an unsafe write safe to retry.
|
|
149
|
+
*
|
|
150
|
+
* The key is scoped to the authority tuple and the operation, so two projects
|
|
151
|
+
* may use the same key without colliding. A replay of the *same* request
|
|
152
|
+
* returns the original result with `replayed: true`. Reusing a key with a
|
|
153
|
+
* *different* request body is a `idempotency_key_reused` conflict, never a
|
|
154
|
+
* silent second write — that distinction is the whole point, and the legacy
|
|
155
|
+
* scheduler had no equivalent.
|
|
156
|
+
*/
|
|
157
|
+
export declare const idempotencyKeySchema: z.ZodString;
|
|
158
|
+
export type IdempotencyKey = z.infer<typeof idempotencyKeySchema>;
|
|
159
|
+
/**
|
|
160
|
+
* The scope a key was used in. Carried on the receipt rather than assumed,
|
|
161
|
+
* because an implementation that keys its receipt store on the idempotency key
|
|
162
|
+
* alone would return one tenant's result to another whenever both happened to
|
|
163
|
+
* send the same key. The scope is part of the record so that mistake cannot be
|
|
164
|
+
* made silently.
|
|
165
|
+
*/
|
|
166
|
+
export declare const idempotencyScopeSchema: z.ZodObject<{
|
|
167
|
+
environment: z.ZodString;
|
|
168
|
+
operation: z.ZodString;
|
|
169
|
+
organization: z.ZodString;
|
|
170
|
+
project: z.ZodString;
|
|
171
|
+
}, z.core.$strip>;
|
|
172
|
+
export type IdempotencyScope = z.infer<typeof idempotencyScopeSchema>;
|
|
173
|
+
export declare const idempotencyReceiptSchema: z.ZodObject<{
|
|
174
|
+
requestFingerprint: z.ZodString;
|
|
175
|
+
replayed: z.ZodBoolean;
|
|
176
|
+
scope: z.ZodObject<{
|
|
177
|
+
environment: z.ZodString;
|
|
178
|
+
operation: z.ZodString;
|
|
179
|
+
organization: z.ZodString;
|
|
180
|
+
project: z.ZodString;
|
|
181
|
+
}, z.core.$strip>;
|
|
182
|
+
storedAt: z.ZodString;
|
|
183
|
+
}, z.core.$strip>;
|
|
184
|
+
export type IdempotencyReceipt = z.infer<typeof idempotencyReceiptSchema>;
|
|
185
|
+
/**
|
|
186
|
+
* The canonical form of a request, as the receipt's `requestFingerprint`
|
|
187
|
+
* describes it: the operation name, then the body with object keys sorted and
|
|
188
|
+
* no insignificant whitespace.
|
|
189
|
+
*
|
|
190
|
+
* Here rather than in each adapter because the receipt schema says every
|
|
191
|
+
* adapter must compute it the same way, and a rule stated in a doc comment and
|
|
192
|
+
* implemented twice is a rule that holds until the second implementation. REST
|
|
193
|
+
* and MCP send the same create differently — one as a JSON body, one as tool
|
|
194
|
+
* arguments — and this is what makes "the same request" mean the same thing to
|
|
195
|
+
* both.
|
|
196
|
+
*
|
|
197
|
+
* Keys are ordered by UTF-16 code unit rather than by `localeCompare`, which is
|
|
198
|
+
* locale-dependent: a canonical form that depends on the machine's locale is
|
|
199
|
+
* not canonical. `undefined`-valued keys are dropped, because `JSON.stringify`
|
|
200
|
+
* drops them too and a caller who omitted a field and one who set it to
|
|
201
|
+
* `undefined` sent the same bytes.
|
|
202
|
+
*/
|
|
203
|
+
export declare function canonicalRequest(operation: string, body: unknown): string;
|
|
204
|
+
/**
|
|
205
|
+
* The fingerprint stored on a receipt: SHA-256 of the canonical request, hex.
|
|
206
|
+
*
|
|
207
|
+
* Hashed rather than stored whole because a body may carry a payload up to
|
|
208
|
+
* `SCHEDULING_BOUNDS.payloadBytes`, and a receipt store that kept every request
|
|
209
|
+
* verbatim would cost the same as the schedules it protects. A cryptographic
|
|
210
|
+
* digest rather than the cheap hash `encodeCursor` uses: a collision there
|
|
211
|
+
* costs a confusing refusal, and a collision here would replay one request's
|
|
212
|
+
* result for a different request, which is the exact failure the receipt exists
|
|
213
|
+
* to prevent.
|
|
214
|
+
*
|
|
215
|
+
* Web Crypto, for the reason `identifiers.ts` gives for `getRandomValues`: it
|
|
216
|
+
* is a standard rather than a Cloudflare extension, so one implementation
|
|
217
|
+
* serves every runtime this package is read from.
|
|
218
|
+
*/
|
|
219
|
+
export declare function requestFingerprint(operation: string, body: unknown): Promise<string>;
|
|
220
|
+
export type IdempotencyOutcome = {
|
|
221
|
+
readonly kind: "accepted";
|
|
222
|
+
} | {
|
|
223
|
+
readonly kind: "replayed";
|
|
224
|
+
} | {
|
|
225
|
+
readonly kind: "conflict";
|
|
226
|
+
};
|
|
227
|
+
/**
|
|
228
|
+
* Decides how a write carrying an idempotency key must be treated. Pure, so
|
|
229
|
+
* both the API and the MCP adapter reach the same answer.
|
|
230
|
+
*
|
|
231
|
+
* A receipt from a different scope is not this caller's key at all — it is
|
|
232
|
+
* accepted as a first use, never replayed and never a conflict.
|
|
233
|
+
*/
|
|
234
|
+
export declare function classifyIdempotency(incoming: {
|
|
235
|
+
readonly fingerprint: string;
|
|
236
|
+
readonly scope: IdempotencyScope;
|
|
237
|
+
}, stored: IdempotencyReceipt | undefined): IdempotencyOutcome;
|
|
238
|
+
/**
|
|
239
|
+
* The headers the mechanics above are carried in, as object schemas whose
|
|
240
|
+
* properties are the header names.
|
|
241
|
+
*
|
|
242
|
+
* The same shape `OperationDefinition.query` uses, for the same reason: a
|
|
243
|
+
* header a route reads and the published document does not carry is drift, and
|
|
244
|
+
* the way to prevent it is for both to derive from one schema. Names are
|
|
245
|
+
* standard where a standard exists — `If-Match` and `ETag` are RFC 9110's, and
|
|
246
|
+
* a conditional write invented a private spelling of them would be a
|
|
247
|
+
* conditional write no HTTP cache, proxy or client library understands.
|
|
248
|
+
*
|
|
249
|
+
* `Idempotency-Key` has no RFC yet; the name is the one the payments industry
|
|
250
|
+
* settled on and the one the IETF draft uses, which is what an integrator will
|
|
251
|
+
* already have a client for.
|
|
252
|
+
*/
|
|
253
|
+
export declare const CONCURRENCY_HEADERS: {
|
|
254
|
+
readonly etag: "ETag";
|
|
255
|
+
readonly idempotencyKey: "Idempotency-Key";
|
|
256
|
+
readonly ifMatch: "If-Match";
|
|
257
|
+
readonly replayed: "Idempotency-Replayed";
|
|
258
|
+
};
|
|
259
|
+
export declare const idempotencyRequestHeaderSchema: z.ZodObject<{
|
|
260
|
+
"Idempotency-Key": z.ZodOptional<z.ZodString>;
|
|
261
|
+
}, z.core.$strip>;
|
|
262
|
+
/**
|
|
263
|
+
* A write for which idempotency is mandatory, rather than an optional
|
|
264
|
+
* protection a caller may opt into.
|
|
265
|
+
*
|
|
266
|
+
* Redelivery uses this shape because retrying an ambiguous redelivery request
|
|
267
|
+
* without the same key could send a second callback. Keeping it distinct from
|
|
268
|
+
* `idempotencyRequestHeaderSchema` lets schedule creation retain its published
|
|
269
|
+
* optional behavior without weakening the redelivery promise.
|
|
270
|
+
*/
|
|
271
|
+
export declare const requiredIdempotencyRequestHeaderSchema: z.ZodObject<{
|
|
272
|
+
"Idempotency-Key": z.ZodString;
|
|
273
|
+
}, z.core.$strip>;
|
|
274
|
+
export declare const conditionalRequestHeaderSchema: z.ZodObject<{
|
|
275
|
+
"If-Match": z.ZodOptional<z.ZodString>;
|
|
276
|
+
}, z.core.$strip>;
|
|
277
|
+
/**
|
|
278
|
+
* Required rather than optional, deliberately: an operation that declares this
|
|
279
|
+
* promises the header on every success, and a client that has to test for its
|
|
280
|
+
* presence before making a conditional write will write the untested branch
|
|
281
|
+
* wrong.
|
|
282
|
+
*/
|
|
283
|
+
export declare const versionResponseHeaderSchema: z.ZodObject<{
|
|
284
|
+
ETag: z.ZodString;
|
|
285
|
+
}, z.core.$strip>;
|
|
286
|
+
/**
|
|
287
|
+
* What a replayed write answers with, on top of the version.
|
|
288
|
+
*
|
|
289
|
+
* `replayed: true` is a promise `docs/contracts/v1-conventions.md` and
|
|
290
|
+
* `idempotencyReceiptSchema` have both made since W0-02 — the receipt's own
|
|
291
|
+
* field is documented as "true when **this response** replays an earlier
|
|
292
|
+
* identical request" — and until W2-02's review nothing carried it on the wire.
|
|
293
|
+
* A caller could not tell a replay from a first create, which makes the
|
|
294
|
+
* difference between "my retry worked" and "I created a second one" unanswerable
|
|
295
|
+
* from the response.
|
|
296
|
+
*
|
|
297
|
+
* Present only on a replay rather than `false` on every create: an absent
|
|
298
|
+
* header is the ordinary case, and a client that has to read a header to learn
|
|
299
|
+
* nothing happened is a client that will not read it.
|
|
300
|
+
*/
|
|
301
|
+
export declare const replayedResponseHeaderSchema: z.ZodObject<{
|
|
302
|
+
ETag: z.ZodString;
|
|
303
|
+
"Idempotency-Replayed": z.ZodOptional<z.ZodLiteral<"true">>;
|
|
304
|
+
}, z.core.$strip>;
|
|
305
|
+
/** Replay marker for an idempotent create whose result has no ETag. */
|
|
306
|
+
export declare const idempotencyReplayResponseHeaderSchema: z.ZodObject<{
|
|
307
|
+
"Idempotency-Replayed": z.ZodOptional<z.ZodLiteral<"true">>;
|
|
308
|
+
}, z.core.$strip>;
|