@ambarltd/core 0.1.17
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/README.md +10 -0
- package/dist/callable.d.ts +1 -0
- package/dist/callable.js +6 -0
- package/dist/future.d.ts +102 -0
- package/dist/future.js +164 -0
- package/dist/helpers/object.d.ts +9 -0
- package/dist/helpers/object.js +31 -0
- package/dist/json/decoder.d.ts +99 -0
- package/dist/json/decoder.js +213 -0
- package/dist/json/encoder.d.ts +50 -0
- package/dist/json/encoder.js +115 -0
- package/dist/json/schema.d.ts +78 -0
- package/dist/json/schema.js +168 -0
- package/dist/json/types.d.ts +6 -0
- package/dist/json/types.js +1 -0
- package/dist/list.d.ts +35 -0
- package/dist/list.js +130 -0
- package/dist/maybe.d.ts +104 -0
- package/dist/maybe.js +106 -0
- package/dist/remote-data.d.ts +117 -0
- package/dist/remote-data.js +148 -0
- package/dist/result.d.ts +75 -0
- package/dist/result.js +126 -0
- package/dist/router.d.ts +117 -0
- package/dist/router.js +106 -0
- package/dist/test.d.ts +63 -0
- package/dist/test.js +470 -0
- package/dist/time.d.ts +118 -0
- package/dist/time.js +387 -0
- package/dist/tracing/opentelemetry.d.ts +27 -0
- package/dist/tracing/opentelemetry.js +215 -0
- package/dist/tracing/proxy.d.ts +20 -0
- package/dist/tracing/proxy.js +103 -0
- package/dist/tracing/simple.d.ts +10 -0
- package/dist/tracing/simple.js +224 -0
- package/dist/tracing.d.ts +29 -0
- package/dist/tracing.js +55 -0
- package/dist/trampoline.d.ts +24 -0
- package/dist/trampoline.js +46 -0
- package/dist/tree-map.d.ts +73 -0
- package/dist/tree-map.js +169 -0
- package/dist/tree-set.d.ts +63 -0
- package/dist/tree-set.js +114 -0
- package/dist/types.d.ts +21 -0
- package/dist/types.js +1 -0
- package/package.json +49 -0
package/dist/router.d.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import * as express from "express";
|
|
2
|
+
import { Future } from "./future";
|
|
3
|
+
type Json = null | string | number | boolean | JsonArray | JsonObject;
|
|
4
|
+
type JsonObject = {
|
|
5
|
+
[x: string]: Json;
|
|
6
|
+
};
|
|
7
|
+
type JsonArray = Array<Json>;
|
|
8
|
+
/**
|
|
9
|
+
* A library for making express.js routes pure.
|
|
10
|
+
*
|
|
11
|
+
* It's a simple idea. A route handler is a function that takes a request
|
|
12
|
+
* and a request environment and returns a response.
|
|
13
|
+
*
|
|
14
|
+
* ```ts
|
|
15
|
+
* function myHandler(req: Request, env: T): Promise<Response>
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* As you can see, the environment can be any type specified by the handler.
|
|
19
|
+
* This will allow for type-safe middlewares as the compiler will ensure that
|
|
20
|
+
* we do not use handlers with invalid environments.
|
|
21
|
+
* Things like session information should live in the environment.
|
|
22
|
+
*
|
|
23
|
+
* Middlewares are functions that take a request and an environment and
|
|
24
|
+
* return a different environment.
|
|
25
|
+
*
|
|
26
|
+
* ```ts
|
|
27
|
+
* function myMiddleware(req: Request, env: T1): Promise<MiddlewareResponse<T2>>
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* This is how you 'modify' an environment. There is no real modification because
|
|
31
|
+
* everything should be immutable.
|
|
32
|
+
*
|
|
33
|
+
* A route is a request handler that returns a success response
|
|
34
|
+
* or a failure response.
|
|
35
|
+
*/
|
|
36
|
+
type Route<T> = (req: express.Request, env: T) => Future<Response, Response>;
|
|
37
|
+
/**
|
|
38
|
+
* We use express.js objects for the request, but we will not mutate them.
|
|
39
|
+
* Instead we will add things to the environment, which is passed alongside
|
|
40
|
+
* the request.
|
|
41
|
+
*/
|
|
42
|
+
type Request = express.Request;
|
|
43
|
+
type Headers = Record<string, string>;
|
|
44
|
+
declare class JSON {
|
|
45
|
+
values: {
|
|
46
|
+
status: number;
|
|
47
|
+
headers: Headers;
|
|
48
|
+
content: Json;
|
|
49
|
+
};
|
|
50
|
+
constructor(values: {
|
|
51
|
+
status: number;
|
|
52
|
+
headers: Headers;
|
|
53
|
+
content: Json;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
declare class Redirect {
|
|
57
|
+
values: {
|
|
58
|
+
path: string;
|
|
59
|
+
};
|
|
60
|
+
constructor(values: {
|
|
61
|
+
path: string;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
declare class Render {
|
|
65
|
+
values: {
|
|
66
|
+
status: number;
|
|
67
|
+
headers: Headers;
|
|
68
|
+
content: string | Json;
|
|
69
|
+
};
|
|
70
|
+
constructor(values: {
|
|
71
|
+
status: number;
|
|
72
|
+
headers: Headers;
|
|
73
|
+
content: string | Json;
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Server-Sent Events response for real-time streaming to clients.
|
|
78
|
+
* Keeps connection alive and allows server to push updates continuously.
|
|
79
|
+
*/
|
|
80
|
+
declare class SSE {
|
|
81
|
+
values: {
|
|
82
|
+
headers: Headers;
|
|
83
|
+
stream: (emit: SendMessage, onError: OnError) => Future<Error, null>;
|
|
84
|
+
};
|
|
85
|
+
constructor(values: {
|
|
86
|
+
headers: Headers;
|
|
87
|
+
stream: (emit: SendMessage, onError: OnError) => Future<Error, null>;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
type SendMessage = (json: Json) => boolean;
|
|
91
|
+
type OnError = (handler: (err: Error) => void) => void;
|
|
92
|
+
type Response = Render | JSON | Redirect | SSE;
|
|
93
|
+
declare const json: ({ status, headers, content, }: {
|
|
94
|
+
status?: number;
|
|
95
|
+
headers?: Headers;
|
|
96
|
+
content: Json;
|
|
97
|
+
}) => Response;
|
|
98
|
+
declare const redirect: (path: string) => Response;
|
|
99
|
+
declare const render: ({ status, headers, content, }: {
|
|
100
|
+
status?: number;
|
|
101
|
+
headers?: Headers;
|
|
102
|
+
content: string | Json;
|
|
103
|
+
}) => Response;
|
|
104
|
+
declare const sse: ({ headers, stream, }: {
|
|
105
|
+
headers?: Headers;
|
|
106
|
+
stream: (emit: SendMessage, onError: OnError) => Future<Error, null>;
|
|
107
|
+
}) => Response;
|
|
108
|
+
/** A middleware is something that transforms the environment. */
|
|
109
|
+
type Middleware<A, B> = (req: express.Request, env: A) => Future<Response, B>;
|
|
110
|
+
declare function middleware<A, B>(fun: Middleware<A, B>, route: Route<B>): Route<A>;
|
|
111
|
+
/**
|
|
112
|
+
* Create an express route handler from a Route.
|
|
113
|
+
* It expects the route environment to be null because the
|
|
114
|
+
* environment will ultimately be enriched through middlewares.
|
|
115
|
+
*/
|
|
116
|
+
declare function route(routeHandler: Route<{}>): express.Handler;
|
|
117
|
+
export { type Response, type Request, route, middleware, redirect, render, json, sse, type JSON, type SSE, type SendMessage, type OnError, };
|
package/dist/router.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
class JSON {
|
|
2
|
+
values;
|
|
3
|
+
constructor(values) {
|
|
4
|
+
this.values = values;
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
class Redirect {
|
|
8
|
+
values;
|
|
9
|
+
constructor(values) {
|
|
10
|
+
this.values = values;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
class Render {
|
|
14
|
+
values;
|
|
15
|
+
constructor(values) {
|
|
16
|
+
this.values = values;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Server-Sent Events response for real-time streaming to clients.
|
|
21
|
+
* Keeps connection alive and allows server to push updates continuously.
|
|
22
|
+
*/
|
|
23
|
+
class SSE {
|
|
24
|
+
values;
|
|
25
|
+
constructor(values) {
|
|
26
|
+
this.values = values;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Convenience constructors for responses.
|
|
30
|
+
const json = ({ status = 200, headers = {}, content, }) => new JSON({ status, headers, content });
|
|
31
|
+
const redirect = (path) => new Redirect({ path });
|
|
32
|
+
const render = ({ status = 200, headers = {}, content, }) => new Render({ status, headers, content });
|
|
33
|
+
const sse = ({ headers = {}, stream, }) => new SSE({ headers, stream });
|
|
34
|
+
function middleware(fun, route) {
|
|
35
|
+
return (req, env) => fun(req, env).chain(res => route(req, res));
|
|
36
|
+
}
|
|
37
|
+
function send(response, res) {
|
|
38
|
+
switch (true) {
|
|
39
|
+
case response instanceof JSON:
|
|
40
|
+
res.status(response.values.status);
|
|
41
|
+
res.set(response.values.headers);
|
|
42
|
+
res.json(response.values.content);
|
|
43
|
+
return;
|
|
44
|
+
case response instanceof Render:
|
|
45
|
+
res.status(response.values.status);
|
|
46
|
+
res.set(response.values.headers);
|
|
47
|
+
res.send(response.values.content);
|
|
48
|
+
return;
|
|
49
|
+
case response instanceof Redirect:
|
|
50
|
+
return res.redirect(response.values.path);
|
|
51
|
+
case response instanceof SSE: {
|
|
52
|
+
res.set({
|
|
53
|
+
...response.values.headers,
|
|
54
|
+
"Content-Type": "text/event-stream",
|
|
55
|
+
"Cache-Control": "no-cache",
|
|
56
|
+
Connection: "keep-alive",
|
|
57
|
+
});
|
|
58
|
+
res.flushHeaders();
|
|
59
|
+
streamSSEResponse(res, response.values.stream);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function streamSSEResponse(res, stream) {
|
|
65
|
+
let connectionClosed = false;
|
|
66
|
+
let endStream = () => { };
|
|
67
|
+
const closeConnection = () => {
|
|
68
|
+
if (connectionClosed)
|
|
69
|
+
return;
|
|
70
|
+
connectionClosed = true;
|
|
71
|
+
endStream();
|
|
72
|
+
res.end();
|
|
73
|
+
};
|
|
74
|
+
const errorHandlers = [];
|
|
75
|
+
const handleError = (err) => {
|
|
76
|
+
if (connectionClosed)
|
|
77
|
+
return;
|
|
78
|
+
errorHandlers.forEach(handler => handler(err));
|
|
79
|
+
closeConnection();
|
|
80
|
+
};
|
|
81
|
+
res.on("close", closeConnection);
|
|
82
|
+
res.on("error", handleError);
|
|
83
|
+
// start streaming
|
|
84
|
+
endStream = stream(function emit(payload) {
|
|
85
|
+
if (connectionClosed)
|
|
86
|
+
return false;
|
|
87
|
+
res.write(`data: ${globalThis.JSON.stringify(payload)}\n\n`);
|
|
88
|
+
return true;
|
|
89
|
+
}, function onError(handler) {
|
|
90
|
+
errorHandlers.push(handler);
|
|
91
|
+
}).fork(handleError, closeConnection);
|
|
92
|
+
}
|
|
93
|
+
function sendResponse(routeHandler, req, res) {
|
|
94
|
+
routeHandler(req, {}).fork(r => send(r, res), r => send(r, res));
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Create an express route handler from a Route.
|
|
98
|
+
* It expects the route environment to be null because the
|
|
99
|
+
* environment will ultimately be enriched through middlewares.
|
|
100
|
+
*/
|
|
101
|
+
function route(routeHandler) {
|
|
102
|
+
return (req, res) => sendResponse(routeHandler, req, res);
|
|
103
|
+
}
|
|
104
|
+
export { route, middleware,
|
|
105
|
+
// responses
|
|
106
|
+
redirect, render, json, sse, };
|
package/dist/test.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export { run, expect, group, test, parseArgs, type RunOptions };
|
|
2
|
+
declare class Test {
|
|
3
|
+
readonly name: string;
|
|
4
|
+
readonly fun: () => void | Promise<void>;
|
|
5
|
+
readonly id: string;
|
|
6
|
+
constructor(name: string, fun: () => void | Promise<void>);
|
|
7
|
+
}
|
|
8
|
+
declare class Group {
|
|
9
|
+
readonly name: string;
|
|
10
|
+
readonly entries: Array<Group | Test>;
|
|
11
|
+
constructor(name: string, entries: Array<Group | Test>);
|
|
12
|
+
}
|
|
13
|
+
declare const test: (x: string, y: () => void | Promise<void>) => Test;
|
|
14
|
+
declare const group: (x: string, y: Array<Group | Test>) => Group;
|
|
15
|
+
type RunOptions = {
|
|
16
|
+
filters: ReadonlyArray<RegExp>;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Run a test suite. This should be the entry point of a test program.
|
|
20
|
+
*
|
|
21
|
+
* A sane, simple testing framework. Instead of a complicated test setup which
|
|
22
|
+
* finds and compiles files, we do the simplest obvious thing: a function that
|
|
23
|
+
* takes a list of tests. To run the tests execute a Node.js program that calls
|
|
24
|
+
* this `run` function.
|
|
25
|
+
*
|
|
26
|
+
* ```ts
|
|
27
|
+
* import { run, test, group, expect, parseArgs } from "@ambarltd/core/test";
|
|
28
|
+
*
|
|
29
|
+
* run(parseArgs(), [
|
|
30
|
+
* group("trivial tests", [
|
|
31
|
+
* test("referential equality", () => expect.equals(1, 3)),
|
|
32
|
+
* test("structural equality", () => expect.json_equals({}, {})),
|
|
33
|
+
* test("async test", async () => {
|
|
34
|
+
* const n = await fetchNumberFromTheInternet();
|
|
35
|
+
* expect.equals(1, n);
|
|
36
|
+
* }),
|
|
37
|
+
* ]),
|
|
38
|
+
* ]);
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* If you want tests in different files, just import them like you would
|
|
42
|
+
* in a normal program.
|
|
43
|
+
*
|
|
44
|
+
* ```ts
|
|
45
|
+
* import { run } from "@ambarltd/core/test";
|
|
46
|
+
* import * as unit from "@test/unitTests";
|
|
47
|
+
* import * as integration from "@test/integrationTests";
|
|
48
|
+
*
|
|
49
|
+
* run(parseArgs(), [unit.tests, integration.tests]);
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
declare function run(options: RunOptions, groups: Array<Group>): Promise<void>;
|
|
53
|
+
declare const expect: {
|
|
54
|
+
fail: (reason: string) => void;
|
|
55
|
+
equals: <T>(a: T, b: T, label?: string) => void;
|
|
56
|
+
deep_equals: <T>(ra: T, rb: T) => void;
|
|
57
|
+
greater_than: <T>(a: T, b: T) => void;
|
|
58
|
+
not_equals: <T>(a: T, b: T) => void;
|
|
59
|
+
json_equals: <T>(a: T, b: T) => void;
|
|
60
|
+
contains: (needle: string, haystack: string) => void;
|
|
61
|
+
throws: (f: () => unknown, g: (e: Error) => void) => void;
|
|
62
|
+
};
|
|
63
|
+
declare function parseArgs(argv?: Array<string>): RunOptions;
|