gruber 0.7.0 → 0.9.0-beta.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/CHANGELOG.md +58 -0
- package/README.md +82 -8
- package/config/README.md +10 -0
- package/config/configuration.d.ts +33 -0
- package/config/configuration.d.ts.map +1 -0
- package/config/configuration.js +181 -0
- package/{core → config}/configuration.test.js +299 -176
- package/config/configuration.ts +275 -0
- package/config/mod.d.ts +7 -0
- package/config/mod.d.ts.map +1 -0
- package/config/mod.js +6 -0
- package/config/mod.ts +6 -0
- package/config/parsers.d.ts +20 -0
- package/config/parsers.d.ts.map +1 -0
- package/config/parsers.js +56 -0
- package/config/parsers.test.js +126 -0
- package/config/parsers.ts +74 -0
- package/config/specifications.d.ts +27 -0
- package/config/specifications.d.ts.map +1 -0
- package/config/specifications.js +46 -0
- package/config/specifications.ts +73 -0
- package/config/standard-schema.d.ts +56 -0
- package/config/standard-schema.d.ts.map +1 -0
- package/config/standard-schema.js +5 -0
- package/config/standard-schema.ts +75 -0
- package/config/struct-context.d.ts +12 -0
- package/config/struct-context.d.ts.map +1 -0
- package/config/struct-context.js +4 -0
- package/config/struct-context.test.js +33 -0
- package/config/struct-context.ts +11 -0
- package/config/struct-error.d.ts +26 -0
- package/config/struct-error.d.ts.map +1 -0
- package/config/struct-error.js +70 -0
- package/config/struct-error.test.js +123 -0
- package/config/struct-error.ts +95 -0
- package/config/structure.d.ts +39 -0
- package/config/structure.d.ts.map +1 -0
- package/config/structure.js +275 -0
- package/{core/structures.test.js → config/structure.test.js} +167 -156
- package/config/structure.ts +345 -0
- package/configuration.d.ts +2 -0
- package/configuration.d.ts.map +1 -0
- package/configuration.js +1 -0
- package/configuration.ts +1 -0
- package/core/README.md +10 -0
- package/core/authentication.d.ts.map +1 -1
- package/core/authentication.js +1 -1
- package/core/authentication.ts +15 -6
- package/core/configuration.d.ts +1 -62
- package/core/configuration.d.ts.map +1 -1
- package/core/configuration.js +1 -239
- package/core/configuration.ts +1 -313
- package/core/container.d.ts +21 -0
- package/core/container.d.ts.map +1 -0
- package/core/container.js +50 -0
- package/core/container.test.js +95 -0
- package/core/container.ts +68 -0
- package/core/mod.d.ts +3 -6
- package/core/mod.d.ts.map +1 -1
- package/core/mod.js +3 -6
- package/core/mod.ts +3 -6
- package/core/postgres.d.ts +1 -1
- package/core/postgres.d.ts.map +1 -1
- package/core/postgres.js +1 -1
- package/core/postgres.ts +1 -1
- package/core/terminator.d.ts.map +1 -1
- package/core/terminator.js +11 -0
- package/core/terminator.ts +13 -0
- package/core/timers.d.ts +1 -0
- package/core/timers.d.ts.map +1 -1
- package/core/timers.js +2 -1
- package/core/timers.ts +4 -0
- package/core/tokens.d.ts +11 -0
- package/core/tokens.d.ts.map +1 -1
- package/core/tokens.js +23 -0
- package/core/tokens.test.js +43 -0
- package/core/tokens.ts +24 -0
- package/core/types.d.ts +3 -2
- package/core/types.d.ts.map +1 -1
- package/core/types.ts +4 -4
- package/core/utilities.d.ts +11 -0
- package/core/utilities.d.ts.map +1 -1
- package/core/utilities.js +22 -0
- package/core/utilities.ts +26 -0
- package/core.d.ts +2 -0
- package/core.d.ts.map +1 -0
- package/core.js +1 -0
- package/core.ts +1 -0
- package/express-router.d.ts +2 -0
- package/express-router.d.ts.map +1 -0
- package/express-router.js +1 -0
- package/express-router.ts +1 -0
- package/http/README.md +10 -0
- package/{core → http}/authorization.d.ts +15 -3
- package/http/authorization.d.ts.map +1 -0
- package/{core → http}/authorization.js +23 -10
- package/{core → http}/authorization.test.js +42 -1
- package/{core → http}/authorization.ts +54 -17
- package/http/cors.d.ts +15 -0
- package/http/cors.d.ts.map +1 -0
- package/http/cors.js +50 -0
- package/http/cors.test.js +134 -0
- package/http/cors.ts +79 -0
- package/http/define-route.d.ts +28 -0
- package/http/define-route.d.ts.map +1 -0
- package/http/define-route.js +9 -0
- package/http/define-route.test.js +44 -0
- package/http/define-route.ts +73 -0
- package/http/fetch-router.d.ts +33 -0
- package/http/fetch-router.d.ts.map +1 -0
- package/{core → http}/fetch-router.js +14 -13
- package/http/fetch-router.test.js +297 -0
- package/{core → http}/fetch-router.ts +36 -30
- package/http/http-error.d.ts +29 -0
- package/http/http-error.d.ts.map +1 -0
- package/{core/http.js → http/http-error.js} +0 -31
- package/{core/http.test.js → http/http-error.test.js} +2 -23
- package/http/http-error.ts +65 -0
- package/http/mod.d.ts +8 -0
- package/http/mod.d.ts.map +1 -0
- package/http/mod.js +7 -0
- package/http/mod.ts +7 -0
- package/http/request-body.d.ts +7 -0
- package/http/request-body.d.ts.map +1 -0
- package/http/request-body.js +36 -0
- package/http/request-body.test.js +73 -0
- package/http/request-body.ts +52 -0
- package/http.d.ts +2 -0
- package/http.d.ts.map +1 -0
- package/http.js +1 -0
- package/http.ts +1 -0
- package/koa-router.d.ts +2 -0
- package/koa-router.d.ts.map +1 -0
- package/koa-router.js +1 -0
- package/koa-router.ts +1 -0
- package/mod.d.ts +2 -0
- package/mod.d.ts.map +1 -0
- package/mod.js +1 -0
- package/mod.ts +1 -0
- package/node-router.d.ts +2 -0
- package/node-router.d.ts.map +1 -0
- package/node-router.js +1 -0
- package/node-router.ts +1 -0
- package/package.json +4 -7
- package/polyfill.d.ts +2 -0
- package/polyfill.d.ts.map +1 -0
- package/polyfill.js +1 -0
- package/polyfill.ts +1 -0
- package/postgres.d.ts +2 -0
- package/postgres.d.ts.map +1 -0
- package/postgres.js +1 -0
- package/postgres.ts +1 -0
- package/source/README.md +10 -0
- package/source/configuration.d.ts +4 -3
- package/source/configuration.d.ts.map +1 -1
- package/source/configuration.js +4 -3
- package/source/configuration.ts +5 -3
- package/source/core.d.ts.map +1 -1
- package/source/core.js +0 -1
- package/source/core.ts +0 -1
- package/source/express-router.d.ts +1 -1
- package/source/express-router.js +1 -1
- package/source/express-router.ts +1 -1
- package/source/http.d.ts +2 -0
- package/source/http.d.ts.map +1 -0
- package/source/http.js +1 -0
- package/source/http.ts +1 -0
- package/source/koa-router.d.ts +1 -1
- package/source/koa-router.js +1 -1
- package/source/koa-router.ts +1 -1
- package/source/mod.d.ts +1 -0
- package/source/mod.d.ts.map +1 -1
- package/source/mod.js +1 -0
- package/source/mod.ts +1 -0
- package/source/node-router.d.ts +15 -7
- package/source/node-router.d.ts.map +1 -1
- package/source/node-router.js +75 -11
- package/source/node-router.ts +115 -18
- package/source/package-lock.json +4 -4
- package/source/package.json +1 -1
- package/source/postgres.d.ts +2 -2
- package/source/postgres.d.ts.map +1 -1
- package/source/postgres.js +2 -2
- package/source/postgres.ts +2 -2
- package/source/testing.d.ts +2 -0
- package/source/testing.d.ts.map +1 -0
- package/source/testing.js +1 -0
- package/source/testing.ts +1 -0
- package/terminator.d.ts +2 -0
- package/terminator.d.ts.map +1 -0
- package/terminator.js +1 -0
- package/terminator.ts +1 -0
- package/testing/README.md +10 -0
- package/testing/mod.d.ts +5 -0
- package/testing/mod.d.ts.map +1 -0
- package/testing/mod.js +4 -0
- package/testing/mod.ts +4 -0
- package/testing/spy.d.ts +5 -0
- package/testing/spy.d.ts.map +1 -0
- package/testing/spy.js +8 -0
- package/testing/spy.ts +14 -0
- package/testing/stubs.d.ts +6 -0
- package/testing/stubs.d.ts.map +1 -0
- package/testing/stubs.js +17 -0
- package/testing/stubs.ts +24 -0
- package/testing/testing-router.d.ts +9 -0
- package/testing/testing-router.d.ts.map +1 -0
- package/testing/testing-router.js +25 -0
- package/testing/testing-router.ts +34 -0
- package/testing/utilities.d.ts +15 -0
- package/testing/utilities.d.ts.map +1 -0
- package/testing/utilities.js +37 -0
- package/testing/utilities.ts +48 -0
- package/testing.d.ts +2 -0
- package/testing.d.ts.map +1 -0
- package/testing.js +1 -0
- package/testing.ts +1 -0
- package/tsconfig.json +4 -3
- package/core/authorization.d.ts.map +0 -1
- package/core/fetch-router.d.ts +0 -36
- package/core/fetch-router.d.ts.map +0 -1
- package/core/fetch-router.test.js +0 -156
- package/core/http.d.ts +0 -57
- package/core/http.d.ts.map +0 -1
- package/core/http.ts +0 -149
- package/core/structures.d.ts +0 -41
- package/core/structures.d.ts.map +0 -1
- package/core/structures.js +0 -251
- package/core/structures.ts +0 -303
- /package/{core → http}/server-sent-events.d.ts +0 -0
- /package/{core → http}/server-sent-events.d.ts.map +0 -0
- /package/{core → http}/server-sent-events.js +0 -0
- /package/{core → http}/server-sent-events.ts +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
permalink: /changelog/
|
|
3
|
+
layout: simple.njk
|
|
4
|
+
---
|
|
5
|
+
|
|
1
6
|
# Change log
|
|
2
7
|
|
|
3
8
|
This file documents notable changes to the project
|
|
4
9
|
|
|
10
|
+
## 0.9
|
|
11
|
+
|
|
12
|
+
**features**
|
|
13
|
+
|
|
14
|
+
- Add `config.external`
|
|
15
|
+
- Set "dependencies" on HTTP routes that are evaluated on-demand per request
|
|
16
|
+
- Revamp website
|
|
17
|
+
- Start splitting code out into modules
|
|
18
|
+
- [StandardSchema](https://standardschema.dev) support
|
|
19
|
+
- Add `AuthorizationService#from`
|
|
20
|
+
- Add `Structure.null`
|
|
21
|
+
- Add `Structure.any`
|
|
22
|
+
- Add `Structure.partial`
|
|
23
|
+
- Add `Structure.date`
|
|
24
|
+
- `assertRequestBody` can return a Promise if you pass a request,
|
|
25
|
+
where it will use `getRequestBody` to get the body then validate it
|
|
26
|
+
- Add `createStoppable` to Node.js module and apply it to `serveHTTP`
|
|
27
|
+
- Add `testing` module with utilities, stubs, fakes and a testing router
|
|
28
|
+
|
|
29
|
+
**improved**
|
|
30
|
+
|
|
31
|
+
- More test coverage
|
|
32
|
+
- Better deprecation comments with `@link`
|
|
33
|
+
- Add `SqlDependency#end` method
|
|
34
|
+
- Export `SqlDependency`
|
|
35
|
+
- Simplified `Structure` and `config` generics with nested structures
|
|
36
|
+
- Update `urlpattern-polyfill` to 10.1.0
|
|
37
|
+
- Move to erasable TypeScript code
|
|
38
|
+
|
|
39
|
+
**fixes**
|
|
40
|
+
|
|
41
|
+
- `AbstractAuthorizationService` includes assert's `options` parameter
|
|
42
|
+
- Configuration correctly follows `flags > env > json > fallback`
|
|
43
|
+
- `Structure.object` sets required fields in schema
|
|
44
|
+
|
|
45
|
+
**deprecations**
|
|
46
|
+
|
|
47
|
+
- `StructError` -> `Structure.Error`
|
|
48
|
+
|
|
49
|
+
## 0.8.0
|
|
50
|
+
|
|
51
|
+
**features**
|
|
52
|
+
|
|
53
|
+
- Add optional `options` parameter to `authz.assert` to check the scope
|
|
54
|
+
- Add experimental `authz.from` method to parse the authenticated user
|
|
55
|
+
- Add experimental `CompositeTokens` to combine multiple TokenServices together
|
|
56
|
+
- Add experimental `Cors` utility for addings CORS headers to responses
|
|
57
|
+
- Add experimental `cors` option to `FetchRouter`
|
|
58
|
+
|
|
59
|
+
**fixes**
|
|
60
|
+
|
|
61
|
+
- Add optional `res` parameter to `getResponseReadable` to propagate stream cancellations
|
|
62
|
+
|
|
5
63
|
## 0.7.0
|
|
6
64
|
|
|
7
65
|
**new**
|
package/README.md
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
permalink: /index.html
|
|
3
|
+
layout: home.njk
|
|
4
|
+
---
|
|
5
|
+
|
|
1
6
|
# Gruber
|
|
2
7
|
|
|
3
8
|
An isomorphic JavaScript library for creating web apps.
|
|
@@ -333,6 +338,13 @@ const struct = config.object({
|
|
|
333
338
|
fallback: "postgres://user:secret@localhost:5432/database",
|
|
334
339
|
}),
|
|
335
340
|
}),
|
|
341
|
+
|
|
342
|
+
auth: config.external(
|
|
343
|
+
new URL("./auth.json", import.meta.url),
|
|
344
|
+
config.object({
|
|
345
|
+
users: Structure.array(Structure.string()),
|
|
346
|
+
}),
|
|
347
|
+
),
|
|
336
348
|
});
|
|
337
349
|
|
|
338
350
|
// Load the configuration and parse it
|
|
@@ -867,6 +879,11 @@ Use it to get a `Response` from the provided request, based on the router's rout
|
|
|
867
879
|
const response = await router.getResponse(new Request("http://localhost"));
|
|
868
880
|
```
|
|
869
881
|
|
|
882
|
+
**experimental**
|
|
883
|
+
|
|
884
|
+
- `options.log` turn on HTTP logging with `true` or a custom
|
|
885
|
+
- `options.cors` apply CORS headers with a [Cors](#cors) instance
|
|
886
|
+
|
|
870
887
|
#### unstable http
|
|
871
888
|
|
|
872
889
|
There are some unstable internal methods too:
|
|
@@ -877,6 +894,31 @@ There are some unstable internal methods too:
|
|
|
877
894
|
- `getRequestBody(request)` Get the JSON of FormData body of a request
|
|
878
895
|
- `assertRequestBody(struct, body)` Assert the body matches a structure and return the parsed value
|
|
879
896
|
|
|
897
|
+
#### Cors
|
|
898
|
+
|
|
899
|
+
There is an unstable API for applying [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) headers to responses.
|
|
900
|
+
|
|
901
|
+
```ts
|
|
902
|
+
import { Cors } from "gruber";
|
|
903
|
+
|
|
904
|
+
const cors = new Cors({
|
|
905
|
+
credentials: true,
|
|
906
|
+
origins: ["http://localhost:8080"],
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
const response = cors.apply(
|
|
910
|
+
new Request("http://localhsot:8000"),
|
|
911
|
+
new Response("ok"),
|
|
912
|
+
);
|
|
913
|
+
```
|
|
914
|
+
|
|
915
|
+
It returns a clone of the response passed to it with CORS headers applied. You should no longer use the response passed into it. The headers it applies are:
|
|
916
|
+
|
|
917
|
+
- `Access-Control-Allow-Methods` set to `GET, HEAD, PUT, PATCH, POST, DELETE`
|
|
918
|
+
- `Access-Control-Allow-Headers` mirrors what is set on `Access-Control-Request-Headers` and adds that to `Vary`
|
|
919
|
+
- `Access-Control-Allow-Origin` allows the `Origin` if it matches the `origins` parameter
|
|
920
|
+
- `Access-Control-Allow-Credentials` is set to `true` if the `credentials` parameter is
|
|
921
|
+
|
|
880
922
|
### Postgres
|
|
881
923
|
|
|
882
924
|
#### getPostgresMigratorOptions
|
|
@@ -1063,16 +1105,16 @@ const redis: RedisClientType;
|
|
|
1063
1105
|
const store = new RedisStore(redis, { prefix: "/v2" });
|
|
1064
1106
|
```
|
|
1065
1107
|
|
|
1066
|
-
###
|
|
1108
|
+
### Tokens
|
|
1067
1109
|
|
|
1068
|
-
An abstraction around signing a
|
|
1110
|
+
An abstraction around signing or storing a token for a user with an access scope.
|
|
1069
1111
|
There is currently one implementation using [jose](https://github.com/panva/jose).
|
|
1070
1112
|
|
|
1071
1113
|
```ts
|
|
1072
|
-
import {
|
|
1114
|
+
import { JoseTokens } from "gruber";
|
|
1073
1115
|
import * as jose from "jose";
|
|
1074
1116
|
|
|
1075
|
-
const jwt = new
|
|
1117
|
+
const jwt = new JoseTokens(
|
|
1076
1118
|
{
|
|
1077
1119
|
secret: "top_secret",
|
|
1078
1120
|
issuer: "myapp.io",
|
|
@@ -1091,6 +1133,22 @@ const token = await jwt.sign("user:books:read", {
|
|
|
1091
1133
|
const parsed = await jwt.verify(token);
|
|
1092
1134
|
```
|
|
1093
1135
|
|
|
1136
|
+
There is also `CompositeTokens` which lets you combine multiple verifiers with one signer.
|
|
1137
|
+
For example, if your app has several methods a client might authenticate and one way it itself signs things,
|
|
1138
|
+
like a user token, or a static service or database app-token.
|
|
1139
|
+
|
|
1140
|
+
> UNSTABLE
|
|
1141
|
+
|
|
1142
|
+
```ts
|
|
1143
|
+
import { CompositeTokens, JoseTokens } from "gruber";
|
|
1144
|
+
import * as jose from "jose";
|
|
1145
|
+
|
|
1146
|
+
const tokens = new CompositeTokens(new JoseTokens("..."), [
|
|
1147
|
+
new JoseTokens("..."),
|
|
1148
|
+
// Different token formats your app accepts
|
|
1149
|
+
]);
|
|
1150
|
+
```
|
|
1151
|
+
|
|
1094
1152
|
### Authorization
|
|
1095
1153
|
|
|
1096
1154
|
> UNSTABLE
|
|
@@ -1110,19 +1168,27 @@ const token = authz.getAuthorization(
|
|
|
1110
1168
|
}),
|
|
1111
1169
|
);
|
|
1112
1170
|
|
|
1113
|
-
// { userId: number |
|
|
1171
|
+
// { kind: 'user', userId: number , scope: string } | { kind: 'service', scope: string }
|
|
1172
|
+
const result = await authz.from(
|
|
1173
|
+
new Request("https://example.com", {
|
|
1174
|
+
headers: { Authorization: "Bearer some-long-secure-token" },
|
|
1175
|
+
}),
|
|
1176
|
+
);
|
|
1177
|
+
|
|
1178
|
+
// { kind: 'user', userId: number , scope: string } | { kind: 'service', scope: string }
|
|
1114
1179
|
const { userId, scope } = await authz.assert(
|
|
1115
1180
|
new Request("https://example.com", {
|
|
1116
1181
|
headers: { Authorization: "Bearer some-long-secure-token" },
|
|
1117
1182
|
}),
|
|
1183
|
+
{ scope: "repo:coffee-club" }, // optional
|
|
1118
1184
|
);
|
|
1119
1185
|
|
|
1120
|
-
// { userId: number, scope: string }
|
|
1186
|
+
// { kind: 'user', userId: number, scope: string }
|
|
1121
1187
|
const { userId, scope } = await authz.assertUser(
|
|
1122
1188
|
new Request("https://example.com", {
|
|
1123
1189
|
headers: { Cookie: "my_session=some-long-secure-token" },
|
|
1124
1190
|
}),
|
|
1125
|
-
{ scope: "user:books:read" },
|
|
1191
|
+
{ scope: "user:books:read" }, // optional
|
|
1126
1192
|
);
|
|
1127
1193
|
|
|
1128
1194
|
includesScope("user:books:read", "user:books:read"); // true
|
|
@@ -1520,11 +1586,18 @@ const server = http.createServer((req) => {
|
|
|
1520
1586
|
`getResponseReadable` creates a [streams:Readable](https://nodejs.org/api/stream.html#class-streamreadable) from the body of a fetch Response.
|
|
1521
1587
|
|
|
1522
1588
|
```js
|
|
1589
|
+
import http from "node:http";
|
|
1523
1590
|
import { getResponseReadable } from "gruber/node-router.js";
|
|
1524
1591
|
|
|
1525
|
-
const
|
|
1592
|
+
const server = http.createServer((req, res) => {
|
|
1593
|
+
const readable = getResponseReadable(new Response("some body"), res);
|
|
1594
|
+
});
|
|
1526
1595
|
```
|
|
1527
1596
|
|
|
1597
|
+
Pass in `res` if you want the readable to be cancelled if reading the response is aborted.
|
|
1598
|
+
|
|
1599
|
+
> NOTE: This relies on the **experimental** [Readable.fromWeb](https://nodejs.org/api/stream.html#streamreadablefromwebreadablestream-options)
|
|
1600
|
+
|
|
1528
1601
|
## Development
|
|
1529
1602
|
|
|
1530
1603
|
WIP stuff
|
|
@@ -1537,6 +1610,7 @@ WIP stuff
|
|
|
1537
1610
|
1. `cd bundle/node`
|
|
1538
1611
|
2. `npm publish`
|
|
1539
1612
|
4. Copy the deno source to the S3 bucket — `bundle/deno` → `esm.r0b.io/gruber@VERSION/`
|
|
1613
|
+
5. Push to GitHub and create a [release](https://github.com/robb-j/gruber/releases/new)
|
|
1540
1614
|
|
|
1541
1615
|
### nice snippets
|
|
1542
1616
|
|
package/config/README.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Structure } from "./structure.ts";
|
|
2
|
+
import { ConfigurationDescription, PrimativeOptions } from "./specifications.ts";
|
|
3
|
+
import { ConfigurationResult } from "./parsers.ts";
|
|
4
|
+
import { StructContext } from "./struct-context.ts";
|
|
5
|
+
export interface ConfigurationOptions {
|
|
6
|
+
readTextFile(url: URL | string): Promise<string | null>;
|
|
7
|
+
getEnvironmentVariable(key: string): string | undefined;
|
|
8
|
+
getCommandArgument(key: string): string | undefined;
|
|
9
|
+
stringify(value: any): string | Promise<string>;
|
|
10
|
+
parse(value: string): any;
|
|
11
|
+
}
|
|
12
|
+
export declare class Configuration {
|
|
13
|
+
static readonly spec: unique symbol;
|
|
14
|
+
options: ConfigurationOptions;
|
|
15
|
+
constructor(options: ConfigurationOptions);
|
|
16
|
+
_loadValue<T>(path: string | URL, structure: Structure<T>, context: StructContext): Promise<T | null>;
|
|
17
|
+
/** Wrap a primativ Structure with configuration logic */
|
|
18
|
+
_primative<T>(struct: Structure<T>, options: PrimativeOptions<T>, deconfigure: (result: ConfigurationResult) => unknown): Structure<T>;
|
|
19
|
+
object<T extends Record<string, unknown>>(fields: {
|
|
20
|
+
[K in keyof T]: Structure<T[K]>;
|
|
21
|
+
}): Structure<T>;
|
|
22
|
+
array<T extends unknown>(item: Structure<T>): Structure<T[]>;
|
|
23
|
+
external<T extends Record<string, unknown> | Array<unknown>>(path: string | URL, struct: Structure<T>): Structure<T>;
|
|
24
|
+
string(options: PrimativeOptions<string>): Structure<string>;
|
|
25
|
+
number(options: PrimativeOptions<number>): Structure<number>;
|
|
26
|
+
boolean(options: PrimativeOptions<boolean>): Structure<boolean>;
|
|
27
|
+
url(options: PrimativeOptions<string | URL>): Structure<URL>;
|
|
28
|
+
load<T>(url: URL | string, struct: Structure<T>): Promise<T>;
|
|
29
|
+
getUsage(struct: unknown, currentValue?: unknown): string;
|
|
30
|
+
describe(value: unknown, prefix?: string): ConfigurationDescription;
|
|
31
|
+
getJSONSchema(struct: Structure<any>): import("./structure.ts").Schema;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=configuration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configuration.d.ts","sourceRoot":"","sources":["configuration.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAS,MAAM,gBAAgB,CAAC;AAElD,OAAO,EAEN,wBAAwB,EAGxB,gBAAgB,EAEhB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAKN,mBAAmB,EACnB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,MAAM,WAAW,oBAAoB;IACpC,YAAY,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACxD,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACxD,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACpD,SAAS,CAAC,KAAK,EAAE,GAAG,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAChD,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC;CAC1B;AAED,qBAAa,aAAa;IACzB,MAAM,CAAC,QAAQ,CAAC,IAAI,gBAAgC;IAEpD,OAAO,EAAE,oBAAoB,CAAC;gBAElB,OAAO,EAAE,oBAAoB;IAOnC,UAAU,CAAC,CAAC,EACjB,IAAI,EAAE,MAAM,GAAG,GAAG,EAClB,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,EACvB,OAAO,EAAE,aAAa,GACpB,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAgBpB,yDAAyD;IACzD,UAAU,CAAC,CAAC,EACX,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,EACpB,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAC5B,WAAW,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,OAAO;IActD,MAAM,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE;SAChD,CAAC,IAAI,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;KAC/B,GAAG,SAAS,CAAC,CAAC,CAAC;IAgBhB,KAAK,CAAC,CAAC,SAAS,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,EAAE,CAAC;IAW5D,QAAQ,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,EAC1D,IAAI,EAAE,MAAM,GAAG,GAAG,EAClB,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,GAClB,SAAS,CAAC,CAAC,CAAC;IAgCf,MAAM,CAAC,OAAO,EAAE,gBAAgB,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC,MAAM,CAAC;IAkB5D,MAAM,CAAC,OAAO,EAAE,gBAAgB,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC,MAAM,CAAC;IAgB5D,OAAO,CAAC,OAAO,EAAE,gBAAgB,CAAC,OAAO,CAAC,GAAG,SAAS,CAAC,OAAO,CAAC;IAgB/D,GAAG,CAAC,OAAO,EAAE,gBAAgB,CAAC,MAAM,GAAG,GAAG,CAAC,GAAG,SAAS,CAAC,GAAG,CAAC;IAyBtD,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,GAAG,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAyBlE,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,YAAY,CAAC,EAAE,OAAO;IAwBhD,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,SAAK,GAAG,wBAAwB;IAM/D,aAAa,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,CAAC;CAGpC"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { Structure } from "./structure.js";
|
|
2
|
+
import { formatMarkdownTable, PromiseList } from "../core/mod.js";
|
|
3
|
+
import { arraySpec, getSpecification, objectSpec, primativeSpec, } from "./specifications.js";
|
|
4
|
+
import { _parseBoolean, _parseFloat, _parsePrimative, _parseURL, } from "./parsers.js";
|
|
5
|
+
export class Configuration {
|
|
6
|
+
static spec = Symbol("configuration.spec");
|
|
7
|
+
options;
|
|
8
|
+
constructor(options) {
|
|
9
|
+
this.options = options;
|
|
10
|
+
}
|
|
11
|
+
//
|
|
12
|
+
// Utilities
|
|
13
|
+
//
|
|
14
|
+
async _loadValue(path, structure, context) {
|
|
15
|
+
const text = await this.options.readTextFile(path);
|
|
16
|
+
// Catch missing files and return null instead, the consumer can decide what to do
|
|
17
|
+
if (!text)
|
|
18
|
+
return null;
|
|
19
|
+
// Parse the text file
|
|
20
|
+
const value = this.options.parse(text);
|
|
21
|
+
// Remove the JSON schema field if it is set
|
|
22
|
+
delete value.$schema;
|
|
23
|
+
// Process the contents
|
|
24
|
+
return structure.process(value, context);
|
|
25
|
+
}
|
|
26
|
+
/** Wrap a primativ Structure with configuration logic */
|
|
27
|
+
_primative(struct, options, deconfigure) {
|
|
28
|
+
return new Structure(struct.schema, (value, context) => {
|
|
29
|
+
return struct.process(deconfigure(_parsePrimative(this.options, options, value)), context);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
//
|
|
33
|
+
// Types
|
|
34
|
+
//
|
|
35
|
+
object(fields) {
|
|
36
|
+
if (typeof fields !== "object" || fields === null) {
|
|
37
|
+
throw new TypeError("options must be a non-null object");
|
|
38
|
+
}
|
|
39
|
+
for (const key in fields) {
|
|
40
|
+
if (!(fields[key] instanceof Structure)) {
|
|
41
|
+
throw new TypeError(`options[${key}] is not a Structure`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const struct = Structure.object(fields);
|
|
45
|
+
Object.defineProperty(struct, Configuration.spec, {
|
|
46
|
+
value: objectSpec(fields),
|
|
47
|
+
});
|
|
48
|
+
return struct;
|
|
49
|
+
}
|
|
50
|
+
array(item) {
|
|
51
|
+
if (!(item instanceof Structure)) {
|
|
52
|
+
throw new TypeError("options is not a Structure");
|
|
53
|
+
}
|
|
54
|
+
const struct = Structure.array(item);
|
|
55
|
+
Object.defineProperty(struct, Configuration.spec, {
|
|
56
|
+
value: arraySpec(item),
|
|
57
|
+
});
|
|
58
|
+
return struct;
|
|
59
|
+
}
|
|
60
|
+
external(path, struct) {
|
|
61
|
+
return new Structure(struct.schema, (value, context) => {
|
|
62
|
+
if (context.type !== "async") {
|
|
63
|
+
throw new SyntaxError("config.external must be used async");
|
|
64
|
+
}
|
|
65
|
+
// Create a dummy value to return for now
|
|
66
|
+
let internal = struct.schema.type === "array" ? [] : {};
|
|
67
|
+
// Register a promise to load the actual value
|
|
68
|
+
context.promises.push(async () => {
|
|
69
|
+
// Try loading the external value
|
|
70
|
+
let loaded = await this._loadValue(path, struct, context);
|
|
71
|
+
// If not found, try the inline value
|
|
72
|
+
if (loaded === null) {
|
|
73
|
+
loaded = struct.process(value, context);
|
|
74
|
+
}
|
|
75
|
+
// Apply the value depending if its an array or not
|
|
76
|
+
// being carful to modifiy the existing reference
|
|
77
|
+
if (struct.schema.type === "array") {
|
|
78
|
+
internal.push(...loaded);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
Object.assign(internal, loaded);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
return internal;
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
string(options) {
|
|
88
|
+
if (typeof options.fallback !== "string") {
|
|
89
|
+
throw new TypeError("options.fallback must be a string");
|
|
90
|
+
}
|
|
91
|
+
const struct = this._primative(Structure.string(), options, (result) => result.value);
|
|
92
|
+
Object.defineProperty(struct, Configuration.spec, {
|
|
93
|
+
value: primativeSpec("string", options),
|
|
94
|
+
});
|
|
95
|
+
return struct;
|
|
96
|
+
}
|
|
97
|
+
number(options) {
|
|
98
|
+
if (typeof options.fallback !== "number") {
|
|
99
|
+
throw new TypeError("options.fallback must be a number");
|
|
100
|
+
}
|
|
101
|
+
const struct = this._primative(Structure.number(), options, (result) => _parseFloat(result));
|
|
102
|
+
Object.defineProperty(struct, Configuration.spec, {
|
|
103
|
+
value: primativeSpec("number", options),
|
|
104
|
+
});
|
|
105
|
+
return struct;
|
|
106
|
+
}
|
|
107
|
+
boolean(options) {
|
|
108
|
+
if (typeof options?.fallback !== "boolean") {
|
|
109
|
+
throw new TypeError("options.fallback must be a boolean");
|
|
110
|
+
}
|
|
111
|
+
const struct = this._primative(Structure.boolean(), options, (result) => _parseBoolean(result));
|
|
112
|
+
Object.defineProperty(struct, Configuration.spec, {
|
|
113
|
+
value: primativeSpec("boolean", options),
|
|
114
|
+
});
|
|
115
|
+
return struct;
|
|
116
|
+
}
|
|
117
|
+
url(options) {
|
|
118
|
+
if (typeof options.fallback !== "string" &&
|
|
119
|
+
!(options.fallback instanceof URL)) {
|
|
120
|
+
throw new TypeError("options.fallback must be a string or URL");
|
|
121
|
+
}
|
|
122
|
+
const opts2 = { ...options, fallback: new URL(options.fallback) };
|
|
123
|
+
const struct = this._primative(Structure.url(), opts2, (result) => _parseURL(result));
|
|
124
|
+
Object.defineProperty(struct, Configuration.spec, {
|
|
125
|
+
value: primativeSpec("url", opts2),
|
|
126
|
+
});
|
|
127
|
+
return struct;
|
|
128
|
+
}
|
|
129
|
+
//
|
|
130
|
+
// Methods
|
|
131
|
+
//
|
|
132
|
+
async load(url, struct) {
|
|
133
|
+
const context = {
|
|
134
|
+
type: "async",
|
|
135
|
+
path: [],
|
|
136
|
+
promises: new PromiseList(),
|
|
137
|
+
};
|
|
138
|
+
// Fail outside the try-catch to surface structure errors
|
|
139
|
+
try {
|
|
140
|
+
let obj = await this._loadValue(url, struct, context);
|
|
141
|
+
if (!obj)
|
|
142
|
+
obj = struct.process({}, context);
|
|
143
|
+
await context.promises.all();
|
|
144
|
+
return obj;
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
if (error instanceof Structure.Error) {
|
|
148
|
+
console.error(error.toFriendlyString());
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
console.error("Configuration failed to parse");
|
|
152
|
+
}
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
getUsage(struct, currentValue) {
|
|
157
|
+
const { fallback, fields } = this.describe(struct);
|
|
158
|
+
const lines = [
|
|
159
|
+
"Usage:",
|
|
160
|
+
"",
|
|
161
|
+
formatMarkdownTable(fields.sort((a, b) => a.name.localeCompare(b.name)), ["name", "type", "flag", "variable", "fallback"], "~"),
|
|
162
|
+
"",
|
|
163
|
+
"",
|
|
164
|
+
"Default:",
|
|
165
|
+
this.options.stringify(fallback),
|
|
166
|
+
];
|
|
167
|
+
if (currentValue) {
|
|
168
|
+
lines.push("", "", "Current:", JSON.stringify(currentValue, null, 2));
|
|
169
|
+
}
|
|
170
|
+
return lines.join("\n");
|
|
171
|
+
}
|
|
172
|
+
describe(value, prefix = "") {
|
|
173
|
+
const spec = getSpecification(value);
|
|
174
|
+
if (!spec)
|
|
175
|
+
return { fallback: undefined, fields: [] };
|
|
176
|
+
return spec(prefix);
|
|
177
|
+
}
|
|
178
|
+
getJSONSchema(struct) {
|
|
179
|
+
return struct.getSchema();
|
|
180
|
+
}
|
|
181
|
+
}
|