@fougere/core 0.3.0-alpha.0 → 0.4.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/README.md +1 -1
- package/dist/boot/HandlerFacade.js +2 -2
- package/dist/boot/HandlerFacade.js.map +1 -1
- package/dist/boot/apply.d.ts.map +1 -1
- package/dist/boot/apply.js +2 -12
- package/dist/boot/apply.js.map +1 -1
- package/dist/boot/binding.js +4 -4
- package/dist/boot/binding.js.map +1 -1
- package/dist/boot/boot.d.ts +1 -1
- package/dist/boot/boot.d.ts.map +1 -1
- package/dist/boot/bootstrap.d.ts.map +1 -1
- package/dist/boot/bootstrap.js +14 -13
- package/dist/boot/bootstrap.js.map +1 -1
- package/dist/boot/frame.d.ts +1 -1
- package/dist/boot/frame.d.ts.map +1 -1
- package/dist/boot/frame.js +5 -14
- package/dist/boot/frame.js.map +1 -1
- package/dist/boot/ownership.d.ts.map +1 -1
- package/dist/boot/ownership.js +5 -5
- package/dist/boot/ownership.js.map +1 -1
- package/dist/boot/remote.d.ts.map +1 -1
- package/dist/boot/remote.js +2 -2
- package/dist/boot/remote.js.map +1 -1
- package/dist/boot/together.d.ts.map +1 -1
- package/dist/boot/together.js +6 -6
- package/dist/boot/together.js.map +1 -1
- package/dist/boot/types.d.ts +2 -2
- package/dist/boot/types.d.ts.map +1 -1
- package/dist/contract.d.ts +1 -1
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +3 -3
- package/dist/contract.js.map +1 -1
- package/dist/dispatch/InFlight.d.ts.map +1 -1
- package/dist/dispatch/InFlight.js.map +1 -1
- package/dist/dispatch/InputValidator.d.ts.map +1 -1
- package/dist/dispatch/InputValidator.js +3 -2
- package/dist/dispatch/InputValidator.js.map +1 -1
- package/dist/dispatch/OutputProjector.js +2 -2
- package/dist/dispatch/OutputProjector.js.map +1 -1
- package/dist/dispatch/StorageGuard.d.ts.map +1 -1
- package/dist/dispatch/StorageGuard.js +2 -2
- package/dist/dispatch/StorageGuard.js.map +1 -1
- package/dist/effective-operation.js +4 -5
- package/dist/effective-operation.js.map +1 -1
- package/dist/emit.d.ts +2 -2
- package/dist/emit.d.ts.map +1 -1
- package/dist/emit.js +2 -2
- package/dist/emit.js.map +1 -1
- package/dist/identity.js +1 -1
- package/dist/identity.js.map +1 -1
- package/dist/imports.d.ts +1 -1
- package/dist/imports.d.ts.map +1 -1
- package/dist/imports.js.map +1 -1
- package/dist/orm.d.ts.map +1 -1
- package/dist/orm.js +4 -4
- package/dist/orm.js.map +1 -1
- package/dist/prefab/collector.js +2 -2
- package/dist/prefab/collector.js.map +1 -1
- package/dist/prefab/mirror.d.ts.map +1 -1
- package/dist/prefab/mirror.js +2 -2
- package/dist/prefab/mirror.js.map +1 -1
- package/dist/prefab/presenter.d.ts.map +1 -1
- package/dist/prefab/presenter.js +2 -2
- package/dist/prefab/presenter.js.map +1 -1
- package/dist/prefab/repository.d.ts.map +1 -1
- package/dist/prefab/repository.js +2 -2
- package/dist/prefab/repository.js.map +1 -1
- package/dist/scan/emit.d.ts.map +1 -1
- package/dist/scan/emit.js +3 -2
- package/dist/scan/emit.js.map +1 -1
- package/dist/scan/handler-parser.d.ts +2 -0
- package/dist/scan/handler-parser.d.ts.map +1 -1
- package/dist/scan/handler-parser.js +52 -12
- package/dist/scan/handler-parser.js.map +1 -1
- package/dist/scan/scanner.d.ts.map +1 -1
- package/dist/scan/scanner.js +37 -10
- package/dist/scan/scanner.js.map +1 -1
- package/dist/verify.js +2 -2
- package/dist/verify.js.map +1 -1
- package/dist/wire/call.d.ts +7 -7
- package/dist/wire/call.d.ts.map +1 -1
- package/dist/wire/call.js +6 -6
- package/dist/wire/call.js.map +1 -1
- package/package.json +6 -4
- package/src/boot/AppLifecycle.ts +50 -0
- package/src/boot/Emissions.ts +263 -0
- package/src/boot/HandlerFacade.ts +242 -0
- package/src/boot/Lifecycle.ts +3 -0
- package/src/boot/ambient-port.ts +41 -0
- package/src/boot/ambient.als.ts +43 -0
- package/src/boot/ambient.queue.ts +75 -0
- package/src/boot/apply.ts +47 -0
- package/src/boot/auth.ts +55 -0
- package/src/boot/binding.ts +182 -0
- package/src/boot/boot.ts +125 -0
- package/src/boot/bootstrap.ts +839 -0
- package/src/boot/frame.ts +210 -0
- package/src/boot/ownership.ts +140 -0
- package/src/boot/ports.ts +66 -0
- package/src/boot/remote.ts +179 -0
- package/src/boot/seed.ts +158 -0
- package/src/boot/together.ts +215 -0
- package/src/boot/types.ts +261 -0
- package/src/builtins/config.ts +19 -0
- package/src/builtins/logger.ts +175 -0
- package/src/config-loader.ts +146 -0
- package/src/contract/Call.ts +18 -0
- package/src/contract/Invocation.ts +72 -0
- package/src/contract/RouteAddress.ts +49 -0
- package/src/contract.ts +74 -0
- package/src/crypto/encoding.ts +59 -0
- package/src/crypto/node.ts +30 -0
- package/src/crypto/port.ts +45 -0
- package/src/crypto/webcrypto.ts +55 -0
- package/src/define.ts +10 -0
- package/src/dispatch/ArgumentResolver.ts +17 -0
- package/src/dispatch/ArrayResult.ts +10 -0
- package/src/dispatch/DispatchEvent.ts +38 -0
- package/src/dispatch/DispatchLifecycle.ts +23 -0
- package/src/dispatch/DispatchPort.ts +6 -0
- package/src/dispatch/Dispatcher.ts +48 -0
- package/src/dispatch/InFlight.ts +49 -0
- package/src/dispatch/InputValidator.ts +28 -0
- package/src/dispatch/LocalRoutePolicy.ts +34 -0
- package/src/dispatch/OperationExecutor.ts +60 -0
- package/src/dispatch/OperationRoute.ts +18 -0
- package/src/dispatch/OutputProjector.ts +32 -0
- package/src/dispatch/OutputView.ts +9 -0
- package/src/dispatch/PresenterArgumentResolver.ts +25 -0
- package/src/dispatch/PresenterExecutor.ts +55 -0
- package/src/dispatch/RemoteRouteResolver.ts +21 -0
- package/src/dispatch/Route.ts +11 -0
- package/src/dispatch/RouteNotFoundError.ts +29 -0
- package/src/dispatch/RoutePolicy.ts +8 -0
- package/src/dispatch/RouteRegistry.ts +73 -0
- package/src/dispatch/RouteResolver.ts +7 -0
- package/src/dispatch/StorageGuard.ts +71 -0
- package/src/effective-operation.ts +597 -0
- package/src/emit.ts +90 -0
- package/src/entry/DynamicFacade.ts +28 -0
- package/src/entry/FacadeEntry.ts +32 -0
- package/src/entry/TransportEntry.ts +30 -0
- package/src/frond-config.ts +138 -0
- package/src/graph.ts +180 -0
- package/src/identity-keys.ts +43 -0
- package/src/identity.ts +235 -0
- package/src/imports.ts +123 -0
- package/src/index.ts +106 -0
- package/src/loader.ts +41 -0
- package/src/node.ts +28 -0
- package/src/orm.ts +252 -0
- package/src/prefab/collector.ts +36 -0
- package/src/prefab/crud.ts +188 -0
- package/src/prefab/mirror.ts +122 -0
- package/src/prefab/prefab.ts +46 -0
- package/src/prefab/presenter.ts +63 -0
- package/src/prefab/repository.ts +170 -0
- package/src/scan/Fronds.ts +73 -0
- package/src/scan/conventions.ts +122 -0
- package/src/scan/emit.ts +231 -0
- package/src/scan/frond.ts +235 -0
- package/src/scan/handler-parser.ts +818 -0
- package/src/scan/scan-cache.ts +115 -0
- package/src/scan/scanner.ts +758 -0
- package/src/verify.ts +193 -0
- package/src/wire/call.ts +369 -0
- package/src/wire/errors.ts +124 -0
- package/src/wire/http-error.ts +77 -0
- package/src/wire/invocation.ts +10 -0
- package/src/wire/loggerMiddleware.ts +29 -0
- package/src/wire/middleware.ts +47 -0
- package/src/wire/operation.ts +258 -0
- package/dist/boot/egress.d.ts +0 -8
- package/dist/boot/egress.d.ts.map +0 -1
- package/dist/boot/egress.js +0 -11
- package/dist/boot/egress.js.map +0 -1
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where each parameter of an operation gets its value — decided once at boot from the
|
|
3
|
+
* parsed signature, replayed per call by `resolveArgs`.
|
|
4
|
+
*
|
|
5
|
+
* The branches are ORDERED and each states why it sits where it does; the list used to
|
|
6
|
+
* be repeated here too, and it had already lost `Fact` — the one whose position is
|
|
7
|
+
* load-bearing, since the fall-through would otherwise hand it the caller's body.
|
|
8
|
+
*/
|
|
9
|
+
import type { ParsedParam } from '../scan/handler-parser.js';
|
|
10
|
+
import type { InvocationContext } from '../wire/invocation.js';
|
|
11
|
+
import { lowerFirst } from '@fougere/schema';
|
|
12
|
+
|
|
13
|
+
// ── Types ─────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
type ParamSource =
|
|
16
|
+
| { kind: 'collector'; typeName: string }
|
|
17
|
+
/**
|
|
18
|
+
* `Fact<PostPublished>` — something that happened, not something a caller typed.
|
|
19
|
+
*
|
|
20
|
+
* It resolves from the body exactly like `body` does, and that is deliberate: an
|
|
21
|
+
* emission and a direct call ARE the same call, so a subscriber cannot tell them apart
|
|
22
|
+
* and does not need to. The branch exists so the PLAN says what this parameter is —
|
|
23
|
+
* that sentence is what makes the subscriber index readable without reparsing types.
|
|
24
|
+
*/
|
|
25
|
+
| { kind: 'fact'; factName: string }
|
|
26
|
+
| { kind: 'param'; name: string; coerce?: 'number' | 'boolean' }
|
|
27
|
+
| { kind: 'body' }
|
|
28
|
+
| { kind: 'context' }
|
|
29
|
+
/** The whole query bag, for an op whose argument IS the options (list). */
|
|
30
|
+
| { kind: 'query' };
|
|
31
|
+
|
|
32
|
+
interface ParamBinding {
|
|
33
|
+
name: string;
|
|
34
|
+
source: ParamSource;
|
|
35
|
+
optional: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type BindingPlan = ParamBinding[];
|
|
39
|
+
|
|
40
|
+
// ── Primitives ────────────────────────────────
|
|
41
|
+
|
|
42
|
+
const PRIMITIVES = new Set(['string', 'number', 'boolean']);
|
|
43
|
+
|
|
44
|
+
function coercionFor(typeName: string): 'number' | 'boolean' | undefined {
|
|
45
|
+
if (typeName === 'number') return 'number';
|
|
46
|
+
if (typeName === 'boolean') return 'boolean';
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── Compute ───────────────────────────────────
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Build a BindingPlan from parsed method params.
|
|
54
|
+
*
|
|
55
|
+
* @param params - Parsed parameter list from AST
|
|
56
|
+
* @param collectorTypeNames - Registration keys of the types a Collector answers for
|
|
57
|
+
*/
|
|
58
|
+
export function computeBindingPlan(
|
|
59
|
+
params: ParsedParam[],
|
|
60
|
+
collectorTypeNames: Set<string>,
|
|
61
|
+
): BindingPlan {
|
|
62
|
+
return params.map((param) => {
|
|
63
|
+
const typeName = param.type.name;
|
|
64
|
+
// `lowerFirst`, never `toLowerCase()`: the collector set is keyed the way the
|
|
65
|
+
// scan spells it, and the two agree on one word only — `AuthorUser` looked up as
|
|
66
|
+
// `authoruser` missed `authorUser` and fell through to branch 4, the request body.
|
|
67
|
+
const typeKey = lowerFirst(typeName);
|
|
68
|
+
|
|
69
|
+
// 0. Fact — `Fact<X>` names itself, so nothing has to be known in advance. It comes
|
|
70
|
+
// FIRST because branch 4 would otherwise hand it the caller's body under the name
|
|
71
|
+
// of something that happened.
|
|
72
|
+
const factOf = param.type.name === 'Fact' ? param.type.generics?.[0]?.name : undefined;
|
|
73
|
+
if (factOf) {
|
|
74
|
+
return {
|
|
75
|
+
name: param.name,
|
|
76
|
+
source: { kind: 'fact' as const, factName: lowerFirst(factOf) },
|
|
77
|
+
optional: param.optional ?? false,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 1. Collector — param type matches a type some collector answers for
|
|
82
|
+
if (collectorTypeNames.has(typeKey)) {
|
|
83
|
+
return {
|
|
84
|
+
name: param.name,
|
|
85
|
+
source: { kind: 'collector' as const, typeName: typeKey },
|
|
86
|
+
optional: param.optional ?? false,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 2. InvocationContext — inject the full context
|
|
91
|
+
if (typeName === 'InvocationContext') {
|
|
92
|
+
return {
|
|
93
|
+
name: param.name,
|
|
94
|
+
source: { kind: 'context' as const },
|
|
95
|
+
optional: param.optional ?? false,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 3. Primitives — matched by name from params > query
|
|
100
|
+
if (PRIMITIVES.has(typeName)) {
|
|
101
|
+
return {
|
|
102
|
+
name: param.name,
|
|
103
|
+
source: {
|
|
104
|
+
kind: 'param' as const,
|
|
105
|
+
name: param.name,
|
|
106
|
+
coerce: coercionFor(typeName),
|
|
107
|
+
},
|
|
108
|
+
optional: param.optional ?? false,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 4. Everything else — body
|
|
113
|
+
return {
|
|
114
|
+
name: param.name,
|
|
115
|
+
source: { kind: 'body' as const },
|
|
116
|
+
optional: param.optional ?? false,
|
|
117
|
+
};
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ── Resolve ───────────────────────────────────
|
|
122
|
+
|
|
123
|
+
export interface CollectorResolver {
|
|
124
|
+
collect(ctx: InvocationContext): Promise<unknown>;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Resolve handler arguments from a BindingPlan + InvocationContext.
|
|
129
|
+
*/
|
|
130
|
+
export async function resolveArgs(
|
|
131
|
+
plan: BindingPlan,
|
|
132
|
+
ctx: InvocationContext,
|
|
133
|
+
resolveCollector?: (typeName: string) => CollectorResolver | undefined,
|
|
134
|
+
): Promise<unknown[]> {
|
|
135
|
+
const args: unknown[] = [];
|
|
136
|
+
|
|
137
|
+
for (const binding of plan) {
|
|
138
|
+
switch (binding.source.kind) {
|
|
139
|
+
case 'collector': {
|
|
140
|
+
const collector = resolveCollector?.(binding.source.typeName);
|
|
141
|
+
args.push(collector ? await collector.collect(ctx) : undefined);
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
case 'context': {
|
|
145
|
+
args.push(ctx);
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
case 'param': {
|
|
149
|
+
// `null` is a value, not a miss. Nullish coalescing used to make an explicit
|
|
150
|
+
// nullable path/GraphQL argument fall through to query (or become undefined),
|
|
151
|
+
// collapsing `T | null` into `T | undefined`. Only undefined means absent.
|
|
152
|
+
const fromParams = ctx.params[binding.source.name];
|
|
153
|
+
let val: unknown = fromParams === undefined
|
|
154
|
+
? ctx.query[binding.source.name]
|
|
155
|
+
: fromParams;
|
|
156
|
+
if (val != null && binding.source.coerce === 'number') val = Number(val);
|
|
157
|
+
if (val != null && binding.source.coerce === 'boolean') val = val === 'true' || val === '1' || val === true;
|
|
158
|
+
args.push(val);
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
case 'fact': {
|
|
162
|
+
// A fact IS the payload — the whole of what happened, never a piece of it.
|
|
163
|
+
// Identical to `body` today, and deliberately not sharing its branch: the two
|
|
164
|
+
// agree by coincidence, not by rule, and the day `body` learns to look up a
|
|
165
|
+
// value by parameter name a subscriber would receive ONE FIELD of the fact it
|
|
166
|
+
// subscribed to. Splitting it costs nothing and removes that trap.
|
|
167
|
+
args.push(ctx.body);
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
case 'body': {
|
|
171
|
+
args.push(ctx.body);
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
case 'query': {
|
|
175
|
+
args.push(ctx.query);
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return args;
|
|
182
|
+
}
|
package/src/boot/boot.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { createApp } from './bootstrap.js';
|
|
2
|
+
import { seeding } from './seed.js';
|
|
3
|
+
import { scanProject } from '../scan/scanner.js';
|
|
4
|
+
import { loadConfig, type FougereConfig } from '../config-loader.js';
|
|
5
|
+
import { Logger } from '../builtins/logger.js';
|
|
6
|
+
import { applyConfig } from './apply.js';
|
|
7
|
+
import type { App, CreateAppOptions } from './types.js';
|
|
8
|
+
import { migrating, type Extension } from './AppLifecycle.js';
|
|
9
|
+
import type { Transport } from '../wire/call.js';
|
|
10
|
+
import type { Container } from '@fougere/container';
|
|
11
|
+
|
|
12
|
+
interface BootOptions {
|
|
13
|
+
/** Project root. Defaults to process.cwd(). */
|
|
14
|
+
root?: string;
|
|
15
|
+
/** Override config (merged with fougere.config.ts). */
|
|
16
|
+
config?: Partial<FougereConfig>;
|
|
17
|
+
/** Container factory. Required. */
|
|
18
|
+
createContainer: () => Container;
|
|
19
|
+
/** Only boot these fronds (by name). Absent = all. */
|
|
20
|
+
fronds?: string[];
|
|
21
|
+
/**
|
|
22
|
+
* Remote fronds — label → address. A declared remote wins over local
|
|
23
|
+
* presence (the frond runs elsewhere). Requires `remoteTransport`.
|
|
24
|
+
*/
|
|
25
|
+
remotes?: Record<string, string>;
|
|
26
|
+
/** Builds the transport to reach `remotes`. Supplied by a layer-2 package. */
|
|
27
|
+
remoteTransport?: (url: string) => Transport;
|
|
28
|
+
/**
|
|
29
|
+
* Carries an announced fact out of this process. Forwarded rather than dropped: a
|
|
30
|
+
* caller handing one in has no other way to reach the emitter, and this path silently
|
|
31
|
+
* kept it to itself.
|
|
32
|
+
*/
|
|
33
|
+
onEmit?: CreateAppOptions['onEmit'];
|
|
34
|
+
/**
|
|
35
|
+
* ORM setup — returns the storage handle (db), an ormFactory, and its two halves.
|
|
36
|
+
* The `db` value is forwarded to the auth provider via AuthContext when `auth` is set.
|
|
37
|
+
*/
|
|
38
|
+
db?: (config: FougereConfig) => {
|
|
39
|
+
db?: unknown;
|
|
40
|
+
ormFactory: CreateAppOptions['ormFactory'];
|
|
41
|
+
/**
|
|
42
|
+
* Bring the schema up to date — an extension's `up`, because it runs after the
|
|
43
|
+
* container. It used to be called `afterBoot`, a word that also meant the host's
|
|
44
|
+
* post-boot and was read in four places under two senses.
|
|
45
|
+
*/
|
|
46
|
+
migrate?: (app: App) => Promise<void> | void;
|
|
47
|
+
/** Closes what the factory opened — `boot()` called it, so `boot()` releases it. */
|
|
48
|
+
close?: () => Promise<void>;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* What this app takes on beyond its fronds. Appended after the framework's own, so a
|
|
52
|
+
* host adds to the ascent — or replaces a member of it by declaring the same name.
|
|
53
|
+
*/
|
|
54
|
+
extensions?: readonly (Extension | undefined)[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Boot a Fougere app from fougere.config.ts.
|
|
59
|
+
*
|
|
60
|
+
* Handles: config loading, container creation, DB setup, seeding.
|
|
61
|
+
* Works for any surface (Nuxt, GraphQL standalone, CLI, tests).
|
|
62
|
+
*/
|
|
63
|
+
export async function boot(options: BootOptions): Promise<App> {
|
|
64
|
+
const bootStart = performance.now();
|
|
65
|
+
const log = new Logger('boot');
|
|
66
|
+
|
|
67
|
+
const root = options.root ?? process.cwd();
|
|
68
|
+
log.info(`root: ${root}`);
|
|
69
|
+
|
|
70
|
+
log.debug('loading config');
|
|
71
|
+
const fileConfig = await loadConfig(root);
|
|
72
|
+
const config: FougereConfig = { ...fileConfig, ...options.config };
|
|
73
|
+
// What this config changes in the process, said the same way at boot and at reload —
|
|
74
|
+
// there is one applier, and a host re-reading later calls the very same function.
|
|
75
|
+
applyConfig(config);
|
|
76
|
+
log.info('config loaded');
|
|
77
|
+
|
|
78
|
+
let dbSetup: ReturnType<NonNullable<BootOptions['db']>> | undefined;
|
|
79
|
+
if (options.db) {
|
|
80
|
+
log.debug('initializing database');
|
|
81
|
+
dbSetup = options.db(config);
|
|
82
|
+
log.info('database initialized');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
log.debug('creating app (scan + container)');
|
|
86
|
+
const app = await createApp({
|
|
87
|
+
// boot() lives on the Node entry, so boot() is what reads the disk. `createApp` is
|
|
88
|
+
// handed the answer and reaches for nothing.
|
|
89
|
+
scan: await scanProject(root, options.fronds, config.conventions),
|
|
90
|
+
createContainer: options.createContainer,
|
|
91
|
+
ormFactory: dbSetup?.ormFactory,
|
|
92
|
+
db: dbSetup?.db,
|
|
93
|
+
auth: config.auth,
|
|
94
|
+
remotes: options.remotes,
|
|
95
|
+
ports: config.ports,
|
|
96
|
+
// Read from the config for the same reason `ports` is, one line up: it is a fact the
|
|
97
|
+
// project states, not one the caller passes. Absent here, `serveRest` and
|
|
98
|
+
// `serveGraphQL` both answered `pass` on an app whose config declared them — the
|
|
99
|
+
// hosts got it right through their own boot (`app/shared/src/boot.ts`), and this
|
|
100
|
+
// path, the conventional one, served nothing.
|
|
101
|
+
adapters: config.adapters,
|
|
102
|
+
// boot() called the factory, so boot() owns closing what it opened. Not an extension:
|
|
103
|
+
// it was opened before the container existed, so it closes after the container goes.
|
|
104
|
+
onDispose: dbSetup?.close,
|
|
105
|
+
/**
|
|
106
|
+
* The whole ascent, in one ordered list — tables, then rows, then whatever the host
|
|
107
|
+
* takes on. It was four call sites under the name `afterBoot`, two of which meant
|
|
108
|
+
* different things and one of which was generated into a Nitro plugin.
|
|
109
|
+
*/
|
|
110
|
+
extensions: [
|
|
111
|
+
migrating(dbSetup?.migrate),
|
|
112
|
+
seeding((message) => log.debug(message)),
|
|
113
|
+
...(options.extensions ?? []),
|
|
114
|
+
],
|
|
115
|
+
onEmit: options.onEmit,
|
|
116
|
+
remoteTransport: options.remoteTransport,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
log.info(`ascent: ${app.extensions().join(' → ') || 'nothing declared'}`);
|
|
120
|
+
|
|
121
|
+
const ms = (performance.now() - bootStart).toFixed(0);
|
|
122
|
+
log.info(`ready in ${ms}ms — ${app.fronds.length} frond(s)`);
|
|
123
|
+
|
|
124
|
+
return app;
|
|
125
|
+
}
|