@geekmidas/constructs 10.0.0-alpha.101 → 10.0.0-alpha.103
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/dist/{Endpoint-BG4UJ9oA.mjs → Endpoint-BW-nFdL_.mjs} +67 -3
- package/dist/Endpoint-BW-nFdL_.mjs.map +1 -0
- package/dist/{Endpoint-aCvBMPoH.d.cts → Endpoint-BWyKCa2x.d.cts} +47 -9
- package/dist/Endpoint-BWyKCa2x.d.cts.map +1 -0
- package/dist/{Endpoint-BoUg8ocM.cjs → Endpoint-B_UHlLxv.cjs} +78 -2
- package/dist/Endpoint-B_UHlLxv.cjs.map +1 -0
- package/dist/{Endpoint-Du-NwPX2.d.mts → Endpoint-C3r2OzOD.d.mts} +47 -9
- package/dist/Endpoint-C3r2OzOD.d.mts.map +1 -0
- package/dist/{EndpointFactory-BDtsrEvJ.mjs → EndpointFactory-Bbmx4vXc.mjs} +7 -2
- package/dist/EndpointFactory-Bbmx4vXc.mjs.map +1 -0
- package/dist/{EndpointFactory-I7s1co3Y.d.cts → EndpointFactory-C1-IBwm4.d.cts} +9 -4
- package/dist/EndpointFactory-C1-IBwm4.d.cts.map +1 -0
- package/dist/{EndpointFactory-DMYdJCOj.d.mts → EndpointFactory-C9eVhDKF.d.mts} +9 -4
- package/dist/EndpointFactory-C9eVhDKF.d.mts.map +1 -0
- package/dist/{EndpointFactory-DJ7gXLpR.cjs → EndpointFactory-DeZTvDjU.cjs} +7 -2
- package/dist/{EndpointFactory-BDtsrEvJ.mjs.map → EndpointFactory-DeZTvDjU.cjs.map} +1 -1
- package/dist/{HonoEndpointAdaptor-PtPlbeKY.mjs → HonoEndpointAdaptor-RSvRnXSl.mjs} +38 -14
- package/dist/HonoEndpointAdaptor-RSvRnXSl.mjs.map +1 -0
- package/dist/{HonoEndpointAdaptor-zVzVLJEO.cjs → HonoEndpointAdaptor-eCKyEOl_.cjs} +41 -17
- package/dist/HonoEndpointAdaptor-eCKyEOl_.cjs.map +1 -0
- package/dist/adaptors/aws.cjs +66 -27
- package/dist/adaptors/aws.cjs.map +1 -1
- package/dist/adaptors/aws.d.cts +23 -10
- package/dist/adaptors/aws.d.cts.map +1 -1
- package/dist/adaptors/aws.d.mts +23 -10
- package/dist/adaptors/aws.d.mts.map +1 -1
- package/dist/adaptors/aws.mjs +63 -24
- package/dist/adaptors/aws.mjs.map +1 -1
- package/dist/adaptors/hono.cjs +1 -1
- package/dist/adaptors/hono.d.cts +1 -1
- package/dist/adaptors/hono.d.cts.map +1 -1
- package/dist/adaptors/hono.d.mts +1 -1
- package/dist/adaptors/hono.d.mts.map +1 -1
- package/dist/adaptors/hono.mjs +1 -1
- package/dist/adaptors/testing.cjs +26 -13
- package/dist/adaptors/testing.cjs.map +1 -1
- package/dist/adaptors/testing.d.cts +2 -2
- package/dist/adaptors/testing.d.cts.map +1 -1
- package/dist/adaptors/testing.d.mts +2 -2
- package/dist/adaptors/testing.d.mts.map +1 -1
- package/dist/adaptors/testing.mjs +24 -11
- package/dist/adaptors/testing.mjs.map +1 -1
- package/dist/credential-DYymbp_7.cjs +257 -0
- package/dist/credential-DYymbp_7.cjs.map +1 -0
- package/dist/credential-DfSAOOdY.mjs +222 -0
- package/dist/credential-DfSAOOdY.mjs.map +1 -0
- package/dist/credential.cjs +7 -155
- package/dist/credential.d.cts +2 -86
- package/dist/credential.d.mts +2 -86
- package/dist/credential.mjs +2 -152
- package/dist/endpoints/index.cjs +15 -9
- package/dist/endpoints/index.d.cts +57 -3
- package/dist/endpoints/index.d.cts.map +1 -1
- package/dist/endpoints/index.d.mts +57 -3
- package/dist/endpoints/index.d.mts.map +1 -1
- package/dist/endpoints/index.mjs +4 -4
- package/dist/external-api.cjs +3 -39
- package/dist/external-api.cjs.map +1 -1
- package/dist/external-api.d.cts +2 -2
- package/dist/external-api.d.mts +2 -2
- package/dist/external-api.mjs +1 -37
- package/dist/external-api.mjs.map +1 -1
- package/dist/fake-BcHTPz7N.d.mts +201 -0
- package/dist/fake-BcHTPz7N.d.mts.map +1 -0
- package/dist/fake-Dvion_Fh.d.cts +201 -0
- package/dist/fake-Dvion_Fh.d.cts.map +1 -0
- package/dist/{processAudits-DasOZjPl.cjs → rateLimit-BwHcMECR.cjs} +26 -5
- package/dist/rateLimit-BwHcMECR.cjs.map +1 -0
- package/dist/{processAudits-BRRVVHUB.mjs → rateLimit-DbOTsP01.mjs} +23 -2
- package/dist/rateLimit-DbOTsP01.mjs.map +1 -0
- package/dist/{lazyAccessors-CFanua0O.mjs → readRequestBody-BTIl3k-D.mjs} +99 -2
- package/dist/readRequestBody-BTIl3k-D.mjs.map +1 -0
- package/dist/{lazyAccessors-hJKGv8Ay.cjs → readRequestBody-Dej_L3c4.cjs} +122 -1
- package/dist/readRequestBody-Dej_L3c4.cjs.map +1 -0
- package/dist/rest-api.cjs +1 -1
- package/dist/rest-api.d.cts +2 -2
- package/dist/rest-api.d.mts +2 -2
- package/dist/rest-api.mjs +1 -1
- package/package.json +31 -31
- package/dist/Endpoint-BG4UJ9oA.mjs.map +0 -1
- package/dist/Endpoint-BoUg8ocM.cjs.map +0 -1
- package/dist/Endpoint-Du-NwPX2.d.mts.map +0 -1
- package/dist/Endpoint-aCvBMPoH.d.cts.map +0 -1
- package/dist/EndpointFactory-DJ7gXLpR.cjs.map +0 -1
- package/dist/EndpointFactory-DMYdJCOj.d.mts.map +0 -1
- package/dist/EndpointFactory-I7s1co3Y.d.cts.map +0 -1
- package/dist/HonoEndpointAdaptor-PtPlbeKY.mjs.map +0 -1
- package/dist/HonoEndpointAdaptor-zVzVLJEO.cjs.map +0 -1
- package/dist/credential.cjs.map +0 -1
- package/dist/credential.d.cts.map +0 -1
- package/dist/credential.d.mts.map +0 -1
- package/dist/credential.mjs.map +0 -1
- package/dist/external-api-MJ8id87V.d.mts +0 -105
- package/dist/external-api-MJ8id87V.d.mts.map +0 -1
- package/dist/external-api-O107yzZJ.d.cts +0 -105
- package/dist/external-api-O107yzZJ.d.cts.map +0 -1
- package/dist/lazyAccessors-CFanua0O.mjs.map +0 -1
- package/dist/lazyAccessors-hJKGv8Ay.cjs.map +0 -1
- package/dist/processAudits-BRRVVHUB.mjs.map +0 -1
- package/dist/processAudits-DasOZjPl.cjs.map +0 -1
package/dist/credential.cjs
CHANGED
|
@@ -1,156 +1,8 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* It sits beside a secret rather than replacing it, because the two differ by
|
|
10
|
-
* *lifecycle* and that is the only difference worth two kinds for: a secret is
|
|
11
|
-
* generated and rotated by the platform and is one opaque string, while a
|
|
12
|
-
* credential is issued by someone else, arrives with several fields, and is
|
|
13
|
-
* worth validating on the way in.
|
|
14
|
-
*
|
|
15
|
-
* ```ts
|
|
16
|
-
* export const stripe = new Credential('Stripe', {
|
|
17
|
-
* schema: z.object({ secretKey: z.string(), webhookSecret: z.string() }),
|
|
18
|
-
* });
|
|
19
|
-
*
|
|
20
|
-
* // in a handler — no await, already parsed and validated
|
|
21
|
-
* services.stripe.secretKey
|
|
22
|
-
* ```
|
|
23
|
-
*
|
|
24
|
-
* **Nothing about it is async at the call site**, which is the part that looks
|
|
25
|
-
* like it should be hard and is not. `Service.register()` may return a promise
|
|
26
|
-
* and `ServiceDiscovery` awaits every one before a handler runs, so fetching a
|
|
27
|
-
* value and validating it — including when a StandardSchema's `~standard.validate`
|
|
28
|
-
* returns a promise of its own — both happen inside that existing seam.
|
|
29
|
-
*
|
|
30
|
-
* **One key holding JSON, not one key per field.** That is the shape a secret
|
|
31
|
-
* manager stores, and it is the only shape that works with an arbitrary
|
|
32
|
-
* StandardSchema: the spec has no introspection API, so enumerating a schema's
|
|
33
|
-
* fields means reaching into one library's internals (`.shape`) and being wrong
|
|
34
|
-
* for every other.
|
|
35
|
-
*/
|
|
36
|
-
var Credential = class {
|
|
37
|
-
options;
|
|
38
|
-
id;
|
|
39
|
-
service;
|
|
40
|
-
/**
|
|
41
|
-
* Declared once and read by both `declare()` and `connect()`, so the key the
|
|
42
|
-
* target publishes and the key the client reads cannot drift.
|
|
43
|
-
*/
|
|
44
|
-
keys;
|
|
45
|
-
/**
|
|
46
|
-
* The resolved value, held as the *promise* rather than the value.
|
|
47
|
-
*
|
|
48
|
-
* Caching the promise is what makes two concurrent registrations share one
|
|
49
|
-
* resolution: caching the value would let both start before either finished,
|
|
50
|
-
* which is a duplicated fetch every cold start rather than never.
|
|
51
|
-
*/
|
|
52
|
-
resolved;
|
|
53
|
-
constructor(id, options) {
|
|
54
|
-
this.options = options;
|
|
55
|
-
const canonical = (0, _geekmidas_manifest.canonicalId)(id);
|
|
56
|
-
this.id = canonical;
|
|
57
|
-
this.keys = { credentials: (0, _geekmidas_manifest.provideKey)(canonical, "credentials") };
|
|
58
|
-
this.service = {
|
|
59
|
-
serviceName: (0, _geekmidas_manifest.serviceKey)(canonical),
|
|
60
|
-
register: (registerOptions) => this.connect(registerOptions)
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* What the stage's `<ID>_CREDENTIALS` must look like — the schema the value
|
|
65
|
-
* is validated against where it is read. Public so a tool setting the value
|
|
66
|
-
* (`gkm secrets:add`) can check it, and describe its fields, before it is
|
|
67
|
-
* stored rather than at the first request.
|
|
68
|
-
*/
|
|
69
|
-
get credentialsSchema() {
|
|
70
|
-
return this.options.schema;
|
|
71
|
-
}
|
|
72
|
-
declare() {
|
|
73
|
-
return [{
|
|
74
|
-
kind: "credential",
|
|
75
|
-
id: this.id,
|
|
76
|
-
provides: [this.keys.credentials]
|
|
77
|
-
}];
|
|
78
|
-
}
|
|
79
|
-
connect(options) {
|
|
80
|
-
if (this.options.refresh) return this.resolve(options);
|
|
81
|
-
this.resolved ??= this.resolve(options);
|
|
82
|
-
return this.resolved;
|
|
83
|
-
}
|
|
84
|
-
resolve(options) {
|
|
85
|
-
return readCredentials(this.id, this.keys.credentials, this.options.schema, options.envParser);
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
/**
|
|
89
|
-
* A credentials key, read and validated against its schema.
|
|
90
|
-
*
|
|
91
|
-
* Shared with every construct that holds a third party's credentials — an
|
|
92
|
-
* `ExternalApi` reads its own the same way — so the shape a stage stores and
|
|
93
|
-
* the error a bad value raises are the same everywhere.
|
|
94
|
-
*
|
|
95
|
-
* @throws {MalformedCredential} when the value is not what the schema says.
|
|
96
|
-
*/
|
|
97
|
-
async function readCredentials(id, key, schema, envParser) {
|
|
98
|
-
const { raw } = envParser.create((get) => ({ raw: get(key).string() })).parse();
|
|
99
|
-
try {
|
|
100
|
-
return await (0, _geekmidas_schema_parser.parseSchema)(schema, decodeCredentials(raw));
|
|
101
|
-
} catch (issues) {
|
|
102
|
-
throw new MalformedCredential(id, key, issues);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* The raw environment value as the thing the schema should see.
|
|
107
|
-
*
|
|
108
|
-
* JSON when it is JSON, and the string itself when it is not — a credential
|
|
109
|
-
* with a single opaque field is ordinary, and requiring `"…"` around it would
|
|
110
|
-
* be ceremony that every operator setting one by hand would get wrong once.
|
|
111
|
-
*
|
|
112
|
-
* Exported so a tool checking a value before it is stored reads it exactly as
|
|
113
|
-
* the process that uses it will.
|
|
114
|
-
*/
|
|
115
|
-
function decodeCredentials(raw) {
|
|
116
|
-
const value = raw.startsWith(JSON_SCHEME) ? raw.slice(JSON_SCHEME.length) : raw;
|
|
117
|
-
try {
|
|
118
|
-
return JSON.parse(value);
|
|
119
|
-
} catch {
|
|
120
|
-
return value;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* The explicit form, for the case a credential legitimately *is* a JSON string
|
|
125
|
-
* and must not be parsed into an object. Rare, and cheaper to support than to
|
|
126
|
-
* explain.
|
|
127
|
-
*/
|
|
128
|
-
const JSON_SCHEME = "json:";
|
|
129
|
-
/** A credential was set, and is not what its schema says it should be. */
|
|
130
|
-
var MalformedCredential = class extends Error {
|
|
131
|
-
id;
|
|
132
|
-
key;
|
|
133
|
-
issues;
|
|
134
|
-
constructor(id, key, issues) {
|
|
135
|
-
super(`${key} is not a valid ${id} credential. It is validated where it is read, so this is the value that is set rather than the code that uses it: ${describe(issues)}`);
|
|
136
|
-
this.id = id;
|
|
137
|
-
this.key = key;
|
|
138
|
-
this.issues = issues;
|
|
139
|
-
this.name = "MalformedCredential";
|
|
140
|
-
}
|
|
141
|
-
};
|
|
142
|
-
/** StandardSchema issues as one line. */
|
|
143
|
-
function describe(issues) {
|
|
144
|
-
if (!Array.isArray(issues)) return String(issues);
|
|
145
|
-
return issues.map((issue) => {
|
|
146
|
-
const path = issue.path?.map((segment) => typeof segment === "object" ? String(segment.key) : String(segment)).join(".");
|
|
147
|
-
return path ? `${path}: ${issue.message}` : issue.message;
|
|
148
|
-
}).join("; ");
|
|
149
|
-
}
|
|
150
|
-
//#endregion
|
|
151
|
-
exports.Credential = Credential;
|
|
152
|
-
exports.MalformedCredential = MalformedCredential;
|
|
153
|
-
exports.decodeCredentials = decodeCredentials;
|
|
154
|
-
exports.readCredentials = readCredentials;
|
|
155
|
-
|
|
156
|
-
//# sourceMappingURL=credential.cjs.map
|
|
2
|
+
const require_credential = require("./credential-DYymbp_7.cjs");
|
|
3
|
+
exports.Credential = require_credential.Credential;
|
|
4
|
+
exports.MalformedCredential = require_credential.MalformedCredential;
|
|
5
|
+
exports.decodeCredentials = require_credential.decodeCredentials;
|
|
6
|
+
exports.fake = require_credential.fake;
|
|
7
|
+
exports.isFake = require_credential.isFake;
|
|
8
|
+
exports.readCredentials = require_credential.readCredentials;
|
package/dist/credential.d.cts
CHANGED
|
@@ -1,86 +1,2 @@
|
|
|
1
|
-
import { t as
|
|
2
|
-
|
|
3
|
-
import { Service, ServiceRegisterOptions } from "@geekmidas/services";
|
|
4
|
-
import { ConstructName, Declaration } from "@geekmidas/manifest";
|
|
5
|
-
//#region src/credential.d.ts
|
|
6
|
-
/** What a credential's schema yields. */
|
|
7
|
-
type Value<TSchema extends StandardSchemaV1> = StandardSchemaV1.InferOutput<TSchema>;
|
|
8
|
-
export interface CredentialOptions<TSchema extends StandardSchemaV1> {
|
|
9
|
-
/**
|
|
10
|
-
* What the credential must look like.
|
|
11
|
-
*
|
|
12
|
-
* Validated at registration, so a malformed or half-set credential fails
|
|
13
|
-
* when the process starts rather than on the first request that needs the
|
|
14
|
-
* one field somebody forgot.
|
|
15
|
-
*/
|
|
16
|
-
schema: TSchema;
|
|
17
|
-
/**
|
|
18
|
-
* Re-resolve on every registration instead of once per process.
|
|
19
|
-
*
|
|
20
|
-
* Off by default: a credential is resolved once and reused, because the
|
|
21
|
-
* alternative is a fetch on every request. The cost of that default is that
|
|
22
|
-
* a rotated credential needs a restart, which is the right trade for a value
|
|
23
|
-
* that changes a few times a year — and this is the escape hatch for when it
|
|
24
|
-
* is not.
|
|
25
|
-
*/
|
|
26
|
-
refresh?: boolean;
|
|
27
|
-
}
|
|
28
|
-
export declare class Credential<TName extends string = string, TSchema extends StandardSchemaV1 = StandardSchemaV1> implements Consumable<TName, Value<TSchema>> {
|
|
29
|
-
private readonly options;
|
|
30
|
-
readonly id: TName;
|
|
31
|
-
readonly service: Service<Uncapitalize<TName>, Value<TSchema>>;
|
|
32
|
-
/**
|
|
33
|
-
* Declared once and read by both `declare()` and `connect()`, so the key the
|
|
34
|
-
* target publishes and the key the client reads cannot drift.
|
|
35
|
-
*/
|
|
36
|
-
private readonly keys;
|
|
37
|
-
/**
|
|
38
|
-
* The resolved value, held as the *promise* rather than the value.
|
|
39
|
-
*
|
|
40
|
-
* Caching the promise is what makes two concurrent registrations share one
|
|
41
|
-
* resolution: caching the value would let both start before either finished,
|
|
42
|
-
* which is a duplicated fetch every cold start rather than never.
|
|
43
|
-
*/
|
|
44
|
-
private resolved?;
|
|
45
|
-
constructor(id: ConstructName<TName>, options: CredentialOptions<TSchema>);
|
|
46
|
-
/**
|
|
47
|
-
* What the stage's `<ID>_CREDENTIALS` must look like — the schema the value
|
|
48
|
-
* is validated against where it is read. Public so a tool setting the value
|
|
49
|
-
* (`gkm secrets:add`) can check it, and describe its fields, before it is
|
|
50
|
-
* stored rather than at the first request.
|
|
51
|
-
*/
|
|
52
|
-
get credentialsSchema(): TSchema;
|
|
53
|
-
declare(): Declaration[];
|
|
54
|
-
private connect;
|
|
55
|
-
private resolve;
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* A credentials key, read and validated against its schema.
|
|
59
|
-
*
|
|
60
|
-
* Shared with every construct that holds a third party's credentials — an
|
|
61
|
-
* `ExternalApi` reads its own the same way — so the shape a stage stores and
|
|
62
|
-
* the error a bad value raises are the same everywhere.
|
|
63
|
-
*
|
|
64
|
-
* @throws {MalformedCredential} when the value is not what the schema says.
|
|
65
|
-
*/
|
|
66
|
-
export declare function readCredentials<TSchema extends StandardSchemaV1>(id: string, key: string, schema: TSchema, envParser: ServiceRegisterOptions['envParser']): Promise<Value<TSchema>>;
|
|
67
|
-
/**
|
|
68
|
-
* The raw environment value as the thing the schema should see.
|
|
69
|
-
*
|
|
70
|
-
* JSON when it is JSON, and the string itself when it is not — a credential
|
|
71
|
-
* with a single opaque field is ordinary, and requiring `"…"` around it would
|
|
72
|
-
* be ceremony that every operator setting one by hand would get wrong once.
|
|
73
|
-
*
|
|
74
|
-
* Exported so a tool checking a value before it is stored reads it exactly as
|
|
75
|
-
* the process that uses it will.
|
|
76
|
-
*/
|
|
77
|
-
export declare function decodeCredentials(raw: string): unknown;
|
|
78
|
-
/** A credential was set, and is not what its schema says it should be. */
|
|
79
|
-
export declare class MalformedCredential extends Error {
|
|
80
|
-
readonly id: string;
|
|
81
|
-
readonly key: string;
|
|
82
|
-
readonly issues: unknown;
|
|
83
|
-
constructor(id: string, key: string, issues: unknown);
|
|
84
|
-
}
|
|
85
|
-
//#endregion
|
|
86
|
-
//# sourceMappingURL=credential.d.cts.map
|
|
1
|
+
import { c as Credential, d as decodeCredentials, f as readCredentials, i as isFake, l as CredentialOptions, r as fake, t as Fake, u as MalformedCredential } from "./fake-Dvion_Fh.cjs";
|
|
2
|
+
export { Credential, CredentialOptions, type Fake, MalformedCredential, decodeCredentials, fake, isFake, readCredentials };
|
package/dist/credential.d.mts
CHANGED
|
@@ -1,86 +1,2 @@
|
|
|
1
|
-
import { t as
|
|
2
|
-
|
|
3
|
-
import { ConstructName, Declaration } from "@geekmidas/manifest";
|
|
4
|
-
import { Service, ServiceRegisterOptions } from "@geekmidas/services";
|
|
5
|
-
//#region src/credential.d.ts
|
|
6
|
-
/** What a credential's schema yields. */
|
|
7
|
-
type Value<TSchema extends StandardSchemaV1> = StandardSchemaV1.InferOutput<TSchema>;
|
|
8
|
-
export interface CredentialOptions<TSchema extends StandardSchemaV1> {
|
|
9
|
-
/**
|
|
10
|
-
* What the credential must look like.
|
|
11
|
-
*
|
|
12
|
-
* Validated at registration, so a malformed or half-set credential fails
|
|
13
|
-
* when the process starts rather than on the first request that needs the
|
|
14
|
-
* one field somebody forgot.
|
|
15
|
-
*/
|
|
16
|
-
schema: TSchema;
|
|
17
|
-
/**
|
|
18
|
-
* Re-resolve on every registration instead of once per process.
|
|
19
|
-
*
|
|
20
|
-
* Off by default: a credential is resolved once and reused, because the
|
|
21
|
-
* alternative is a fetch on every request. The cost of that default is that
|
|
22
|
-
* a rotated credential needs a restart, which is the right trade for a value
|
|
23
|
-
* that changes a few times a year — and this is the escape hatch for when it
|
|
24
|
-
* is not.
|
|
25
|
-
*/
|
|
26
|
-
refresh?: boolean;
|
|
27
|
-
}
|
|
28
|
-
export declare class Credential<TName extends string = string, TSchema extends StandardSchemaV1 = StandardSchemaV1> implements Consumable<TName, Value<TSchema>> {
|
|
29
|
-
private readonly options;
|
|
30
|
-
readonly id: TName;
|
|
31
|
-
readonly service: Service<Uncapitalize<TName>, Value<TSchema>>;
|
|
32
|
-
/**
|
|
33
|
-
* Declared once and read by both `declare()` and `connect()`, so the key the
|
|
34
|
-
* target publishes and the key the client reads cannot drift.
|
|
35
|
-
*/
|
|
36
|
-
private readonly keys;
|
|
37
|
-
/**
|
|
38
|
-
* The resolved value, held as the *promise* rather than the value.
|
|
39
|
-
*
|
|
40
|
-
* Caching the promise is what makes two concurrent registrations share one
|
|
41
|
-
* resolution: caching the value would let both start before either finished,
|
|
42
|
-
* which is a duplicated fetch every cold start rather than never.
|
|
43
|
-
*/
|
|
44
|
-
private resolved?;
|
|
45
|
-
constructor(id: ConstructName<TName>, options: CredentialOptions<TSchema>);
|
|
46
|
-
/**
|
|
47
|
-
* What the stage's `<ID>_CREDENTIALS` must look like — the schema the value
|
|
48
|
-
* is validated against where it is read. Public so a tool setting the value
|
|
49
|
-
* (`gkm secrets:add`) can check it, and describe its fields, before it is
|
|
50
|
-
* stored rather than at the first request.
|
|
51
|
-
*/
|
|
52
|
-
get credentialsSchema(): TSchema;
|
|
53
|
-
declare(): Declaration[];
|
|
54
|
-
private connect;
|
|
55
|
-
private resolve;
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* A credentials key, read and validated against its schema.
|
|
59
|
-
*
|
|
60
|
-
* Shared with every construct that holds a third party's credentials — an
|
|
61
|
-
* `ExternalApi` reads its own the same way — so the shape a stage stores and
|
|
62
|
-
* the error a bad value raises are the same everywhere.
|
|
63
|
-
*
|
|
64
|
-
* @throws {MalformedCredential} when the value is not what the schema says.
|
|
65
|
-
*/
|
|
66
|
-
export declare function readCredentials<TSchema extends StandardSchemaV1>(id: string, key: string, schema: TSchema, envParser: ServiceRegisterOptions['envParser']): Promise<Value<TSchema>>;
|
|
67
|
-
/**
|
|
68
|
-
* The raw environment value as the thing the schema should see.
|
|
69
|
-
*
|
|
70
|
-
* JSON when it is JSON, and the string itself when it is not — a credential
|
|
71
|
-
* with a single opaque field is ordinary, and requiring `"…"` around it would
|
|
72
|
-
* be ceremony that every operator setting one by hand would get wrong once.
|
|
73
|
-
*
|
|
74
|
-
* Exported so a tool checking a value before it is stored reads it exactly as
|
|
75
|
-
* the process that uses it will.
|
|
76
|
-
*/
|
|
77
|
-
export declare function decodeCredentials(raw: string): unknown;
|
|
78
|
-
/** A credential was set, and is not what its schema says it should be. */
|
|
79
|
-
export declare class MalformedCredential extends Error {
|
|
80
|
-
readonly id: string;
|
|
81
|
-
readonly key: string;
|
|
82
|
-
readonly issues: unknown;
|
|
83
|
-
constructor(id: string, key: string, issues: unknown);
|
|
84
|
-
}
|
|
85
|
-
//#endregion
|
|
86
|
-
//# sourceMappingURL=credential.d.mts.map
|
|
1
|
+
import { c as Credential, d as decodeCredentials, f as readCredentials, i as isFake, l as CredentialOptions, r as fake, t as Fake, u as MalformedCredential } from "./fake-BcHTPz7N.mjs";
|
|
2
|
+
export { Credential, CredentialOptions, type Fake, MalformedCredential, decodeCredentials, fake, isFake, readCredentials };
|
package/dist/credential.mjs
CHANGED
|
@@ -1,152 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
//#region src/credential.ts
|
|
4
|
-
/**
|
|
5
|
-
* `Credential` — a third-party credential with a shape.
|
|
6
|
-
*
|
|
7
|
-
* The answer to the design's first open question, "where does `Secret` sit?".
|
|
8
|
-
* It sits beside a secret rather than replacing it, because the two differ by
|
|
9
|
-
* *lifecycle* and that is the only difference worth two kinds for: a secret is
|
|
10
|
-
* generated and rotated by the platform and is one opaque string, while a
|
|
11
|
-
* credential is issued by someone else, arrives with several fields, and is
|
|
12
|
-
* worth validating on the way in.
|
|
13
|
-
*
|
|
14
|
-
* ```ts
|
|
15
|
-
* export const stripe = new Credential('Stripe', {
|
|
16
|
-
* schema: z.object({ secretKey: z.string(), webhookSecret: z.string() }),
|
|
17
|
-
* });
|
|
18
|
-
*
|
|
19
|
-
* // in a handler — no await, already parsed and validated
|
|
20
|
-
* services.stripe.secretKey
|
|
21
|
-
* ```
|
|
22
|
-
*
|
|
23
|
-
* **Nothing about it is async at the call site**, which is the part that looks
|
|
24
|
-
* like it should be hard and is not. `Service.register()` may return a promise
|
|
25
|
-
* and `ServiceDiscovery` awaits every one before a handler runs, so fetching a
|
|
26
|
-
* value and validating it — including when a StandardSchema's `~standard.validate`
|
|
27
|
-
* returns a promise of its own — both happen inside that existing seam.
|
|
28
|
-
*
|
|
29
|
-
* **One key holding JSON, not one key per field.** That is the shape a secret
|
|
30
|
-
* manager stores, and it is the only shape that works with an arbitrary
|
|
31
|
-
* StandardSchema: the spec has no introspection API, so enumerating a schema's
|
|
32
|
-
* fields means reaching into one library's internals (`.shape`) and being wrong
|
|
33
|
-
* for every other.
|
|
34
|
-
*/
|
|
35
|
-
var Credential = class {
|
|
36
|
-
options;
|
|
37
|
-
id;
|
|
38
|
-
service;
|
|
39
|
-
/**
|
|
40
|
-
* Declared once and read by both `declare()` and `connect()`, so the key the
|
|
41
|
-
* target publishes and the key the client reads cannot drift.
|
|
42
|
-
*/
|
|
43
|
-
keys;
|
|
44
|
-
/**
|
|
45
|
-
* The resolved value, held as the *promise* rather than the value.
|
|
46
|
-
*
|
|
47
|
-
* Caching the promise is what makes two concurrent registrations share one
|
|
48
|
-
* resolution: caching the value would let both start before either finished,
|
|
49
|
-
* which is a duplicated fetch every cold start rather than never.
|
|
50
|
-
*/
|
|
51
|
-
resolved;
|
|
52
|
-
constructor(id, options) {
|
|
53
|
-
this.options = options;
|
|
54
|
-
const canonical = canonicalId(id);
|
|
55
|
-
this.id = canonical;
|
|
56
|
-
this.keys = { credentials: provideKey(canonical, "credentials") };
|
|
57
|
-
this.service = {
|
|
58
|
-
serviceName: serviceKey(canonical),
|
|
59
|
-
register: (registerOptions) => this.connect(registerOptions)
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* What the stage's `<ID>_CREDENTIALS` must look like — the schema the value
|
|
64
|
-
* is validated against where it is read. Public so a tool setting the value
|
|
65
|
-
* (`gkm secrets:add`) can check it, and describe its fields, before it is
|
|
66
|
-
* stored rather than at the first request.
|
|
67
|
-
*/
|
|
68
|
-
get credentialsSchema() {
|
|
69
|
-
return this.options.schema;
|
|
70
|
-
}
|
|
71
|
-
declare() {
|
|
72
|
-
return [{
|
|
73
|
-
kind: "credential",
|
|
74
|
-
id: this.id,
|
|
75
|
-
provides: [this.keys.credentials]
|
|
76
|
-
}];
|
|
77
|
-
}
|
|
78
|
-
connect(options) {
|
|
79
|
-
if (this.options.refresh) return this.resolve(options);
|
|
80
|
-
this.resolved ??= this.resolve(options);
|
|
81
|
-
return this.resolved;
|
|
82
|
-
}
|
|
83
|
-
resolve(options) {
|
|
84
|
-
return readCredentials(this.id, this.keys.credentials, this.options.schema, options.envParser);
|
|
85
|
-
}
|
|
86
|
-
};
|
|
87
|
-
/**
|
|
88
|
-
* A credentials key, read and validated against its schema.
|
|
89
|
-
*
|
|
90
|
-
* Shared with every construct that holds a third party's credentials — an
|
|
91
|
-
* `ExternalApi` reads its own the same way — so the shape a stage stores and
|
|
92
|
-
* the error a bad value raises are the same everywhere.
|
|
93
|
-
*
|
|
94
|
-
* @throws {MalformedCredential} when the value is not what the schema says.
|
|
95
|
-
*/
|
|
96
|
-
async function readCredentials(id, key, schema, envParser) {
|
|
97
|
-
const { raw } = envParser.create((get) => ({ raw: get(key).string() })).parse();
|
|
98
|
-
try {
|
|
99
|
-
return await parseSchema(schema, decodeCredentials(raw));
|
|
100
|
-
} catch (issues) {
|
|
101
|
-
throw new MalformedCredential(id, key, issues);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
|
-
* The raw environment value as the thing the schema should see.
|
|
106
|
-
*
|
|
107
|
-
* JSON when it is JSON, and the string itself when it is not — a credential
|
|
108
|
-
* with a single opaque field is ordinary, and requiring `"…"` around it would
|
|
109
|
-
* be ceremony that every operator setting one by hand would get wrong once.
|
|
110
|
-
*
|
|
111
|
-
* Exported so a tool checking a value before it is stored reads it exactly as
|
|
112
|
-
* the process that uses it will.
|
|
113
|
-
*/
|
|
114
|
-
function decodeCredentials(raw) {
|
|
115
|
-
const value = raw.startsWith(JSON_SCHEME) ? raw.slice(JSON_SCHEME.length) : raw;
|
|
116
|
-
try {
|
|
117
|
-
return JSON.parse(value);
|
|
118
|
-
} catch {
|
|
119
|
-
return value;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* The explicit form, for the case a credential legitimately *is* a JSON string
|
|
124
|
-
* and must not be parsed into an object. Rare, and cheaper to support than to
|
|
125
|
-
* explain.
|
|
126
|
-
*/
|
|
127
|
-
const JSON_SCHEME = "json:";
|
|
128
|
-
/** A credential was set, and is not what its schema says it should be. */
|
|
129
|
-
var MalformedCredential = class extends Error {
|
|
130
|
-
id;
|
|
131
|
-
key;
|
|
132
|
-
issues;
|
|
133
|
-
constructor(id, key, issues) {
|
|
134
|
-
super(`${key} is not a valid ${id} credential. It is validated where it is read, so this is the value that is set rather than the code that uses it: ${describe(issues)}`);
|
|
135
|
-
this.id = id;
|
|
136
|
-
this.key = key;
|
|
137
|
-
this.issues = issues;
|
|
138
|
-
this.name = "MalformedCredential";
|
|
139
|
-
}
|
|
140
|
-
};
|
|
141
|
-
/** StandardSchema issues as one line. */
|
|
142
|
-
function describe(issues) {
|
|
143
|
-
if (!Array.isArray(issues)) return String(issues);
|
|
144
|
-
return issues.map((issue) => {
|
|
145
|
-
const path = issue.path?.map((segment) => typeof segment === "object" ? String(segment.key) : String(segment)).join(".");
|
|
146
|
-
return path ? `${path}: ${issue.message}` : issue.message;
|
|
147
|
-
}).join("; ");
|
|
148
|
-
}
|
|
149
|
-
//#endregion
|
|
150
|
-
export { Credential, MalformedCredential, decodeCredentials, readCredentials };
|
|
151
|
-
|
|
152
|
-
//# sourceMappingURL=credential.mjs.map
|
|
1
|
+
import { a as fake, i as readCredentials, n as MalformedCredential, o as isFake, r as decodeCredentials, t as Credential } from "./credential-DfSAOOdY.mjs";
|
|
2
|
+
export { Credential, MalformedCredential, decodeCredentials, fake, isFake, readCredentials };
|
package/dist/endpoints/index.cjs
CHANGED
|
@@ -1,25 +1,31 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_EndpointFactory = require("../EndpointFactory-
|
|
2
|
+
const require_EndpointFactory = require("../EndpointFactory-DeZTvDjU.cjs");
|
|
3
3
|
const require_functions = require("../functions-CfzJG9hK.cjs");
|
|
4
|
-
const require_Endpoint = require("../Endpoint-
|
|
4
|
+
const require_Endpoint = require("../Endpoint-B_UHlLxv.cjs");
|
|
5
5
|
const require_surfaceEnv = require("../surfaceEnv-CNimK76p.cjs");
|
|
6
|
-
const
|
|
6
|
+
const require_readRequestBody = require("../readRequestBody-Dej_L3c4.cjs");
|
|
7
7
|
exports.BUILT_IN_SECURITY_SCHEMES = require_EndpointFactory.BUILT_IN_SECURITY_SCHEMES;
|
|
8
8
|
exports.Endpoint = require_Endpoint.Endpoint;
|
|
9
9
|
exports.EndpointBuilder = require_EndpointFactory.EndpointBuilder;
|
|
10
10
|
exports.EndpointFactory = require_EndpointFactory.EndpointFactory;
|
|
11
|
+
exports.MalformedRequestBody = require_readRequestBody.MalformedRequestBody;
|
|
11
12
|
exports.NoAuthenticator = require_Endpoint.NoAuthenticator;
|
|
12
13
|
exports.RLS_BYPASS = require_EndpointFactory.RLS_BYPASS;
|
|
14
|
+
exports.RedirectStatus = require_Endpoint.RedirectStatus;
|
|
13
15
|
exports.ResponseBuilder = require_Endpoint.ResponseBuilder;
|
|
14
|
-
exports.
|
|
15
|
-
exports.
|
|
16
|
+
exports.SUPPORTED_BODY_TYPES = require_readRequestBody.SUPPORTED_BODY_TYPES;
|
|
17
|
+
exports.SuccessStatus = require_Endpoint.SuccessStatus;
|
|
18
|
+
exports.UnsupportedRequestContentType = require_readRequestBody.UnsupportedRequestContentType;
|
|
19
|
+
exports.createApiGatewayCookies = require_readRequestBody.createApiGatewayCookies;
|
|
20
|
+
exports.createApiGatewayHeaders = require_readRequestBody.createApiGatewayHeaders;
|
|
16
21
|
exports.createAuthorizer = require_EndpointFactory.createAuthorizer;
|
|
17
|
-
exports.createHonoCookies =
|
|
18
|
-
exports.createHonoHeaders =
|
|
19
|
-
exports.createNoopCookies =
|
|
20
|
-
exports.createNoopHeaders =
|
|
22
|
+
exports.createHonoCookies = require_readRequestBody.createHonoCookies;
|
|
23
|
+
exports.createHonoHeaders = require_readRequestBody.createHonoHeaders;
|
|
24
|
+
exports.createNoopCookies = require_readRequestBody.createNoopCookies;
|
|
25
|
+
exports.createNoopHeaders = require_readRequestBody.createNoopHeaders;
|
|
21
26
|
exports.defaultEnvParser = require_surfaceEnv.defaultEnvParser;
|
|
22
27
|
exports.envParserFor = require_surfaceEnv.envParserFor;
|
|
23
28
|
exports.getSecurityScheme = require_EndpointFactory.getSecurityScheme;
|
|
24
29
|
exports.isBuiltInSecurityScheme = require_EndpointFactory.isBuiltInSecurityScheme;
|
|
25
30
|
exports.publishConstructEvents = require_functions.publishConstructEvents;
|
|
31
|
+
exports.readRequestBody = require_readRequestBody.readRequestBody;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { s as publishConstructEvents } from "../Construct-BQALFCh2.cjs";
|
|
2
|
-
import { A as
|
|
3
|
-
import { n as EndpointBuilder, t as EndpointFactory } from "../EndpointFactory-
|
|
2
|
+
import { A as SecurityScheme, C as ActorExtractor, D as BuiltInSecuritySchemeId, E as BUILT_IN_SECURITY_SCHEMES, M as getSecurityScheme, N as isBuiltInSecurityScheme, O as OAuthFlow, S as RlsContextExtractor, T as Authorizer, _ as SessionAuth, a as EndpointContext, b as RlsConfig, c as EndpointSchemas, d as HeaderFn, f as RedirectStatus, g as NoAuthenticator, h as SuccessStatus, i as Endpoint, j as createAuthorizer, k as OAuthFlows, l as EndpointStatus, n as ConvertRouteParams, o as EndpointHandler, p as ResponseBuilder, r as CookieFn, s as EndpointOutput, u as EndpointSurface, v as RLS_BYPASS, w as MappedAudit, x as RlsContext, y as RlsBypass } from "../Endpoint-BWyKCa2x.cjs";
|
|
3
|
+
import { n as EndpointBuilder, t as EndpointFactory } from "../EndpointFactory-C1-IBwm4.cjs";
|
|
4
4
|
import { EnvironmentParser } from "@geekmidas/envkit";
|
|
5
5
|
import { Context } from "hono";
|
|
6
|
+
import { HttpError } from "@geekmidas/errors";
|
|
6
7
|
//#region src/endpoints/lazyAccessors.d.ts
|
|
7
8
|
/**
|
|
8
9
|
* Create a lazy header accessor for Hono.
|
|
@@ -35,6 +36,59 @@ export declare function createNoopHeaders(): HeaderFn;
|
|
|
35
36
|
*/
|
|
36
37
|
export declare function createNoopCookies(): CookieFn;
|
|
37
38
|
//#endregion
|
|
39
|
+
//#region src/endpoints/readRequestBody.d.ts
|
|
40
|
+
/**
|
|
41
|
+
* The media types an endpoint's `.body()` schema can be handed — as written
|
|
42
|
+
* in the 415 a request in any other type is answered with.
|
|
43
|
+
*/
|
|
44
|
+
export declare const SUPPORTED_BODY_TYPES: readonly ['application/json', 'application/x-www-form-urlencoded', 'multipart/form-data', 'text/*'];
|
|
45
|
+
/**
|
|
46
|
+
* A request whose body is in a media type no endpoint reads — XML, a bare
|
|
47
|
+
* octet stream. Answered with a 415, before the body schema runs.
|
|
48
|
+
*/
|
|
49
|
+
export declare class UnsupportedRequestContentType extends HttpError {
|
|
50
|
+
readonly contentType: string;
|
|
51
|
+
constructor(contentType: string);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A body that says it is JSON and is not. Answered with a 400 — the request
|
|
55
|
+
* is broken, not merely invalid against the schema.
|
|
56
|
+
*/
|
|
57
|
+
export declare class MalformedRequestBody extends HttpError {
|
|
58
|
+
readonly contentType: string;
|
|
59
|
+
constructor(contentType: string, cause?: Error);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* What a body is read from: a `Request`, or Hono's request — which caches
|
|
63
|
+
* what it read, so a later reader is not handed a spent stream.
|
|
64
|
+
*/
|
|
65
|
+
interface RequestBodySource {
|
|
66
|
+
json(): Promise<unknown>;
|
|
67
|
+
text(): Promise<string>;
|
|
68
|
+
formData(): Promise<FormData>;
|
|
69
|
+
}
|
|
70
|
+
interface ReadRequestBodyOptions {
|
|
71
|
+
/**
|
|
72
|
+
* A body with no Content-Type: read as JSON (`'json'`, what API Gateway
|
|
73
|
+
* has always done), or not read at all and handed to the schema as `{}`
|
|
74
|
+
* (`'empty'`, what a Hono server has always done).
|
|
75
|
+
*/
|
|
76
|
+
untyped: 'json' | 'empty';
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* A request body, read by its Content-Type into what an endpoint's `.body()`
|
|
80
|
+
* schema validates — the one place every adaptor reads a body:
|
|
81
|
+
*
|
|
82
|
+
* - `application/json` (and `application/*+json`) — the parsed JSON.
|
|
83
|
+
* - `application/x-www-form-urlencoded` and `multipart/form-data` — a plain
|
|
84
|
+
* object of fields, as an HTML form posts them. A repeated field, or one
|
|
85
|
+
* named `field[]`, is an array; a file part is a `File`.
|
|
86
|
+
* - `text/*` — the text.
|
|
87
|
+
*
|
|
88
|
+
* Anything else is an {@link UnsupportedRequestContentType}.
|
|
89
|
+
*/
|
|
90
|
+
export declare function readRequestBody(source: RequestBodySource, contentType: string | null | undefined, options: ReadRequestBodyOptions): Promise<unknown>;
|
|
91
|
+
//#endregion
|
|
38
92
|
//#region src/endpoints/surfaceEnv.d.ts
|
|
39
93
|
/**
|
|
40
94
|
* The default parser, built once.
|
|
@@ -47,5 +101,5 @@ export declare function defaultEnvParser(): EnvironmentParser<{}>;
|
|
|
47
101
|
/** The parser for an endpoint, from its surface or the default. */
|
|
48
102
|
export declare function envParserFor(surface: EndpointSurface | undefined): EnvironmentParser<{}>;
|
|
49
103
|
//#endregion
|
|
50
|
-
export { type ActorExtractor, type Authorizer, BUILT_IN_SECURITY_SCHEMES, type BuiltInSecuritySchemeId, type ConvertRouteParams, Endpoint, EndpointBuilder, type EndpointContext, EndpointFactory, type EndpointHandler, type EndpointOutput, type EndpointSchemas, type MappedAudit, NoAuthenticator, type OAuthFlow, type OAuthFlows, RLS_BYPASS, ResponseBuilder, type RlsBypass, type RlsConfig, type RlsContext, type RlsContextExtractor, type SecurityScheme, type SessionAuth, createAuthorizer, getSecurityScheme, isBuiltInSecurityScheme, publishConstructEvents };
|
|
104
|
+
export { type ActorExtractor, type Authorizer, BUILT_IN_SECURITY_SCHEMES, type BuiltInSecuritySchemeId, type ConvertRouteParams, Endpoint, EndpointBuilder, type EndpointContext, EndpointFactory, type EndpointHandler, type EndpointOutput, type EndpointSchemas, type EndpointStatus, type MappedAudit, NoAuthenticator, type OAuthFlow, type OAuthFlows, RLS_BYPASS, RedirectStatus, type RequestBodySource, ResponseBuilder, type RlsBypass, type RlsConfig, type RlsContext, type RlsContextExtractor, type SecurityScheme, type SessionAuth, SuccessStatus, createAuthorizer, getSecurityScheme, isBuiltInSecurityScheme, publishConstructEvents };
|
|
51
105
|
//# sourceMappingURL=index.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../../src/endpoints/lazyAccessors.ts","../../src/endpoints/surfaceEnv.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../../src/endpoints/lazyAccessors.ts","../../src/endpoints/readRequestBody.ts","../../src/endpoints/surfaceEnv.ts"],"mappings":";;;;;;;;;;;;wBAgBgB,kBAAkB,GAAG,UAAU;;;;;;wBAqB/B,kBAAkB,GAAG,UAAU;;;;;wBAgC/B,wBACf,SAAS,wDACP;;;;;wBAoCa,wBACf,+BACA,mCACE;;;;wBAmHa,qBAAqB;;;;wBAUrB,qBAAqB;;;;;;;qBCrOxB;;;;;qBAWA,sCAAsC;WAC7B;EAArB,YAAqB;;;;;;qBAiBT,6BAA6B;WAE/B;EADV,YACU,qBACT,QAAQ;;;;;;UAeO;EAChB,QAAQ;EACR,QAAQ;EACR,YAAY,QAAQ;;UAGJ;;;;;;EAMhB;;;;;;;;;;;;;;wBAeqB,gBACrB,QAAQ,mBACR,wCACA,SAAS,yBACP;;;;;;;;;;wBCnDa,oBAAoB;;wBASpB,aACf,SAAS,8BACP"}
|