@afora/core 0.0.8 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (226) hide show
  1. package/README.md +149 -23
  2. package/dist/adapters.d.ts +25 -0
  3. package/dist/adapters.js +32 -0
  4. package/dist/core/application/application.d.ts +30 -0
  5. package/dist/core/application/application.js +1291 -0
  6. package/dist/core/application/exceptions.d.ts +17 -0
  7. package/dist/core/application/exceptions.js +85 -0
  8. package/dist/core/application/migrate.d.ts +19 -0
  9. package/dist/core/application/migrate.js +67 -0
  10. package/dist/core/application/providers.js +108 -0
  11. package/dist/core/application/registry.js +23 -0
  12. package/dist/core/application/schema.d.ts +30 -0
  13. package/dist/core/application/schema.js +398 -0
  14. package/dist/core/application/start.js +162 -0
  15. package/dist/core/application/types/application.interface.d.ts +60 -3
  16. package/dist/core/auth/jwt.d.ts +30 -0
  17. package/dist/core/auth/jwt.js +200 -0
  18. package/dist/core/auth/types/jwt.interface.d.ts +74 -0
  19. package/dist/core/blueprint/blueprint-plugin.js +7 -0
  20. package/dist/core/blueprint/blueprint.js +146 -0
  21. package/dist/core/blueprint/callable.js +13 -0
  22. package/dist/core/blueprint/exceptions.d.ts +5 -0
  23. package/dist/core/blueprint/exceptions.js +20 -0
  24. package/dist/core/blueprint/is-blueprint.js +4 -0
  25. package/dist/core/blueprint/list.js +19 -0
  26. package/dist/core/blueprint/optional.d.ts +3 -1
  27. package/dist/core/blueprint/optional.js +69 -0
  28. package/dist/core/blueprint/record-field.js +14 -0
  29. package/dist/core/blueprint/slice.js +127 -0
  30. package/dist/core/blueprint/types/blueprint.interface.d.ts +95 -5
  31. package/dist/core/blueprint/variant.d.ts +32 -0
  32. package/dist/core/blueprint/variant.js +72 -0
  33. package/dist/core/command/command.js +428 -0
  34. package/dist/core/command/exceptions.js +30 -0
  35. package/dist/core/command/types/command.interface.d.ts +10 -1
  36. package/dist/core/entity/abstract-entity.d.ts +24 -0
  37. package/dist/core/entity/abstract-entity.js +181 -0
  38. package/dist/core/entity/entity.d.ts +1 -1
  39. package/dist/core/entity/entity.js +209 -0
  40. package/dist/core/entity/event.d.ts +39 -0
  41. package/dist/core/entity/event.js +80 -0
  42. package/dist/core/entity/exceptions.d.ts +9 -0
  43. package/dist/core/entity/exceptions.js +27 -0
  44. package/dist/core/entity/trust.d.ts +24 -0
  45. package/dist/core/entity/trust.js +7 -0
  46. package/dist/core/entity/types/entity.interface.d.ts +30 -0
  47. package/dist/core/entity/version.js +13 -0
  48. package/dist/core/environment/environment.d.ts +26 -3
  49. package/dist/core/environment/environment.js +71 -0
  50. package/dist/core/event-policy/event-policy.js +76 -0
  51. package/dist/core/event-policy/exceptions.js +12 -0
  52. package/dist/core/event-policy/publish.js +98 -0
  53. package/dist/core/event-policy/publishers.js +18 -0
  54. package/dist/core/event-policy/types/event-policy.interface.d.ts +8 -2
  55. package/dist/core/exception/enums/exception-layers.js +15 -0
  56. package/dist/core/exception/exception.d.ts +15 -0
  57. package/dist/core/exception/exception.js +128 -0
  58. package/dist/core/exception/exported.js +49 -0
  59. package/dist/core/exception/guard.d.ts +20 -1
  60. package/dist/core/exception/guard.js +16 -0
  61. package/dist/core/exception/types/exception.interface.d.ts +13 -0
  62. package/dist/core/json.js +19 -0
  63. package/dist/core/logger/exceptions.js +7 -0
  64. package/dist/core/logger/logger.d.ts +14 -1
  65. package/dist/core/logger/logger.js +178 -0
  66. package/dist/core/openapi/exceptions.js +7 -0
  67. package/dist/core/openapi/openapi.js +132 -0
  68. package/dist/core/openapi/types/openapi.interface.d.ts +7 -1
  69. package/dist/core/output/access.d.ts +16 -0
  70. package/dist/core/output/access.js +66 -0
  71. package/dist/core/output/coerce.js +38 -0
  72. package/dist/core/output/exceptions.d.ts +16 -0
  73. package/dist/core/output/exceptions.js +22 -0
  74. package/dist/core/output/http.d.ts +36 -0
  75. package/dist/core/output/http.js +351 -0
  76. package/dist/core/output/openapi-sink.d.ts +33 -4
  77. package/dist/core/output/openapi-sink.js +571 -0
  78. package/dist/core/output/output.js +192 -0
  79. package/dist/core/output/problem.js +30 -0
  80. package/dist/core/output/select.js +28 -0
  81. package/dist/core/output/stage.d.ts +7 -2
  82. package/dist/core/output/stage.js +111 -0
  83. package/dist/core/output/status.d.ts +1 -1
  84. package/dist/core/output/status.js +52 -0
  85. package/dist/core/output/types/http.interface.d.ts +134 -0
  86. package/dist/core/output/types/output.interface.d.ts +49 -5
  87. package/dist/core/parse/issue.d.ts +28 -3
  88. package/dist/core/parse/issue.js +31 -0
  89. package/dist/core/parse/parse.d.ts +16 -0
  90. package/dist/core/parse/parse.js +323 -0
  91. package/dist/core/parse/strip.js +48 -0
  92. package/dist/core/property/abstract-property.d.ts +9 -0
  93. package/dist/core/property/abstract-property.js +37 -0
  94. package/dist/core/property/define-property.d.ts +34 -6
  95. package/dist/core/property/define-property.js +209 -0
  96. package/dist/core/property/enums/property-states.js +8 -0
  97. package/dist/core/property/exceptions.js +12 -0
  98. package/dist/core/property/input.js +10 -0
  99. package/dist/core/property/is-property.js +3 -0
  100. package/dist/core/property/lineage.js +8 -0
  101. package/dist/core/property/property.d.ts +27 -0
  102. package/dist/core/property/property.js +46 -0
  103. package/dist/core/property/refine.js +103 -0
  104. package/dist/core/property/types/property-options.interface.d.ts +10 -0
  105. package/dist/core/property/types/property-type.interface.d.ts +4 -0
  106. package/dist/core/record/abstract-record.d.ts +8 -0
  107. package/dist/core/record/abstract-record.js +307 -0
  108. package/dist/core/record/exceptions.js +49 -0
  109. package/dist/core/record/marks.js +7 -0
  110. package/dist/core/record/mask.js +60 -0
  111. package/dist/core/record/name.js +23 -0
  112. package/dist/core/record/plugin-catalog.js +115 -0
  113. package/dist/core/record/plugin.js +198 -0
  114. package/dist/core/record/record.js +117 -0
  115. package/dist/core/record/types/record.interface.d.ts +17 -1
  116. package/dist/core/repository/cursor.js +83 -0
  117. package/dist/core/repository/exceptions.d.ts +13 -0
  118. package/dist/core/repository/exceptions.js +72 -0
  119. package/dist/core/repository/in-memory.d.ts +2 -2
  120. package/dist/core/repository/in-memory.js +196 -0
  121. package/dist/core/repository/migrate.d.ts +20 -0
  122. package/dist/core/repository/migrate.js +254 -0
  123. package/dist/core/repository/plan.js +27 -0
  124. package/dist/core/repository/provider.js +29 -0
  125. package/dist/core/repository/query.js +153 -0
  126. package/dist/core/repository/references.js +196 -0
  127. package/dist/core/repository/repository.d.ts +22 -2
  128. package/dist/core/repository/repository.js +720 -0
  129. package/dist/core/repository/state.d.ts +2 -0
  130. package/dist/core/repository/state.js +138 -0
  131. package/dist/core/repository/types/query.interface.d.ts +13 -0
  132. package/dist/core/repository/types/repository.interface.d.ts +50 -5
  133. package/dist/core/repository/types/schema.interface.d.ts +132 -0
  134. package/dist/core/repository/upcast.d.ts +64 -0
  135. package/dist/core/repository/upcast.js +179 -0
  136. package/dist/core/schema/schema.d.ts +37 -0
  137. package/dist/core/schema/schema.js +542 -0
  138. package/dist/core/standard/standard-schema.js +1 -0
  139. package/dist/core/standard/standard.js +23 -0
  140. package/dist/core/symbols/demo.d.ts +4 -0
  141. package/dist/core/symbols/demo.js +1 -0
  142. package/dist/core/symbols/kind.d.ts +1 -1
  143. package/dist/core/symbols/kind.js +1 -0
  144. package/dist/core/symbols/meta.js +1 -0
  145. package/dist/core/symbols/state.d.ts +5 -0
  146. package/dist/core/symbols/state.js +1 -0
  147. package/dist/core/tracing/exceptions.d.ts +4 -0
  148. package/dist/core/tracing/exceptions.js +7 -0
  149. package/dist/core/tracing/tracing.d.ts +72 -0
  150. package/dist/core/tracing/tracing.js +101 -0
  151. package/dist/core/use-case/access.d.ts +12 -0
  152. package/dist/core/use-case/access.js +56 -0
  153. package/dist/core/use-case/decorators.d.ts +40 -0
  154. package/dist/core/use-case/decorators.js +65 -0
  155. package/dist/core/use-case/exceptions.d.ts +5 -1
  156. package/dist/core/use-case/exceptions.js +48 -0
  157. package/dist/core/use-case/plugin-catalog.js +68 -0
  158. package/dist/core/use-case/plugin.js +133 -0
  159. package/dist/core/use-case/route.d.ts +18 -0
  160. package/dist/core/use-case/route.js +200 -0
  161. package/dist/core/use-case/scope.d.ts +5 -0
  162. package/dist/core/use-case/scope.js +4 -0
  163. package/dist/core/use-case/steps/describe.d.ts +5 -1
  164. package/dist/core/use-case/steps/describe.js +24 -0
  165. package/dist/core/use-case/steps/references.js +105 -0
  166. package/dist/core/use-case/steps/run.js +139 -0
  167. package/dist/core/use-case/steps/types/check.interface.d.ts +233 -0
  168. package/dist/core/use-case/steps/types/steps.interface.d.ts +8 -1
  169. package/dist/core/use-case/steps/validate.js +368 -0
  170. package/dist/core/use-case/target.js +100 -0
  171. package/dist/core/use-case/types/access.interface.d.ts +39 -0
  172. package/dist/core/use-case/types/route.interface.d.ts +86 -0
  173. package/dist/core/use-case/use-case.d.ts +127 -2
  174. package/dist/core/use-case/use-case.js +493 -0
  175. package/dist/experimental.d.ts +18 -1
  176. package/dist/experimental.js +18 -123
  177. package/dist/index.d.ts +49 -51
  178. package/dist/index.js +213 -443
  179. package/dist/std/blueprint-plugin.js +42 -0
  180. package/dist/std/blueprint.d.ts +21 -0
  181. package/dist/std/blueprint.js +3 -0
  182. package/dist/std/command.js +3 -0
  183. package/dist/std/entity.d.ts +20 -1
  184. package/dist/std/entity.js +30 -0
  185. package/dist/std/event-policies.js +3 -0
  186. package/dist/std/properties/contact.d.ts +3 -0
  187. package/dist/std/properties/contact.js +29 -0
  188. package/dist/std/properties/enum.js +26 -0
  189. package/dist/std/properties/exceptions.js +14 -0
  190. package/dist/std/properties/headless.js +45 -0
  191. package/dist/std/properties/identity.js +28 -0
  192. package/dist/std/properties/json.js +161 -0
  193. package/dist/std/properties/password.d.ts +0 -2
  194. package/dist/std/properties/password.js +79 -0
  195. package/dist/std/properties/primitives.d.ts +4 -0
  196. package/dist/std/properties/primitives.js +28 -0
  197. package/dist/std/properties/time.js +55 -0
  198. package/dist/std/record-plugins.d.ts +2 -2
  199. package/dist/std/record-plugins.js +73 -0
  200. package/dist/std/record.d.ts +20 -1
  201. package/dist/std/record.js +3 -0
  202. package/dist/std/resource.d.ts +31 -0
  203. package/dist/std/resource.js +39 -0
  204. package/dist/std/runtime.d.ts +6 -0
  205. package/dist/std/runtime.js +193 -0
  206. package/dist/std/stages.js +4 -0
  207. package/dist/std/use-cases/auth.d.ts +5 -1
  208. package/dist/std/use-cases/auth.js +193 -0
  209. package/dist/std/use-cases/crud.js +261 -0
  210. package/dist/std/use-cases/index.d.ts +1 -1
  211. package/dist/std/use-cases/index.js +12 -0
  212. package/dist/std/use-cases/lifecycle.js +97 -0
  213. package/dist/std/use-cases/shared.d.ts +2 -2
  214. package/dist/std/use-cases/shared.js +232 -0
  215. package/dist/std/use-cases/types.d.ts +102 -2
  216. package/dist/std/use-cases/types.js +0 -0
  217. package/dist/testing/http-provider-contract.d.ts +32 -0
  218. package/dist/testing/http-provider-contract.js +344 -0
  219. package/dist/testing/index.d.ts +2 -0
  220. package/dist/testing/index.js +3 -944
  221. package/dist/testing/memory-publisher.js +12 -0
  222. package/dist/testing/provider-contract.d.ts +26 -1
  223. package/dist/testing/provider-contract.js +1071 -0
  224. package/package.json +23 -3
  225. package/dist/index-qxfkyd6k.js +0 -1721
  226. package/dist/index-t5qwtyj5.js +0 -8194
