@fougere/core 0.8.4-alpha.0 → 0.9.0-alpha.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/dist/boot/AppLifecycle.d.ts +8 -0
- package/dist/boot/AppLifecycle.d.ts.map +1 -1
- package/dist/boot/AppLifecycle.js.map +1 -1
- package/dist/boot/Emissions.d.ts +26 -0
- package/dist/boot/Emissions.d.ts.map +1 -1
- package/dist/boot/Emissions.js +143 -8
- package/dist/boot/Emissions.js.map +1 -1
- package/dist/boot/bootstrap.d.ts.map +1 -1
- package/dist/boot/bootstrap.js +77 -18
- package/dist/boot/bootstrap.js.map +1 -1
- package/dist/boot/hosted.d.ts.map +1 -1
- package/dist/boot/hosted.js +1 -0
- package/dist/boot/hosted.js.map +1 -1
- package/dist/boot/install.d.ts +2 -0
- package/dist/boot/install.d.ts.map +1 -1
- package/dist/boot/install.js +33 -4
- package/dist/boot/install.js.map +1 -1
- package/dist/boot/ports.d.ts +12 -3
- package/dist/boot/ports.d.ts.map +1 -1
- package/dist/boot/ports.js +51 -21
- package/dist/boot/ports.js.map +1 -1
- package/dist/boot/remote.d.ts.map +1 -1
- package/dist/boot/remote.js +3 -3
- package/dist/boot/remote.js.map +1 -1
- package/dist/boot/types.d.ts +16 -1
- package/dist/boot/types.d.ts.map +1 -1
- package/dist/builtin/LogLine.d.ts +38 -0
- package/dist/builtin/LogLine.d.ts.map +1 -0
- package/dist/builtin/LogLine.js +37 -0
- package/dist/builtin/LogLine.js.map +1 -0
- package/dist/builtin/logger.d.ts +57 -0
- package/dist/builtin/logger.d.ts.map +1 -1
- package/dist/builtin/logger.js +72 -17
- package/dist/builtin/logger.js.map +1 -1
- package/dist/config-loader.d.ts +2 -1
- package/dist/config-loader.d.ts.map +1 -1
- package/dist/config-loader.js.map +1 -1
- package/dist/contract.d.ts +3 -7
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +3 -4
- package/dist/contract.js.map +1 -1
- package/dist/conventions.d.ts +6 -0
- package/dist/conventions.d.ts.map +1 -1
- package/dist/conventions.js +3 -2
- package/dist/conventions.js.map +1 -1
- package/dist/declare.d.ts +11 -1
- package/dist/declare.d.ts.map +1 -1
- package/dist/declare.js +9 -0
- package/dist/declare.js.map +1 -1
- package/dist/descriptor/frond.d.ts +34 -0
- package/dist/descriptor/frond.d.ts.map +1 -1
- package/dist/descriptor/index.d.ts +1 -1
- package/dist/descriptor/index.d.ts.map +1 -1
- package/dist/descriptor/index.js +1 -1
- package/dist/descriptor/index.js.map +1 -1
- package/dist/dispatch/ArgumentResolver.d.ts.map +1 -1
- package/dist/dispatch/ArgumentResolver.js +6 -2
- package/dist/dispatch/ArgumentResolver.js.map +1 -1
- package/dist/dispatch/HandlerFacade.d.ts.map +1 -1
- package/dist/dispatch/HandlerFacade.js +2 -2
- package/dist/dispatch/HandlerFacade.js.map +1 -1
- package/dist/effective-operation.d.ts.map +1 -1
- package/dist/effective-operation.js +11 -7
- package/dist/effective-operation.js.map +1 -1
- package/dist/frond-config.d.ts +24 -0
- package/dist/frond-config.d.ts.map +1 -1
- package/dist/frond-config.js.map +1 -1
- package/dist/index.d.ts +30 -33
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -13
- package/dist/index.js.map +1 -1
- package/dist/wire/Invocation.d.ts +1 -3
- package/dist/wire/Invocation.d.ts.map +1 -1
- package/dist/wire/Invocation.js +1 -5
- package/dist/wire/Invocation.js.map +1 -1
- package/dist/wire/binding.d.ts +9 -0
- package/dist/wire/binding.d.ts.map +1 -1
- package/dist/wire/binding.js +11 -6
- package/dist/wire/binding.js.map +1 -1
- package/dist/wire/emit.d.ts +35 -3
- package/dist/wire/emit.d.ts.map +1 -1
- package/dist/wire/emit.js +26 -3
- package/dist/wire/emit.js.map +1 -1
- package/dist/wire/loggerMiddleware.d.ts.map +1 -1
- package/dist/wire/loggerMiddleware.js +6 -0
- package/dist/wire/loggerMiddleware.js.map +1 -1
- package/package.json +4 -4
- package/src/boot/AppLifecycle.ts +8 -0
- package/src/boot/Emissions.ts +176 -8
- package/src/boot/bootstrap.ts +89 -17
- package/src/boot/hosted.ts +3 -1
- package/src/boot/install.ts +35 -4
- package/src/boot/ports.ts +71 -27
- package/src/boot/remote.ts +3 -3
- package/src/boot/types.ts +16 -1
- package/src/builtin/LogLine.ts +38 -0
- package/src/builtin/logger.ts +109 -17
- package/src/config-loader.ts +2 -1
- package/src/contract.ts +5 -9
- package/src/conventions.ts +9 -2
- package/src/declare.ts +19 -1
- package/src/descriptor/frond.ts +31 -0
- package/src/descriptor/index.ts +1 -1
- package/src/dispatch/ArgumentResolver.ts +6 -2
- package/src/dispatch/HandlerFacade.ts +2 -2
- package/src/effective-operation.ts +13 -7
- package/src/frond-config.ts +20 -0
- package/src/index.ts +36 -50
- package/src/wire/Invocation.ts +2 -7
- package/src/wire/binding.ts +17 -6
- package/src/wire/emit.ts +58 -6
- package/src/wire/loggerMiddleware.ts +6 -0
package/dist/wire/binding.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"binding.js","sourceRoot":"","sources":["../../src/wire/binding.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"binding.js","sourceRoot":"","sources":["../../src/wire/binding.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AA4B7C,iDAAiD;AAEjD,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC;AAE5D,SAAS,WAAW,CAAC,QAAgB;IACnC,IAAI,QAAQ,KAAK,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC3C,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC7C,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,iDAAiD;AAEjD,qDAAqD;AACrD,MAAM,UAAU,kBAAkB,CAChC,MAAe,EACf,kBAA+B;IAE/B,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QAC1B,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;QACjC,8EAA8E;QAC9E,iFAAiF;QACjF,oFAAoF;QACpF,MAAM,OAAO,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;QAErC,qFAAqF;QACrF,uFAAuF;QACvF,0EAA0E;QAC1E,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM;YACxE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI;YAChC,CAAC,CAAC,SAAS,CAAC;QACd,IAAI,SAAS,EAAE,CAAC;YACd,OAAO;gBACL,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,MAAM,EAAE;oBACN,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAoB;oBACvE,QAAQ,EAAE,UAAU,CAAC,SAAS,CAAC;iBAChC;gBACD,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,KAAK;aAClC,CAAC;QACJ,CAAC;QAED,sEAAsE;QACtE,IAAI,kBAAkB,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YACpC,OAAO;gBACL,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,MAAM,EAAE,EAAE,IAAI,EAAE,WAAoB,EAAE,QAAQ,EAAE,OAAO,EAAE;gBACzD,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,KAAK;aAClC,CAAC;QACJ,CAAC;QAED,iDAAiD;QACjD,IAAI,QAAQ,KAAK,mBAAmB,EAAE,CAAC;YACrC,OAAO;gBACL,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,MAAM,EAAE,EAAE,IAAI,EAAE,SAAkB,EAAE;gBACpC,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,KAAK;aAClC,CAAC;QACJ,CAAC;QAED,sDAAsD;QACtD,IAAI,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC7B,OAAO;gBACL,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,MAAM,EAAE;oBACN,IAAI,EAAE,OAAgB;oBACtB,IAAI,EAAE,KAAK,CAAC,IAAI;oBAChB,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC;iBAC9B;gBACD,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,KAAK;aAClC,CAAC;QACJ,CAAC;QAED,6BAA6B;QAC7B,OAAO;YACL,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,MAAM,EAAE,EAAE,IAAI,EAAE,OAAgB,EAAE;YAClC,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,KAAK;SAClC,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
|
package/dist/wire/emit.d.ts
CHANGED
|
@@ -1,13 +1,45 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* What an emitter injects. PARTIAL, because announcing is what REALIZES the fact's
|
|
3
|
+
* `lifecycle.create` — an `at: created()` is stamped by `Emissions`, so requiring it from
|
|
4
|
+
* the announcer made every emitter cast its way past its own type. A missing field is
|
|
5
|
+
* still refused, by the judge that reads the fact at announce time.
|
|
6
|
+
*/
|
|
7
|
+
export type Emit<T, A = never> = (fact: Partial<T>) => Promise<A[]>;
|
|
8
|
+
/**
|
|
9
|
+
* What a subscriber accepts — and what it PROMISES about itself.
|
|
10
|
+
*
|
|
11
|
+
* What it ANSWERS is its own to choose: `Promise<void>` says nothing, any other type is an
|
|
12
|
+
* opinion. It reaches an announcer that asked for one — `Emit<CanBook, Verdict>` — and
|
|
13
|
+
* nobody otherwise, since a plain announcement does not wait.
|
|
14
|
+
*/
|
|
4
15
|
export type Fact<T> = T;
|
|
16
|
+
/**
|
|
17
|
+
* The same fact, BEFORE it is final: an op taking one ANSWERS the value every subscriber
|
|
18
|
+
* then receives, so it is the declared form of what `Emissions.stamped` already does for
|
|
19
|
+
* `created()`.
|
|
20
|
+
*
|
|
21
|
+
* Not a second announcement and not a chain a subscriber joins: the links run once, before
|
|
22
|
+
* anyone is handed anything, which is what keeps a fact the same for every reader.
|
|
23
|
+
*/
|
|
24
|
+
export type Pipe<T> = T;
|
|
5
25
|
/** The container key of an emission — THE one place that spells the format. */
|
|
6
26
|
export declare function emitKeyOf(fact: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* The key of an emission that WAITS — `Emit<CanBook, Verdict>`, where the second type is
|
|
29
|
+
* the whole difference. A separate key because they are two functions: one hands the fact
|
|
30
|
+
* over and returns, the other waits for every subscriber and gives back what they said.
|
|
31
|
+
*/
|
|
32
|
+
export declare function awaitKeyOf(fact: string): string;
|
|
7
33
|
/** The fact behind an emission key, or `undefined` when the key is not one. */
|
|
8
34
|
export declare function factOfEmitKey(key: string): string | undefined;
|
|
35
|
+
/** The fact behind an awaited emission's key — the dual, read the same way. */
|
|
36
|
+
export declare function factOfAwaitKey(key: string): string | undefined;
|
|
9
37
|
/** What a set of handlers ANNOUNCES — `Emit<T>` read back out of their dependencies. */
|
|
10
38
|
export declare function factsAnnouncedBy(handlers: readonly {
|
|
11
39
|
deps: readonly string[];
|
|
12
40
|
}[]): string[];
|
|
41
|
+
/** What a set of handlers announces AND WAITS FOR — the same reading, one suffix apart. */
|
|
42
|
+
export declare function factsAwaitedBy(handlers: readonly {
|
|
43
|
+
deps: readonly string[];
|
|
44
|
+
}[]): string[];
|
|
13
45
|
//# sourceMappingURL=emit.d.ts.map
|
package/dist/wire/emit.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emit.d.ts","sourceRoot":"","sources":["../../src/wire/emit.ts"],"names":[],"mappings":"AAGA
|
|
1
|
+
{"version":3,"file":"emit.d.ts","sourceRoot":"","sources":["../../src/wire/emit.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,MAAM,MAAM,IAAI,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;AAEpE;;;;;;GAMG;AACH,MAAM,MAAM,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;AAExB;;;;;;;GAOG;AACH,MAAM,MAAM,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;AAExB,+EAA+E;AAC/E,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/C;AAKD,+EAA+E;AAC/E,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE7D;AAED,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE9D;AAQD,wFAAwF;AACxF,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,SAAS;IAAE,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,EAAE,GAAG,MAAM,EAAE,CAE3F;AAED,2FAA2F;AAC3F,wBAAgB,cAAc,CAAC,QAAQ,EAAE,SAAS;IAAE,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,EAAE,GAAG,MAAM,EAAE,CAEzF"}
|
package/dist/wire/emit.js
CHANGED
|
@@ -4,18 +4,41 @@ import { lowerFirst } from '@fougere/schema';
|
|
|
4
4
|
export function emitKeyOf(fact) {
|
|
5
5
|
return `${lowerFirst(fact)}Emit`;
|
|
6
6
|
}
|
|
7
|
+
/**
|
|
8
|
+
* The key of an emission that WAITS — `Emit<CanBook, Verdict>`, where the second type is
|
|
9
|
+
* the whole difference. A separate key because they are two functions: one hands the fact
|
|
10
|
+
* over and returns, the other waits for every subscriber and gives back what they said.
|
|
11
|
+
*/
|
|
12
|
+
export function awaitKeyOf(fact) {
|
|
13
|
+
return `${lowerFirst(fact)}Await`;
|
|
14
|
+
}
|
|
7
15
|
const SUFFIX = 'Emit';
|
|
16
|
+
const AWAIT_SUFFIX = 'Await';
|
|
8
17
|
/** The fact behind an emission key, or `undefined` when the key is not one. */
|
|
9
18
|
export function factOfEmitKey(key) {
|
|
10
|
-
return key
|
|
11
|
-
|
|
19
|
+
return behind(key, SUFFIX);
|
|
20
|
+
}
|
|
21
|
+
/** The fact behind an awaited emission's key — the dual, read the same way. */
|
|
22
|
+
export function factOfAwaitKey(key) {
|
|
23
|
+
return behind(key, AWAIT_SUFFIX);
|
|
24
|
+
}
|
|
25
|
+
function behind(key, suffix) {
|
|
26
|
+
return key.length > suffix.length && key.endsWith(suffix)
|
|
27
|
+
? key.slice(0, -suffix.length)
|
|
12
28
|
: undefined;
|
|
13
29
|
}
|
|
14
30
|
/** What a set of handlers ANNOUNCES — `Emit<T>` read back out of their dependencies. */
|
|
15
31
|
export function factsAnnouncedBy(handlers) {
|
|
32
|
+
return factsIn(handlers, factOfEmitKey);
|
|
33
|
+
}
|
|
34
|
+
/** What a set of handlers announces AND WAITS FOR — the same reading, one suffix apart. */
|
|
35
|
+
export function factsAwaitedBy(handlers) {
|
|
36
|
+
return factsIn(handlers, factOfAwaitKey);
|
|
37
|
+
}
|
|
38
|
+
function factsIn(handlers, behindKey) {
|
|
16
39
|
return [...new Set(handlers
|
|
17
40
|
.flatMap((handler) => handler.deps)
|
|
18
|
-
.map(
|
|
41
|
+
.map(behindKey)
|
|
19
42
|
.filter((fact) => fact !== undefined))];
|
|
20
43
|
}
|
|
21
44
|
//# sourceMappingURL=emit.js.map
|
package/dist/wire/emit.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emit.js","sourceRoot":"","sources":["../../src/wire/emit.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"emit.js","sourceRoot":"","sources":["../../src/wire/emit.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AA6B7C,+EAA+E;AAC/E,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC;AACnC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC;AACpC,CAAC;AAED,MAAM,MAAM,GAAG,MAAM,CAAC;AACtB,MAAM,YAAY,GAAG,OAAO,CAAC;AAE7B,+EAA+E;AAC/E,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,OAAO,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;AAC7B,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,OAAO,MAAM,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;AACnC,CAAC;AAED,SAAS,MAAM,CAAC,GAAW,EAAE,MAAc;IACzC,OAAO,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC;QACvD,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC;QAC9B,CAAC,CAAC,SAAS,CAAC;AAChB,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,gBAAgB,CAAC,QAAgD;IAC/E,OAAO,OAAO,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;AAC1C,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,cAAc,CAAC,QAAgD;IAC7E,OAAO,OAAO,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;AAC3C,CAAC;AAED,SAAS,OAAO,CACd,QAAgD,EAChD,SAA8C;IAE9C,OAAO,CAAC,GAAG,IAAI,GAAG,CAChB,QAAQ;aACL,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;aAClC,GAAG,CAAC,SAAS,CAAC;aACd,MAAM,CAAC,CAAC,IAAI,EAAkB,EAAE,CAAC,IAAI,KAAK,SAAS,CAAC,CACxD,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loggerMiddleware.d.ts","sourceRoot":"","sources":["../../src/wire/loggerMiddleware.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"loggerMiddleware.d.ts","sourceRoot":"","sources":["../../src/wire/loggerMiddleware.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAGnD,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAoB9D"}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
import { CARRIES_LINE } from '../builtin/LogLine.js';
|
|
1
2
|
/** Create a logger middleware that logs operation entry, exit, and errors. */
|
|
2
3
|
export function loggerMiddleware(logger) {
|
|
3
4
|
return async (ctx, next) => {
|
|
5
|
+
// An op that CARRIES a line writes none: it is dispatched, so logging it announces a
|
|
6
|
+
// line inside the announcement of one — `Emission cycle: logLine → logLine`, which is
|
|
7
|
+
// the emission catching what `onLog`'s try/catch used to swallow.
|
|
8
|
+
if (CARRIES_LINE.has(ctx.entity))
|
|
9
|
+
return next();
|
|
4
10
|
const start = performance.now();
|
|
5
11
|
logger.info(`${ctx.entity}.${ctx.operation}`);
|
|
6
12
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loggerMiddleware.js","sourceRoot":"","sources":["../../src/wire/loggerMiddleware.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"loggerMiddleware.js","sourceRoot":"","sources":["../../src/wire/loggerMiddleware.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAErD,8EAA8E;AAC9E,MAAM,UAAU,gBAAgB,CAAC,MAAc;IAC7C,OAAO,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QACzB,qFAAqF;QACrF,sFAAsF;QACtF,kEAAkE;QAClE,IAAI,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC;YAAE,OAAO,IAAI,EAAE,CAAC;QAEhD,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;QAChC,MAAM,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC;QAC9C,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,EAAE,CAAC;YAC5B,MAAM,EAAE,GAAG,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YAClD,MAAM,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,SAAS,KAAK,EAAE,KAAK,CAAC,CAAC;YACxD,OAAO,MAAM,CAAC;QAChB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,EAAE,GAAG,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;YAClD,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,SAAS,KAAK,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;YAC9D,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fougere/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0-alpha.0",
|
|
4
4
|
"description": "The core — scan, call contract, façades, remote stand-ins.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fougere",
|
|
@@ -61,12 +61,12 @@
|
|
|
61
61
|
"dependencies": {
|
|
62
62
|
"@typescript/typescript6": "^6.0.2",
|
|
63
63
|
"dequal": "^2.0.3",
|
|
64
|
-
"@fougere/
|
|
65
|
-
"@fougere/
|
|
64
|
+
"@fougere/schema": "0.9.0-alpha.0",
|
|
65
|
+
"@fougere/container": "0.9.0-alpha.0"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@types/node": "^22.0.0",
|
|
69
|
-
"@fougere/compiler": "0.
|
|
69
|
+
"@fougere/compiler": "0.9.0-alpha.0"
|
|
70
70
|
},
|
|
71
71
|
"publishConfig": {
|
|
72
72
|
"access": "public"
|
package/src/boot/AppLifecycle.ts
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import type { App } from './types.js';
|
|
2
|
+
import type { FrondDescriptor } from '../descriptor/frond.js';
|
|
2
3
|
|
|
3
4
|
/** One process-level extension and its reversible lifecycle. */
|
|
4
5
|
export interface Extension {
|
|
5
6
|
name: string;
|
|
7
|
+
/**
|
|
8
|
+
* Fronds it brings — read BEFORE the ascent, since a frond is installed while the app is
|
|
9
|
+
* still being made and `up` receives one that already exists. This is how an optional
|
|
10
|
+
* package accepts a fact: `calls` and `observability` both want every `LogLine`, and a
|
|
11
|
+
* signature is the only way to ask for one.
|
|
12
|
+
*/
|
|
13
|
+
fronds?: readonly FrondDescriptor[];
|
|
6
14
|
up?(app: App): void | Promise<void>;
|
|
7
15
|
down?(app: App): void | Promise<void>;
|
|
8
16
|
}
|
package/src/boot/Emissions.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { applyCreate, dotted, type SchemaView } from '@fougere/schema';
|
|
1
|
+
import { applyCreate, dotted, lowerFirst, type SchemaView } from '@fougere/schema';
|
|
2
2
|
import type { Container } from '@fougere/container';
|
|
3
3
|
import { validationErrorsOf } from '../wire/errors.js';
|
|
4
|
-
import { emitKeyOf, factsAnnouncedBy } from '../wire/emit.js';
|
|
4
|
+
import { awaitKeyOf, emitKeyOf, factsAnnouncedBy, factsAwaitedBy } from '../wire/emit.js';
|
|
5
5
|
import { ambient } from '#ambient';
|
|
6
|
-
import {
|
|
6
|
+
import { Invocation } from '../wire/Invocation.js';
|
|
7
7
|
import type { Logger } from '../builtin/logger.js';
|
|
8
8
|
import type { Fronds } from '../descriptor/Fronds.js';
|
|
9
9
|
import type { OperationsMap } from '../wire/operation.js';
|
|
@@ -22,6 +22,12 @@ export class Emissions {
|
|
|
22
22
|
/** Who listens to what. Filled as each door's contracts are resolved. */
|
|
23
23
|
private readonly subscribers = new Map<string, Listener[]>();
|
|
24
24
|
|
|
25
|
+
/** Who FINISHES a fact, in the order they run — see `orderPipes`. */
|
|
26
|
+
private readonly pipes = new Map<string, Listener[]>();
|
|
27
|
+
|
|
28
|
+
/** The order its OWNER declared, by fact — `pipes:` in `frond.config.ts`. */
|
|
29
|
+
private readonly ordered: Map<string, string[]>;
|
|
30
|
+
|
|
25
31
|
/**
|
|
26
32
|
* What is announced here, read from the DEPS and not from the subscribers: a handler declaring
|
|
27
33
|
* `Emit<PostPublished>` must resolve it whether or not anybody listens, and announcing to nobody
|
|
@@ -29,6 +35,9 @@ export class Emissions {
|
|
|
29
35
|
*/
|
|
30
36
|
private readonly announced: Set<string>;
|
|
31
37
|
|
|
38
|
+
/** What is announced AND waited for — `Emit<T, A>`, where the second type says so. */
|
|
39
|
+
private readonly awaited: Set<string>;
|
|
40
|
+
|
|
32
41
|
constructor(
|
|
33
42
|
fronds: Fronds,
|
|
34
43
|
private readonly shapes: Map<string, SchemaView>,
|
|
@@ -37,6 +46,23 @@ export class Emissions {
|
|
|
37
46
|
private readonly carry?: Carrier,
|
|
38
47
|
) {
|
|
39
48
|
this.announced = new Set(fronds.flatMap((frond) => factsAnnouncedBy(frond.handlers)));
|
|
49
|
+
this.awaited = new Set(fronds.flatMap((frond) => factsAwaitedBy(frond.handlers)));
|
|
50
|
+
// Read from the frond that OWNS the fact: ordering is a decision about the fact, and
|
|
51
|
+
// a decision has one owner. A frond ordering a neighbour's fact is refused below.
|
|
52
|
+
this.ordered = new Map(fronds.flatMap((frond) => {
|
|
53
|
+
const owned = new Set(frond.entities.map((entity) => entity.name));
|
|
54
|
+
|
|
55
|
+
return Object.entries(frond.pipes ?? {}).map(([fact, order]) => {
|
|
56
|
+
if (!owned.has(lowerFirst(fact))) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`Frond '${frond.name}' orders the links of '${fact}', which it does not own.\n`
|
|
59
|
+
+ ' The order belongs to the frond that declares the entity — state `pipes:` there.',
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return [lowerFirst(fact), order] as const;
|
|
64
|
+
});
|
|
65
|
+
}));
|
|
40
66
|
}
|
|
41
67
|
|
|
42
68
|
/**
|
|
@@ -46,6 +72,10 @@ export class Emissions {
|
|
|
46
72
|
note(contracts: OperationsMap, door: string): void {
|
|
47
73
|
for (const [op, contract] of contracts) {
|
|
48
74
|
for (const bound of contract.binding ?? []) {
|
|
75
|
+
if (bound.source.kind === 'pipe') {
|
|
76
|
+
this.claimPipe(bound.source.factName, { door, op });
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
49
79
|
if (bound.source.kind !== 'fact') continue;
|
|
50
80
|
const listeners = this.subscribers.get(bound.source.factName) ?? [];
|
|
51
81
|
listeners.push({ door, op });
|
|
@@ -54,6 +84,51 @@ export class Emissions {
|
|
|
54
84
|
}
|
|
55
85
|
}
|
|
56
86
|
|
|
87
|
+
/** One more op that finishes this fact. What ORDER they run in is settled at `register`. */
|
|
88
|
+
private claimPipe(fact: string, taking: Listener): void {
|
|
89
|
+
const held = this.pipes.get(fact) ?? [];
|
|
90
|
+
if (held.some((one) => `${one.door}.${one.op}` === `${taking.door}.${taking.op}`)) return;
|
|
91
|
+
held.push(taking);
|
|
92
|
+
this.pipes.set(fact, held);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Put the links in the order their fact's owner declared, and refuse what it did not.
|
|
97
|
+
*
|
|
98
|
+
* Two links with no order refuse: nothing would say which finished the fact, and scan
|
|
99
|
+
* order is not an answer — the same reason two implementations of a port refuse. One
|
|
100
|
+
* link needs no declaration, because there is nothing to order.
|
|
101
|
+
*/
|
|
102
|
+
private orderPipes(): void {
|
|
103
|
+
for (const [fact, links] of this.pipes) {
|
|
104
|
+
const declared = this.ordered.get(fact);
|
|
105
|
+
const named = (one: Listener) => `${one.door}.${one.op}`;
|
|
106
|
+
|
|
107
|
+
if (!declared) {
|
|
108
|
+
if (links.length < 2) continue;
|
|
109
|
+
throw new Error(
|
|
110
|
+
`${links.length} ops finish the fact '${fact}': ${links.map(named).join(', ')}.\n`
|
|
111
|
+
+ ' They run one after another and nothing says in which order. State it on the '
|
|
112
|
+
+ `frond that owns '${fact}':\n`
|
|
113
|
+
+ ` export default { pipes: { ${fact}: ['FirstHandler', 'SecondHandler'] } };\n`
|
|
114
|
+
+ ' Or make all but one accept `Fact<…>`, which changes nothing and reads it.',
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const at = (one: Listener) => declared.findIndex((name) => doorOf(name) === one.door);
|
|
119
|
+
const unlisted = links.filter((one) => at(one) < 0);
|
|
120
|
+
if (unlisted.length > 0) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
`${unlisted.map(named).join(', ')} finish${unlisted.length > 1 ? '' : 'es'} the fact `
|
|
123
|
+
+ `'${fact}', and '${fact}' orders ${declared.join(', ')} — so where it runs is not said.\n`
|
|
124
|
+
+ ' Add it to `pipes:`, or make it accept `Fact<…>`.',
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
this.pipes.set(fact, [...links].sort((one, other) => at(one) - at(other)));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
57
132
|
/** The shape a fact is validated by, when the fact is a declared entity. */
|
|
58
133
|
shapeOf(fact: string): SchemaView | undefined {
|
|
59
134
|
return this.shapes.get(fact);
|
|
@@ -64,18 +139,39 @@ export class Emissions {
|
|
|
64
139
|
return [...this.subscribers.keys()];
|
|
65
140
|
}
|
|
66
141
|
|
|
142
|
+
/** The doors that accept one fact — the addresses a middleware must leave alone. */
|
|
143
|
+
doorsFor(fact: string): string[] {
|
|
144
|
+
return (this.subscribers.get(fact) ?? []).map(({ door }) => door);
|
|
145
|
+
}
|
|
146
|
+
|
|
67
147
|
/** Register one emission value per fact — announced here, or merely listened to. */
|
|
68
148
|
register(): void {
|
|
149
|
+
this.orderPipes();
|
|
69
150
|
for (const fact of new Set([...this.announced, ...this.subscribers.keys()])) {
|
|
70
151
|
this.container.registerValue(emitKeyOf(fact), (raw: unknown) => this.announce(fact, raw));
|
|
71
152
|
}
|
|
153
|
+
for (const fact of this.awaited) {
|
|
154
|
+
// Waiting means knowing WHO, and a carrier publishes to whoever subscribed elsewhere
|
|
155
|
+
// without bringing anything back — so the answers would hold this process's
|
|
156
|
+
// subscribers only, and say nothing about it. Refused here rather than half-answered
|
|
157
|
+
// at the first call: the second type is in a signature, so a boot can read it.
|
|
158
|
+
if (this.carry) {
|
|
159
|
+
throw new Error(
|
|
160
|
+
`'${fact}' is announced with an answer type, and this app has a carrier (\`onEmit\`).\n`
|
|
161
|
+
+ ' Waiting means knowing who answers; a carrier reaches whoever subscribed '
|
|
162
|
+
+ 'elsewhere and brings nothing back.\n'
|
|
163
|
+
+ ` Name the subscribers in \`remotes:\`, or drop the second type of \`Emit<${fact}, …>\`.`,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
this.container.registerValue(awaitKeyOf(fact), (raw: unknown) => this.announce(fact, raw, true));
|
|
167
|
+
}
|
|
72
168
|
if (ambient.degraded && this.subscribers.size > 0) {
|
|
73
169
|
this.log.warn('no async context on this runtime — an emission ring is not detected');
|
|
74
170
|
}
|
|
75
171
|
}
|
|
76
172
|
|
|
77
173
|
/** Announcing. */
|
|
78
|
-
private async announce(fact: string, raw: unknown): Promise<
|
|
174
|
+
private async announce(fact: string, raw: unknown, waiting = false): Promise<unknown[]> {
|
|
79
175
|
/**
|
|
80
176
|
* A fact announced inside a frame that then rolls back is a lie, and nothing can take it back:
|
|
81
177
|
* announcing is DISPATCH — every subscriber has been handed the fact and the carrier has
|
|
@@ -83,15 +179,45 @@ export class Emissions {
|
|
|
83
179
|
*/
|
|
84
180
|
await ambient.beforeAnnounce(fact);
|
|
85
181
|
|
|
86
|
-
const payload = this.stamped(fact, raw);
|
|
182
|
+
const payload = await this.finished(fact, this.stamped(fact, raw));
|
|
87
183
|
|
|
88
184
|
/** Whoever is not in this process — and it is the ONLY way to reach them. */
|
|
89
185
|
const delivery = this.carry?.(fact, payload);
|
|
90
186
|
if (delivery) void Promise.resolve(delivery).catch((cause) => this.log.error(`${fact} — carrier refused it`, cause));
|
|
91
187
|
|
|
92
|
-
|
|
93
|
-
|
|
188
|
+
const handed = this.handToListeners(fact, payload);
|
|
189
|
+
if (!waiting) {
|
|
190
|
+
for (const { door, op, done } of handed) {
|
|
191
|
+
void done.catch((cause) => this.log.error(`${fact} → ${door}.${op}`, this.describeRefusal(fact, cause) ?? cause));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return [];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const settled = await Promise.allSettled(handed.map((one) => one.done));
|
|
198
|
+
|
|
199
|
+
// A subscriber that did not answer REFUSES the announcement, it does not shrink it: an
|
|
200
|
+
// announcer handed the survivors cannot tell three answers from two, and its own law
|
|
201
|
+
// then reads silence as consent — the room one would have refused gets booked.
|
|
202
|
+
const missing = settled.flatMap((result, at) =>
|
|
203
|
+
(result.status === 'rejected' ? [{ ...handed[at]!, reason: result.reason as unknown }] : []));
|
|
204
|
+
if (missing.length > 0) {
|
|
205
|
+
for (const { door, op, reason } of missing) {
|
|
206
|
+
this.log.error(`${fact} → ${door}.${op}`, this.describeRefusal(fact, reason) ?? reason);
|
|
207
|
+
}
|
|
208
|
+
throw new AggregateError(
|
|
209
|
+
missing.map((one) => one.reason),
|
|
210
|
+
`${fact} — ${missing.length} of ${handed.length} subscriber(s) did not answer`
|
|
211
|
+
+ ` (${missing.map((one) => `${one.door}.${one.op}`).join(', ')}).`
|
|
212
|
+
+ ' An announcement with an answer type waits for everyone: a partial answer would'
|
|
213
|
+
+ ' look like a complete one.',
|
|
214
|
+
);
|
|
94
215
|
}
|
|
216
|
+
|
|
217
|
+
// What answers NOTHING contributes nothing — a subscriber is free to have no opinion,
|
|
218
|
+
// and `Promise<void>` is how it says so.
|
|
219
|
+
return settled.flatMap((result) =>
|
|
220
|
+
(result.status === 'fulfilled' && result.value != null ? [result.value] : []));
|
|
95
221
|
}
|
|
96
222
|
|
|
97
223
|
/** Receiving. */
|
|
@@ -114,6 +240,38 @@ export class Emissions {
|
|
|
114
240
|
}
|
|
115
241
|
}
|
|
116
242
|
|
|
243
|
+
/**
|
|
244
|
+
* What the declared link answers, or the value as it stands when nothing declared one.
|
|
245
|
+
*
|
|
246
|
+
* Before anyone is handed anything, and once: every subscriber reads the same fact, which
|
|
247
|
+
* is the whole reason a fact can be said to be what happened. A link that refuses stops
|
|
248
|
+
* the announcement — it was finishing the fact, and half a fact is not one.
|
|
249
|
+
*/
|
|
250
|
+
private async finished(fact: string, payload: unknown): Promise<unknown> {
|
|
251
|
+
let carried = payload;
|
|
252
|
+
for (const link of this.pipes.get(fact) ?? []) {
|
|
253
|
+
const facade = this.container.resolve<Record<string, Function>>(link.door);
|
|
254
|
+
const answered = await facade[link.op]({ ...Invocation.empty, input: carried });
|
|
255
|
+
|
|
256
|
+
// A link that answers nothing SUPPRESSES the fact — every subscriber was then handed
|
|
257
|
+
// `null` and crashed reading it, one message each. It is refused rather than named,
|
|
258
|
+
// because a fact is what HAPPENED: the announcer already said so and cannot be told
|
|
259
|
+
// otherwise, `Emit` returning void. Filtering belongs to whoever announces, or to
|
|
260
|
+
// each reader; it is not a link's to decide for everyone.
|
|
261
|
+
if (answered === null || answered === undefined) {
|
|
262
|
+
throw new Error(
|
|
263
|
+
`${link.door}.${link.op} finishes the fact '${fact}' and answered nothing.\n`
|
|
264
|
+
+ ' A link says what the fact IS, it does not take it back — the announcer has '
|
|
265
|
+
+ 'already said it happened, and nothing can tell it otherwise.\n'
|
|
266
|
+
+ ' Return the fact, amended or as it stands.',
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
carried = answered;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return carried;
|
|
273
|
+
}
|
|
274
|
+
|
|
117
275
|
/**
|
|
118
276
|
* The announcement realizes the fact's own `lifecycle.create` — a `created()` stamped, an id
|
|
119
277
|
* generated, a default applied.
|
|
@@ -151,7 +309,7 @@ export class Emissions {
|
|
|
151
309
|
} catch (cause) {
|
|
152
310
|
throw new Error(`${fact} → ${door} could not be reached`, { cause });
|
|
153
311
|
}
|
|
154
|
-
return facade[op]({ ...
|
|
312
|
+
return facade[op]({ ...Invocation.empty, input: payload });
|
|
155
313
|
}),
|
|
156
314
|
}));
|
|
157
315
|
}
|
|
@@ -164,3 +322,13 @@ export class Emissions {
|
|
|
164
322
|
+ ` If '${fact}' gained a field, this copy is older than the sender's: re-run \`fougere sync\`.`;
|
|
165
323
|
}
|
|
166
324
|
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* The container key a declared CLASS NAME answers under — `RedactHandler` → `redactHandler`,
|
|
328
|
+
* the same key `facadeKeyOf` builds from an address.
|
|
329
|
+
*/
|
|
330
|
+
function doorOf(declared: string): string {
|
|
331
|
+
const key = lowerFirst(declared);
|
|
332
|
+
|
|
333
|
+
return key.endsWith('Handler') ? key : `${key}Handler`;
|
|
334
|
+
}
|