gruber 0.2.0 → 0.4.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.
Files changed (73) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +156 -16
  3. package/core/configuration.d.ts +157 -0
  4. package/core/configuration.js +222 -96
  5. package/core/configuration.test.d.ts +1 -0
  6. package/core/configuration.test.js +242 -53
  7. package/{types/core → core}/fetch-router.d.ts +0 -1
  8. package/core/fetch-router.test.d.ts +1 -0
  9. package/{types/core → core}/http.d.ts +30 -12
  10. package/core/http.js +42 -17
  11. package/core/http.test.d.ts +1 -0
  12. package/core/http.test.js +57 -35
  13. package/{types/core → core}/migrator.d.ts +0 -1
  14. package/core/migrator.test.d.ts +1 -0
  15. package/{types/core → core}/mod.d.ts +1 -1
  16. package/core/mod.js +1 -0
  17. package/{types/core → core}/postgres.d.ts +0 -1
  18. package/core/structures.d.ts +91 -0
  19. package/core/structures.js +260 -0
  20. package/core/structures.test.d.ts +1 -0
  21. package/core/structures.test.js +474 -0
  22. package/core/test-deps.d.ts +1 -0
  23. package/core/test-deps.js +1 -1
  24. package/{types/core → core}/types.d.ts +0 -1
  25. package/{types/core → core}/utilities.d.ts +0 -1
  26. package/core/utilities.test.d.ts +1 -0
  27. package/package.json +4 -5
  28. package/{types/source → source}/configuration.d.ts +4 -9
  29. package/source/configuration.js +0 -2
  30. package/source/core.d.ts +1 -0
  31. package/{types/source → source}/express-router.d.ts +2 -3
  32. package/source/express-router.js +1 -1
  33. package/{types/source → source}/koa-router.d.ts +0 -1
  34. package/{types/source → source}/mod.d.ts +0 -3
  35. package/source/mod.js +2 -2
  36. package/{types/source → source}/node-router.d.ts +8 -8
  37. package/source/node-router.js +1 -1
  38. package/source/package-lock.json +3 -9
  39. package/source/package.json +0 -2
  40. package/source/polyfill.d.ts +1 -0
  41. package/{types/source → source}/postgres.d.ts +0 -1
  42. package/tsconfig.json +0 -2
  43. package/types/core/configuration.d.ts +0 -57
  44. package/types/core/configuration.d.ts.map +0 -1
  45. package/types/core/configuration.test.d.ts +0 -2
  46. package/types/core/configuration.test.d.ts.map +0 -1
  47. package/types/core/fetch-router.d.ts.map +0 -1
  48. package/types/core/fetch-router.test.d.ts +0 -2
  49. package/types/core/fetch-router.test.d.ts.map +0 -1
  50. package/types/core/http.d.ts.map +0 -1
  51. package/types/core/http.test.d.ts +0 -2
  52. package/types/core/http.test.d.ts.map +0 -1
  53. package/types/core/migrator.d.ts.map +0 -1
  54. package/types/core/migrator.test.d.ts +0 -2
  55. package/types/core/migrator.test.d.ts.map +0 -1
  56. package/types/core/mod.d.ts.map +0 -1
  57. package/types/core/postgres.d.ts.map +0 -1
  58. package/types/core/test-deps.d.ts +0 -2
  59. package/types/core/test-deps.d.ts.map +0 -1
  60. package/types/core/types.d.ts.map +0 -1
  61. package/types/core/utilities.d.ts.map +0 -1
  62. package/types/core/utilities.test.d.ts +0 -2
  63. package/types/core/utilities.test.d.ts.map +0 -1
  64. package/types/source/configuration.d.ts.map +0 -1
  65. package/types/source/core.d.ts +0 -2
  66. package/types/source/core.d.ts.map +0 -1
  67. package/types/source/express-router.d.ts.map +0 -1
  68. package/types/source/koa-router.d.ts.map +0 -1
  69. package/types/source/mod.d.ts.map +0 -1
  70. package/types/source/node-router.d.ts.map +0 -1
  71. package/types/source/polyfill.d.ts +0 -2
  72. package/types/source/polyfill.d.ts.map +0 -1
  73. package/types/source/postgres.d.ts.map +0 -1
package/core/http.test.js CHANGED
@@ -1,8 +1,8 @@
1
- import { assertInstanceOf, assertEquals } from "./test-deps.js";
1
+ import { assertInstanceOf, assertEquals, describe, it } from "./test-deps.js";
2
2
  import { defineRoute, HTTPError } from "./http.js";