@@ -0,0 +1,30 @@
1
+ import type { Jwt, JwtOptions } from "./types/jwt.interface.js";
2
+ /** `jwtOf` got options it cannot use, or its secret is missing; `details.reason` says what. */
3
+ export declare const InvalidJwtOptions: import("../../index.js").DeclaredExceptionClass<{
4
+ readonly reason: string;
5
+ }>;
6
+ /**
7
+ * Why `options` cannot be the options of `jwtOf`, naming them `where`;
8
+ * `undefined` when they can. `extra` are other keys the caller accepts, and
9
+ * `described` is for options that `describe()` and `afora.json` show, where
10
+ * the secret must be named by its environment variable, never written.
11
+ */
12
+ export declare function jwtOptionsIssue(options: unknown, where: string, extra?: readonly string[], described?: boolean): string | undefined;
13
+ /**
14
+ * Signs and verifies JSON Web Tokens with a shared secret (HMAC), on Web
15
+ * Crypto, so it runs on Bun, Node and Workers alike:
16
+ *
17
+ * ```ts
18
+ * const jwt = jwtOf({ secret: { env: "JWT_SECRET" }, issuer: "blog" });
19
+ * const token = await jwt.sign({ subject: user.id, scopes: ["posts:write"] });
20
+ * const principal = await jwt.verify(token); // { subject, scopes, claims? }
21
+ * hono(options, { authenticator: jwt.authenticator() });
22
+ * ```
23
+ *
24
+ * A token is refused (`Unauthenticated`) when it is malformed, signed with
25
+ * another algorithm or secret, expired, not yet valid, of another issuer,
26
+ * audience or use, or has no subject. Throws `InvalidJwtOptions` for options
27
+ * it cannot use, and when the secret of `{ env }` is missing or shorter than
28
+ * 32 characters, on the first token.
29
+ */
30
+ export declare function jwtOf(options: JwtOptions): Jwt;
@@ -0,0 +1,200 @@
1
+ import { ExceptionLayers } from "../exception/enums/exception-layers.js";
2
+ import { exceptionOf } from "../exception/exception.js";
3
+ import { Unauthenticated } from "../output/exceptions.js";
4
+ export const InvalidJwtOptions = exceptionOf({
5
+ layer: ExceptionLayers.Output,
6
+ name: "InvalidJwtOptions",
7
+ message: ({ reason }) => reason
8
+ });
9
+ const HASHES = {
10
+ HS256: "SHA-256",
11
+ HS384: "SHA-384",
12
+ HS512: "SHA-512"
13
+ };
14
+ const MIN_SECRET = 32;
15
+ const DEFAULT_EXPIRES_IN = 900;
16
+ const REGISTERED = new Set([
17
+ "sub",
18
+ "scope",
19
+ "iat",
20
+ "exp",
21
+ "nbf",
22
+ "iss",
23
+ "aud",
24
+ "use"
25
+ ]);
26
+ const encoder = new TextEncoder;
27
+ const decoder = new TextDecoder;
28
+ function base64url(bytes) {
29
+ let binary = "";
30
+ for (const byte of bytes)
31
+ binary += String.fromCharCode(byte);
32
+ return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
33
+ }
34
+ function bytesOf(text) {
35
+ if (!/^[A-Za-z0-9_-]*$/.test(text))
36
+ return;
37
+ try {
38
+ const binary = atob(text.replaceAll("-", "+").replaceAll("_", "/"));
39
+ const bytes = new Uint8Array(binary.length);
40
+ for (let i = 0;i < binary.length; i++)
41
+ bytes[i] = binary.charCodeAt(i);
42
+ return bytes;
43
+ } catch {
44
+ return;
45
+ }
46
+ }
47
+ function jsonOf(part) {
48
+ const bytes = bytesOf(part);
49
+ if (!bytes)
50
+ return;
51
+ try {
52
+ const value = JSON.parse(decoder.decode(bytes));
53
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
54
+ } catch {
55
+ return;
56
+ }
57
+ }
58
+ export function jwtOptionsIssue(options, where, extra = [], described = false) {
59
+ if (typeof options !== "object" || options === null || Array.isArray(options)) {
60
+ return `Expected ${where} to be { secret, issuer?, audience?, expiresIn?, algorithm? }.`;
61
+ }
62
+ const given = options;
63
+ const known = [
64
+ "secret",
65
+ "issuer",
66
+ "audience",
67
+ "expiresIn",
68
+ "algorithm",
69
+ ...extra
70
+ ];
71
+ for (const key of Object.keys(given)) {
72
+ if (!known.includes(key))
73
+ return `${where} has no option "${key}".`;
74
+ }
75
+ const { secret, issuer, audience, expiresIn, algorithm } = given;
76
+ const named = typeof secret === "object" && secret !== null && !Array.isArray(secret) ? secret : undefined;
77
+ if (named ? Object.keys(named).join() !== "env" || typeof named.env !== "string" || named.env === "" : typeof secret !== "string") {
78
+ return `Expected ${where}.secret to be the secret or { "env": "NAME" }.`;
79
+ }
80
+ if (described && !named) {
81
+ return `Expected ${where}.secret to be { "env": "NAME" }: these options are described, so the secret is named, not written.`;
82
+ }
83
+ if (typeof secret === "string" && secret.length < MIN_SECRET) {
84
+ return `Expected ${where}.secret to have at least ${MIN_SECRET} characters.`;
85
+ }
86
+ for (const [key, value] of [
87
+ ["issuer", issuer],
88
+ ["audience", audience]
89
+ ]) {
90
+ if (value !== undefined && (typeof value !== "string" || value === "")) {
91
+ return `Expected ${where}.${key} to be a non-empty string.`;
92
+ }
93
+ }
94
+ if (expiresIn !== undefined && (!Number.isInteger(expiresIn) || expiresIn <= 0)) {
95
+ return `Expected ${where}.expiresIn to be a whole number of seconds above 0.`;
96
+ }
97
+ if (algorithm !== undefined && !Object.hasOwn(HASHES, algorithm)) {
98
+ return `Expected ${where}.algorithm to be one of ${Object.keys(HASHES).join(", ")}.`;
99
+ }
100
+ return;
101
+ }
102
+ export function jwtOf(options) {
103
+ const issue = jwtOptionsIssue(options, "jwtOf");
104
+ if (issue) {
105
+ throw new InvalidJwtOptions({
106
+ source: "jwtOf",
107
+ details: { reason: issue }
108
+ });
109
+ }
110
+ const algorithm = options.algorithm ?? "HS256";
111
+ const lifetime = options.expiresIn ?? DEFAULT_EXPIRES_IN;
112
+ const header = base64url(encoder.encode(JSON.stringify({ alg: algorithm, typ: "JWT" })));
113
+ let imported;
114
+ const keyOf = () => {
115
+ if (imported)
116
+ return imported;
117
+ let secret;
118
+ if (typeof options.secret === "string")
119
+ secret = options.secret;
120
+ else {
121
+ const name = options.secret.env;
122
+ secret = globalThis.process?.env?.[name];
123
+ if (typeof secret !== "string" || secret.length < MIN_SECRET) {
124
+ throw new InvalidJwtOptions({
125
+ source: "jwtOf",
126
+ details: {
127
+ reason: `The environment variable ${name} must hold a secret of at least ${MIN_SECRET} characters.`
128
+ }
129
+ });
130
+ }
131
+ }
132
+ imported = crypto.subtle.importKey("raw", encoder.encode(secret), { name: "HMAC", hash: HASHES[algorithm] }, false, ["sign", "verify"]);
133
+ return imported;
134
+ };
135
+ const refused = () => {
136
+ throw new Unauthenticated({ source: "jwt" });
137
+ };
138
+ const jwt = {
139
+ async sign(subject, token = {}) {
140
+ const now = Math.floor(Date.now() / 1000);
141
+ const payload = {
142
+ ...subject.claims ?? {},
143
+ sub: subject.subject,
144
+ scope: (subject.scopes ?? []).join(" "),
145
+ iat: now,
146
+ exp: now + (token.expiresIn ?? lifetime),
147
+ ...options.issuer === undefined ? {} : { iss: options.issuer },
148
+ ...options.audience === undefined ? {} : { aud: options.audience },
149
+ ...token.use === undefined ? {} : { use: token.use }
150
+ };
151
+ const signing = `${header}.${base64url(encoder.encode(JSON.stringify(payload)))}`;
152
+ const signature = await crypto.subtle.sign("HMAC", await keyOf(), encoder.encode(signing));
153
+ return `${signing}.${base64url(new Uint8Array(signature))}`;
154
+ },
155
+ async verify(token, expected = {}) {
156
+ if (typeof token !== "string")
157
+ return refused();
158
+ const parts = token.split(".");
159
+ if (parts.length !== 3)
160
+ return refused();
161
+ const [head, body, sign] = parts;
162
+ if (jsonOf(head)?.alg !== algorithm)
163
+ return refused();
164
+ const signature = bytesOf(sign);
165
+ if (!signature)
166
+ return refused();
167
+ const valid = await crypto.subtle.verify("HMAC", await keyOf(), signature, encoder.encode(`${head}.${body}`));
168
+ const payload = valid ? jsonOf(body) : undefined;
169
+ if (!payload)
170
+ return refused();
171
+ const now = Math.floor(Date.now() / 1000);
172
+ const { sub, scope, exp, nbf, iss, aud, use } = payload;
173
+ if (typeof sub !== "string" || sub === "" || typeof exp !== "number" || exp <= now || nbf !== undefined && (typeof nbf !== "number" || nbf > now) || options.issuer !== undefined && iss !== options.issuer || options.audience !== undefined && aud !== options.audience && !(Array.isArray(aud) && aud.includes(options.audience)) || use !== expected.use) {
174
+ return refused();
175
+ }
176
+ const claims = {};
177
+ for (const [key, value] of Object.entries(payload)) {
178
+ if (!REGISTERED.has(key))
179
+ claims[key] = value;
180
+ }
181
+ return {
182
+ subject: sub,
183
+ scopes: typeof scope === "string" && scope !== "" ? scope.split(" ") : [],
184
+ ...Object.keys(claims).length > 0 ? { claims } : {}
185
+ };
186
+ },
187
+ authenticator(reading = {}) {
188
+ const header = (reading.header ?? "authorization").toLowerCase();
189
+ const { cookie } = reading;
190
+ return async (request) => {
191
+ const sent = request.header(header);
192
+ const token = sent !== undefined ? sent.replace(/^Bearer +/i, "").trim() : cookie !== undefined ? request.cookie(cookie) : undefined;
193
+ if (token === undefined || token === "")
194
+ return null;
195
+ return jwt.verify(token);
196
+ };
197
+ }
198
+ };
199
+ return Object.freeze(jwt);
200
+ }
@@ -0,0 +1,74 @@
1
+ import type { Authenticator } from "../../output/types/http.interface.js";
2
+ import type { Principal } from "../../use-case/types/access.interface.js";
3
+ /** The algorithms `jwtOf` signs and verifies with: HMAC with a shared secret. */
4
+ export type JwtAlgorithm = "HS256" | "HS384" | "HS512";
5
+ /** The options of `jwtOf`. JSON, so they can live in the options of a sink. */
6
+ export interface JwtOptions {
7
+ /**
8
+ * The secret tokens are signed with: the text itself, or the name of the
9
+ * environment variable that holds it (`{ env: "JWT_SECRET" }`), read when
10
+ * the first token is signed or verified. At least 32 characters.
11
+ */
12
+ readonly secret: string | {
13
+ readonly env: string;
14
+ };
15
+ /** The `iss` claim: set when signing, required when verifying. */
16
+ readonly issuer?: string;
17
+ /** The `aud` claim: set when signing, required when verifying. */
18
+ readonly audience?: string;
19
+ /**
20
+ * The seconds a token lives.
21
+ *
22
+ * @default 900
23
+ */
24
+ readonly expiresIn?: number;
25
+ /**
26
+ * The algorithm the tokens are signed and verified with.
27
+ *
28
+ * @default "HS256"
29
+ */
30
+ readonly algorithm?: JwtAlgorithm;
31
+ }
32
+ /** What a token is signed for. */
33
+ export interface JwtSubject {
34
+ readonly subject: string;
35
+ readonly scopes?: readonly string[];
36
+ readonly claims?: Principal["claims"];
37
+ }
38
+ /** What changes for one token. */
39
+ export interface JwtTokenOptions {
40
+ /** The seconds this token lives, instead of the `expiresIn` of the options. */
41
+ readonly expiresIn?: number;
42
+ /**
43
+ * What the token is for, e.g. `"refresh"`: kept in its `use` claim, and a
44
+ * token verifies only for the use it was signed for (none by default), so
45
+ * a refresh token is not accepted as an access token.
46
+ */
47
+ readonly use?: string;
48
+ }
49
+ /** Signs and verifies the tokens of one secret (see `jwtOf`). */
50
+ export interface Jwt {
51
+ /** A signed token for `subject`: `sub`, `scope` (the scopes, space separated), `iat`, `exp` and the claims. */
52
+ sign(subject: JwtSubject, options?: JwtTokenOptions): Promise<string>;
53
+ /** The caller a token was signed for. Rejects with `Unauthenticated` for a token it does not accept. */
54
+ verify(token: string, options?: Pick<JwtTokenOptions, "use">): Promise<Principal>;
55
+ /**
56
+ * An authenticator that reads the token from the `authorization` header
57
+ * (`Bearer <token>`; another header with `header`) and, with `cookie`,
58
+ * from that cookie when the header is absent. No token is an anonymous
59
+ * caller; one that does not verify is refused.
60
+ */
61
+ authenticator(options?: {
62
+ readonly header?: string;
63
+ readonly cookie?: string;
64
+ }): Authenticator;
65
+ }
66
+ /**
67
+ * The options of `jwtOf` where they are described (`describe()`,
68
+ * `afora.json`): the secret is named by its environment variable.
69
+ */
70
+ export interface DescribedJwtOptions extends Omit<JwtOptions, "secret"> {
71
+ readonly secret: {
72
+ readonly env: string;
73
+ };
74
+ }
@@ -0,0 +1,7 @@
1
+ export function blueprintPluginOf(name, properties, factories = {}) {
2
+ return Object.freeze({
3
+ name,
4
+ properties: Object.freeze({ ...properties }),
5
+ factories: Object.freeze({ ...factories })
6
+ });
7
+ }
@@ -0,0 +1,146 @@
1
+ import { Type } from "typebox";
2
+ import { collectionParserOf } from "../parse/parse.js";
3
+ import { isProperty } from "../property/is-property.js";
4
+ import { propertyOf } from "../property/property.js";
5
+ import { rawTypeOf, schemaOf } from "../schema/schema.js";
6
+ import { withStandard } from "../standard/standard.js";
7
+ import { Kind } from "../symbols/kind.js";
8
+ import { callableOf, fieldOf } from "./callable.js";
9
+ import {
10
+ ReservedBlueprintKey,
11
+ UndeclaredBlueprintProperty
12
+ } from "./exceptions.js";
13
+ import { isBlueprint } from "./is-blueprint.js";
14
+ import { isBlueprintList, listOf } from "./list.js";
15
+ import { optionalOf } from "./optional.js";
16
+ import { recordFieldOf } from "./record-field.js";
17
+ import { blueprintOfVariant, isBlueprintVariant, variantOf } from "./variant.js";
18
+ export function blueprintBuilderOf(...base) {
19
+ return (sourcesOrDefine, maybeDefine) => {
20
+ const [sources, define] = typeof sourcesOrDefine === "function" ? [[], sourcesOrDefine] : [
21
+ sourcesOrDefine,
22
+ maybeDefine
23
+ ];
24
+ const all = [...base, ...sources];
25
+ return create(all.flatMap((source) => isBlueprint(source) ? source.plugins : [source]), { ...define(contextOf(all)) });
26
+ };
27
+ }
28
+ export function blueprintFromProperties(plugins, properties) {
29
+ return create(plugins, { ...properties });
30
+ }
31
+ function create(plugins, properties) {
32
+ const shape = {};
33
+ for (const [key, field] of Object.entries(properties)) {
34
+ properties[key] = fieldOf(field);
35
+ shape[key] = schemaOfField(key, properties[key]);
36
+ }
37
+ const SchemaConstructor = schemaOf("blueprint", Type.Object(shape));
38
+ const safeParse = collectionParserOf(properties, "blueprint");
39
+ const blueprint = {
40
+ [Kind]: "blueprint",
41
+ plugins: Object.freeze([...new Set(plugins)]),
42
+ properties: Object.freeze(properties),
43
+ schema: new SchemaConstructor,
44
+ optional: false,
45
+ safeParse,
46
+ join: (...others) => create([blueprint, ...others].flatMap((source) => source.plugins), Object.assign({}, ...[blueprint, ...others].map((source) => source.properties)))
47
+ };
48
+ return Object.freeze(withStandard(blueprint, safeParse));
49
+ }
50
+ const HELPERS = { listOf, optionalOf, propertyOf, variantOf };
51
+ const RESERVED_KEYS = ["optional", "list", ...Object.keys(HELPERS)];
52
+ function lazyMirror(keys, make) {
53
+ const fields = {};
54
+ for (const key of keys) {
55
+ let field;
56
+ Object.defineProperty(fields, key, {
57
+ get: () => {
58
+ field ??= make(key);
59
+ return field;
60
+ },
61
+ enumerable: true
62
+ });
63
+ }
64
+ return fields;
65
+ }
66
+ function contextOf(sources) {
67
+ for (const key of RESERVED_KEYS) {
68
+ const reserving = sources.find((source) => Object.hasOwn(source.properties, key));
69
+ if (reserving) {
70
+ throw new ReservedBlueprintKey({
71
+ source: isBlueprint(reserving) ? "blueprint" : reserving.name,
72
+ details: { key }
73
+ });
74
+ }
75
+ }
76
+ const factories = Object.assign({}, ...sources.map((source) => isBlueprint(source) ? {} : source.factories ?? {}));
77
+ for (const key of RESERVED_KEYS) {
78
+ if (Object.hasOwn(factories, key)) {
79
+ const reserving = sources.find((source) => !isBlueprint(source) && Object.hasOwn(source.factories ?? {}, key));
80
+ throw new ReservedBlueprintKey({
81
+ source: reserving.name,
82
+ details: { key }
83
+ });
84
+ }
85
+ }
86
+ const available = Object.assign({}, ...sources.map((source) => source.properties));
87
+ for (const key of Object.keys(factories))
88
+ delete available[key];
89
+ const mirror = (fields, map) => Object.fromEntries(Object.entries(fields).map(([key, field]) => [key, map(field)]));
90
+ const callable = (field) => isProperty(field) ? callableOf(field) : field;
91
+ const keys = Object.keys(available);
92
+ const list = lazyMirror(keys, (key) => listOf(available[key]));
93
+ const optional = lazyMirror(keys, (key) => callable(optionalOf(available[key])));
94
+ const listOptional = Object.freeze(lazyMirror(keys, (key) => optionalOf(list[key])));
95
+ Object.defineProperty(list, "optional", {
96
+ value: listOptional,
97
+ enumerable: true
98
+ });
99
+ return Object.freeze({
100
+ ...mirror(available, callable),
101
+ ...factories,
102
+ ...HELPERS,
103
+ optional: Object.freeze(optional),
104
+ list: Object.freeze(list)
105
+ });
106
+ }
107
+ function schemaOfField(key, field) {
108
+ if (isBlueprintList(field)) {
109
+ const { item, minItems, maxItems } = field;
110
+ const schema = Type.Array(schemaOfField(key, item), {
111
+ ...minItems === undefined ? {} : { minItems },
112
+ ...maxItems === undefined ? {} : { maxItems }
113
+ });
114
+ return field.optional ? Type.Optional(schema) : schema;
115
+ }
116
+ if (isBlueprint(field)) {
117
+ const schema = rawTypeOf(field.schema);
118
+ return field.optional ? Type.Optional(schema) : schema;
119
+ }
120
+ if (isBlueprintVariant(field)) {
121
+ const schema = Type.Union(Object.entries(field.variants).map(([tag, variant]) => {
122
+ const object = rawTypeOf(blueprintOfVariant(variant).schema);
123
+ return {
124
+ ...object,
125
+ properties: {
126
+ [field.key]: Type.Literal(tag),
127
+ ...object.properties
128
+ },
129
+ required: [field.key, ...object.required ?? []]
130
+ };
131
+ }));
132
+ return field.optional ? Type.Optional(schema) : schema;
133
+ }
134
+ const record = recordFieldOf(field);
135
+ if (record) {
136
+ const schema = rawTypeOf(record.record.blueprint.schema);
137
+ return record.optional ? Type.Optional(schema) : schema;
138
+ }
139
+ if (isProperty(field)) {
140
+ return rawTypeOf(field.type.schema);
141
+ }
142
+ throw new UndeclaredBlueprintProperty({
143
+ source: "blueprint",
144
+ details: { key }
145
+ });
146
+ }
@@ -0,0 +1,13 @@
1
+ import { isProperty } from "../property/is-property.js";
2
+ import { refinePropertyOf } from "../property/refine.js";
3
+ const targets = new WeakMap;
4
+ export function callableOf(Property) {
5
+ const callable = new Proxy(Property, {
6
+ apply: (target, _this, [options]) => refinePropertyOf(target, options ?? {})
7
+ });
8
+ targets.set(callable, Property);
9
+ return callable;
10
+ }
11
+ export function fieldOf(field) {
12
+ return (isProperty(field) ? targets.get(field) : undefined) ?? field;
13
+ }
@@ -2,6 +2,11 @@
2
2
  export declare const ReservedBlueprintKey: import("../../index.js").DeclaredExceptionClass<{
3
3
  readonly key: string;
4
4
  }>;
