@fougere/app 0.6.0-alpha.0 → 0.7.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/auth.d.ts +1 -7
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js.map +1 -1
- package/dist/boot.d.ts +14 -85
- package/dist/boot.d.ts.map +1 -1
- package/dist/boot.js +17 -77
- package/dist/boot.js.map +1 -1
- package/dist/client.d.ts +5 -24
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +6 -32
- package/dist/client.js.map +1 -1
- package/dist/express.d.ts +2 -12
- package/dist/express.d.ts.map +1 -1
- package/dist/express.js +4 -52
- package/dist/express.js.map +1 -1
- package/dist/form.d.ts +11 -56
- package/dist/form.d.ts.map +1 -1
- package/dist/form.js +15 -33
- package/dist/form.js.map +1 -1
- package/dist/graphql.d.ts +2 -28
- package/dist/graphql.d.ts.map +1 -1
- package/dist/graphql.js +1 -6
- package/dist/graphql.js.map +1 -1
- package/dist/index.d.ts +1 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -11
- package/dist/index.js.map +1 -1
- package/dist/rest.d.ts +2 -16
- package/dist/rest.d.ts.map +1 -1
- package/dist/rest.js +4 -34
- package/dist/rest.js.map +1 -1
- package/dist/serve.d.ts +10 -57
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +12 -55
- package/dist/serve.js.map +1 -1
- package/dist/session.d.ts +2 -7
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +2 -7
- package/dist/session.js.map +1 -1
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +1 -11
- package/dist/state.js.map +1 -1
- package/dist/web.d.ts +3 -19
- package/dist/web.d.ts.map +1 -1
- package/dist/web.js +4 -32
- package/dist/web.js.map +1 -1
- package/package.json +11 -11
- package/src/auth.ts +1 -7
- package/src/boot.ts +28 -134
- package/src/client.ts +7 -33
- package/src/express.ts +4 -52
- package/src/form.ts +20 -73
- package/src/graphql.ts +2 -28
- package/src/index.ts +1 -11
- package/src/rest.ts +4 -34
- package/src/serve.ts +13 -60
- package/src/session.ts +2 -7
- package/src/state.ts +1 -11
- package/src/web.ts +4 -32
package/dist/serve.js
CHANGED
|
@@ -1,44 +1,17 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The three doors, decided — and nothing about how a request arrives.
|
|
3
|
-
*
|
|
4
|
-
* A host (Nuxt, Next) owns exactly two translations: read the request into the
|
|
5
|
-
* plain values below, and write the outcome back out. Everything between — which
|
|
6
|
-
* operation a verb and a path name, which audience a segment selects, what a
|
|
7
|
-
* refusal becomes — is decided here, once, for every host.
|
|
8
|
-
*
|
|
9
|
-
* The split matters because the alternative was measured in this repo: two copies
|
|
10
|
-
* of a REST rule drifted until the Nuxt door answered differently from
|
|
11
|
-
* `schema-rest` on the verb, the path AND the exposure. A second host would have
|
|
12
|
-
* been a third copy.
|
|
13
|
-
*/
|
|
1
|
+
/** The three doors, decided — and nothing about how a request arrives. */
|
|
14
2
|
import { createAppRunner, callValueOf, toHttpError, } from '@fougere/core';
|
|
15
3
|
import { handleRpc, PARSE_ERROR } from '@fougere/transport-http';
|
|
16
4
|
import { matchRoute, tableOf } from './rest.js';
|
|
17
5
|
// ── The call envelope ────────────────────────────
|
|
18
|
-
/**
|
|
19
|
-
* The audience this door serves — the path segment after `/_fougere/call`.
|
|
20
|
-
*
|
|
21
|
-
* The envelope is a surface like REST and GraphQL, so it selects its audience like
|
|
22
|
-
* they do; the difference is only that it takes it from the path instead of an
|
|
23
|
-
* option, because a door is mounted, not called. The same word names the directory
|
|
24
|
-
* (`handlers/public/`), the config key (`surfaces: { public: [...] }`) and this
|
|
25
|
-
* segment — derived, never configured.
|
|
26
|
-
*
|
|
27
|
-
* No escalation to guard: a named surface serves the entities it names and nothing
|
|
28
|
-
* else (closed by naming), so every one of them is a subset of what the bare path
|
|
29
|
-
* already serves.
|
|
30
|
-
*/
|
|
6
|
+
/** The audience this door serves — the path segment after `/_fougere/call`. */
|
|
31
7
|
export function surfaceOf(path) {
|
|
32
8
|
const named = /^\/_fougere\/call\/([A-Za-z0-9_-]+)/.exec(path.replace(/\?.*$/, ''));
|
|
33
9
|
return named?.[1];
|
|
34
10
|
}
|
|
35
11
|
/**
|
|
36
|
-
* Receiving end for the browser — same wire as process-to-process (JSON-RPC),
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* The runner follows the app's topology: local façades and remote doublures alike
|
|
41
|
-
* — the browser never knows where a Frond lives.
|
|
12
|
+
* Receiving end for the browser — same wire as process-to-process (JSON-RPC), different trust
|
|
13
|
+
* boundary: the browser sits outside the topology, so `state` is whatever the host resolved
|
|
14
|
+
* server-side, never what the payload claims.
|
|
42
15
|
*/
|
|
43
16
|
export async function serveRpc(app, request) {
|
|
44
17
|
const runner = createAppRunner(app, surfaceOf(request.path));
|
|
@@ -50,12 +23,7 @@ export function rpcParseError() {
|
|
|
50
23
|
}
|
|
51
24
|
// ── REST ─────────────────────────────────────────
|
|
52
25
|
/**
|
|
53
|
-
* Match the URL against the canonical table, invoke the call it names, shape the
|
|
54
|
-
* result for HTTP. The decision lives in `rest.ts`; dispatch belongs to the runner.
|
|
55
|
-
*
|
|
56
|
-
* `path` is what follows the REST mount point, so `/api/blog/posts/1` arrives as
|
|
57
|
-
* `blog/posts/1`. A path this door does not serve returns `pass`, and that is what
|
|
58
|
-
* lets an app keep its own `/api/*` handlers.
|
|
26
|
+
* Match the URL against the canonical table, invoke the call it names, shape the result for HTTP.
|
|
59
27
|
*/
|
|
60
28
|
export async function serveRest(app, request) {
|
|
61
29
|
// The app decides, not the host. A route file may exist and a middleware may be
|
|
@@ -84,7 +52,7 @@ export async function serveRest(app, request) {
|
|
|
84
52
|
const { route, params } = match;
|
|
85
53
|
let result;
|
|
86
54
|
try {
|
|
87
|
-
result = await invokeOn(app, { entity: route.entityName, op: route.operationName }, { params, query: request.query,
|
|
55
|
+
result = await invokeOn(app, { entity: route.entityName, op: route.operationName }, { params, query: request.query, input: request.body }, undefined, request.state);
|
|
88
56
|
}
|
|
89
57
|
catch (err) {
|
|
90
58
|
const { status, body } = toHttpError(err);
|
|
@@ -92,13 +60,7 @@ export async function serveRest(app, request) {
|
|
|
92
60
|
}
|
|
93
61
|
return shapeRest(route.operationName, result);
|
|
94
62
|
}
|
|
95
|
-
/**
|
|
96
|
-
* What an operation's return becomes on the wire.
|
|
97
|
-
*
|
|
98
|
-
* Separate from `serveRest` because it is a DECISION and dispatch is not: it can be
|
|
99
|
-
* pinned without a runner, and both hosts get it whether the call ran in memory or
|
|
100
|
-
* came back over JSON-RPC.
|
|
101
|
-
*/
|
|
63
|
+
/** What an operation's return becomes on the wire. */
|
|
102
64
|
export function shapeRest(operationName, result) {
|
|
103
65
|
if (result === null)
|
|
104
66
|
return { kind: 'error', status: 404, body: { message: 'Not found' } };
|
|
@@ -118,19 +80,14 @@ export function shapeRest(operationName, result) {
|
|
|
118
80
|
return { kind: 'ok', status: 200, body: result };
|
|
119
81
|
}
|
|
120
82
|
/**
|
|
121
|
-
* Name a call server-side and let the runner place it — local façade → direct
|
|
122
|
-
*
|
|
123
|
-
* never knows which.
|
|
124
|
-
*
|
|
125
|
-
* `state` is explicit here. Each host wraps this with its own way of finding the
|
|
126
|
-
* current request (Nitro's async context, Next's `headers()` scope), because that
|
|
127
|
-
* is the one part a host actually owns.
|
|
83
|
+
* Name a call server-side and let the runner place it — local façade → direct in-memory execution,
|
|
84
|
+
* a frond in `remotes` → JSON-RPC on the wire.
|
|
128
85
|
*/
|
|
129
86
|
export async function invokeOn(app, target, opOrInput, input, state = {}) {
|
|
130
87
|
const { call, invocation } = callValueOf(target, opOrInput, input);
|
|
131
88
|
// An explicit `state` on the input wins over the request's — the caller who spells
|
|
132
89
|
// it is answering for it, which is what makes a call outside any request possible.
|
|
133
|
-
const
|
|
134
|
-
return (await createAppRunner(app)(call, { ...invocation, state:
|
|
90
|
+
const explicit = typeof opOrInput === 'string' ? input : opOrInput;
|
|
91
|
+
return (await createAppRunner(app)(call, { ...invocation, state: explicit?.state ?? state }));
|
|
135
92
|
}
|
|
136
93
|
//# sourceMappingURL=serve.js.map
|
package/dist/serve.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serve.js","sourceRoot":"","sources":["../src/serve.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"serve.js","sourceRoot":"","sources":["../src/serve.ts"],"names":[],"mappings":"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"}
|
package/dist/session.d.ts
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The session view — the one place that turns the server-resolved
|
|
3
|
-
*
|
|
4
|
-
* is allowed to see. One resolution, three readers: the page by
|
|
5
|
-
* hydration, the refresh route over the wire, handlers by invocation.
|
|
6
|
-
*
|
|
7
|
-
* The app-declared context (viewer enrichment) will attach here when
|
|
8
|
-
* a real case lands — this function is the seam.
|
|
2
|
+
* The session view — the one place that turns the server-resolved request context (filled by the
|
|
3
|
+
* auth middleware) into what the client is allowed to see.
|
|
9
4
|
*/
|
|
10
5
|
export interface SessionView {
|
|
11
6
|
user: Record<string, unknown> | null;
|
package/dist/session.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CACtC;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,WAAW,CAK3E"}
|
package/dist/session.js
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The session view — the one place that turns the server-resolved
|
|
3
|
-
*
|
|
4
|
-
* is allowed to see. One resolution, three readers: the page by
|
|
5
|
-
* hydration, the refresh route over the wire, handlers by invocation.
|
|
6
|
-
*
|
|
7
|
-
* The app-declared context (viewer enrichment) will attach here when
|
|
8
|
-
* a real case lands — this function is the seam.
|
|
2
|
+
* The session view — the one place that turns the server-resolved request context (filled by the
|
|
3
|
+
* auth middleware) into what the client is allowed to see.
|
|
9
4
|
*/
|
|
10
5
|
export function sessionViewOf(context) {
|
|
11
6
|
const raw = context.user;
|
package/dist/session.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,MAAM,UAAU,aAAa,CAAC,OAAgC;IAC5D,MAAM,GAAG,GAAG,OAAO,CAAC,IAA2C,CAAC;IAChE,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAChC,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,GAAG,IAAI,EAAE,GAAG,GAAG,CAAC;IACrD,OAAO,EAAE,IAAI,EAAE,CAAC;AAClB,CAAC"}
|
package/dist/state.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAOA,mGAAmG;AACnG,wBAAsB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAcjF"}
|
package/dist/state.js
CHANGED
|
@@ -1,14 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Who the caller is, resolved server-side from the request's own headers.
|
|
3
|
-
*
|
|
4
|
-
* Nuxt answers this with a Nitro middleware that stamps `event.context`; a
|
|
5
|
-
* Web-standard host has no such seam on a route handler, so it resolves here
|
|
6
|
-
* instead. Same source (the auth runtime mounted on `app.auth`), same result shape
|
|
7
|
-
* (`{ user, session }`), so `serveRpc` and `serveRest` cannot tell hosts apart.
|
|
8
|
-
*
|
|
9
|
-
* What must stay true in both: this is what the SERVER resolved. A browser sits
|
|
10
|
-
* outside the topology, so nothing here may come from the payload.
|
|
11
|
-
*/
|
|
1
|
+
/** Who the caller is, resolved server-side from the request's own headers. */
|
|
12
2
|
import { useFougereApp } from './boot.js';
|
|
13
3
|
/** The request state for these headers. Empty when no auth is declared, or nobody is signed in. */
|
|
14
4
|
export async function stateFor(headers) {
|
package/dist/state.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.js","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"state.js","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAM1C,mGAAmG;AACnG,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,OAAgB;IAC7C,MAAM,GAAG,GAAG,MAAM,aAAa,EAAE,CAAC;IAClC,IAAI,CAAC,GAAG,CAAC,IAAI;QAAE,OAAO,EAAE,CAAC;IACzB,wFAAwF;IACxF,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC;QAAE,OAAO,EAAE,CAAC;IAEtC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAO,GAAG,CAAC,IAAI,CAAC,GAA6B,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;QACrF,IAAI,MAAM,EAAE,OAAO,IAAI,MAAM,EAAE,IAAI;YAAE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;IAC7F,CAAC;IAAC,MAAM,CAAC;QACP,8EAA8E;QAC9E,wEAAwE;IAC1E,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC"}
|
package/dist/web.d.ts
CHANGED
|
@@ -1,25 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The call envelope. A named surface is the path segment after the door —
|
|
3
|
-
* `/_fougere/call/public` serves the `public` audience — and `surfaceOf` reads it,
|
|
4
|
-
* so a host only has to mount this at a path that keeps the segments.
|
|
5
|
-
*/
|
|
1
|
+
/** The call envelope. */
|
|
6
2
|
export declare function fougereCall(request: Request): Promise<Response>;
|
|
7
|
-
/**
|
|
8
|
-
* The REST projection, mounted under `/api`.
|
|
9
|
-
*
|
|
10
|
-
* `pass` — a path this app does not serve — becomes a 404 here rather than a
|
|
11
|
-
* fall-through, because a Web handler has nobody to fall through to. Hosts that
|
|
12
|
-
* resolve a static route before a catch-all (Next does) still let an app keep its
|
|
13
|
-
* own `/api/*` handlers: they are reached BEFORE this one, not after.
|
|
14
|
-
*/
|
|
3
|
+
/** The REST projection, mounted under `/api`. */
|
|
15
4
|
export declare function fougereRest(request: Request): Promise<Response>;
|
|
16
5
|
/** The session view over the wire, for a client refreshing after login or logout. */
|
|
17
6
|
export declare function fougereSession(request: Request): Promise<Response>;
|
|
18
|
-
/**
|
|
19
|
-
* GraphQL, at whatever path the host mounted it — `/graphql` by convention.
|
|
20
|
-
*
|
|
21
|
-
* Answers `404` when the app declares no GraphQL adapter, because unlike REST this
|
|
22
|
-
* door is mounted at a path of its own: there is no app route underneath it to pass to.
|
|
23
|
-
*/
|
|
7
|
+
/** GraphQL, at whatever path the host mounted it — `/graphql` by convention. */
|
|
24
8
|
export declare function fougereGraphQL(request: Request): Promise<Response>;
|
|
25
9
|
//# sourceMappingURL=web.d.ts.map
|
package/dist/web.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../src/web.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../src/web.ts"],"names":[],"mappings":"AAQA,yBAAyB;AACzB,wBAAsB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAqBrE;AAED,iDAAiD;AACjD,wBAAsB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAoBrE;AAED,qFAAqF;AACrF,wBAAsB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAExE;AAED,gFAAgF;AAChF,wBAAsB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAkBxE"}
|
package/dist/web.js
CHANGED
|
@@ -1,26 +1,10 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The three doors as Web-standard handlers: `Request` in, `Response` out.
|
|
3
|
-
*
|
|
4
|
-
* This is the whole server surface for any host built on fetch semantics — Next
|
|
5
|
-
* route handlers, TanStack Start server routes, Hono, a bare `Deno.serve`. Such a
|
|
6
|
-
* host mounts these; it does not translate anything, because there is nothing left
|
|
7
|
-
* to translate.
|
|
8
|
-
*
|
|
9
|
-
* Nuxt is the exception and keeps its own translation, for a reason worth stating:
|
|
10
|
-
* an h3 event is not a `Request`, and reading its body has to straddle two h3
|
|
11
|
-
* majors (`server/routes/call.post.ts` carries that hundred lines). That file is
|
|
12
|
-
* what a NON-Web-standard host costs.
|
|
13
|
-
*/
|
|
1
|
+
/** The three doors as Web-standard handlers: `Request` in, `Response` out. */
|
|
14
2
|
import { serveRest, serveRpc, rpcParseError, useFougereApp, serveGraphQL } from './index.js';
|
|
15
3
|
import { MAX_BODY_BYTES } from '@fougere/core';
|
|
16
4
|
import { sessionViewOf } from './session.js';
|
|
17
5
|
import { stateFor } from './state.js';
|
|
18
6
|
const WITH_BODY = new Set(['POST', 'PUT', 'PATCH']);
|
|
19
|
-
/**
|
|
20
|
-
* The call envelope. A named surface is the path segment after the door —
|
|
21
|
-
* `/_fougere/call/public` serves the `public` audience — and `surfaceOf` reads it,
|
|
22
|
-
* so a host only has to mount this at a path that keeps the segments.
|
|
23
|
-
*/
|
|
7
|
+
/** The call envelope. */
|
|
24
8
|
export async function fougereCall(request) {
|
|
25
9
|
const declared = Number(request.headers.get('content-length'));
|
|
26
10
|
if (Number.isFinite(declared) && declared > MAX_BODY_BYTES) {
|
|
@@ -40,14 +24,7 @@ export async function fougereCall(request) {
|
|
|
40
24
|
state: await stateFor(request.headers),
|
|
41
25
|
}));
|
|
42
26
|
}
|
|
43
|
-
/**
|
|
44
|
-
* The REST projection, mounted under `/api`.
|
|
45
|
-
*
|
|
46
|
-
* `pass` — a path this app does not serve — becomes a 404 here rather than a
|
|
47
|
-
* fall-through, because a Web handler has nobody to fall through to. Hosts that
|
|
48
|
-
* resolve a static route before a catch-all (Next does) still let an app keep its
|
|
49
|
-
* own `/api/*` handlers: they are reached BEFORE this one, not after.
|
|
50
|
-
*/
|
|
27
|
+
/** The REST projection, mounted under `/api`. */
|
|
51
28
|
export async function fougereRest(request) {
|
|
52
29
|
const app = await useFougereApp();
|
|
53
30
|
const url = new URL(request.url);
|
|
@@ -71,12 +48,7 @@ export async function fougereRest(request) {
|
|
|
71
48
|
export async function fougereSession(request) {
|
|
72
49
|
return Response.json(sessionViewOf(await stateFor(request.headers)));
|
|
73
50
|
}
|
|
74
|
-
/**
|
|
75
|
-
* GraphQL, at whatever path the host mounted it — `/graphql` by convention.
|
|
76
|
-
*
|
|
77
|
-
* Answers `404` when the app declares no GraphQL adapter, because unlike REST this
|
|
78
|
-
* door is mounted at a path of its own: there is no app route underneath it to pass to.
|
|
79
|
-
*/
|
|
51
|
+
/** GraphQL, at whatever path the host mounted it — `/graphql` by convention. */
|
|
80
52
|
export async function fougereGraphQL(request) {
|
|
81
53
|
const app = await useFougereApp();
|
|
82
54
|
const body = (await request.json().catch(() => ({})));
|
package/dist/web.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web.js","sourceRoot":"","sources":["../src/web.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"web.js","sourceRoot":"","sources":["../src/web.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fougere/app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0-alpha.0",
|
|
4
4
|
"description": "The host-independent half of a Fougere app: boot, call envelope, REST table, form contract.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fougere",
|
|
@@ -42,24 +42,24 @@
|
|
|
42
42
|
],
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"jiti": "^2.6.1",
|
|
45
|
-
"@fougere/core": "0.
|
|
46
|
-
"@fougere/
|
|
47
|
-
"@fougere/
|
|
48
|
-
"@fougere/
|
|
49
|
-
"@fougere/http": "0.
|
|
50
|
-
"@fougere/container": "0.
|
|
51
|
-
"@fougere/adapter-memory": "0.
|
|
52
|
-
"@fougere/
|
|
45
|
+
"@fougere/core": "0.7.0-alpha.0",
|
|
46
|
+
"@fougere/adapter-rest": "0.7.0-alpha.0",
|
|
47
|
+
"@fougere/defaults": "0.7.0-alpha.0",
|
|
48
|
+
"@fougere/schema": "0.7.0-alpha.0",
|
|
49
|
+
"@fougere/http": "0.7.0-alpha.0",
|
|
50
|
+
"@fougere/container": "0.7.0-alpha.0",
|
|
51
|
+
"@fougere/adapter-memory": "0.7.0-alpha.0",
|
|
52
|
+
"@fougere/transport-http": "0.7.0-alpha.0"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"vitest": "^4.1.0",
|
|
56
|
-
"@fougere/adapter-graphql": "0.
|
|
56
|
+
"@fougere/adapter-graphql": "0.7.0-alpha.0"
|
|
57
57
|
},
|
|
58
58
|
"publishConfig": {
|
|
59
59
|
"access": "public"
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|
|
62
|
-
"@fougere/adapter-graphql": "0.
|
|
62
|
+
"@fougere/adapter-graphql": "0.7.0-alpha.0"
|
|
63
63
|
},
|
|
64
64
|
"peerDependenciesMeta": {
|
|
65
65
|
"@fougere/adapter-graphql": {
|
package/src/auth.ts
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Auth accessor — reads the AuthRuntime that the core mounted on the app.
|
|
3
|
-
*
|
|
4
|
-
* Auth is no longer a separate singleton: it lives on `app.auth`, built once
|
|
5
|
-
* during boot from the `auth` field of fougere.config.ts. This accessor is a
|
|
6
|
-
* thin async helper for server code (middleware, routes, /api/me).
|
|
7
|
-
*/
|
|
1
|
+
/** Auth accessor — reads the AuthRuntime that the core mounted on the app. */
|
|
8
2
|
import type { AuthRuntime } from '@fougere/core';
|
|
9
3
|
import { useFougereApp } from './boot.js';
|
|
10
4
|
|
package/src/boot.ts
CHANGED
|
@@ -1,99 +1,35 @@
|
|
|
1
1
|
import { Role } from '@fougere/schema';
|
|
2
2
|
import { Lifecycle } from '@fougere/schema';
|
|
3
|
-
/**
|
|
4
|
-
* Fougere server bootstrap — single entry point for an app's lifecycle,
|
|
5
|
-
* whatever hosts it.
|
|
6
|
-
*
|
|
7
|
-
* Nothing here knows h3, Nitro, Vue or React: a boot reads `fougere.config.ts`,
|
|
8
|
-
* scans fronds off the filesystem and hands back an `App`. That is why it lives
|
|
9
|
-
* in `@fougere/app` rather than in one of the two adapters — the second host
|
|
10
|
-
* would otherwise have copied it, and a copied boot drifts (the seeding loop
|
|
11
|
-
* already did, `core/src/boot/seed.ts`).
|
|
12
|
-
*
|
|
13
|
-
* Default path (zero-config): fougere.config.ts declares `db: 'sqlite'` and
|
|
14
|
-
* everything else; this module auto-resolves the storage handle, builds an
|
|
15
|
-
* storageFactory, runs auto-DDL from the entities, and boots the app.
|
|
16
|
-
*
|
|
17
|
-
* Escape hatch: `configureFougere({ db, storageFactory })` can be called from the
|
|
18
|
-
* host's own startup (a Nitro plugin, a Next instrumentation hook) if the user
|
|
19
|
-
* wants a custom data layer — alternative driver, managed migrations, etc.
|
|
20
|
-
*/
|
|
3
|
+
/** Fougere server bootstrap — single entry point for an app's lifecycle, whatever hosts it. */
|
|
21
4
|
import { createApp, identityFromEnv, Logger, migrating, seeding } from '@fougere/core';
|
|
22
5
|
import { scanProject, loadCascadedConfig, setModuleLoader, frondAliases, resolveConventions } from '@fougere/core/node';
|
|
23
6
|
import type { Extension } from '@fougere/core';
|
|
24
7
|
import { createContainer } from '@fougere/container';
|
|
25
8
|
import { createMemoryStorage } from '@fougere/adapter-memory';
|
|
26
9
|
import type { App, CreateAppOptions, Storage, FougereConfig, Transport } from '@fougere/core';
|
|
10
|
+
import type { ResolvedStorage } from '@fougere/defaults';
|
|
27
11
|
import { applyCreate, applyUpdate, type SchemaView } from '@fougere/schema';
|
|
28
12
|
|
|
29
13
|
// ── Public types ─────────────────────────────────
|
|
30
14
|
|
|
31
15
|
export interface FougereServerConfig {
|
|
32
|
-
/** Storage handle. Forwarded to the auth provider via AuthContext.db. */
|
|
33
|
-
db?: unknown;
|
|
34
|
-
/** Per-entity storage factory. */
|
|
35
|
-
storageFactory?: (entity: SchemaView, name: string) => Storage;
|
|
36
16
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* seeding — Nuxt's generated plugin did exactly that, and its copy of the seeding loop
|
|
41
|
-
* drifted. Declaring `{ name: 'seeds', up }` replaces that one member and leaves the
|
|
42
|
-
* rest of the ascent alone.
|
|
17
|
+
* The whole data layer, as `resolveStorage` composed it — where the rows are, which source
|
|
18
|
+
* transacts, what to close. Handed over as ONE subject: a host that passed the factory alone
|
|
19
|
+
* lost the transaction and the connection's owner, and nothing said so.
|
|
43
20
|
*/
|
|
21
|
+
storage?: ResolvedStorage;
|
|
22
|
+
/** What this app takes on beyond its fronds, each stating what it does and what it undoes. */
|
|
44
23
|
extensions?: CreateAppOptions['extensions'];
|
|
45
|
-
/**
|
|
46
|
-
* What the boot line names as the host — 'Nuxt/Nitro', 'Next'. Stated by the
|
|
47
|
-
* adapter, never sniffed: a boot that guesses its host from what happens to be
|
|
48
|
-
* importable is the hidden runtime the doctrine refuses.
|
|
49
|
-
*/
|
|
24
|
+
/** What the boot line names as the host — 'Nuxt/Nitro', 'Next'. */
|
|
50
25
|
host?: string;
|
|
51
|
-
/**
|
|
52
|
-
* What this app is built from, when the host already knows.
|
|
53
|
-
*
|
|
54
|
-
* Absent, `boot()` reads the fronds off the disk — right on a server, impossible on a
|
|
55
|
-
* runtime that has none: measured on workerd, `readdir` throws through unenv's shim and
|
|
56
|
-
* the app comes up with ZERO fronds, so every page renders and every door answers
|
|
57
|
-
* NOT_FOUND. A host that scanned at BUILD time can say so instead, and `fougere build`
|
|
58
|
-
* writes exactly this value down.
|
|
59
|
-
*
|
|
60
|
-
* Same slot as `CreateAppOptions.scan` and for the same reason: producing the value
|
|
61
|
-
* reads a disk, consuming it does not.
|
|
62
|
-
*/
|
|
26
|
+
/** What this app is built from, when the host already knows. */
|
|
63
27
|
scan?: CreateAppOptions['scan'];
|
|
64
|
-
/**
|
|
65
|
-
* What this app STATES it hosts — `frond('blog', { entities: [Post] })`.
|
|
66
|
-
*
|
|
67
|
-
* Stating this and no `scan` is how a host stops scanning at boot: nothing reads a
|
|
68
|
-
* disk, nothing loads `typescript`, and what was not named does not exist. It is the
|
|
69
|
-
* one door Next, Vite, React, Svelte and a bare Express share — none of them scans on
|
|
70
|
-
* its own, they all arrive here, and this line used to end in a scan for every one.
|
|
71
|
-
*/
|
|
28
|
+
/** What this app STATES it hosts — `frond('blog', { entities: [Post] })`. */
|
|
72
29
|
fronds?: CreateAppOptions['fronds'];
|
|
73
|
-
/**
|
|
74
|
-
* What `fougere.config.ts` says, when the host already read it.
|
|
75
|
-
*
|
|
76
|
-
* The same rule as `scan`, and found the same way: `boot()` re-reads the file at
|
|
77
|
-
* runtime, which a Worker cannot do — measured, a consumer's `remotes:` never reached
|
|
78
|
-
* the boot and its pages rendered empty with nothing said. A host that read the config
|
|
79
|
-
* at BUILD time states it here instead.
|
|
80
|
-
*
|
|
81
|
-
* `auth` is deliberately not part of what a codegen'd host can carry: it holds a live
|
|
82
|
-
* provider, not a value. An app that authenticates reads its own config.
|
|
83
|
-
*/
|
|
30
|
+
/** What `fougere.config.ts` says, when the host already read it. */
|
|
84
31
|
config?: Partial<FougereConfig>;
|
|
85
|
-
/**
|
|
86
|
-
* Who performs an outgoing call, when the default cannot.
|
|
87
|
-
*
|
|
88
|
-
* `boot()` builds an HTTP transport from `remotes:` and that is right nearly
|
|
89
|
-
* everywhere. It is not right on Cloudflare: a Worker calling a sibling's public URL
|
|
90
|
-
* is refused by the edge with error 1042, so two Workers of one account reach each
|
|
91
|
-
* other through a SERVICE BINDING and through nothing else. A binding is a value only
|
|
92
|
-
* the host holds, so only the host can state this.
|
|
93
|
-
*
|
|
94
|
-
* It replaces the default entirely — signing included, since a host that builds its
|
|
95
|
-
* own transport is the one that knows what to put on the wire.
|
|
96
|
-
*/
|
|
32
|
+
/** Who performs an outgoing call, when the default cannot. */
|
|
97
33
|
remoteTransport?: (url: string) => Transport;
|
|
98
34
|
}
|
|
99
35
|
|
|
@@ -113,15 +49,7 @@ export function configureFougere(config: FougereServerConfig) {
|
|
|
113
49
|
_appPromise = null;
|
|
114
50
|
}
|
|
115
51
|
|
|
116
|
-
/**
|
|
117
|
-
* Add to what is already stated, instead of replacing it.
|
|
118
|
-
*
|
|
119
|
-
* A host states its app in PIECES when the pieces are known at different moments: a
|
|
120
|
-
* build writes the scan and the config into a generated plugin, and a value only the
|
|
121
|
-
* running process holds — a Cloudflare service binding — cannot be written there at all.
|
|
122
|
-
* Its dual is `configureFougere`, which replaces; `reloadFougere` depends on that
|
|
123
|
-
* replacement, so merging silently would have broken the turn of the ring.
|
|
124
|
-
*/
|
|
52
|
+
/** Add to what is already stated, instead of replacing it. */
|
|
125
53
|
export function extendFougere(config: Partial<FougereServerConfig>) {
|
|
126
54
|
_config = { ..._config, ...config };
|
|
127
55
|
_appPromise = null;
|
|
@@ -135,22 +63,7 @@ export function useFougereApp(): Promise<App> {
|
|
|
135
63
|
return _appPromise;
|
|
136
64
|
}
|
|
137
65
|
|
|
138
|
-
/**
|
|
139
|
-
* Turn the ring: instantiate the app again, then let the previous one go.
|
|
140
|
-
*
|
|
141
|
-
* This is what "reload" means for anything the config CONSUMED — a value that built
|
|
142
|
-
* something cannot move under what it built, so the thing is built again. Its dual is
|
|
143
|
-
* `applyConfig`, for values that are merely consulted and need no turn at all.
|
|
144
|
-
*
|
|
145
|
-
* Every door reaches the app through `useFougereApp()` inside the request it serves and
|
|
146
|
-
* none holds it across two, which is what makes the swap invisible: the next request
|
|
147
|
-
* lands on the new app whether or not the old one has finished being released.
|
|
148
|
-
*
|
|
149
|
-
* A call already running finishes on the OLD app: it is drained before being released,
|
|
150
|
-
* so nothing has its storage closed underneath it. `timeoutMs` bounds that wait, and a
|
|
151
|
-
* drain that runs out REJECTS — the app is left alone rather than released under work,
|
|
152
|
-
* because a caller who cannot wait must choose that on purpose.
|
|
153
|
-
*/
|
|
66
|
+
/** Turn the ring. */
|
|
154
67
|
export async function reloadFougere(timeoutMs?: number): Promise<App> {
|
|
155
68
|
const previous = _appPromise;
|
|
156
69
|
_appPromise = null;
|
|
@@ -209,32 +122,16 @@ async function boot(): Promise<App> {
|
|
|
209
122
|
// Auto-resolve the data layer from config.db when the user didn't provide a
|
|
210
123
|
// custom one via configureFougere. The resolution itself lives in @fougere/defaults
|
|
211
124
|
// — this host must not know which storage package backs `db:`.
|
|
212
|
-
let
|
|
213
|
-
|
|
214
|
-
// The storage's two halves, kept together: its ascent is an extension, its connection
|
|
215
|
-
// is not — it is opened here, before the container, so it closes after the container.
|
|
216
|
-
let storageMigrate: Extension['up'] | undefined;
|
|
217
|
-
let closeStorage: (() => Promise<void>) | undefined;
|
|
218
|
-
// Where the rows are, and how to open a transaction there — read from the same storage
|
|
219
|
-
// resolution, because a frame's realization is decided by `sources:` and nothing else.
|
|
220
|
-
let sourceOf: ((entityName: string) => string) | undefined;
|
|
221
|
-
let transacted: CreateAppOptions['transacted'];
|
|
222
|
-
if (!storageFactory) {
|
|
125
|
+
let storage = _config.storage;
|
|
126
|
+
if (!storage) {
|
|
223
127
|
const { resolveStorage } = await import('@fougere/defaults');
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
log.debug('auto-resolving storage from config.db');
|
|
227
|
-
db = storage.db;
|
|
228
|
-
storageFactory = storage.storageFactory;
|
|
229
|
-
sourceOf = storage.sourceOf;
|
|
230
|
-
transacted = storage.transacted as never;
|
|
231
|
-
storageMigrate = storage.migrate;
|
|
232
|
-
closeStorage = storage.close;
|
|
233
|
-
} else {
|
|
234
|
-
log.debug('no db declared — falling back to in-memory storage');
|
|
235
|
-
storageFactory = createMemoryStorage;
|
|
236
|
-
}
|
|
128
|
+
storage = resolveStorage(fileConfig.db as never, (fileConfig as { sources?: unknown }).sources as never);
|
|
129
|
+
log.debug(storage.storageFactory ? 'auto-resolving storage from config.db' : 'no db declared — falling back to in-memory storage');
|
|
237
130
|
}
|
|
131
|
+
// The storage's two halves, kept together: its ascent is an extension, its connection
|
|
132
|
+
// is not — it is opened here, before the container, so it closes after the container.
|
|
133
|
+
const storageFactory = storage.storageFactory ?? createMemoryStorage;
|
|
134
|
+
const storageMigrate: Extension['up'] | undefined = storage.migrate;
|
|
238
135
|
|
|
239
136
|
// Layer-2 wiring: `remotes: { catalog: 'http://...' }` in fougere.config.ts
|
|
240
137
|
// is all the user writes — the default transport comes from here.
|
|
@@ -267,18 +164,15 @@ async function boot(): Promise<App> {
|
|
|
267
164
|
: {}),
|
|
268
165
|
createContainer,
|
|
269
166
|
storageFactory,
|
|
270
|
-
sourceOf,
|
|
271
|
-
|
|
272
|
-
|
|
167
|
+
sourceOf: storage.sourceOf,
|
|
168
|
+
transacts: storage.transacts,
|
|
169
|
+
transacted: storage.transacted as never,
|
|
170
|
+
db: storage.db,
|
|
273
171
|
auth: fileConfig.auth,
|
|
274
172
|
adapters: fileConfig.adapters,
|
|
275
173
|
remotes: fileConfig.remotes,
|
|
276
174
|
remoteTransport,
|
|
277
|
-
/**
|
|
278
|
-
* The whole ascent, one ordered list: tables, then rows, then what the host adds.
|
|
279
|
-
* A host wanting its OWN seeding declares `{ name: 'seeds', … }` and replaces that
|
|
280
|
-
* member — it no longer has to claim everything after the boot to get it.
|
|
281
|
-
*/
|
|
175
|
+
/** The whole ascent, one ordered list. */
|
|
282
176
|
extensions: [
|
|
283
177
|
// The slot is declared even when this host resolved no storage — a host that resolved
|
|
284
178
|
// its own (the Nitro plugin does, for its bundler) then REPLACES this member in place
|
|
@@ -290,7 +184,7 @@ async function boot(): Promise<App> {
|
|
|
290
184
|
// Opened before the container, so released after it. Never wired here until now:
|
|
291
185
|
// this host boots the storage and no host closed one, which is what made a reload
|
|
292
186
|
// leak the pool of every app it discarded.
|
|
293
|
-
onDispose:
|
|
187
|
+
onDispose: storage.close,
|
|
294
188
|
});
|
|
295
189
|
|
|
296
190
|
log.info(`ascent: ${app.extensions().join(' → ') || 'nothing declared'}`);
|