@fougere/app 0.2.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.
Files changed (50) hide show
  1. package/LICENSE +21 -0
  2. package/dist/auth.d.ts +11 -0
  3. package/dist/auth.d.ts.map +1 -0
  4. package/dist/auth.js +10 -0
  5. package/dist/auth.js.map +1 -0
  6. package/dist/boot.d.ts +35 -0
  7. package/dist/boot.d.ts.map +1 -0
  8. package/dist/boot.js +208 -0
  9. package/dist/boot.js.map +1 -0
  10. package/dist/client.d.ts +64 -0
  11. package/dist/client.d.ts.map +1 -0
  12. package/dist/client.js +124 -0
  13. package/dist/client.js.map +1 -0
  14. package/dist/express.d.ts +25 -0
  15. package/dist/express.d.ts.map +1 -0
  16. package/dist/express.js +180 -0
  17. package/dist/express.js.map +1 -0
  18. package/dist/form.d.ts +102 -0
  19. package/dist/form.d.ts.map +1 -0
  20. package/dist/form.js +104 -0
  21. package/dist/form.js.map +1 -0
  22. package/dist/graphql.d.ts +34 -0
  23. package/dist/graphql.d.ts.map +1 -0
  24. package/dist/graphql.js +58 -0
  25. package/dist/graphql.js.map +1 -0
  26. package/dist/index.d.ts +20 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +20 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/rest.d.ts +41 -0
  31. package/dist/rest.d.ts.map +1 -0
  32. package/dist/rest.js +92 -0
  33. package/dist/rest.js.map +1 -0
  34. package/dist/serve.d.ts +110 -0
  35. package/dist/serve.d.ts.map +1 -0
  36. package/dist/serve.js +136 -0
  37. package/dist/serve.js.map +1 -0
  38. package/dist/session.d.ts +14 -0
  39. package/dist/session.d.ts.map +1 -0
  40. package/dist/session.js +17 -0
  41. package/dist/session.js.map +1 -0
  42. package/dist/state.d.ts +3 -0
  43. package/dist/state.d.ts.map +1 -0
  44. package/dist/state.js +32 -0
  45. package/dist/state.js.map +1 -0
  46. package/dist/web.d.ts +25 -0
  47. package/dist/web.d.ts.map +1 -0
  48. package/dist/web.js +93 -0
  49. package/dist/web.js.map +1 -0
  50. package/package.json +86 -0
