gruber 0.2.0 → 0.3.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 +12 -0
- package/README.md +96 -10
- package/{types/core → core}/configuration.d.ts +15 -13
- package/core/configuration.js +35 -37
- package/core/configuration.test.d.ts +1 -0
- package/core/configuration.test.js +32 -42
- package/{types/core → core}/fetch-router.d.ts +0 -1
- package/core/fetch-router.test.d.ts +1 -0
- package/{types/core → core}/http.d.ts +0 -1
- package/core/http.test.d.ts +1 -0
- package/{types/core → core}/migrator.d.ts +0 -1
- package/core/migrator.test.d.ts +1 -0
- package/{types/core → core}/mod.d.ts +0 -1
- package/{types/core → core}/postgres.d.ts +0 -1
- package/core/structures.d.ts +78 -0
- package/core/structures.js +202 -0
- package/core/structures.test.d.ts +1 -0
- package/core/structures.test.js +349 -0
- package/core/test-deps.d.ts +1 -0
- package/core/test-deps.js +1 -1
- package/{types/core → core}/types.d.ts +0 -1
- package/{types/core → core}/utilities.d.ts +0 -1
- package/core/utilities.test.d.ts +1 -0
- package/package.json +4 -5
- package/{types/source → source}/configuration.d.ts +4 -9
- package/source/configuration.js +0 -2
- package/source/core.d.ts +1 -0
- package/{types/source → source}/express-router.d.ts +2 -3
- package/source/express-router.js +1 -1
- package/{types/source → source}/koa-router.d.ts +0 -1
- package/{types/source → source}/mod.d.ts +0 -3
- package/source/mod.js +2 -2
- package/{types/source → source}/node-router.d.ts +8 -8
- package/source/node-router.js +1 -1
- package/source/package-lock.json +3 -9
- package/source/package.json +0 -2
- package/source/polyfill.d.ts +1 -0
- package/{types/source → source}/postgres.d.ts +0 -1
- package/tsconfig.json +0 -2
- package/types/core/configuration.d.ts.map +0 -1
- package/types/core/configuration.test.d.ts +0 -2
- package/types/core/configuration.test.d.ts.map +0 -1
- package/types/core/fetch-router.d.ts.map +0 -1
- package/types/core/fetch-router.test.d.ts +0 -2
- package/types/core/fetch-router.test.d.ts.map +0 -1
- package/types/core/http.d.ts.map +0 -1
- package/types/core/http.test.d.ts +0 -2
- package/types/core/http.test.d.ts.map +0 -1
- package/types/core/migrator.d.ts.map +0 -1
- package/types/core/migrator.test.d.ts +0 -2
- package/types/core/migrator.test.d.ts.map +0 -1
- package/types/core/mod.d.ts.map +0 -1
- package/types/core/postgres.d.ts.map +0 -1
- package/types/core/test-deps.d.ts +0 -2
- package/types/core/test-deps.d.ts.map +0 -1
- package/types/core/types.d.ts.map +0 -1
- package/types/core/utilities.d.ts.map +0 -1
- package/types/core/utilities.test.d.ts +0 -2
- package/types/core/utilities.test.d.ts.map +0 -1
- package/types/source/configuration.d.ts.map +0 -1
- package/types/source/core.d.ts +0 -2
- package/types/source/core.d.ts.map +0 -1
- package/types/source/express-router.d.ts.map +0 -1
- package/types/source/koa-router.d.ts.map +0 -1
- package/types/source/mod.d.ts.map +0 -1
- package/types/source/node-router.d.ts.map +0 -1
- package/types/source/polyfill.d.ts +0 -2
- package/types/source/polyfill.d.ts.map +0 -1
- package/types/source/postgres.d.ts.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
This file documents notable changes to the project
|
|
4
4
|
|
|
5
|
+
## 0.3.0
|
|
6
|
+
|
|
7
|
+
**new**
|
|
8
|
+
|
|
9
|
+
- Removed use of superstruct in favour of new `structures.js` implementation
|
|
10
|
+
- Added `getJSONSchema` method to `Configuration`
|
|
11
|
+
|
|
12
|
+
**fixed**
|
|
13
|
+
|
|
14
|
+
- Node.js types should work now
|
|
15
|
+
- Node.js types includes a url-pattern polyfil
|
|
16
|
+
|
|
5
17
|
## 0.2.0
|
|
6
18
|
|
|
7
19
|
**new**
|
package/README.md
CHANGED
|
@@ -235,11 +235,11 @@ Building on the [HTTP server](#http-server) above, we'll setup configuration. St
|
|
|
235
235
|
**config.js**
|
|
236
236
|
|
|
237
237
|
```js
|
|
238
|
-
import
|
|
238
|
+
import fs from "node:fs";
|
|
239
239
|
import { getNodeConfiguration } from "gruber";
|
|
240
240
|
|
|
241
241
|
const pkg = JSON.parse(fs.readFileSync("./package.json", "utf8"));
|
|
242
|
-
const config = getNodeConfiguration(
|
|
242
|
+
const config = getNodeConfiguration();
|
|
243
243
|
|
|
244
244
|
export function getSpecification() {
|
|
245
245
|
return config.object({
|
|
@@ -286,6 +286,11 @@ export const appConfig = await loadConfiguration(
|
|
|
286
286
|
export function getConfigurationUsage() {
|
|
287
287
|
return config.getUsage(getSpecification());
|
|
288
288
|
}
|
|
289
|
+
|
|
290
|
+
// Export a method to generate a JSON Schema for the configuration
|
|
291
|
+
export function getConfigurationSchema() {
|
|
292
|
+
return config.getJSONSchema(getSpecification());
|
|
293
|
+
}
|
|
289
294
|
```
|
|
290
295
|
|
|
291
296
|
### Usage info
|
|
@@ -327,11 +332,11 @@ You can provide a configuration file like **config.json** to load through the co
|
|
|
327
332
|
"selfUrl": "http://localhost:3000",
|
|
328
333
|
"meta": {
|
|
329
334
|
"name": "gruber-app",
|
|
330
|
-
"version": "1.2.3"
|
|
335
|
+
"version": "1.2.3"
|
|
331
336
|
},
|
|
332
337
|
"database": {
|
|
333
|
-
"url": "postgres://user:secret@localhost:5432/database"
|
|
334
|
-
}
|
|
338
|
+
"url": "postgres://user:secret@localhost:5432/database"
|
|
339
|
+
}
|
|
335
340
|
}
|
|
336
341
|
```
|
|
337
342
|
|
|
@@ -672,7 +677,6 @@ To see how it works, look at the [Node](./node/source/configuration.js) and [Den
|
|
|
672
677
|
You can use the static `getOptions` method both subclasses provide and override the parts you want.
|
|
673
678
|
These are the options:
|
|
674
679
|
|
|
675
|
-
- `superstruct` — Configuration is based on [superstruct](https://docs.superstructjs.org/), you can pass your own instance if you like.
|
|
676
680
|
- `readTextFile(url)` — How to load a text file from the file system
|
|
677
681
|
- `getEnvironmentVariable(key)` — Return a matching environment "variable" for a key
|
|
678
682
|
- `getCommandArgument(key)` — Get the corresponding "flag" from a CLI argument
|
|
@@ -684,10 +688,9 @@ For example, to override in Node:
|
|
|
684
688
|
```js
|
|
685
689
|
import { Configuration, getNodeConfigOptions } from "gruber";
|
|
686
690
|
import Yaml from "yaml";
|
|
687
|
-
import superstruct from "superstruct";
|
|
688
691
|
|
|
689
692
|
const config = new Configuration({
|
|
690
|
-
...getNodeConfigOptions(
|
|
693
|
+
...getNodeConfigOptions(),
|
|
691
694
|
getEnvionmentVariable: () => undefined,
|
|
692
695
|
stringify: (v) => Yaml.stringify(v),
|
|
693
696
|
parse: (v) => Yaml.parse(v),
|
|
@@ -870,7 +873,7 @@ You can use it and override parts of it to customise how the postgres migrator w
|
|
|
870
873
|
|
|
871
874
|
### Utilities
|
|
872
875
|
|
|
873
|
-
|
|
876
|
+
#### loader
|
|
874
877
|
|
|
875
878
|
`loader` let's you memoize the result of a function to create a singleton from it.
|
|
876
879
|
It works synchronously or with promises.
|
|
@@ -914,6 +917,89 @@ This will generate the table:
|
|
|
914
917
|
| Tyler Rockwell | ~ |
|
|
915
918
|
```
|
|
916
919
|
|
|
920
|
+
#### Structure
|
|
921
|
+
|
|
922
|
+
This is an internal primative for validating objects, strings, numbers and URLs for use in [Configuration](#configuration).
|
|
923
|
+
It is based on a very specific use of [superstruct](https://github.com/ianstormtaylor/superstruct) which it made sense to internalise to make the code base more portable.
|
|
924
|
+
A `Structure` is a type that validates a value is correct by throwing an error if validation fails, i.e. the wrong type is passed.
|
|
925
|
+
Every struct has an intrinsic `fallback` so that if no value (`undefined`) is passed, that is used instead.
|
|
926
|
+
|
|
927
|
+
```js
|
|
928
|
+
import { Structure } from "gruber/structures.js";
|
|
929
|
+
|
|
930
|
+
// A string primative, or use "Geoff Testington" if no value is passed.
|
|
931
|
+
const name = Structure.string("Geoff Testington");
|
|
932
|
+
|
|
933
|
+
// A URL instance or a string that contains a valid URL, always converting to a URL
|
|
934
|
+
const website = Structure.url("https://example.com");
|
|
935
|
+
|
|
936
|
+
// A number primative, falling back to 42
|
|
937
|
+
const age = Structure.number(42);
|
|
938
|
+
|
|
939
|
+
// An object with all of the fields above and nothing else
|
|
940
|
+
// defaulting to create { name: "Geoff..", age: 42, website: "https..." } with the same fallback values
|
|
941
|
+
const person = Structure.object({ name, age, website });
|
|
942
|
+
|
|
943
|
+
// Process the Structure and get a value out. The returned value is strongly typed!
|
|
944
|
+
// This will throw if the value passed does not match the schema.
|
|
945
|
+
const value = person.process(/* ... */);
|
|
946
|
+
```
|
|
947
|
+
|
|
948
|
+
Those static Structure methods return a `Structure` instance. You can also create your own types with the constructor. This example shows how to do that, and also starts to unveil how the internals work a bit with [StructError](#structerror).
|
|
949
|
+
|
|
950
|
+
```js
|
|
951
|
+
import { Structure, StructError } from "gruber/structures.js";
|
|
952
|
+
|
|
953
|
+
// Create a new boolean structure (this should probably be included as Structure.boolean tbh)
|
|
954
|
+
const boolean = new Structure(
|
|
955
|
+
{ type: "boolean", default: false },
|
|
956
|
+
(input, context) => {
|
|
957
|
+
if (input === undefined) return false;
|
|
958
|
+
if (typeof input !== "boolean") {
|
|
959
|
+
throw new StructError("Expected a boolean", context?.path);
|
|
960
|
+
}
|
|
961
|
+
return input;
|
|
962
|
+
},
|
|
963
|
+
);
|
|
964
|
+
```
|
|
965
|
+
|
|
966
|
+
To create a custom Structure, you give it a [JSON schema](https://json-schema.org/) and a "process" function.
|
|
967
|
+
The function is called to validate a value against the structure. It should return the processed value or throw a `StructError`.
|
|
968
|
+
|
|
969
|
+
The `context` object might not be set and this means the struct is at the root level. If it is nested in an `object` then the context contains the path that the struct is located at, all the way from the root object. That path is expressed as an array of strings. That path is used to generate friendlier error messages to explain which nested field failed.
|
|
970
|
+
|
|
971
|
+
With a Structure, you can generate a JSON Schema:
|
|
972
|
+
|
|
973
|
+
```js
|
|
974
|
+
import { Structure } from "gruber/structures.js";
|
|
975
|
+
|
|
976
|
+
const person = Structure.object({
|
|
977
|
+
name: Structure.string("Geoff Testington"),
|
|
978
|
+
age: Structure.number(42),
|
|
979
|
+
website: Structure.url("https://example.com"),
|
|
980
|
+
});
|
|
981
|
+
|
|
982
|
+
console.log(JSON.stringify(person.getSchema(), null, 2));
|
|
983
|
+
```
|
|
984
|
+
|
|
985
|
+
This is a bit WIP, but you could use this to generate a JSON schema to lint configurations in your IDE.
|
|
986
|
+
|
|
987
|
+
#### StructError
|
|
988
|
+
|
|
989
|
+
This Error subclass contains extra information about why parsing a `Structure` failed.
|
|
990
|
+
|
|
991
|
+
- The `message` field is a description of what went wrong, in the context of the structure.
|
|
992
|
+
- An extra `path` field exists to describe the path from the root object to get to this failed structure
|
|
993
|
+
- `children` is also available to let a structure have multiple child errors, i.e. for an object to have failures for each of the fields that have failed.
|
|
994
|
+
|
|
995
|
+
On the error, there are also methods to help use it:
|
|
996
|
+
|
|
997
|
+
- `toFriendlyString` goes through all nested failures and outputs a single message to describe everything that went wrong.
|
|
998
|
+
- `getOneLiner` converts the error to a succint one-line error message, concatentating the path and message
|
|
999
|
+
- `[Symbol.iterator]` is also available if you want to loop through all children nodes, only those that do not have children themselves.
|
|
1000
|
+
|
|
1001
|
+
There is also the static method `StructError.chain(error, context)` which is useful for catching errors and applying a context to them (if they are not already a StructError).
|
|
1002
|
+
|
|
917
1003
|
## Node.js library
|
|
918
1004
|
|
|
919
1005
|
There are some specific helpers to help use Gruber in Node.js apps.
|
|
@@ -969,7 +1055,7 @@ For older version of Node.js that don't support the latest web-standards,
|
|
|
969
1055
|
there is a polyfil import you can use to add support for them to your runtime.
|
|
970
1056
|
|
|
971
1057
|
```js
|
|
972
|
-
import "gruber/
|
|
1058
|
+
import "gruber/polyfill.js";
|
|
973
1059
|
```
|
|
974
1060
|
|
|
975
1061
|
This currently polyfils these APIs:
|
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
/** @typedef {Record<string, import("superstruct").Struct<any, any>>} ObjectSchema */
|
|
2
1
|
export class Configuration {
|
|
3
2
|
static spec: symbol;
|
|
4
3
|
/** @param {ConfigurationOptions} options */
|
|
5
4
|
constructor(options: ConfigurationOptions);
|
|
6
|
-
options: ConfigurationOptions;
|
|
5
|
+
/** @type {ConfigurationOptions} */ options: ConfigurationOptions;
|
|
7
6
|
/**
|
|
8
|
-
* @template {
|
|
7
|
+
* @template {Record<string, Structure<any>>} T
|
|
9
8
|
* @param {T} spec
|
|
9
|
+
* @returns {Structure<{ [K in keyof T]: import("./structures.js").Infer<T[K]> }>}
|
|
10
10
|
*/
|
|
11
|
-
object<T extends
|
|
11
|
+
object<T extends Record<string, Structure<any>>>(spec: T): Structure<{ [K in keyof T]: import("./structures.js").Infer<T[K]>; }>;
|
|
12
12
|
/**
|
|
13
13
|
* @template {SpecOptions} Spec @param {Spec} spec
|
|
14
|
-
* @returns {
|
|
14
|
+
* @returns {Structure<string>}
|
|
15
15
|
*/
|
|
16
|
-
string<Spec extends SpecOptions>(spec?: Spec):
|
|
16
|
+
string<Spec extends SpecOptions>(spec?: Spec): Structure<string>;
|
|
17
17
|
/**
|
|
18
18
|
* @template {SpecOptions} Spec @param {Spec} spec
|
|
19
|
-
* @returns {
|
|
19
|
+
* @returns {Structure<URL>}
|
|
20
20
|
*/
|
|
21
|
-
url<Spec_1 extends SpecOptions>(spec: Spec_1):
|
|
21
|
+
url<Spec_1 extends SpecOptions>(spec: Spec_1): Structure<URL>;
|
|
22
22
|
/** @param {SpecOptions} spec */
|
|
23
23
|
_getValue(spec: SpecOptions): string;
|
|
24
24
|
/**
|
|
25
25
|
* @template T
|
|
26
26
|
* @param {URL} url
|
|
27
|
-
* @param {
|
|
27
|
+
* @param {Structure<T>} spec
|
|
28
28
|
* @returns {Promise<T>}
|
|
29
29
|
*/
|
|
30
|
-
load<T_1>(url: URL, spec:
|
|
30
|
+
load<T_1>(url: URL, spec: Structure<T_1>): Promise<T_1>;
|
|
31
31
|
/** @template T @param {T} config */
|
|
32
32
|
getUsage<T_2>(spec: any): string;
|
|
33
33
|
/**
|
|
@@ -39,6 +39,10 @@ export class Configuration {
|
|
|
39
39
|
config: any;
|
|
40
40
|
fields: [string, string];
|
|
41
41
|
};
|
|
42
|
+
/** @param {Structure<any>} spec */
|
|
43
|
+
getJSONSchema(spec: Structure<any>): {
|
|
44
|
+
$schema: string;
|
|
45
|
+
};
|
|
42
46
|
}
|
|
43
47
|
export type SpecOptions = {
|
|
44
48
|
variable?: string;
|
|
@@ -46,12 +50,10 @@ export type SpecOptions = {
|
|
|
46
50
|
fallback: string;
|
|
47
51
|
};
|
|
48
52
|
export type ConfigurationOptions = {
|
|
49
|
-
superstruct: typeof import("superstruct");
|
|
50
53
|
readTextFile: (url: URL) => Promise<string | null>;
|
|
51
54
|
getEnvironmentVariable: (key: string) => (string | undefined);
|
|
52
55
|
getCommandArgument: (key: string) => (string | undefined);
|
|
53
56
|
stringify: (value: any) => (string | Promise<string>);
|
|
54
57
|
parse: (value: string) => (any);
|
|
55
58
|
};
|
|
56
|
-
|
|
57
|
-
//# sourceMappingURL=configuration.d.ts.map
|
|
59
|
+
import { Structure } from "./structures.js";
|
package/core/configuration.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { formatMarkdownTable } from "./utilities.js";
|
|
2
|
+
import { Structure, StructError } from "./structures.js";
|
|
3
|
+
|
|
4
|
+
// NOTE: it would be nice to reverse the object/string/url methods around so they return the "spec" value, then the "struct" is stored under a string. This could mean the underlying architecture could change in the future. I'm not sure if that is possible with the structure nesting in play.
|
|
5
|
+
|
|
6
|
+
// NOTE: the schema generation will include whatever value is passed to the structure, in the context of configuration it will be whatever is configured and may be something secret
|
|
2
7
|
|
|
3
8
|
/**
|
|
4
9
|
* @typedef {object} SpecOptions
|
|
@@ -9,7 +14,6 @@ import { formatMarkdownTable } from "./utilities.js";
|
|
|
9
14
|
|
|
10
15
|
/**
|
|
11
16
|
* @typedef {object} ConfigurationOptions
|
|
12
|
-
* @property {import("superstruct")} superstruct
|
|
13
17
|
* @property {(url: URL) => Promise<string | null>} readTextFile
|
|
14
18
|
* @property {(key: string) => (string | undefined)} getEnvironmentVariable
|
|
15
19
|
* @property {(key: string) => (string | undefined)} getCommandArgument
|
|
@@ -18,7 +22,6 @@ import { formatMarkdownTable } from "./utilities.js";
|
|
|
18
22
|
*/
|
|
19
23
|
|
|
20
24
|
const _requiredOptions = [
|
|
21
|
-
"superstruct",
|
|
22
25
|
"readTextFile",
|
|
23
26
|
"getEnvironmentVariable",
|
|
24
27
|
"getCommandArgument",
|
|
@@ -26,12 +29,10 @@ const _requiredOptions = [
|
|
|
26
29
|
"parse",
|
|
27
30
|
];
|
|
28
31
|
|
|
29
|
-
/** @typedef {Record<string, import("superstruct").Struct<any, any>>} ObjectSchema */
|
|
30
|
-
|
|
31
32
|
export class Configuration {
|
|
32
33
|
static spec = Symbol("Configuration.spec");
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
/** @type {ConfigurationOptions} */ options;
|
|
35
36
|
|
|
36
37
|
/** @param {ConfigurationOptions} options */
|
|
37
38
|
constructor(options) {
|
|
@@ -42,52 +43,40 @@ export class Configuration {
|
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
/**
|
|
45
|
-
* @template {
|
|
46
|
+
* @template {Record<string, Structure<any>>} T
|
|
46
47
|
* @param {T} spec
|
|
48
|
+
* @returns {Structure<{ [K in keyof T]: import("./structures.js").Infer<T[K]> }>}
|
|
47
49
|
*/
|
|
48
50
|
object(spec) {
|
|
49
|
-
const struct =
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
)
|
|
53
|
-
struct[Configuration.spec]= { type: "object", value: spec }
|
|
54
|
-
return struct
|
|
51
|
+
const struct = Structure.object(spec);
|
|
52
|
+
struct[Configuration.spec] = { type: "object", value: spec };
|
|
53
|
+
return struct;
|
|
55
54
|
}
|
|
56
55
|
|
|
57
56
|
/**
|
|
58
57
|
* @template {SpecOptions} Spec @param {Spec} spec
|
|
59
|
-
* @returns {
|
|
58
|
+
* @returns {Structure<string>}
|
|
60
59
|
*/
|
|
61
60
|
string(spec = {}) {
|
|
62
61
|
if (typeof spec.fallback !== "string") {
|
|
63
62
|
throw new TypeError("spec.fallback must be a string: " + spec.fallback);
|
|
64
63
|
}
|
|
65
|
-
const struct = this.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
)
|
|
69
|
-
struct[Configuration.spec] = { type: "string", value: spec }
|
|
70
|
-
return struct
|
|
64
|
+
const struct = Structure.string(this._getValue(spec));
|
|
65
|
+
struct[Configuration.spec] = { type: "string", value: spec };
|
|
66
|
+
return struct;
|
|
71
67
|
}
|
|
72
68
|
|
|
73
69
|
/**
|
|
74
70
|
* @template {SpecOptions} Spec @param {Spec} spec
|
|
75
|
-
* @returns {
|
|
71
|
+
* @returns {Structure<URL>}
|
|
76
72
|
*/
|
|
77
73
|
url(spec) {
|
|
78
74
|
if (typeof spec.fallback !== "string") {
|
|
79
75
|
throw new TypeError("spec.fallback must be a string");
|
|
80
76
|
}
|
|
81
|
-
const struct = this.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
this.options.superstruct.string(),
|
|
85
|
-
(value) => new URL(value),
|
|
86
|
-
),
|
|
87
|
-
this._getValue(spec),
|
|
88
|
-
)
|
|
89
|
-
struct[Configuration.spec]= { type: "url", value: spec }
|
|
90
|
-
return struct
|
|
77
|
+
const struct = Structure.url(this._getValue(spec));
|
|
78
|
+
struct[Configuration.spec] = { type: "url", value: spec };
|
|
79
|
+
return struct;
|
|
91
80
|
}
|
|
92
81
|
|
|
93
82
|
/** @param {SpecOptions} spec */
|
|
@@ -106,7 +95,7 @@ export class Configuration {
|
|
|
106
95
|
/**
|
|
107
96
|
* @template T
|
|
108
97
|
* @param {URL} url
|
|
109
|
-
* @param {
|
|
98
|
+
* @param {Structure<T>} spec
|
|
110
99
|
* @returns {Promise<T>}
|
|
111
100
|
*/
|
|
112
101
|
async load(url, spec) {
|
|
@@ -114,15 +103,19 @@ export class Configuration {
|
|
|
114
103
|
|
|
115
104
|
// Catch missing files and create a default configuration
|
|
116
105
|
if (!file) {
|
|
117
|
-
return
|
|
106
|
+
return spec.process({});
|
|
118
107
|
}
|
|
119
108
|
|
|
120
109
|
// Fail outside the try-catch to surface structure errors
|
|
121
|
-
|
|
122
|
-
await this.options.parse(file)
|
|
123
|
-
|
|
124
|
-
"Configuration failed to parse"
|
|
125
|
-
|
|
110
|
+
try {
|
|
111
|
+
return spec.process(await this.options.parse(file));
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error("Configuration failed to parse");
|
|
114
|
+
if (error instanceof StructError) {
|
|
115
|
+
error.message = error.toFriendlyString();
|
|
116
|
+
}
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
126
119
|
}
|
|
127
120
|
|
|
128
121
|
/** @template T @param {T} config */
|
|
@@ -184,4 +177,9 @@ export class Configuration {
|
|
|
184
177
|
}
|
|
185
178
|
throw new TypeError("Invalid [Configuration.spec].type '" + type + "'");
|
|
186
179
|
}
|
|
180
|
+
|
|
181
|
+
/** @param {Structure<any>} spec */
|
|
182
|
+
getJSONSchema(spec) {
|
|
183
|
+
return spec.getSchema();
|
|
184
|
+
}
|
|
187
185
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { Configuration } from "./configuration.js";
|
|
2
|
-
import { assertEquals, assertThrows } from "./test-deps.js";
|
|
3
|
-
import { superstruct } from "./test-deps.js";
|
|
2
|
+
import { assertEquals, assertThrows, describe, it } from "./test-deps.js";
|
|
4
3
|
|
|
5
4
|
/** @type {import("./configuration.js").ConfigurationOptions} */
|
|
6
5
|
const bareOptions = {
|
|
7
|
-
superstruct,
|
|
8
6
|
readTextFile() {},
|
|
9
7
|
getEnvironmentVariable(_key) {},
|
|
10
8
|
getCommandArgument(_key) {},
|
|
@@ -12,11 +10,10 @@ const bareOptions = {
|
|
|
12
10
|
parse(_value) {},
|
|
13
11
|
};
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
describe("Configuration", () => {
|
|
14
|
+
describe("constructor", () => {
|
|
15
|
+
it("validates options", () => {
|
|
18
16
|
new Configuration({
|
|
19
|
-
superstruct,
|
|
20
17
|
readTextFile() {},
|
|
21
18
|
getEnvironmentVariable(_key) {},
|
|
22
19
|
getCommandArgument(_key) {},
|
|
@@ -26,30 +23,28 @@ Deno.test("Configuration", async ({ step }) => {
|
|
|
26
23
|
});
|
|
27
24
|
});
|
|
28
25
|
|
|
29
|
-
|
|
26
|
+
describe("object", () => {
|
|
30
27
|
const config = new Configuration(bareOptions);
|
|
31
28
|
|
|
32
|
-
|
|
29
|
+
it("stores the spec", () => {
|
|
33
30
|
const result = config.object({});
|
|
34
31
|
assertEquals(result[Configuration.spec].type, "object");
|
|
35
32
|
assertEquals(result[Configuration.spec].value, {});
|
|
36
33
|
});
|
|
37
34
|
});
|
|
38
35
|
|
|
39
|
-
|
|
36
|
+
describe("string", () => {
|
|
40
37
|
const config = new Configuration(bareOptions);
|
|
41
38
|
|
|
42
|
-
|
|
39
|
+
it("requires a fallback", () => {
|
|
43
40
|
assertThrows(() => config.string({}), TypeError);
|
|
44
41
|
});
|
|
45
|
-
|
|
46
|
-
await step("uses the fallback", () => {
|
|
42
|
+
it("uses the fallback", () => {
|
|
47
43
|
const struct = config.string({ fallback: "Geoff Testington" });
|
|
48
|
-
const result =
|
|
44
|
+
const result = struct.process(undefined);
|
|
49
45
|
assertEquals(result, "Geoff Testington");
|
|
50
46
|
});
|
|
51
|
-
|
|
52
|
-
await step("stores spec", () => {
|
|
47
|
+
it("stores the spec", () => {
|
|
53
48
|
const result = config.string({
|
|
54
49
|
variable: "SOME_VAR",
|
|
55
50
|
fallback: "value",
|
|
@@ -62,26 +57,23 @@ Deno.test("Configuration", async ({ step }) => {
|
|
|
62
57
|
});
|
|
63
58
|
});
|
|
64
59
|
|
|
65
|
-
|
|
60
|
+
describe("url", () => {
|
|
66
61
|
const config = new Configuration(bareOptions);
|
|
67
62
|
|
|
68
|
-
|
|
63
|
+
it("requires a fallback", () => {
|
|
69
64
|
assertThrows(() => config.url({}), TypeError);
|
|
70
65
|
});
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
const result = superstruct.create("https://example.com", struct);
|
|
66
|
+
it("converts to URL", () => {
|
|
67
|
+
const struct = config.url({ fallback: "https://fallback.example.com" });
|
|
68
|
+
const result = struct.process("https://example.com");
|
|
75
69
|
assertEquals(result, new URL("https://example.com"));
|
|
76
70
|
});
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
assertEquals(result, new URL("https://example.com"));
|
|
71
|
+
it("uses the fallback", () => {
|
|
72
|
+
const struct = config.url({ fallback: "https://fallback.example.com" });
|
|
73
|
+
const result = struct.process(undefined);
|
|
74
|
+
assertEquals(result, new URL("https://fallback.example.com"));
|
|
82
75
|
});
|
|
83
|
-
|
|
84
|
-
await step("stores spec", () => {
|
|
76
|
+
it("stores the spec", () => {
|
|
85
77
|
const result = config.url({
|
|
86
78
|
variable: "SOME_VAR",
|
|
87
79
|
fallback: "https://example.com",
|
|
@@ -94,8 +86,8 @@ Deno.test("Configuration", async ({ step }) => {
|
|
|
94
86
|
});
|
|
95
87
|
});
|
|
96
88
|
|
|
97
|
-
|
|
98
|
-
|
|
89
|
+
describe("_getValue", () => {
|
|
90
|
+
it("uses arguments", () => {
|
|
99
91
|
const args = { "--option": "value-from-arg" };
|
|
100
92
|
const config = new Configuration({
|
|
101
93
|
...bareOptions,
|
|
@@ -106,8 +98,7 @@ Deno.test("Configuration", async ({ step }) => {
|
|
|
106
98
|
});
|
|
107
99
|
assertEquals(result, "value-from-arg");
|
|
108
100
|
});
|
|
109
|
-
|
|
110
|
-
await step("uses environment variables", () => {
|
|
101
|
+
it("uses environment variables", () => {
|
|
111
102
|
const env = { MY_VAR: "value-from-env" };
|
|
112
103
|
const config = new Configuration({
|
|
113
104
|
...bareOptions,
|
|
@@ -118,15 +109,14 @@ Deno.test("Configuration", async ({ step }) => {
|
|
|
118
109
|
});
|
|
119
110
|
assertEquals(result, "value-from-env");
|
|
120
111
|
});
|
|
121
|
-
|
|
122
|
-
await step("uses the fallback", () => {
|
|
112
|
+
it("uses the fallback", () => {
|
|
123
113
|
const config = new Configuration(bareOptions);
|
|
124
114
|
const result = config._getValue({ fallback: "value-from-fallback" });
|
|
125
115
|
assertEquals(result, "value-from-fallback");
|
|
126
116
|
});
|
|
127
117
|
});
|
|
128
118
|
|
|
129
|
-
|
|
119
|
+
describe("load", () => {
|
|
130
120
|
const files = {
|
|
131
121
|
"config.json":
|
|
132
122
|
'{"env":"production","meta":{"version":"1.2.3"},"selfUrl":"https://example.com"}',
|
|
@@ -146,7 +136,7 @@ Deno.test("Configuration", async ({ step }) => {
|
|
|
146
136
|
selfUrl: config.url({ fallback: "http://localhost" }),
|
|
147
137
|
});
|
|
148
138
|
|
|
149
|
-
|
|
139
|
+
it("loads config", async () => {
|
|
150
140
|
const result = await config.load("config.json", spec);
|
|
151
141
|
assertEquals(result, {
|
|
152
142
|
env: "production",
|
|
@@ -155,7 +145,7 @@ Deno.test("Configuration", async ({ step }) => {
|
|
|
155
145
|
});
|
|
156
146
|
});
|
|
157
147
|
|
|
158
|
-
|
|
148
|
+
it("uses the fallback", async () => {
|
|
159
149
|
const result = await config.load("missing-config.json", spec);
|
|
160
150
|
assertEquals(result, {
|
|
161
151
|
env: "development",
|
|
@@ -165,8 +155,8 @@ Deno.test("Configuration", async ({ step }) => {
|
|
|
165
155
|
});
|
|
166
156
|
});
|
|
167
157
|
|
|
168
|
-
|
|
169
|
-
|
|
158
|
+
describe("describeSpecification", () => {
|
|
159
|
+
it("processes strings", () => {
|
|
170
160
|
const config = new Configuration(bareOptions);
|
|
171
161
|
const result = config.describeSpecification(
|
|
172
162
|
config.string({
|
|
@@ -188,7 +178,7 @@ Deno.test("Configuration", async ({ step }) => {
|
|
|
188
178
|
]);
|
|
189
179
|
});
|
|
190
180
|
|
|
191
|
-
|
|
181
|
+
it("processes urls", () => {
|
|
192
182
|
const config = new Configuration(bareOptions);
|
|
193
183
|
const result = config.describeSpecification(
|
|
194
184
|
config.url({
|
|
@@ -210,7 +200,7 @@ Deno.test("Configuration", async ({ step }) => {
|
|
|
210
200
|
]);
|
|
211
201
|
});
|
|
212
202
|
|
|
213
|
-
|
|
203
|
+
it("processes objects", () => {
|
|
214
204
|
const config = new Configuration(bareOptions);
|
|
215
205
|
const result = config.describeSpecification(
|
|
216
206
|
config.object({
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -52,4 +52,3 @@ export type RouteContext<T> = import("./types.ts").RouteContext<T>;
|
|
|
52
52
|
export type RouteHandler<T> = import("./types.ts").RouteHandler<T>;
|
|
53
53
|
export type RouteOptions<T extends string> = import("./types.ts").RouteOptions<T>;
|
|
54
54
|
export type RouteDefinition<T = any> = import("./types.ts").RouteDefinition<T>;
|
|
55
|
-
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|