5
+ /** `variantOf` was given something it cannot make a field of; `reason` says what. */
6
+ export declare const InvalidBlueprintVariant: import("../../index.js").DeclaredExceptionClass<{
7
+ readonly key: string;
8
+ readonly reason: string;
9
+ }>;
5
10
  /** A blueprint picks a key that no plugin provides. */
6
11
  export declare const UndeclaredBlueprintProperty: import("../../index.js").DeclaredExceptionClass<{
7
12
  readonly key: string;
@@ -0,0 +1,20 @@
1
+ import { ExceptionLayers } from "../exception/enums/exception-layers.js";
2
+ import { exceptionOf } from "../exception/exception.js";
3
+ export const ReservedBlueprintKey = exceptionOf({
4
+ layer: ExceptionLayers.Blueprint,
5
+ name: "ReservedBlueprintKey",
6
+ message: ({ key }) => `"${key}" is reserved and cannot be a plugin property.`
7
+ });
8
+ export const InvalidBlueprintVariant = exceptionOf({
9
+ layer: ExceptionLayers.Blueprint,
10
+ name: "InvalidBlueprintVariant",
11
+ message: ({
12
+ key,
13
+ reason
14
+ }) => `The variant field of key "${key}" cannot be declared: ${reason}.`
15
+ });
16
+ export const UndeclaredBlueprintProperty = exceptionOf({
17
+ layer: ExceptionLayers.Blueprint,
18
+ name: "UndeclaredBlueprintProperty",
19
+ message: ({ key }) => `Property "${key}" of the blueprint is not declared.`
20
+ });
@@ -0,0 +1,4 @@
1
+ import { Kind } from "../symbols/kind.js";
2
+ export function isBlueprint(value) {
3
+ return typeof value === "object" && value !== null && value[Kind] === "blueprint";
4
+ }
@@ -0,0 +1,19 @@
1
+ import { parseField } from "../parse/parse.js";
2
+ import { withStandard } from "../standard/standard.js";
3
+ import { Kind } from "../symbols/kind.js";
4
+ import { fieldOf } from "./callable.js";
5
+ export function listOf(item, { minItems, maxItems } = {}) {
6
+ return standardListOf({
7
+ [Kind]: "list",
8
+ item: fieldOf(item),
9
+ optional: false,
10
+ minItems,
11
+ maxItems
12
+ });
13
+ }
14
+ export function standardListOf(list) {
15
+ return Object.freeze(withStandard(list, (value) => parseField(list, value, [], "list")));
16
+ }
17
+ export function isBlueprintList(value) {
18
+ return typeof value === "object" && value !== null && value[Kind] === "list";
19
+ }
@@ -1,6 +1,6 @@
1
1
  import type { OptionalProperty } from "../property/types/optional-property.type.js";
2
2
  import type { AnyPropertyConstructor } from "../property/types/property-collection.type.js";
3
- import type { AnyBlueprint, AnyBlueprintList, AnyRecordClass, BlueprintField, OptionalBlueprint, OptionalBlueprintList, OptionalRecord } from "./types/blueprint.interface.js";
3
+ import type { AnyBlueprint, AnyBlueprintList, AnyBlueprintVariant, AnyRecordClass, BlueprintField, OptionalBlueprint, OptionalBlueprintList, OptionalBlueprintVariant, OptionalRecord } from "./types/blueprint.interface.js";
4
4
  /**
5
5
  * The version of `Property` that accepts `undefined`. Returns `Property` itself
6
6
  * when it is already optional, and the same class for the same `Property`.
@@ -13,6 +13,8 @@ export declare function optionalOf<Property extends AnyPropertyConstructor>(Prop
13
13
  export declare function optionalOf<Blueprint extends AnyBlueprint>(blueprint: Blueprint): OptionalBlueprint<Blueprint>;
14
14
  /** The version of `list` that, as a field of a blueprint, accepts `undefined`. */
15
15
  export declare function optionalOf<List extends AnyBlueprintList>(list: List): OptionalBlueprintList<List>;
16
+ /** The version of `variant` that, as a field of a blueprint, accepts `undefined`. */
17
+ export declare function optionalOf<Variant extends AnyBlueprintVariant>(variant: Variant): OptionalBlueprintVariant<Variant>;
16
18
  /**
17
19
  * The version of `record` that, as a field of a blueprint, accepts `undefined`
18
20
  * or an instance of `record`. Returns `record` itself when it is already optional.
@@ -0,0 +1,69 @@
1
+ import { Type } from "typebox";
2
+ import { recordPropertyLineage } from "../property/lineage.js";
3
+ import { derivePropertyOf } from "../property/refine.js";
4
+ import { rawTypeOf, schemaOf } from "../schema/schema.js";
5
+ import { withStandard } from "../standard/standard.js";
6
+ import { Kind } from "../symbols/kind.js";
7
+ import { fieldOf } from "./callable.js";
8
+ import { isBlueprint } from "./is-blueprint.js";
9
+ import { isBlueprintList, standardListOf } from "./list.js";
10
+ import { isOptionalRecord, isRecordClass } from "./record-field.js";
11
+ import { isBlueprintVariant, standardVariantOf } from "./variant.js";
12
+ const cache = new WeakMap;
13
+ export function optionalOf(given) {
14
+ const field = fieldOf(given);
15
+ const cached = cache.get(field);
16
+ if (cached)
17
+ return cached;
18
+ if (isOptionalRecord(field))
19
+ return field;
20
+ if (isRecordClass(field)) {
21
+ const optional = Object.freeze(withStandard({
22
+ [Kind]: "record",
23
+ record: field,
24
+ optional: true
25
+ }, (value) => value === undefined ? { value } : field.safeParse(value)));
26
+ cache.set(field, optional);
27
+ return optional;
28
+ }
29
+ if (isBlueprint(field)) {
30
+ if (field.optional)
31
+ return field;
32
+ const optional = {
33
+ ...field,
34
+ optional: true,
35
+ safeParse: (value) => value === undefined ? { value } : field.safeParse(value)
36
+ };
37
+ Object.freeze(withStandard(optional, (value) => optional.safeParse(value)));
38
+ cache.set(field, optional);
39
+ return optional;
40
+ }
41
+ if (isBlueprintList(field)) {
42
+ if (field.optional)
43
+ return field;
44
+ const optional = standardListOf({ ...field, optional: true });
45
+ cache.set(field, optional);
46
+ return optional;
47
+ }
48
+ if (isBlueprintVariant(field)) {
49
+ if (field.optional)
50
+ return field;
51
+ const optional = standardVariantOf({ ...field, optional: true });
52
+ cache.set(field, optional);
53
+ return optional;
54
+ }
55
+ const Property = field;
56
+ if (Property.type.optional)
57
+ return Property;
58
+ const base = Property.type;
59
+ const SchemaConstructor = schemaOf(base.name, Type.Optional(rawTypeOf(base.schema)));
60
+ const type = {
61
+ ...base,
62
+ schema: new SchemaConstructor,
63
+ optional: true
64
+ };
65
+ const Optional = derivePropertyOf(Property, type);
66
+ recordPropertyLineage(Optional, { kind: "optional", base: Property });
67
+ cache.set(Property, Optional);
68
+ return Optional;
69
+ }
@@ -0,0 +1,14 @@
1
+ import { Kind } from "../symbols/kind.js";
2
+ export function isRecordClass(value) {
3
+ return typeof value === "function" && value[Kind] === "record";
4
+ }
5
+ export function isOptionalRecord(value) {
6
+ return typeof value === "object" && value !== null && value[Kind] === "record";
7
+ }
8
+ export function recordFieldOf(field) {
9
+ if (isRecordClass(field))
10
+ return { record: field, optional: false };
11
+ if (isOptionalRecord(field))
12
+ return field;
13
+ return;
14
+ }