3
3
 
4
- Deno.test("defineRoute", async (t) => {
5
- await t.step("sets the method", () => {
4
+ describe("defineRoute", () => {
5
+ it("sets the method", () => {
6
6
  const result = defineRoute({
7
7
  method: "GET",
8
8
  pathname: "/",
@@ -11,7 +11,7 @@ Deno.test("defineRoute", async (t) => {
11
11
  assertEquals(result.method, "GET");
12
12
  });
13
13
 
14
- await t.step("creates a URLPattern", () => {
14
+ it("creates a URLPattern", () => {
15
15
  const result = defineRoute({
16
16
  method: "GET",
17
17
  pathname: "/hello/:name",
@@ -22,47 +22,69 @@ Deno.test("defineRoute", async (t) => {
22
22
  });
23
23
  });
24
24
 
25
- Deno.test("HTTPError", async (t) => {
26
- await t.step("constructor", () => {
27
- const result = new HTTPError(418, "I'm a teapot");
28
- assertEquals(result.status, 418);
29
- assertEquals(result.statusText, "I'm a teapot");
30
- assertEquals(result.name, "HTTPError");
25
+ describe("HTTPError", () => {
26
+ describe("constructor", () => {
27
+ it("creates the error", () => {
28
+ const result = new HTTPError(418, "I'm a teapot");
29
+ assertEquals(result.status, 418);
30
+ assertEquals(result.statusText, "I'm a teapot");
31
+ assertEquals(result.name, "HTTPError");
32
+ });
31
33
  });
32
34
 
33
- await t.step("toResponse", () => {
34
- const result = new HTTPError(200, "OK").toResponse();
35
- assertEquals(result.status, 200);
36
- assertEquals(result.statusText, "OK");
37
- });
35
+ describe("toResponse", () => {
36
+ it("creates the error", () => {
37
+ const result = new HTTPError(200, "OK").toResponse();
38
+ assertEquals(result.status, 200);
39
+ assertEquals(result.statusText, "OK");
40
+ });
41
+ it("sets headers", () => {
42
+ const error = new HTTPError(200, "OK", null, {
43
+ "X-HOTEL-BAR": "Hotel Bar?",
44
+ });
38
45
 
39
- await t.step("badRequest", () => {
40
- const result = HTTPError.badRequest("body");
41
- assertEquals(result.status, 400);
42
- assertEquals(result.statusText, "Bad Request");
46
+ const result = error.toResponse();
47
+ assertEquals(result.headers.get("X-HOTEL-BAR"), "Hotel Bar?");
48
+ });
43
49
  });
44
50
 
45
- await t.step("unauthorized", () => {
46
- const result = HTTPError.unauthorized();
47
- assertEquals(result.status, 401);
48
- assertEquals(result.statusText, "Unauthorized");
51
+ describe("badRequest", () => {
52
+ it("creates the error", () => {
53
+ const result = HTTPError.badRequest("body");
54
+ assertEquals(result.status, 400);
55
+ assertEquals(result.statusText, "Bad Request");
56
+ });
49
57
  });
50
58
 
51
- await t.step("notFound", () => {
52
- const result = HTTPError.notFound();
53
- assertEquals(result.status, 404);
54
- assertEquals(result.statusText, "Not Found");
59
+ describe("unauthorized", () => {
60
+ it("creates the error", () => {
61
+ const result = HTTPError.unauthorized();
62
+ assertEquals(result.status, 401);
63
+ assertEquals(result.statusText, "Unauthorized");
64
+ });
55
65
  });
56
66
 
57
- await t.step("internalServerError", () => {
58
- const result = HTTPError.internalServerError();
59
- assertEquals(result.status, 500);
60
- assertEquals(result.statusText, "Internal Server Error");
67
+ describe("notFound", () => {
68
+ it("creates the error", () => {
69
+ const result = HTTPError.notFound();
70
+ assertEquals(result.status, 404);
71
+ assertEquals(result.statusText, "Not Found");
72
+ });
61
73
  });
62
74
 
63
- await t.step("notImplemented", () => {
64
- const result = HTTPError.notImplemented();
65
- assertEquals(result.status, 501);
66
- assertEquals(result.statusText, "Not Implemented");
75
+ describe("internalServerError", () => {
76
+ it("creates the error", () => {
77
+ const result = HTTPError.internalServerError();
78
+ assertEquals(result.status, 500);
79
+ assertEquals(result.statusText, "Internal Server Error");
80
+ });
81
+
82
+ describe("notImplemented", () => {
83
+ it("creates the error", () => {
84
+ const result = HTTPError.notImplemented();
85
+ assertEquals(result.status, 501);
86
+ assertEquals(result.statusText, "Not Implemented");
87
+ });
88
+ });
67
89
  });
68
90
  });
@@ -53,4 +53,3 @@ export type MigratorOptions<T> = {
53
53
  getRecords: () => Promise<MigrationRecord[]>;
54
54
  execute: (def: MigrationDefinition<T>, direction: "up" | "down") => void | Promise<void>;
55
55
  };
56
- //# sourceMappingURL=migrator.d.ts.map
@@ -0,0 +1 @@
1
+ export {};
@@ -3,5 +3,5 @@ export * from "./fetch-router.js";
3
3
  export * from "./http.js";
4
4
  export * from "./migrator.js";
5
5
  export * from "./postgres.js";
6
+ export * from "./structures.js";
6
7
  export * from "./utilities.js";
7
- //# sourceMappingURL=mod.d.ts.map
package/core/mod.js CHANGED
@@ -3,4 +3,5 @@ export * from "./fetch-router.js";
3
3
  export * from "./http.js";
4
4
  export * from "./migrator.js";
5
5
  export * from "./postgres.js";
6
+ export * from "./structures.js";
6
7
  export * from "./utilities.js";
@@ -40,4 +40,3 @@ export type MigrationDefinition = import("./migrator.js").MigrationDefinition;
40
40
  export type PostgresMigratorOptions = {
41
41
  sql: Sql;
42
42
  };
43
- //# sourceMappingURL=postgres.d.ts.map
@@ -0,0 +1,91 @@
1
+ export class StructError extends Error {
2
+ /**
3
+ * @param {unknown} error
4
+ * @param {StructContext} context
5
+ * @returns {StructError}
6
+ */
7
+ static chain(error: unknown, context?: StructContext): StructError;
8
+ /**
9
+ * @param {string} message
10
+ * @param {string[]} path
11
+ * @param {StructError[]} children
12
+ */
13
+ constructor(message: string, path?: string[], children?: StructError[]);
14
+ path: string[];
15
+ children: StructError[];
16
+ getOneLiner(): string;
17
+ toFriendlyString(): string;
18
+ /** @returns {Iterator<StructError>} */
19
+ [Symbol.iterator](): Iterator<StructError>;
20
+ }
21
+ /**
22
+ * @typedef {Record<string,unknown>} Schema
23
+ */
24
+ /**
25
+ * @template T
26
+ * @typedef {(input?: unknown, context?: StructContext) => T} StructExec
27
+ */
28
+ /**
29
+ * @template T
30
+ * @typedef {T extends Structure<infer U> ? U : never} Infer
31
+ */
32
+ /**
33
+ * @template T
34
+ */
35
+ export class Structure<T> {
36
+ /**
37
+ * @param {string} [fallback]
38
+ * @returns {Structure<string>}
39
+ */
40
+ static string(fallback?: string): Structure<string>;
41
+ /**
42
+ * @param {number} [fallback]
43
+ * @returns {Structure<number>}
44
+ */
45
+ static number(fallback?: number): Structure<number>;
46
+ /**
47
+ * @param {boolean} fallback
48
+ * @returns {Structure<boolean>}
49
+ */
50
+ static boolean(fallback: boolean): Structure<boolean>;
51
+ /**
52
+ * @param {string | URL} [fallback]
53
+ * @returns {Structure<URL>}
54
+ */
55
+ static url(fallback?: string | URL): Structure<URL>;
56
+ /**
57
+ * @template {Record<string, Structure<any>>} U
58
+ * @param {U} fields
59
+ * @returns {Structure<{ [K in keyof U]: Infer<U[K]> }>}
60
+ */
61
+ static object<U extends Record<string, Structure<any>>>(fields: U): Structure<{ [K in keyof U]: Infer<U[K]>; }>;
62
+ /**
63
+ * **UNSTABLE** use at your own risk
64
+ *
65
+ * @template {Structure<any>} U
66
+ * @param {U} struct
67
+ * @returns {Structure<Array<Infer<U>>>}
68
+ */
69
+ static array<U_1 extends Structure<any>>(struct: U_1): Structure<Infer<U_1>[]>;
70
+ /**
71
+ * @param {Schema} schema
72
+ * @param {StructExec<T>} process
73
+ */
74
+ constructor(schema: Schema, process: StructExec<T>);
75
+ schema: Schema;
76
+ process: StructExec<T>;
77
+ getSchema(): {
78
+ $schema: string;
79
+ };
80
+ }
81
+ export type StructContext = {
82
+ path: string[];
83
+ };
84
+ export type StructOptions<Type, Schema> = {
85
+ type: 'object' | 'string' | 'url';
86
+ schema: Schema;
87
+ fn: (value: unknown, ctx: StructContext) => Type;
88
+ };
89
+ export type Schema = Record<string, unknown>;
90
+ export type StructExec<T> = (input?: unknown, context?: StructContext) => T;
91
+ export type Infer<T> = T extends Structure<infer U> ? U : never;
@@ -0,0 +1,260 @@
1
+ /** @typedef {{ path: string[] }} StructContext */
2
+
3
+ /**
4
+ * @template Type
5
+ * @template Schema
6
+ * @typedef {object} StructOptions
7
+ * @property {'object' | 'string' | 'url'} type
8
+ * @property {Schema} schema
9
+ * @property {(value: unknown, ctx: StructContext) => Type} fn
10
+ */
11
+
12
+ const DEFAULT_CONTEXT = { path: [] };
13
+
14
+ export class StructError extends Error {
15
+ /**
16
+ * @param {string} message
17
+ * @param {string[]} path
18
+ * @param {StructError[]} children
19
+ */
20
+ constructor(message, path = [], children = []) {
21
+ super(message);
22
+ this.path = path;
23
+ this.children = children;
24
+ this.name = "StructError";
25
+ Error.captureStackTrace(this, StructError);
26
+ }
27
+
28
+ /**
29
+ * @param {unknown} error
30
+ * @param {StructContext} context
31
+ * @returns {StructError}
32
+ */
33
+ static chain(error, context = DEFAULT_CONTEXT) {
34
+ let chained;
35
+ if (error instanceof StructError) {
36
+ return error;
37
+ } else if (error instanceof Error) {
38
+ chained = new StructError(error.message, context.path);
39
+ } else {
40
+ chained = new StructError("Unknown error", context.path);
41
+ }
42
+ Error.captureStackTrace(chained, StructError.chain);
43
+ return chained;
44
+ }
45
+
46
+ getOneLiner() {
47
+ return (this.path.join(".") || ".") + " — " + this.message;
48
+ }
49
+
50
+ /** @returns {Iterator<StructError>} */
51
+ *[Symbol.iterator]() {
52
+ if (this.children.length === 0) {
53
+ yield this;
54
+ } else {
55
+ for (const child of this.children) {
56
+ yield* child;
57
+ }
58
+ }
59
+ }
60
+
61
+ toFriendlyString() {
62
+ const messages = [];
63
+ for (const child of this) {
64
+ messages.push(" " + child.getOneLiner());
65
+ }
66
+
67
+ return [
68
+ this.message,
69
+ ...messages.toSorted((a, b) => a.localeCompare(b)),
70
+ ].join("\n");
71
+ }
72
+ }
73
+
74
+ /**
75
+ * @typedef {Record<string,unknown>} Schema
76
+ */
77
+
78
+ /**
79
+ * @template T
80
+ * @typedef {(input?: unknown, context?: StructContext) => T} StructExec
81
+ */
82
+
83
+ /**
84
+ * @template T
85
+ * @typedef {T extends Structure<infer U> ? U : never} Infer
86
+ */
87
+
88
+ /**
89
+ * @template T
90
+ */
91
+ export class Structure {
92
+ /**
93
+ * @param {Schema} schema
94
+ * @param {StructExec<T>} process
95
+ */
96
+ constructor(schema, process) {
97
+ this.schema = schema;
98
+ this.process = process;
99
+ }
100
+
101
+ getSchema() {
102
+ return {
103
+ $schema: "https://json-schema.org/draft/2020-12/schema",
104
+ ...this.schema,
105
+ };
106
+ }
107
+
108
+ /**
109
+ * @param {string} [fallback]
110
+ * @returns {Structure<string>}
111
+ */
112
+ static string(fallback = undefined) {
113
+ const schema = { type: "string" };
114
+ if (fallback !== undefined) schema.default = fallback;
115
+
116
+ return new Structure(schema, (input = fallback, context = undefined) => {
117
+ if (input === undefined) {
118
+ throw new StructError("Missing value", context?.path);
119
+ }
120
+ if (typeof input !== "string") {
121
+ throw new StructError("Expected a string", context?.path);
122
+ }
123
+ return input;
124
+ });
125
+ }
126
+
127
+ /**
128
+ * @param {number} [fallback]
129
+ * @returns {Structure<number>}
130
+ */
131
+ static number(fallback) {
132
+ const schema = { type: "number" };
133
+ if (fallback !== undefined) schema.default = fallback;
134
+
135
+ return new Structure(schema, (input = fallback, context = undefined) => {
136
+ if (input === undefined) {
137
+ throw new StructError("Missing value", context?.path);
138
+ }
139
+ // if (typeof input === "string") {
140
+ // const parsed = Number.parseFloat(input);
141
+ // if (!Number.isNaN(parsed)) return parsed;
142
+ // }
143
+ if (typeof input !== "number") {
144
+ throw new StructError("Expected a number", context?.path);
145
+ }
146
+ return input;
147
+ });
148
+ }
149
+
150
+ /**
151
+ * @param {boolean} fallback
152
+ * @returns {Structure<boolean>}
153
+ */
154
+ static boolean(fallback) {
155
+ const schema = { type: "boolean" };
156
+ if (fallback !== undefined) schema.default = fallback;
157
+
158
+ return new Structure(schema, (input, context) => {
159
+ if (input === undefined) return fallback;
160
+ if (typeof input !== "boolean") {
161
+ throw new StructError("Not a boolean", context?.path);
162
+ }
163
+ return input;
164
+ });
165
+ }
166
+
167
+ /**
168
+ * @param {string | URL} [fallback]
169
+ * @returns {Structure<URL>}
170
+ */
171
+ static url(fallback) {
172
+ const schema = { type: "string", format: "uri" };
173
+ if (fallback !== undefined) schema.default = fallback.toString();
174
+
175
+ // ~ make sure the fallback is valid ~
176
+ const url = fallback ? new URL(fallback) : undefined;
177
+ return new Structure(schema, (input = url, context = undefined) => {
178
+ if (input === undefined) {
179
+ throw new StructError("Missing value", context?.path);
180
+ }
181
+ if (input instanceof URL) return input;
182
+ if (typeof input !== "string") {
183
+ throw new StructError("Not a string or URL", context?.path);
184
+ }
185
+ return new URL(input);
186
+ });
187
+ }
188
+
189
+ /**
190
+ * @template {Record<string, Structure<any>>} U
191
+ * @param {U} fields
192
+ * @returns {Structure<{ [K in keyof U]: Infer<U[K]> }>}
193
+ */
194
+ static object(fields) {
195
+ const schema = {
196
+ type: "object",
197
+ properties: {},
198
+ default: {},
199
+ additionalProperties: false,
200
+ };
201
+ for (const [key, struct] of Object.entries(fields)) {
202
+ schema.properties[key] = struct.schema;
203
+ }
204
+ return new Structure(schema, (input = {}, context = undefined) => {
205
+ const path = context?.path ?? [];
206
+ if (input && typeof input !== "object") {
207
+ throw new StructError("Expected an object", path);
208
+ }
209
+ const output = {};
210
+ const errors = [];
211
+ for (const key in fields) {
212
+ const ctx = { path: [...path, key] };
213
+ try {
214
+ output[key] = fields[key].process(input[key], ctx);
215
+ } catch (error) {
216
+ errors.push(StructError.chain(error, ctx));
217
+ }
218
+ }
219
+ if (errors.length > 0) {
220
+ throw new StructError("Object does not match schema", path, errors);
221
+ }
222
+ return output;
223
+ });
224
+ }
225
+
226
+ /**
227
+ * **UNSTABLE** use at your own risk
228
+ *
229
+ * @template {Structure<any>} U
230
+ * @param {U} struct
231
+ * @returns {Structure<Array<Infer<U>>>}
232
+ */
233
+ static array(struct) {
234
+ const schema = {
235
+ type: "array",
236
+ items: struct.schema,
237
+ default: [],
238
+ };
239
+ return new Structure(schema, (input = [], context = undefined) => {
240
+ const path = context?.path ?? [];
241
+ if (!Array.isArray(input)) {
242
+ throw new StructError("Expected an array", path);
243
+ }
244
+ const output = [];
245
+ const errors = [];
246
+ for (let i = 0; i < input.length; i++) {
247
+ const ctx = { path: [...path, `${i}`] };
248
+ try {
249
+ output.push(struct.process(input[i], ctx));
250
+ } catch (error) {
251
+ errors.push(StructError.chain(error, ctx));
252
+ }
253
+ }
254
+ if (errors.length > 0) {
255
+ throw new StructError("Array item does not match schema", path, errors);
256
+ }
257
+ return output;
258
+ });
259
+ }
260
+ }
@@ -0,0 +1 @@
1
+ export {};