package/dist/rest.js ADDED
@@ -0,0 +1,92 @@
1
+ /**
2
+ * The REST table this app serves, and the rule that matches a request against it.
3
+ *
4
+ * Separated from the h3 handler on purpose: this file holds the whole of what the door
5
+ * DECIDES — which operation a verb and a path name — while `api/crud.ts` only translates
6
+ * that decision into h3. The rule was previously inline, untested, and had drifted into a
7
+ * second REST projection that answered differently from `schema-rest` on all three counts
8
+ * (verb, path, exposure).
9
+ */
10
+ import { generateRoutes } from '@fougere/schema-rest';
11
+ // Keyed on the app, which `useFougereApp` memoizes — the table derives from the boot, so
12
+ // it changes exactly when the app does.
13
+ const tables = new WeakMap();
14
+ /**
15
+ * The table, per frond.
16
+ *
17
+ * `generateRoutes` prefixes every path the same way, while this door addresses a frond by
18
+ * name (`/api/{frond}/{plural}`) — so it runs once per frond, each with its own prefix and
19
+ * a filter naming it. That frond loop is the only thing this file knows that `schema-rest`
20
+ * does not; the verbs, the paths and the membership rule all come from there.
21
+ */
22
+ export function tableOf(app) {
23
+ const cached = tables.get(app);
24
+ if (cached)
25
+ return cached;
26
+ const table = app.fronds.flatMap((frond) => generateRoutes(app, {
27
+ prefix: `/${frond.name}`,
28
+ filter: (_entity, frondName) => frondName === frond.name,
29
+ }).map((route) => ({
30
+ method: route.method,
31
+ segments: route.path.split('/').filter(Boolean),
32
+ path: route.path,
33
+ entityName: route.entityName,
34
+ operationName: route.operationName,
35
+ })));
36
+ tables.set(app, table);
37
+ return table;
38
+ }
39
+ /** The captured params if this pattern accepts these segments, else null. */
40
+ function paramsOf(route, segments) {
41
+ if (route.segments.length !== segments.length)
42
+ return null;
43
+ const params = {};
44
+ for (let i = 0; i < segments.length; i++) {
45
+ const pattern = route.segments[i];
46
+ if (pattern.startsWith(':'))
47
+ params[pattern.slice(1)] = decodeURIComponent(segments[i]);
48
+ else if (pattern !== segments[i])
49
+ return null;
50
+ }
51
+ return params;
52
+ }
53
+ /** How many segments a pattern leaves open — fewer is more specific. */
54
+ function openness(route) {
55
+ return route.segments.filter((s) => s.startsWith(':')).length;
56
+ }
57
+ /**
58
+ * Verbs this door accepts in place of the one the table names.
59
+ *
60
+ * `deriveMethod` gives `update` a single verb, PUT, while this door has always served
61
+ * PATCH on it too — and says so (`docs/infra/surfaces`, "PUT · PATCH"). An alias keeps
62
+ * that promise without giving the table a second row: both are mutations on a row, so
63
+ * nothing is widened, and the one thing the table decides — WHICH operation a path names —
64
+ * is still decided there alone.
65
+ */
66
+ const ALIASES = { PATCH: 'PUT' };
67
+ /**
68
+ * Path first, method second — a router's order, and what makes a 405 possible at all.
69
+ *
70
+ * The order matters where the two overlap: `/posts/publish` and `/posts/:id` both accept
71
+ * `GET /posts/publish`. Taking the most specific path first means the answer is "that verb
72
+ * is refused here", not `findById('publish')` — and never `publish()`, which is what this
73
+ * door used to do with the caller's session cookie attached.
74
+ */
75
+ export function matchRoute(table, method, segments) {
76
+ const matches = table
77
+ .map((route) => ({ route, params: paramsOf(route, segments) }))
78
+ .filter((m) => m.params !== null);
79
+ if (matches.length === 0)
80
+ return null;
81
+ const best = Math.min(...matches.map((m) => openness(m.route)));
82
+ const candidates = matches.filter((m) => openness(m.route) === best);
83
+ const wanted = ALIASES[method] ?? method;
84
+ const matched = candidates.find((m) => m.route.method === wanted);
85
+ if (matched)
86
+ return { kind: 'match', route: matched.route, params: matched.params };
87
+ return {
88
+ kind: 'method-not-allowed',
89
+ allow: [...new Set(candidates.map((m) => m.route.method))].sort(),
90
+ };
91
+ }
92
+ //# sourceMappingURL=rest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rest.js","sourceRoot":"","sources":["../src/rest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,110 @@
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
+ */
14
+ import { type App, type FrondCall, type InvocationContext } from '@fougere/core';
15
+ /** What a host must read off the request before any decision is possible. */
16
+ export interface DoorRequest {
17
+ method: string;
18
+ /** Path with no query string. The REST door strips its own `/api` prefix. */
19
+ path: string;
20
+ query: Record<string, string>;
21
+ body?: unknown;
22
+ /**
23
+ * The server-resolved session. Stamped by the host from what IT resolved —
24
+ * never taken from the wire, which is the whole trust boundary of the browser
25
+ * door (`transport/http/src/server.ts` carries the same warning for the split).
26
+ */
27
+ state: Record<string, unknown>;
28
+ }
29
+ /** What a host must write back. `pass` is the one that keeps a door additive. */
30
+ export type Outcome =
31
+ /** Not ours — the host's own routes must still reach their handler. */
32
+ {
33
+ kind: 'pass';
34
+ } | {
35
+ kind: 'ok';
36
+ status: number;
37
+ body: unknown;
38
+ } | {
39
+ kind: 'error';
40
+ status: number;
41
+ body: {
42
+ message: string;
43
+ } & Record<string, unknown>;
44
+ headers?: Record<string, string>;
45
+ };
46
+ /**
47
+ * The audience this door serves — the path segment after `/_fougere/call`.
48
+ *
49
+ * The envelope is a surface like REST and GraphQL, so it selects its audience like
50
+ * they do; the difference is only that it takes it from the path instead of an
51
+ * option, because a door is mounted, not called. The same word names the directory
52
+ * (`handlers/public/`), the config key (`surfaces: { public: [...] }`) and this
53
+ * segment — derived, never configured.
54
+ *
55
+ * No escalation to guard: a named surface serves the entities it names and nothing
56
+ * else (closed by naming), so every one of them is a subset of what the bare path
57
+ * already serves.
58
+ */
59
+ export declare function surfaceOf(path: string): string | undefined;
60
+ /**
61
+ * Receiving end for the browser — same wire as process-to-process (JSON-RPC),
62
+ * different trust boundary: the browser sits outside the topology, so `state` is
63
+ * whatever the host resolved server-side, never what the payload claims.
64
+ *
65
+ * The runner follows the app's topology: local façades and remote doublures alike
66
+ * — the browser never knows where a Frond lives.
67
+ */
68
+ export declare function serveRpc(app: App, request: Pick<DoorRequest, 'path' | 'body' | 'state'>): Promise<unknown>;
69
+ /** The answer a host returns when it could not even parse the payload. */
70
+ export declare function rpcParseError(): {
71
+ jsonrpc: '2.0';
72
+ id: null;
73
+ error: {
74
+ code: number;
75
+ message: string;
76
+ };
77
+ };
78
+ /**
79
+ * Match the URL against the canonical table, invoke the call it names, shape the
80
+ * result for HTTP. The decision lives in `rest.ts`; dispatch belongs to the runner.
81
+ *
82
+ * `path` is what follows the REST mount point, so `/api/blog/posts/1` arrives as
83
+ * `blog/posts/1`. A path this door does not serve returns `pass`, and that is what
84
+ * lets an app keep its own `/api/*` handlers.
85
+ */
86
+ export declare function serveRest(app: App, request: DoorRequest): Promise<Outcome>;
87
+ /**
88
+ * What an operation's return becomes on the wire.
89
+ *
90
+ * Separate from `serveRest` because it is a DECISION and dispatch is not: it can be
91
+ * pinned without a runner, and both hosts get it whether the call ran in memory or
92
+ * came back over JSON-RPC.
93
+ */
94
+ export declare function shapeRest(operationName: string, result: unknown): Outcome;
95
+ type EntityClass = {
96
+ name: string;
97
+ };
98
+ type CallInput = Partial<InvocationContext>;
99
+ /**
100
+ * Name a call server-side and let the runner place it — local façade → direct
101
+ * in-memory execution, a frond in `remotes` → JSON-RPC on the wire. The caller
102
+ * never knows which.
103
+ *
104
+ * `state` is explicit here. Each host wraps this with its own way of finding the
105
+ * current request (Nitro's async context, Next's `headers()` scope), because that
106
+ * is the one part a host actually owns.
107
+ */
108
+ export declare function invokeOn<T = unknown>(app: App, target: EntityClass | FrondCall, opOrInput?: string | CallInput, input?: CallInput, state?: Record<string, unknown>): Promise<T>;
109
+ export {};
110
+ //# sourceMappingURL=serve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../src/serve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAIL,KAAK,GAAG,EACR,KAAK,SAAS,EACd,KAAK,iBAAiB,EACvB,MAAM,eAAe,CAAC;AAIvB,6EAA6E;AAC7E,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED,iFAAiF;AACjF,MAAM,MAAM,OAAO;AACjB,uEAAuE;AACrE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAC7C;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAAC;AAI7H;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAG1D;AAED;;;;;;;GAOG;AACH,wBAAsB,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAGhH;AAED,0EAA0E;AAC1E,wBAAgB,aAAa;aACT,KAAK;;;;;;EACxB;AAID;;;;;;;GAOG;AACH,wBAAsB,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAyChF;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAkBzE;AAID,KAAK,WAAW,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AACpC,KAAK,SAAS,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAE5C;;;;;;;;GAQG;AACH,wBAAsB,QAAQ,CAAC,CAAC,GAAG,OAAO,EACxC,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,WAAW,GAAG,SAAS,EAC/B,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,EAC9B,KAAK,CAAC,EAAE,SAAS,EACjB,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAClC,OAAO,CAAC,CAAC,CAAC,CAMZ"}
package/dist/serve.js ADDED
@@ -0,0 +1,136 @@
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
+ */
14
+ import { createAppRunner, callValueOf, toHttpError, } from '@fougere/core';
15
+ import { handleRpc, PARSE_ERROR } from '@fougere/transport-http';
16
+ import { matchRoute, tableOf } from './rest.js';
17
+ // ── 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
+ */
31
+ export function surfaceOf(path) {
32
+ const named = /^\/_fougere\/call\/([A-Za-z0-9_-]+)/.exec(path.replace(/\?.*$/, ''));
33
+ return named?.[1];
34
+ }
35
+ /**
36
+ * Receiving end for the browser — same wire as process-to-process (JSON-RPC),
37
+ * different trust boundary: the browser sits outside the topology, so `state` is
38
+ * whatever the host resolved server-side, never what the payload claims.
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.
42
+ */
43
+ export async function serveRpc(app, request) {
44
+ const runner = createAppRunner(app, surfaceOf(request.path));
45
+ return handleRpc((call, invocation) => runner(call, { ...invocation, state: request.state }), request.body);
46
+ }
47
+ /** The answer a host returns when it could not even parse the payload. */
48
+ export function rpcParseError() {
49
+ return { jsonrpc: '2.0', id: null, error: { code: PARSE_ERROR, message: 'Parse error' } };
50
+ }
51
+ // ── REST ─────────────────────────────────────────
52
+ /**
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.
59
+ */
60
+ export async function serveRest(app, request) {
61
+ // The app decides, not the host. A route file may exist and a middleware may be
62
+ // installed; if `fougere.config.ts` does not declare `adapters: { rest: true }`,
63
+ // this serves nothing and the request carries on to whatever the app itself routes.
64
+ if (!app.adapters?.rest)
65
+ return { kind: 'pass' };
66
+ const segments = request.path.split('/').filter(Boolean);
67
+ if (segments.length < 2)
68
+ return { kind: 'pass' };
69
+ const method = request.method.toUpperCase();
70
+ const match = matchRoute(tableOf(app), method, segments);
71
+ if (!match)
72
+ return { kind: 'pass' };
73
+ if (match.kind === 'method-not-allowed') {
74
+ return {
75
+ kind: 'error',
76
+ status: 405,
77
+ body: {
78
+ message: `Method ${method} not allowed on /${request.path} — try ${match.allow.join(', ')}`,
79
+ allow: match.allow,
80
+ },
81
+ headers: { allow: match.allow.join(', ') },
82
+ };
83
+ }
84
+ const { route, params } = match;
85
+ let result;
86
+ try {
87
+ result = await invokeOn(app, { entity: route.entityName, op: route.operationName }, { params, query: request.query, body: request.body }, undefined, request.state);
88
+ }
89
+ catch (err) {
90
+ const { status, body } = toHttpError(err);
91
+ return { kind: 'error', status, body: body };
92
+ }
93
+ return shapeRest(route.operationName, result);
94
+ }
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
+ */
102
+ export function shapeRest(operationName, result) {
103
+ if (result === null)
104
+ return { kind: 'error', status: 404, body: { message: 'Not found' } };
105
+ // A list reads as { items, total, hasMore, endCursor } on the wire — the page-level
106
+ // facts ride beside the rows instead of on the array, where JSON drops them.
107
+ if (operationName === 'list' && Array.isArray(result)) {
108
+ const page = result;
109
+ return {
110
+ kind: 'ok',
111
+ status: 200,
112
+ body: { items: [...result], total: page.total, hasMore: page.hasMore, endCursor: page.endCursor },
113
+ };
114
+ }
115
+ // 200 on every verb, including POST — what the Nuxt door has always answered. A 201
116
+ // would be better REST and is a change of behaviour, so it belongs to `schema-rest`
117
+ // (which owns what a verb means) and not to a refactor that moved this code.
118
+ return { kind: 'ok', status: 200, body: result };
119
+ }
120
+ /**
121
+ * Name a call server-side and let the runner place it — local façade → direct
122
+ * in-memory execution, a frond in `remotes` → JSON-RPC on the wire. The caller
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.
128
+ */
129
+ export async function invokeOn(app, target, opOrInput, input, state = {}) {
130
+ const { call, invocation } = callValueOf(target, opOrInput, input);
131
+ // An explicit `state` on the input wins over the request's — the caller who spells
132
+ // it is answering for it, which is what makes a call outside any request possible.
133
+ const given = typeof opOrInput === 'string' ? input : opOrInput;
134
+ return (await createAppRunner(app)(call, { ...invocation, state: given?.state ?? state }));
135
+ }
136
+ //# sourceMappingURL=serve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve.js","sourceRoot":"","sources":["../src/serve.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The session view — the one place that turns the server-resolved
3
+ * request context (filled by the auth middleware) into what the client
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.
9
+ */
10
+ export interface SessionView {
11
+ user: Record<string, unknown> | null;
12
+ }
13
+ export declare function sessionViewOf(context: Record<string, unknown>): SessionView;
14
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The session view — the one place that turns the server-resolved
3
+ * request context (filled by the auth middleware) into what the client
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.
9
+ */
10
+ export function sessionViewOf(context) {
11
+ const raw = context.user;
12
+ if (!raw)
13
+ return { user: null };
14
+ const { passwordHash: _passwordHash, ...user } = raw;
15
+ return { user };
16
+ }
17
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;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"}
@@ -0,0 +1,3 @@
1
+ /** The request state for these headers. Empty when no auth is declared, or nobody is signed in. */
2
+ export declare function stateFor(headers: Headers): Promise<Record<string, unknown>>;
3
+ //# sourceMappingURL=state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAiBA,mGAAmG;AACnG,wBAAsB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAcjF"}
package/dist/state.js ADDED
@@ -0,0 +1,32 @@
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
+ */
12
+ import { useFougereApp } from './boot.js';
13
+ /** The request state for these headers. Empty when no auth is declared, or nobody is signed in. */
14
+ export async function stateFor(headers) {
15
+ const app = await useFougereApp();
16
+ if (!app.auth)
17
+ return {};
18
+ // No cookie, no session — asking the provider would be a round-trip for a known answer.
19
+ if (!headers.get('cookie'))
20
+ return {};
21
+ try {
22
+ const result = await app.auth.api.getSession({ headers });
23
+ if (result?.session && result?.user)
24
+ return { user: result.user, session: result.session };
25
+ }
26
+ catch {
27
+ // An unreachable or misconfigured provider leaves the caller anonymous rather
28
+ // than failing the request — the same choice the Nuxt middleware makes.
29
+ }
30
+ return {};
31
+ }
32
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,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 ADDED
@@ -0,0 +1,25 @@
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
+ */
6
+ 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
+ */
15
+ export declare function fougereRest(request: Request): Promise<Response>;
16
+ /** The session view over the wire, for a client refreshing after login or logout. */
17
+ 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
+ */
24
+ export declare function fougereGraphQL(request: Request): Promise<Response>;
25
+ //# sourceMappingURL=web.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../src/web.ts"],"names":[],"mappings":"AAoBA;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAqBrE;AAED;;;;;;;GAOG;AACH,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;;;;;GAKG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAkBxE"}
package/dist/web.js ADDED
@@ -0,0 +1,93 @@
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
+ */
14
+ import { serveRest, serveRpc, rpcParseError, useFougereApp, serveGraphQL } from './index.js';
15
+ import { sessionViewOf } from './session.js';
16
+ import { stateFor } from './state.js';
17
+ const MAX_BODY_BYTES = 1024 * 1024;
18
+ 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
+ */
24
+ export async function fougereCall(request) {
25
+ const declared = Number(request.headers.get('content-length'));
26
+ if (Number.isFinite(declared) && declared > MAX_BODY_BYTES) {
27
+ return Response.json({ message: 'Payload too large' }, { status: 413 });
28
+ }
29
+ const app = await useFougereApp();
30
+ let body;
31
+ try {
32
+ body = await request.json();
33
+ }
34
+ catch {
35
+ return Response.json(rpcParseError());
36
+ }
37
+ return Response.json(await serveRpc(app, {
38
+ path: new URL(request.url).pathname,
39
+ body,
40
+ state: await stateFor(request.headers),
41
+ }));
42
+ }
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
+ */
51
+ export async function fougereRest(request) {
52
+ const app = await useFougereApp();
53
+ const url = new URL(request.url);
54
+ const method = request.method.toUpperCase();
55
+ const outcome = await serveRest(app, {
56
+ method,
57
+ path: url.pathname.replace(/^\/api\//, ''),
58
+ query: Object.fromEntries(url.searchParams),
59
+ body: WITH_BODY.has(method) ? await request.json().catch(() => undefined) : undefined,
60
+ state: await stateFor(request.headers),
61
+ });
62
+ if (outcome.kind === 'pass') {
63
+ return Response.json({ message: `No route for ${method} ${url.pathname}` }, { status: 404 });
64
+ }
65
+ if (outcome.kind === 'error') {
66
+ return Response.json(outcome.body, { status: outcome.status, headers: outcome.headers });
67
+ }
68
+ return Response.json(outcome.body, { status: outcome.status });
69
+ }
70
+ /** The session view over the wire, for a client refreshing after login or logout. */
71
+ export async function fougereSession(request) {
72
+ return Response.json(sessionViewOf(await stateFor(request.headers)));
73
+ }
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
+ */
80
+ export async function fougereGraphQL(request) {
81
+ const app = await useFougereApp();
82
+ const body = (await request.json().catch(() => ({})));
83
+ const outcome = await serveGraphQL(app, {
84
+ ...body,
85
+ surface: new URL(request.url).pathname.split('/').filter(Boolean)[1],
86
+ state: await stateFor(request.headers),
87
+ });
88
+ if (outcome.kind === 'pass') {
89
+ return Response.json({ message: 'GraphQL is not served by this app' }, { status: 404 });
90
+ }
91
+ return Response.json(outcome.body, { status: outcome.status });
92
+ }
93
+ //# sourceMappingURL=web.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web.js","sourceRoot":"","sources":["../src/web.ts"],"names":[],"mappings":"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"}