@sponson/adapters 0.4.0 → 0.5.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/README.md +1 -1
- package/dist/algolia.d.ts +6 -0
- package/dist/algolia.js +151 -0
- package/dist/algolia.js.map +1 -0
- package/dist/clerk.js +1 -1
- package/dist/clerk.js.map +1 -1
- package/dist/cloudflare.d.ts +25 -0
- package/dist/cloudflare.js +310 -0
- package/dist/cloudflare.js.map +1 -0
- package/dist/common.d.ts +2 -6
- package/dist/common.js.map +1 -1
- package/dist/digest.d.ts +46 -0
- package/dist/digest.js +129 -0
- package/dist/digest.js.map +1 -0
- package/dist/digitalocean.d.ts +5 -0
- package/dist/digitalocean.js +243 -0
- package/dist/digitalocean.js.map +1 -0
- package/dist/http-adapter-action.d.ts +63 -0
- package/dist/http-adapter-action.js +369 -0
- package/dist/http-adapter-action.js.map +1 -0
- package/dist/http-adapter-auth.d.ts +88 -0
- package/dist/http-adapter-auth.js +437 -0
- package/dist/http-adapter-auth.js.map +1 -0
- package/dist/http-adapter-client.d.ts +51 -0
- package/dist/http-adapter-client.js +156 -0
- package/dist/http-adapter-client.js.map +1 -0
- package/dist/http-adapter-graphql.d.ts +39 -0
- package/dist/http-adapter-graphql.js +157 -0
- package/dist/http-adapter-graphql.js.map +1 -0
- package/dist/http-adapter-list.d.ts +33 -0
- package/dist/http-adapter-list.js +421 -0
- package/dist/http-adapter-list.js.map +1 -0
- package/dist/http-adapter-resource.d.ts +29 -0
- package/dist/http-adapter-resource.js +462 -0
- package/dist/http-adapter-resource.js.map +1 -0
- package/dist/http-adapter-spec.d.ts +216 -0
- package/dist/http-adapter-spec.js +660 -0
- package/dist/http-adapter-spec.js.map +1 -0
- package/dist/http-adapter.d.ts +16 -0
- package/dist/http-adapter.js +28 -0
- package/dist/http-adapter.js.map +1 -0
- package/dist/http.d.ts +61 -2
- package/dist/http.js +79 -18
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +12 -0
- package/dist/index.js +24 -2
- package/dist/index.js.map +1 -1
- package/dist/launchdarkly.d.ts +15 -0
- package/dist/launchdarkly.js +337 -0
- package/dist/launchdarkly.js.map +1 -0
- package/dist/manual.d.ts +13 -0
- package/dist/manual.js +201 -0
- package/dist/manual.js.map +1 -0
- package/dist/neon.js +1 -1
- package/dist/neon.js.map +1 -1
- package/dist/netlify.d.ts +18 -0
- package/dist/netlify.js +436 -0
- package/dist/netlify.js.map +1 -0
- package/dist/planetscale.d.ts +10 -0
- package/dist/planetscale.js +311 -0
- package/dist/planetscale.js.map +1 -0
- package/dist/providers.d.ts +26 -0
- package/dist/providers.js +36 -0
- package/dist/providers.js.map +1 -0
- package/dist/recipes.d.ts +167 -0
- package/dist/recipes.js +246 -0
- package/dist/recipes.js.map +1 -0
- package/dist/secrets.d.ts +44 -0
- package/dist/secrets.js +279 -0
- package/dist/secrets.js.map +1 -1
- package/dist/sigv4.d.ts +56 -0
- package/dist/sigv4.js +118 -0
- package/dist/sigv4.js.map +1 -0
- package/dist/supabase.d.ts +9 -0
- package/dist/supabase.js +385 -0
- package/dist/supabase.js.map +1 -0
- package/dist/vercel.js +1 -1
- package/dist/vercel.js.map +1 -1
- package/dist/xata.d.ts +8 -0
- package/dist/xata.js +142 -0
- package/dist/xata.js.map +1 -0
- package/dist/xml.d.ts +25 -0
- package/dist/xml.js +211 -0
- package/dist/xml.js.map +1 -0
- package/package.json +11 -4
- package/recipes/amplify.yaml +147 -0
- package/recipes/auth0.yaml +178 -0
- package/recipes/azure_swa.yaml +116 -0
- package/recipes/cloud_run.yaml +97 -0
- package/recipes/cloudflare.yaml +108 -0
- package/recipes/cockroachdb.yaml +132 -0
- package/recipes/configcat.yaml +123 -0
- package/recipes/datadog.yaml +136 -0
- package/recipes/firebase.yaml +84 -0
- package/recipes/flagsmith.yaml +98 -0
- package/recipes/fly.yaml +97 -0
- package/recipes/fly_machines.yaml +67 -0
- package/recipes/gcs.yaml +102 -0
- package/recipes/github.yaml +123 -0
- package/recipes/growthbook.yaml +124 -0
- package/recipes/heroku.yaml +98 -0
- package/recipes/honeycomb.yaml +89 -0
- package/recipes/inngest.yaml +97 -0
- package/recipes/kinde.yaml +120 -0
- package/recipes/mongodb_atlas.yaml +119 -0
- package/recipes/okta.yaml +177 -0
- package/recipes/posthog.yaml +127 -0
- package/recipes/postmark.yaml +105 -0
- package/recipes/postmark_server.yaml +93 -0
- package/recipes/prisma.yaml +150 -0
- package/recipes/qstash.yaml +134 -0
- package/recipes/railway.yaml +130 -0
- package/recipes/render.yaml +103 -0
- package/recipes/resend.yaml +130 -0
- package/recipes/s3.yaml +108 -0
- package/recipes/sendgrid.yaml +165 -0
- package/recipes/sentry.yaml +158 -0
- package/recipes/split.yaml +121 -0
- package/recipes/statsig.yaml +124 -0
- package/recipes/stripe.yaml +310 -0
- package/recipes/stytch.yaml +113 -0
- package/recipes/svix.yaml +109 -0
- package/recipes/turso.yaml +98 -0
- package/recipes/unleash.yaml +149 -0
- package/recipes/upstash.yaml +130 -0
- package/recipes/vercel.yaml +116 -0
- package/recipes/workos.yaml +70 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The generic `http` adapter's client and the requests both of its ops send: the API from the line's provider
|
|
3
|
+
* block (`actx.provider` is `providers.http.<api>`, narrowed by `OpSpec.providerFor`), its credential from the
|
|
4
|
+
* environment variable(s) the block names, and writes with the idempotency each method has.
|
|
5
|
+
*/
|
|
6
|
+
import { SponsonError, canonicalJson, markerKind } from "@sponson/core";
|
|
7
|
+
import { clientFor, optionalEnv } from "./common.js";
|
|
8
|
+
import { isObject, isProviderError } from "./http.js";
|
|
9
|
+
import { DEFAULT_AUTH_DEPS, credentialFor } from "./http-adapter-auth.js";
|
|
10
|
+
import { ADAPTER, at, pointerTokens, parseApi } from "./http-adapter-spec.js";
|
|
11
|
+
/** The dependencies an op was built with, by the adapter context each of its calls received (see `withDeps`). */
|
|
12
|
+
const DEPS = new WeakMap();
|
|
13
|
+
/**
|
|
14
|
+
* `op` with every call's adapter context carrying `deps` (how `auth.command` runs a CLI, the clock), so the
|
|
15
|
+
* clients built inside see them. The built-in adapter uses the real ones; tests inject fakes.
|
|
16
|
+
*/
|
|
17
|
+
export function withDeps(op, deps) {
|
|
18
|
+
const bind = (actx) => {
|
|
19
|
+
const bound = { ...actx };
|
|
20
|
+
DEPS.set(bound, deps);
|
|
21
|
+
return bound;
|
|
22
|
+
};
|
|
23
|
+
return {
|
|
24
|
+
...op,
|
|
25
|
+
read: (actx, params) => op.read(bind(actx), params),
|
|
26
|
+
apply: (actx, params, live) => op.apply(bind(actx), params, live),
|
|
27
|
+
destroy: (actx, resources) => op.destroy(bind(actx), resources),
|
|
28
|
+
...(op.listScope ? { listScope: (actx, params) => op.listScope(bind(actx), params) } : {}),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/** A line's own credential value (`credential`), by the adapter context its calls use (see `withLineCredential`). */
|
|
32
|
+
const LINE_CREDENTIAL = new WeakMap();
|
|
33
|
+
/**
|
|
34
|
+
* `actx` for a line with `credential` (ADR 0017, "Line credentials"): its clients send that value instead of the
|
|
35
|
+
* API block's variable. The value is registered as a secret before any request carries it (the engine masks
|
|
36
|
+
* sensitive outputs and secrets already; this covers an engine without the hook). Without one, `actx` itself.
|
|
37
|
+
*/
|
|
38
|
+
export function withLineCredential(actx, value) {
|
|
39
|
+
if (value === undefined)
|
|
40
|
+
return actx;
|
|
41
|
+
if (typeof value !== "string" || value === "" || markerKind(value) !== null) {
|
|
42
|
+
throw new SponsonError("PARAM_INVALID", `${ADAPTER}: \`credential\` has no value to authenticate with (a reference not resolved yet, or a once-only output kept from an earlier run)`, { adapter: ADAPTER, param: "credential" });
|
|
43
|
+
}
|
|
44
|
+
actx.secret?.(value);
|
|
45
|
+
const bound = { ...actx };
|
|
46
|
+
const deps = DEPS.get(actx);
|
|
47
|
+
if (deps)
|
|
48
|
+
DEPS.set(bound, deps);
|
|
49
|
+
LINE_CREDENTIAL.set(bound, value);
|
|
50
|
+
return bound;
|
|
51
|
+
}
|
|
52
|
+
/** The variables holding a credential's secret part, for `refuse_credential`. */
|
|
53
|
+
function secretEnvs(auth) {
|
|
54
|
+
switch (auth.kind) {
|
|
55
|
+
case "bearer":
|
|
56
|
+
case "header":
|
|
57
|
+
return [auth.env];
|
|
58
|
+
case "headers":
|
|
59
|
+
return auth.headers.map((h) => h.env);
|
|
60
|
+
case "basic":
|
|
61
|
+
case "digest":
|
|
62
|
+
return [auth.passwordEnv];
|
|
63
|
+
case "oauth2":
|
|
64
|
+
return [auth.clientSecretEnv];
|
|
65
|
+
case "sigv4":
|
|
66
|
+
return [auth.secretAccessKeyEnv];
|
|
67
|
+
case "command":
|
|
68
|
+
case "none":
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Refuse a credential the API refuses (`refuse_credential`): PROVIDER_AUTH naming the variable and the prefix,
|
|
74
|
+
* never the value, before any request is sent.
|
|
75
|
+
*/
|
|
76
|
+
function refuseCredentials(cfg, env, line) {
|
|
77
|
+
if (!cfg.refuse || /^(1|true)$/i.test(env[cfg.refuse.unlessEnv] ?? ""))
|
|
78
|
+
return;
|
|
79
|
+
// A line's own credential is checked like the variable it replaces, and named as `credential`.
|
|
80
|
+
const values = line === undefined ? secretEnvs(cfg.auth).map((name) => [name, env[name] ?? ""]) : [["credential", line]];
|
|
81
|
+
for (const [name, value] of values) {
|
|
82
|
+
const prefix = cfg.refuse.prefixes.find((p) => value.startsWith(p));
|
|
83
|
+
if (!prefix)
|
|
84
|
+
continue;
|
|
85
|
+
throw new SponsonError("PROVIDER_AUTH", `${ADAPTER}: ${name} holds a \`${prefix}…\` credential, which this API refuses (refuse_credential): a preview environment must not write with it. ` +
|
|
86
|
+
`Use a test-mode credential, or set ${cfg.refuse.unlessEnv}=1 if this run really is meant to use it.`, { adapter: ADAPTER, env: name });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The client for the line's API. The credential (a static header, or a signer for the forms obtained or computed
|
|
91
|
+
* per request) comes from http-adapter-auth.ts; everything it holds or obtains is masked in this client's error text
|
|
92
|
+
* too, whatever the engine already masks. `opts`: where the block is, for messages; whether a block without `auth`
|
|
93
|
+
* sends no credential (a manual step's verify request); the adapter named in errors.
|
|
94
|
+
*/
|
|
95
|
+
export function httpClient(actx, req = {}, opts = {}) {
|
|
96
|
+
const cfg = parseApi(actx.provider, opts.where ?? "providers.http.<api>", opts);
|
|
97
|
+
const baseUrl = (cfg.baseUrlEnv ? optionalEnv(actx.env, cfg.baseUrlEnv) : undefined) ?? cfg.baseUrl;
|
|
98
|
+
let hidden = () => [];
|
|
99
|
+
const redact = (t) => hidden().reduce((s, h) => (h.length >= 4 ? s.split(h).join("****") : s), actx.redact(t));
|
|
100
|
+
const line = LINE_CREDENTIAL.get(actx);
|
|
101
|
+
refuseCredentials(cfg, actx.env, line);
|
|
102
|
+
const cred = credentialFor(actx, cfg.auth, DEPS.get(actx) ?? DEFAULT_AUTH_DEPS, redact, line);
|
|
103
|
+
hidden = () => [cred.token, ...cred.masks()];
|
|
104
|
+
const contentType = req.contentType ?? cfg.mediaType;
|
|
105
|
+
return clientFor({ ...actx, redact }, opts.adapter ?? ADAPTER, {
|
|
106
|
+
baseUrl,
|
|
107
|
+
token: cred.token,
|
|
108
|
+
authHeader: cred.authHeader,
|
|
109
|
+
...(cred.signer ? { signer: cred.signer } : {}),
|
|
110
|
+
headers: { ...cfg.headers, ...(req.headers ?? {}) },
|
|
111
|
+
encoding: req.encoding ?? cfg.encoding,
|
|
112
|
+
...(contentType ? { contentType } : {}),
|
|
113
|
+
...(cfg.mediaType ? { accept: cfg.mediaType } : {}),
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* A write that sets state. PUT is idempotent by method; a PATCH or POST that sets fields (or replaces a whole
|
|
118
|
+
* list) may safely be sent twice, so a dropped connection is retried like a read. `idempotent: false` is for a
|
|
119
|
+
* create without an idempotency key: sending it twice could make two objects.
|
|
120
|
+
*/
|
|
121
|
+
export function write(api, method, path, body, idempotent) {
|
|
122
|
+
if (method === "PUT")
|
|
123
|
+
return api.put(path, body);
|
|
124
|
+
if (method === "PATCH")
|
|
125
|
+
return api.patch(path, body, undefined, { idempotent });
|
|
126
|
+
if (method === "DELETE")
|
|
127
|
+
return api.delete(path);
|
|
128
|
+
return api.post(path, body, undefined, { idempotent });
|
|
129
|
+
}
|
|
130
|
+
/** A provider error whose status is one of `statuses`, or of `code`. */
|
|
131
|
+
export function failedWith(e, code, statuses) {
|
|
132
|
+
if (isProviderError(e, code))
|
|
133
|
+
return true;
|
|
134
|
+
return isProviderError(e) && e.details.status !== undefined && statuses.includes(e.details.status);
|
|
135
|
+
}
|
|
136
|
+
/** An output value: literals as they are, anything structured as canonical JSON, absent as undefined. */
|
|
137
|
+
export function literalOf(v) {
|
|
138
|
+
if (v === undefined || v === null)
|
|
139
|
+
return undefined;
|
|
140
|
+
if (typeof v === "string" || typeof v === "number" || typeof v === "boolean")
|
|
141
|
+
return v;
|
|
142
|
+
return canonicalJson(v);
|
|
143
|
+
}
|
|
144
|
+
/** A provider id at `idPath` in `item`: a non-empty string or a number. */
|
|
145
|
+
export function idOf(item, idPath) {
|
|
146
|
+
const v = at(item, pointerTokens(idPath));
|
|
147
|
+
if ((typeof v === "string" && v !== "") || typeof v === "number")
|
|
148
|
+
return String(v);
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
/** The object at `itemPath` in a response body, when it is one. */
|
|
152
|
+
export function itemOf(body, itemPath) {
|
|
153
|
+
const v = at(body, pointerTokens(itemPath));
|
|
154
|
+
return isObject(v) ? v : undefined;
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=http-adapter-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-adapter-client.js","sourceRoot":"","sources":["../src/http-adapter-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type ApiClient, type ProviderErrorCode } from "./http.js";
|
|
2
|
+
/** One GraphQL request of a line: the document, its variables (values may be references), and where the result is. */
|
|
3
|
+
export interface GraphqlSpec {
|
|
4
|
+
query: string;
|
|
5
|
+
variables: Record<string, unknown>;
|
|
6
|
+
/** JSON pointer into `data` to the result (the object, the list); absent: the op's `item_path` / `find.list_path`. */
|
|
7
|
+
dataPath?: string;
|
|
8
|
+
/** The mutation sets state (an upsert), so a dropped connection may be retried. */
|
|
9
|
+
idempotent: boolean;
|
|
10
|
+
}
|
|
11
|
+
/** The placeholder a variable may hold to receive the object's id: a string leaf that is exactly this. */
|
|
12
|
+
export declare const ID_VARIABLE = "{id}";
|
|
13
|
+
export declare function parseGraphql(v: unknown, param: string, idAllowed: boolean): GraphqlSpec;
|
|
14
|
+
/**
|
|
15
|
+
* The first top-level field the operation selects (`variables` in `query($p: String!) { variables(projectId: $p) }`,
|
|
16
|
+
* after any alias): it names the request in keys and labels. Undefined when the document has none.
|
|
17
|
+
*/
|
|
18
|
+
export declare function rootField(query: string): string | undefined;
|
|
19
|
+
/** True when some string leaf of `v` is exactly `{id}`. */
|
|
20
|
+
export declare function usesId(v: unknown): boolean;
|
|
21
|
+
/** `v` with every `{id}` leaf replaced by `id`. */
|
|
22
|
+
export declare function fillVariables(v: unknown, id: string | undefined): unknown;
|
|
23
|
+
/** True when a variable is still a pending reference or an unresolved secret (the request cannot be made yet). */
|
|
24
|
+
export declare function variablesPending(v: unknown): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* How a locating request (`read`, `find`) is named in resource keys: `graphql:variables({"environmentId":"…"})`.
|
|
27
|
+
* Never the query text, so rewording a query does not re-identify the resource.
|
|
28
|
+
*/
|
|
29
|
+
export declare function requestName(g: GraphqlSpec): string;
|
|
30
|
+
/**
|
|
31
|
+
* The PROVIDER_* code of a GraphQL error: its message first (Railway answers a denied token with
|
|
32
|
+
* `INTERNAL_SERVER_ERROR` "Not Authorized"), then `extensions.code`, then PROVIDER_INVALID.
|
|
33
|
+
*/
|
|
34
|
+
export declare function classifyGraphqlError(message: string, code: string | undefined): ProviderErrorCode;
|
|
35
|
+
/**
|
|
36
|
+
* Send one GraphQL request and return its `data` (null when absent). HTTP failures are classified by the client as
|
|
37
|
+
* for any request; a 2xx answer with `errors` is classified here.
|
|
38
|
+
*/
|
|
39
|
+
export declare function graphqlRequest(api: ApiClient, g: GraphqlSpec, id: string | undefined, redact: (t: string) => string, mutation: boolean): Promise<unknown>;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GraphQL requests for `http.resource`: any of `find`, `read`, `create`, `update` and `delete` may be
|
|
3
|
+
* `{ graphql: { query, variables?, data_path? } }` instead of a method and a path. The request is a POST of
|
|
4
|
+
* `{ query, variables }` as JSON to the API's `base_url` (the GraphQL endpoint); what it answers is `data`, and a
|
|
5
|
+
* non-empty `errors` array is a provider error classified like an HTTP status would be (not found, conflict, not
|
|
6
|
+
* authorised, …). See ADR 0017 (amendment) and docs/plan-format.md#graphql-requests.
|
|
7
|
+
*/
|
|
8
|
+
import { SponsonError, canonicalJson, markerKind } from "@sponson/core";
|
|
9
|
+
import { paramError } from "./common.js";
|
|
10
|
+
import { isObject } from "./http.js";
|
|
11
|
+
const ADAPTER = "http";
|
|
12
|
+
/** The placeholder a variable may hold to receive the object's id: a string leaf that is exactly this. */
|
|
13
|
+
export const ID_VARIABLE = "{id}";
|
|
14
|
+
const GRAPHQL_KEYS = ["query", "variables", "data_path", "idempotent"];
|
|
15
|
+
/** `<param>.graphql`, checked. `{id}` may appear as a variable value only where the id is known (not in find or create). */
|
|
16
|
+
function queryParam(v, param) {
|
|
17
|
+
if (typeof v !== "string" || !v.includes("{") || rootField(v) === undefined) {
|
|
18
|
+
throw paramError(ADAPTER, `\`${param}\` must be a GraphQL document with one operation, e.g. "mutation($input: X!) { thing(input: $input) }"`, param);
|
|
19
|
+
}
|
|
20
|
+
return v;
|
|
21
|
+
}
|
|
22
|
+
function variablesParam(v, param, idAllowed) {
|
|
23
|
+
const variables = v ?? {};
|
|
24
|
+
if (!isObject(variables))
|
|
25
|
+
throw paramError(ADAPTER, `\`${param}\` must be a map of variable name to value`, param);
|
|
26
|
+
if (!idAllowed && usesId(variables))
|
|
27
|
+
throw paramError(ADAPTER, `\`${param}\` uses "${ID_VARIABLE}", which is not known here (the id exists only once the object does)`, param);
|
|
28
|
+
return variables;
|
|
29
|
+
}
|
|
30
|
+
export function parseGraphql(v, param, idAllowed) {
|
|
31
|
+
if (!isObject(v))
|
|
32
|
+
throw paramError(ADAPTER, `\`${param}\` must be a map: { query, variables?, data_path? }`, param);
|
|
33
|
+
const extra = Object.keys(v).find((k) => !GRAPHQL_KEYS.includes(k));
|
|
34
|
+
if (extra)
|
|
35
|
+
throw paramError(ADAPTER, `\`${param}.${extra}\` is not known (known: ${GRAPHQL_KEYS.join(", ")})`, `${param}.${extra}`);
|
|
36
|
+
const query = queryParam(v.query, `${param}.query`);
|
|
37
|
+
const variables = variablesParam(v.variables, `${param}.variables`, idAllowed);
|
|
38
|
+
const dp = v.data_path;
|
|
39
|
+
if (dp !== undefined && (typeof dp !== "string" || (dp !== "" && !dp.startsWith("/"))))
|
|
40
|
+
throw paramError(ADAPTER, `\`${param}.data_path\` must be a JSON pointer into \`data\`, e.g. /app/secrets`, `${param}.data_path`);
|
|
41
|
+
if (v.idempotent !== undefined && typeof v.idempotent !== "boolean")
|
|
42
|
+
throw paramError(ADAPTER, `\`${param}.idempotent\` must be true or false`, `${param}.idempotent`);
|
|
43
|
+
return { query, variables, ...(typeof dp === "string" ? { dataPath: dp } : {}), idempotent: v.idempotent === true };
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The first top-level field the operation selects (`variables` in `query($p: String!) { variables(projectId: $p) }`,
|
|
47
|
+
* after any alias): it names the request in keys and labels. Undefined when the document has none.
|
|
48
|
+
*/
|
|
49
|
+
export function rootField(query) {
|
|
50
|
+
const withoutComments = query.replace(/#[^\n]*/g, "");
|
|
51
|
+
const body = withoutComments.slice(withoutComments.indexOf("{") + 1);
|
|
52
|
+
const m = /^\s*([_A-Za-z][_0-9A-Za-z]*)(\s*:\s*([_A-Za-z][_0-9A-Za-z]*))?/.exec(body);
|
|
53
|
+
return m ? (m[3] ?? m[1]) : undefined;
|
|
54
|
+
}
|
|
55
|
+
/** True when some string leaf of `v` is exactly `{id}`. */
|
|
56
|
+
export function usesId(v) {
|
|
57
|
+
if (v === ID_VARIABLE)
|
|
58
|
+
return true;
|
|
59
|
+
if (Array.isArray(v))
|
|
60
|
+
return v.some(usesId);
|
|
61
|
+
return isObject(v) && Object.values(v).some(usesId);
|
|
62
|
+
}
|
|
63
|
+
/** `v` with every `{id}` leaf replaced by `id`. */
|
|
64
|
+
export function fillVariables(v, id) {
|
|
65
|
+
if (v === ID_VARIABLE && id !== undefined)
|
|
66
|
+
return id;
|
|
67
|
+
if (Array.isArray(v))
|
|
68
|
+
return v.map((x) => fillVariables(x, id));
|
|
69
|
+
if (isObject(v))
|
|
70
|
+
return Object.fromEntries(Object.entries(v).map(([k, x]) => [k, fillVariables(x, id)]));
|
|
71
|
+
return v;
|
|
72
|
+
}
|
|
73
|
+
/** True when a variable is still a pending reference or an unresolved secret (the request cannot be made yet). */
|
|
74
|
+
export function variablesPending(v) {
|
|
75
|
+
const kind = markerKind(v);
|
|
76
|
+
if (kind === "pending" || kind === "secret")
|
|
77
|
+
return true;
|
|
78
|
+
if (Array.isArray(v))
|
|
79
|
+
return v.some(variablesPending);
|
|
80
|
+
return isObject(v) && Object.values(v).some(variablesPending);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* How a locating request (`read`, `find`) is named in resource keys: `graphql:variables({"environmentId":"…"})`.
|
|
84
|
+
* Never the query text, so rewording a query does not re-identify the resource.
|
|
85
|
+
*/
|
|
86
|
+
export function requestName(g) {
|
|
87
|
+
return `graphql:${rootField(g.query) ?? "operation"}(${canonicalJson(g.variables)})`;
|
|
88
|
+
}
|
|
89
|
+
const BY_CODE = {
|
|
90
|
+
UNAUTHENTICATED: "PROVIDER_AUTH",
|
|
91
|
+
UNAUTHORIZED: "PROVIDER_AUTH",
|
|
92
|
+
FORBIDDEN: "PROVIDER_AUTH",
|
|
93
|
+
NOT_FOUND: "PROVIDER_NOT_FOUND",
|
|
94
|
+
CONFLICT: "PROVIDER_CONFLICT",
|
|
95
|
+
ALREADY_EXISTS: "PROVIDER_CONFLICT",
|
|
96
|
+
RATE_LIMITED: "PROVIDER_TRANSIENT",
|
|
97
|
+
TOO_MANY_REQUESTS: "PROVIDER_TRANSIENT",
|
|
98
|
+
SERVICE_UNAVAILABLE: "PROVIDER_TRANSIENT",
|
|
99
|
+
BAD_USER_INPUT: "PROVIDER_INVALID",
|
|
100
|
+
GRAPHQL_VALIDATION_FAILED: "PROVIDER_INVALID",
|
|
101
|
+
GRAPHQL_PARSE_FAILED: "PROVIDER_INVALID",
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* The PROVIDER_* code of a GraphQL error: its message first (Railway answers a denied token with
|
|
105
|
+
* `INTERNAL_SERVER_ERROR` "Not Authorized"), then `extensions.code`, then PROVIDER_INVALID.
|
|
106
|
+
*/
|
|
107
|
+
export function classifyGraphqlError(message, code) {
|
|
108
|
+
if (/\b(not authori[sz]ed|unauthori[sz]ed|unauthenticated|forbidden|permission denied)\b/i.test(message))
|
|
109
|
+
return "PROVIDER_AUTH";
|
|
110
|
+
if (/\b(not found|does not exist|could not find|no such)\b/i.test(message))
|
|
111
|
+
return "PROVIDER_NOT_FOUND";
|
|
112
|
+
if (/\b(already exists|already taken|duplicate)\b/i.test(message))
|
|
113
|
+
return "PROVIDER_CONFLICT";
|
|
114
|
+
const byCode = code === undefined ? undefined : BY_CODE[code.toUpperCase()];
|
|
115
|
+
if (byCode)
|
|
116
|
+
return byCode;
|
|
117
|
+
return code?.toUpperCase() === "INTERNAL_SERVER_ERROR" ? "PROVIDER_TRANSIENT" : "PROVIDER_INVALID";
|
|
118
|
+
}
|
|
119
|
+
function errorOf(errors, name, redact) {
|
|
120
|
+
const first = isObject(errors[0]) ? errors[0] : {};
|
|
121
|
+
const message = typeof first.message === "string" ? first.message : JSON.stringify(first);
|
|
122
|
+
const ext = isObject(first.extensions) ? first.extensions : {};
|
|
123
|
+
const code = typeof ext.code === "string" ? ext.code : undefined;
|
|
124
|
+
const text = redact(message).replace(/\s+/g, " ").slice(0, 300);
|
|
125
|
+
const more = errors.length > 1 ? ` (and ${errors.length - 1} more)` : "";
|
|
126
|
+
return new SponsonError(classifyGraphqlError(message, code), `${ADAPTER}: GraphQL ${name} → ${code ?? "error"}: ${text}${more}`, {
|
|
127
|
+
adapter: ADAPTER,
|
|
128
|
+
method: "POST",
|
|
129
|
+
path: name,
|
|
130
|
+
...(code ? { graphqlCode: code } : {}),
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Send one GraphQL request and return its `data` (null when absent). HTTP failures are classified by the client as
|
|
135
|
+
* for any request; a 2xx answer with `errors` is classified here.
|
|
136
|
+
*/
|
|
137
|
+
export async function graphqlRequest(api, g, id, redact, mutation) {
|
|
138
|
+
const body = { query: g.query, variables: fillVariables(g.variables, id) };
|
|
139
|
+
// Named by its root field only: a mutation's variables may hold the very secret it writes.
|
|
140
|
+
const name = rootField(g.query) ?? "operation";
|
|
141
|
+
let answer;
|
|
142
|
+
try {
|
|
143
|
+
answer = await api.post("", body, undefined, { idempotent: !mutation || g.idempotent });
|
|
144
|
+
}
|
|
145
|
+
catch (e) {
|
|
146
|
+
// The client names the request `POST <path>`, and a GraphQL request's path is empty: name the operation instead.
|
|
147
|
+
if (e instanceof SponsonError)
|
|
148
|
+
throw new SponsonError(e.code, e.message.replace(/^http: POST {2}→/, `${ADAPTER}: GraphQL ${name} →`), { ...e.details, path: name });
|
|
149
|
+
throw e;
|
|
150
|
+
}
|
|
151
|
+
if (!isObject(answer))
|
|
152
|
+
throw new SponsonError("PROVIDER_RESPONSE", `${ADAPTER}: GraphQL ${name}: the answer is not a JSON object`, { adapter: ADAPTER, method: "POST", path: name });
|
|
153
|
+
if (Array.isArray(answer.errors) && answer.errors.length > 0)
|
|
154
|
+
throw errorOf(answer.errors, name, redact);
|
|
155
|
+
return answer.data ?? null;
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=http-adapter-graphql.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-adapter-graphql.js","sourceRoot":"","sources":["../src/http-adapter-graphql.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AACxE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,QAAQ,EAA0C,MAAM,WAAW,CAAC;AAE7E,MAAM,OAAO,GAAG,MAAM,CAAC;AAYvB,0GAA0G;AAC1G,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC;AAElC,MAAM,YAAY,GAAG,CAAC,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,CAAC,CAAC;AAEvE,4HAA4H;AAC5H,SAAS,UAAU,CAAC,CAAU,EAAE,KAAa;IAC3C,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,KAAK,SAAS,EAAE,CAAC;QAC5E,MAAM,UAAU,CAAC,OAAO,EAAE,KAAK,KAAK,wGAAwG,EAAE,KAAK,CAAC,CAAC;IACvJ,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,SAAS,cAAc,CAAC,CAAU,EAAE,KAAa,EAAE,SAAkB;IACnE,MAAM,SAAS,GAAG,CAAC,IAAI,EAAE,CAAC;IAC1B,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC;QAAE,MAAM,UAAU,CAAC,OAAO,EAAE,KAAK,KAAK,4CAA4C,EAAE,KAAK,CAAC,CAAC;IACnH,IAAI,CAAC,SAAS,IAAI,MAAM,CAAC,SAAS,CAAC;QAAE,MAAM,UAAU,CAAC,OAAO,EAAE,KAAK,KAAK,YAAY,WAAW,sEAAsE,EAAE,KAAK,CAAC,CAAC;IAC/K,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,CAAU,EAAE,KAAa,EAAE,SAAkB;IACxE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;QAAE,MAAM,UAAU,CAAC,OAAO,EAAE,KAAK,KAAK,qDAAqD,EAAE,KAAK,CAAC,CAAC;IACpH,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;IACpE,IAAI,KAAK;QAAE,MAAM,UAAU,CAAC,OAAO,EAAE,KAAK,KAAK,IAAI,KAAK,2BAA2B,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,KAAK,EAAE,CAAC,CAAC;IACpI,MAAM,KAAK,GAAG,UAAU,CAAC,CAAC,CAAC,KAAK,EAAE,GAAG,KAAK,QAAQ,CAAC,CAAC;IACpD,MAAM,SAAS,GAAG,cAAc,CAAC,CAAC,CAAC,SAAS,EAAE,GAAG,KAAK,YAAY,EAAE,SAAS,CAAC,CAAC;IAC/E,MAAM,EAAE,GAAG,CAAC,CAAC,SAAS,CAAC;IACvB,IAAI,EAAE,KAAK,SAAS,IAAI,CAAC,OAAO,EAAE,KAAK,QAAQ,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;QAAE,MAAM,UAAU,CAAC,OAAO,EAAE,KAAK,KAAK,sEAAsE,EAAE,GAAG,KAAK,YAAY,CAAC,CAAC;IAC1N,IAAI,CAAC,CAAC,UAAU,KAAK,SAAS,IAAI,OAAO,CAAC,CAAC,UAAU,KAAK,SAAS;QAAE,MAAM,UAAU,CAAC,OAAO,EAAE,KAAK,KAAK,qCAAqC,EAAE,GAAG,KAAK,aAAa,CAAC,CAAC;IACvK,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,CAAC,OAAO,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,UAAU,KAAK,IAAI,EAAE,CAAC;AACtH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,MAAM,eAAe,GAAG,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IACtD,MAAM,IAAI,GAAG,eAAe,CAAC,KAAK,CAAC,eAAe,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IACrE,MAAM,CAAC,GAAG,gEAAgE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtF,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACxC,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,MAAM,CAAC,CAAU;IAC/B,IAAI,CAAC,KAAK,WAAW;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC5C,OAAO,QAAQ,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AACtD,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,aAAa,CAAC,CAAU,EAAE,EAAsB;IAC9D,IAAI,CAAC,KAAK,WAAW,IAAI,EAAE,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC;IACrD,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IAChE,IAAI,QAAQ,CAAC,CAAC,CAAC;QAAE,OAAO,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACzG,OAAO,CAAC,CAAC;AACX,CAAC;AAED,kHAAkH;AAClH,MAAM,UAAU,gBAAgB,CAAC,CAAU;IACzC,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;IAC3B,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACzD,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IACtD,OAAO,QAAQ,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;AAChE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,CAAc;IACxC,OAAO,WAAW,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,WAAW,IAAI,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC;AACvF,CAAC;AAED,MAAM,OAAO,GAAsC;IACjD,eAAe,EAAE,eAAe;IAChC,YAAY,EAAE,eAAe;IAC7B,SAAS,EAAE,eAAe;IAC1B,SAAS,EAAE,oBAAoB;IAC/B,QAAQ,EAAE,mBAAmB;IAC7B,cAAc,EAAE,mBAAmB;IACnC,YAAY,EAAE,oBAAoB;IAClC,iBAAiB,EAAE,oBAAoB;IACvC,mBAAmB,EAAE,oBAAoB;IACzC,cAAc,EAAE,kBAAkB;IAClC,yBAAyB,EAAE,kBAAkB;IAC7C,oBAAoB,EAAE,kBAAkB;CACzC,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAe,EAAE,IAAwB;IAC5E,IAAI,sFAAsF,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,eAAe,CAAC;IACjI,IAAI,wDAAwD,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,oBAAoB,CAAC;IACxG,IAAI,+CAA+C,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,mBAAmB,CAAC;IAC9F,MAAM,MAAM,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;IAC5E,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAC1B,OAAO,IAAI,EAAE,WAAW,EAAE,KAAK,uBAAuB,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,kBAAkB,CAAC;AACrG,CAAC;AAED,SAAS,OAAO,CAAC,MAAiB,EAAE,IAAY,EAAE,MAA6B;IAC7E,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACnD,MAAM,OAAO,GAAG,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC1F,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/D,MAAM,IAAI,GAAG,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IACjE,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAChE,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;IACzE,OAAO,IAAI,YAAY,CAAC,oBAAoB,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,GAAG,OAAO,aAAa,IAAI,MAAM,IAAI,IAAI,OAAO,KAAK,IAAI,GAAG,IAAI,EAAE,EAAE;QAC/H,OAAO,EAAE,OAAO;QAChB,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,IAAI;QACV,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACvC,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,GAAc,EAAE,CAAc,EAAE,EAAsB,EAAE,MAA6B,EAAE,QAAiB;IAC3I,MAAM,IAAI,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,SAAS,EAAE,aAAa,CAAC,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,EAAE,CAAC;IAC3E,2FAA2F;IAC3F,MAAM,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,WAAW,CAAC;IAC/C,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,UAAU,EAAE,CAAC,QAAQ,IAAI,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC;IAC1F,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,iHAAiH;QACjH,IAAI,CAAC,YAAY,YAAY;YAAE,MAAM,IAAI,YAAY,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,kBAAkB,EAAE,GAAG,OAAO,aAAa,IAAI,IAAI,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QACpK,MAAM,CAAC,CAAC;IACV,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,MAAM,IAAI,YAAY,CAAC,mBAAmB,EAAE,GAAG,OAAO,aAAa,IAAI,mCAAmC,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IACrL,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IACzG,OAAO,MAAM,CAAC,IAAI,IAAI,IAAI,CAAC;AAC7B,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `http.list_item`: one value kept in a collection held by a parent object (an allowed origin, a callback URL, a
|
|
3
|
+
* config var), by read-modify-write. The collection is a JSON array, a delimited string or a keyed map (`shape`).
|
|
4
|
+
* See ADR 0017 and docs/plan-format.md#httplist_item.
|
|
5
|
+
*
|
|
6
|
+
* - Key: `<parent.path>#<list_path>=<item>`: which collection, and which item in it (for map items of an array,
|
|
7
|
+
* the value of `key_field`; for a keyed map, the entry's name). The provider block is part of the ledger
|
|
8
|
+
* identity already.
|
|
9
|
+
* - Id: the item's locator as canonical JSON (how to read and write the parent, where and how the collection is
|
|
10
|
+
* stored, which item), so `destroy` needs nothing but the ledger.
|
|
11
|
+
* - Hash: the item's state in canonical JSON: the value itself, the declared fields of a map item, or a keyed
|
|
12
|
+
* entry's value.
|
|
13
|
+
* - Writes replace the whole collection (`PUT`, or `PATCH`/`POST` sent as idempotent, so a dropped connection is
|
|
14
|
+
* retried), except a keyed map with `send: field`, which is sent as a merge patch of the one entry (`null`
|
|
15
|
+
* deletes it). Then the parent is read again: the write counts only once the item is seen as planned. A
|
|
16
|
+
* collection that loses the item to a concurrent writer is written again, up to MAX_ATTEMPTS times.
|
|
17
|
+
* - Lock: the parent object (`lockOn`, ADR 0019), so Sponson's writers in other scopes and environments never
|
|
18
|
+
* interleave between a read and a write. There is no precondition (ETag): only a writer outside Sponson can.
|
|
19
|
+
*/
|
|
20
|
+
import { type OpSpec } from "@sponson/core";
|
|
21
|
+
import { type ListItemSpec } from "./http-adapter-spec.js";
|
|
22
|
+
/** Read-modify-write rounds before giving up on a collection that keeps changing under us. */
|
|
23
|
+
export declare const MAX_ATTEMPTS = 3;
|
|
24
|
+
/**
|
|
25
|
+
* What the ledger knows a line's item by: its key and record id (the locator), or undefined while pending. The
|
|
26
|
+
* recipe identity lock (recipes.test.ts) pins it per recipe op.
|
|
27
|
+
*/
|
|
28
|
+
export declare function listItemIdentity(spec: ListItemSpec): {
|
|
29
|
+
key: string;
|
|
30
|
+
id: string;
|
|
31
|
+
} | undefined;
|
|
32
|
+
/** `http.list_item`: one value kept in a collection on a parent object, by read-modify-write. */
|
|
33
|
+
export declare const listItem: OpSpec;
|