@cosmicdrift/kumiko-dev-server 0.38.0 → 0.40.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/package.json +3 -3
- package/src/__tests__/create-kumiko-server.integration.test.ts +51 -0
- package/src/__tests__/run-prod-app-env-source.test.ts +39 -0
- package/src/create-kumiko-server.ts +7 -27
- package/src/extra-routes-deps.ts +47 -0
- package/src/run-dev-app.ts +6 -0
- package/src/run-prod-app.ts +15 -31
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-dev-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.40.0",
|
|
4
4
|
"description": "Development server bootstrap for Kumiko apps. Bundles the client, mints dev-JWTs, injects the resolved AppSchema, and seeds an admin. Not for production.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
"kumiko-schema-check": "./bin/kumiko-schema-check.ts"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@cosmicdrift/kumiko-bundled-features": "0.
|
|
50
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
49
|
+
"@cosmicdrift/kumiko-bundled-features": "0.38.0",
|
|
50
|
+
"@cosmicdrift/kumiko-framework": "0.38.0",
|
|
51
51
|
"ts-morph": "^28.0.0"
|
|
52
52
|
},
|
|
53
53
|
"publishConfig": {
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
createTextField,
|
|
10
10
|
defineFeature,
|
|
11
11
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
12
|
+
import { z } from "zod";
|
|
12
13
|
import { createKumikoServer, type KumikoServerHandle } from "../create-kumiko-server";
|
|
13
14
|
|
|
14
15
|
// Integration-Test: bootet createKumikoServer mit echtem Postgres,
|
|
@@ -27,6 +28,18 @@ const probeEntity = createEntity({
|
|
|
27
28
|
|
|
28
29
|
const probeFeature = defineFeature("dev-server-probe", (r) => {
|
|
29
30
|
r.entity("probe", probeEntity);
|
|
31
|
+
// SystemAdmin-gated write — Ziel des extraRoutes.dispatchSystemWrite-
|
|
32
|
+
// Tests (252/2): Echo von user.tenantId + roles beweist Dispatch durch
|
|
33
|
+
// den echten Dispatcher (Zod + Access-Check) mit Ziel-Tenant-SystemUser.
|
|
34
|
+
r.writeHandler({
|
|
35
|
+
name: "probe-write",
|
|
36
|
+
schema: z.object({ note: z.string() }),
|
|
37
|
+
access: { roles: ["SystemAdmin"] },
|
|
38
|
+
handler: async (event) => ({
|
|
39
|
+
isSuccess: true as const,
|
|
40
|
+
data: { tenantSeen: event.user.tenantId, roles: event.user.roles },
|
|
41
|
+
}),
|
|
42
|
+
});
|
|
30
43
|
});
|
|
31
44
|
|
|
32
45
|
let handle: KumikoServerHandle | undefined;
|
|
@@ -285,3 +298,41 @@ describe("createKumikoServer (Multi-Entry)", () => {
|
|
|
285
298
|
expect(res.status).toBe(404);
|
|
286
299
|
});
|
|
287
300
|
});
|
|
301
|
+
|
|
302
|
+
// 252/2: der Dev-Pfad bekommt dieselbe extraRoutes-deps-Closure wie
|
|
303
|
+
// runProdApp (seit der Extraktion nach extra-routes-deps.ts geteilt) —
|
|
304
|
+
// hier der analoge Beweis gegen createKumikoServer.
|
|
305
|
+
describe("createKumikoServer extraRoutes-deps", () => {
|
|
306
|
+
test("dispatchSystemWrite schreibt als SystemAdmin des Ziel-Tenants, registry verfügbar", async () => {
|
|
307
|
+
const tenantId = "00000000-0000-4000-8000-000000000042";
|
|
308
|
+
let registryHasProbe = false;
|
|
309
|
+
handle = await createKumikoServer({
|
|
310
|
+
features: [probeFeature],
|
|
311
|
+
port: 0,
|
|
312
|
+
installSignalHandlers: false,
|
|
313
|
+
extraRoutes: (app, deps) => {
|
|
314
|
+
registryHasProbe = deps.registry.features.has("dev-server-probe");
|
|
315
|
+
app.post("/webhook-probe", async (c) => {
|
|
316
|
+
const result = await deps.dispatchSystemWrite({
|
|
317
|
+
handlerQn: "dev-server-probe:write:probe-write",
|
|
318
|
+
payload: { note: "from-webhook" },
|
|
319
|
+
tenantId: tenantId as import("@cosmicdrift/kumiko-framework/engine").TenantId,
|
|
320
|
+
});
|
|
321
|
+
return c.json(result);
|
|
322
|
+
});
|
|
323
|
+
},
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
expect(registryHasProbe).toBe(true);
|
|
327
|
+
|
|
328
|
+
const res = await handle.fetch(new Request("http://test/webhook-probe", { method: "POST" }));
|
|
329
|
+
expect(res.status).toBe(200);
|
|
330
|
+
const body = (await res.json()) as {
|
|
331
|
+
isSuccess: boolean;
|
|
332
|
+
data?: { tenantSeen: string; roles: string[] };
|
|
333
|
+
};
|
|
334
|
+
expect(body.isSuccess).toBe(true);
|
|
335
|
+
expect(body.data?.tenantSeen).toBe(tenantId);
|
|
336
|
+
expect(body.data?.roles).toContain("SystemAdmin");
|
|
337
|
+
});
|
|
338
|
+
});
|
|
@@ -92,6 +92,45 @@ describe("runProdApp boot-mode env-source", () => {
|
|
|
92
92
|
await handle.stop();
|
|
93
93
|
});
|
|
94
94
|
|
|
95
|
+
test("boot-mode constructs no eager Redis client — kein TCP-Connect auf REDIS_URL", async () => {
|
|
96
|
+
// Kern-Garantie (224/2), als Netzwerk-Beweis statt Konstruktor-Spy:
|
|
97
|
+
// REDIS_URL zeigt auf einen lokalen Listener — `new Redis(...)`
|
|
98
|
+
// connectet eager, der Boot-Exit MUSS also vorher liegen, sonst
|
|
99
|
+
// zählt der Listener eine Connection.
|
|
100
|
+
let connections = 0;
|
|
101
|
+
const listener = Bun.listen({
|
|
102
|
+
hostname: "127.0.0.1",
|
|
103
|
+
port: 0,
|
|
104
|
+
socket: {
|
|
105
|
+
open(socket) {
|
|
106
|
+
connections += 1;
|
|
107
|
+
socket.end();
|
|
108
|
+
},
|
|
109
|
+
data() {},
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const originalLog = console.log;
|
|
114
|
+
console.log = () => {};
|
|
115
|
+
try {
|
|
116
|
+
const handle = await runProdApp({
|
|
117
|
+
features: [probeFeature],
|
|
118
|
+
autoListen: false,
|
|
119
|
+
migrations: false,
|
|
120
|
+
envSource: { ...DUMMY_ENV, REDIS_URL: `redis://127.0.0.1:${listener.port}` },
|
|
121
|
+
});
|
|
122
|
+
await handle.stop();
|
|
123
|
+
} finally {
|
|
124
|
+
console.log = originalLog;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Ein eager Connect wäre bereits beim runProdApp-await passiert;
|
|
128
|
+
// kleine Nachfrist für asynchrone Socket-Anläufe.
|
|
129
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
130
|
+
listener.stop(true);
|
|
131
|
+
expect(connections).toBe(0);
|
|
132
|
+
});
|
|
133
|
+
|
|
95
134
|
test("resolves PORT from envSource, not process.env", async () => {
|
|
96
135
|
const logs: string[] = [];
|
|
97
136
|
const originalLog = console.log;
|
|
@@ -20,12 +20,7 @@ import { readFile, watch } from "node:fs/promises";
|
|
|
20
20
|
import { tmpdir } from "node:os";
|
|
21
21
|
import { join, resolve } from "node:path";
|
|
22
22
|
import { type AuthRoutesConfig, generateToken } from "@cosmicdrift/kumiko-framework/api";
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
buildAppSchema,
|
|
26
|
-
createSystemUser,
|
|
27
|
-
type FeatureDefinition,
|
|
28
|
-
} from "@cosmicdrift/kumiko-framework/engine";
|
|
23
|
+
import { buildAppSchema, type FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
|
|
29
24
|
import { createEventsTable } from "@cosmicdrift/kumiko-framework/event-store";
|
|
30
25
|
import {
|
|
31
26
|
pushEntityProjectionTables,
|
|
@@ -34,6 +29,7 @@ import {
|
|
|
34
29
|
type TestStackOptions,
|
|
35
30
|
TestUsers,
|
|
36
31
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
32
|
+
import { type ExtraRoutesSystemDeps, makeDispatchSystemWrite } from "./extra-routes-deps";
|
|
37
33
|
import { injectSchema } from "./inject-schema";
|
|
38
34
|
import { canResolveTailwindStylesheet, resolveTailwindCli } from "./resolve-tailwind-cli";
|
|
39
35
|
import { buildBunServeOptions } from "./run-prod-app";
|
|
@@ -193,24 +189,7 @@ export type CreateKumikoServerOptions = {
|
|
|
193
189
|
* Static/HTML-Auslieferung aufgerufen, sodass eigene GETs (/feed.xml,
|
|
194
190
|
* /og-image, …) Vorrang vor dem Dev-Asset-Pfad haben. `deps` statt
|
|
195
191
|
* `ctx` weil dies kein HandlerContext ist — kein user/tenant. */
|
|
196
|
-
readonly extraRoutes?: (
|
|
197
|
-
app: import("hono").Hono,
|
|
198
|
-
deps: {
|
|
199
|
-
db: TestStack["db"];
|
|
200
|
-
redis: TestStack["redis"];
|
|
201
|
-
/** Feature-registry — z.B. für Plugin-Lookups via
|
|
202
|
-
* `registry.getExtensionUsages("subscriptionProvider")`. */
|
|
203
|
-
registry: TestStack["registry"];
|
|
204
|
-
/** System-Write durch den Command-Dispatcher — Semantik identisch zu
|
|
205
|
-
* runProdApp.extraRoutes (SystemAdmin des Ziel-Tenants, kein
|
|
206
|
-
* Access-Check; nur für signatur-authentifizierte Pfade). */
|
|
207
|
-
dispatchSystemWrite: (args: {
|
|
208
|
-
readonly handlerQn: string;
|
|
209
|
-
readonly payload: unknown;
|
|
210
|
-
readonly tenantId: import("@cosmicdrift/kumiko-framework/engine").TenantId;
|
|
211
|
-
}) => Promise<import("@cosmicdrift/kumiko-framework/engine").WriteResult>;
|
|
212
|
-
},
|
|
213
|
-
) => void;
|
|
192
|
+
readonly extraRoutes?: (app: import("hono").Hono, deps: ExtraRoutesSystemDeps) => void;
|
|
214
193
|
};
|
|
215
194
|
|
|
216
195
|
export type KumikoServerHandle = {
|
|
@@ -702,10 +681,11 @@ export async function createKumikoServer(
|
|
|
702
681
|
if (options.extraRoutes !== undefined) {
|
|
703
682
|
options.extraRoutes(stack.app, {
|
|
704
683
|
db: stack.db,
|
|
705
|
-
|
|
684
|
+
// Der nackte ioredis-Client (nicht der TestRedis-Wrapper) —
|
|
685
|
+
// Parität mit runProdApp, App-Code soll in dev+prod dasselbe sehen.
|
|
686
|
+
redis: stack.redis.redis,
|
|
706
687
|
registry: stack.registry,
|
|
707
|
-
dispatchSystemWrite: (
|
|
708
|
-
stack.dispatcher.write(handlerQn, payload, createSystemUser(tenantId, [ROLES.SystemAdmin])),
|
|
688
|
+
dispatchSystemWrite: makeDispatchSystemWrite(stack.dispatcher),
|
|
709
689
|
});
|
|
710
690
|
}
|
|
711
691
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ROLES } from "@cosmicdrift/kumiko-framework/auth";
|
|
2
|
+
import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
|
|
3
|
+
import {
|
|
4
|
+
createSystemUser,
|
|
5
|
+
type Registry,
|
|
6
|
+
type SessionUser,
|
|
7
|
+
type TenantId,
|
|
8
|
+
type WriteResult,
|
|
9
|
+
} from "@cosmicdrift/kumiko-framework/engine";
|
|
10
|
+
import type Redis from "ioredis";
|
|
11
|
+
|
|
12
|
+
/** Deps für `extraRoutes` — geteilt zwischen runProdApp (prod) und
|
|
13
|
+
* createKumikoServer (dev), damit die beiden Pfade nicht driften.
|
|
14
|
+
* Naming: `deps` statt `ctx` weil im Framework `ctx` der HandlerContext
|
|
15
|
+
* mit user/tenant/registry ist — hier ist der Scope absichtlich kleiner
|
|
16
|
+
* (Routes laufen außerhalb der Auth/Tenant-Pipeline). */
|
|
17
|
+
export type ExtraRoutesSystemDeps = {
|
|
18
|
+
readonly db: DbConnection;
|
|
19
|
+
readonly redis: Redis;
|
|
20
|
+
/** Feature-registry — z.B. für Plugin-Lookups via
|
|
21
|
+
* `registry.getExtensionUsages("subscriptionProvider")`. */
|
|
22
|
+
readonly registry: Registry;
|
|
23
|
+
/** Schreibt durch den /api/*-Command-Dispatcher (gleiche Idempotency/
|
|
24
|
+
* Job-Hooks) — aber als auto-konstruierter SystemAdmin des Ziel-
|
|
25
|
+
* Tenants, OHNE Access-Check der Route. Privilege-Scope: SystemAdmin
|
|
26
|
+
* ist die höchste nicht-tenant-scoped Rolle — der Call erreicht JEDEN
|
|
27
|
+
* SystemAdmin-gegateten Handler auf jedem Tenant; das Rollen-Set ist
|
|
28
|
+
* nicht konfigurierbar. Nur für Pfade, die ihre Authentizität selbst
|
|
29
|
+
* beweisen (Provider-Webhook-Signaturen,
|
|
30
|
+
* createSubscriptionWebhookHandler et al.). */
|
|
31
|
+
readonly dispatchSystemWrite: (args: {
|
|
32
|
+
readonly handlerQn: string;
|
|
33
|
+
readonly payload: unknown;
|
|
34
|
+
readonly tenantId: TenantId;
|
|
35
|
+
}) => Promise<WriteResult>;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
type SystemWriteDispatcher = {
|
|
39
|
+
readonly write: (handlerQn: string, payload: unknown, user: SessionUser) => Promise<WriteResult>;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export function makeDispatchSystemWrite(
|
|
43
|
+
dispatcher: SystemWriteDispatcher,
|
|
44
|
+
): ExtraRoutesSystemDeps["dispatchSystemWrite"] {
|
|
45
|
+
return ({ handlerQn, payload, tenantId }) =>
|
|
46
|
+
dispatcher.write(handlerQn, payload, createSystemUser(tenantId, [ROLES.SystemAdmin]));
|
|
47
|
+
}
|
package/src/run-dev-app.ts
CHANGED
|
@@ -90,6 +90,9 @@ export type RunDevAppAuthOptions = {
|
|
|
90
90
|
readonly signup?: SignupSetup;
|
|
91
91
|
/** Tenant-Invite flow (Magic-Link). Symmetric. */
|
|
92
92
|
readonly invite?: InviteSetup;
|
|
93
|
+
/** Domain attribute for both auth cookies (see
|
|
94
|
+
* AuthRoutesConfig.cookieDomain). Symmetric zu RunProdAppAuthOptions. */
|
|
95
|
+
readonly cookieDomain?: string;
|
|
93
96
|
};
|
|
94
97
|
|
|
95
98
|
/** Hook for app-specific seeding (demo data, fixtures). Runs after the
|
|
@@ -268,6 +271,9 @@ export async function runDevApp(options: RunDevAppOptions): Promise<KumikoServer
|
|
|
268
271
|
[AuthErrors.invalidCredentials]: 401,
|
|
269
272
|
[AuthErrors.noMembership]: 403,
|
|
270
273
|
},
|
|
274
|
+
...(options.auth.cookieDomain !== undefined && {
|
|
275
|
+
cookieDomain: options.auth.cookieDomain,
|
|
276
|
+
}),
|
|
271
277
|
...sessionAuthFragment,
|
|
272
278
|
...(options.auth.passwordReset && {
|
|
273
279
|
passwordReset: {
|
package/src/run-prod-app.ts
CHANGED
|
@@ -41,20 +41,16 @@ import { createSessionCallbacks } from "@cosmicdrift/kumiko-bundled-features/ses
|
|
|
41
41
|
import { TenantQueries } from "@cosmicdrift/kumiko-bundled-features/tenant";
|
|
42
42
|
import { UserQueries } from "@cosmicdrift/kumiko-bundled-features/user";
|
|
43
43
|
import { createSseBroker, type SseBroker } from "@cosmicdrift/kumiko-framework/api";
|
|
44
|
-
import { ROLES } from "@cosmicdrift/kumiko-framework/auth";
|
|
45
44
|
import { createDbConnection, type DbRunner } from "@cosmicdrift/kumiko-framework/db";
|
|
46
45
|
import {
|
|
47
46
|
buildAppSchema,
|
|
48
47
|
createRegistry,
|
|
49
|
-
createSystemUser,
|
|
50
48
|
type EffectiveFeaturesResolver,
|
|
51
49
|
type FeatureDefinition,
|
|
52
50
|
findTierResolverUsage,
|
|
53
|
-
type Registry,
|
|
54
51
|
type TenantId,
|
|
55
52
|
type TierResolverPlugin,
|
|
56
53
|
validateBoot,
|
|
57
|
-
type WriteResult,
|
|
58
54
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
59
55
|
import {
|
|
60
56
|
type ApiEntrypoint,
|
|
@@ -86,6 +82,7 @@ import Redis from "ioredis";
|
|
|
86
82
|
import { applyBootSeeds } from "./boot/apply-boot-seeds";
|
|
87
83
|
import { ASSETS_DIR } from "./build-prod-bundle";
|
|
88
84
|
import { buildComposeAuthOptions, composeFeatures } from "./compose-features";
|
|
85
|
+
import { type ExtraRoutesSystemDeps, makeDispatchSystemWrite } from "./extra-routes-deps";
|
|
89
86
|
import { injectSchema } from "./inject-schema";
|
|
90
87
|
import { tryHonoFirst } from "./try-hono-first";
|
|
91
88
|
|
|
@@ -276,6 +273,10 @@ export type RunProdAppAuthOptions = {
|
|
|
276
273
|
* /api/auth/invite-accept-with-login, /api/auth/invite-signup-complete
|
|
277
274
|
* are mounted. */
|
|
278
275
|
readonly invite?: InviteSetup;
|
|
276
|
+
/** Domain attribute for both auth cookies (see
|
|
277
|
+
* AuthRoutesConfig.cookieDomain). Set to the registrable parent
|
|
278
|
+
* domain when login and app live on different subdomains. */
|
|
279
|
+
readonly cookieDomain?: string;
|
|
279
280
|
};
|
|
280
281
|
|
|
281
282
|
/** Hook for app-specific seeding — runs after the admin (when auth is
|
|
@@ -334,6 +335,10 @@ export type HostDispatchResult =
|
|
|
334
335
|
export type HostDispatchFn = (req: {
|
|
335
336
|
readonly host: string;
|
|
336
337
|
readonly path: string;
|
|
338
|
+
/** Query-String inkl. führendem `?`, `""` wenn keiner. Redirects die
|
|
339
|
+
* den Pfad auf einen anderen Host umbiegen (z.B. Auth-Routen mit
|
|
340
|
+
* `?token=` aus alten Mail-Links) MÜSSEN ihn an `to` anhängen. */
|
|
341
|
+
readonly search: string;
|
|
337
342
|
}) => HostDispatchResult;
|
|
338
343
|
|
|
339
344
|
export type RunProdAppOptions = {
|
|
@@ -420,26 +425,7 @@ export type RunProdAppOptions = {
|
|
|
420
425
|
* Naming: `deps` statt `ctx` weil im Framework `ctx` der HandlerContext
|
|
421
426
|
* mit user/tenant/registry ist — hier ist der Scope absichtlich kleiner
|
|
422
427
|
* (Routes laufen außerhalb der Auth/Tenant-Pipeline). */
|
|
423
|
-
readonly extraRoutes?: (
|
|
424
|
-
app: import("hono").Hono,
|
|
425
|
-
deps: {
|
|
426
|
-
db: import("@cosmicdrift/kumiko-framework/db").DbConnection;
|
|
427
|
-
redis: import("ioredis").default;
|
|
428
|
-
/** Feature-registry — z.B. für Plugin-Lookups via
|
|
429
|
-
* `registry.getExtensionUsages("subscriptionProvider")`. */
|
|
430
|
-
registry: Registry;
|
|
431
|
-
/** Schreibt durch den /api/*-Command-Dispatcher (gleiche Idempotency/
|
|
432
|
-
* Job-Hooks) — aber als auto-konstruierter SystemAdmin des Ziel-
|
|
433
|
-
* Tenants, OHNE Access-Check der Route. Nur für Pfade, die ihre
|
|
434
|
-
* Authentizität selbst beweisen (Provider-Webhook-Signaturen,
|
|
435
|
-
* createSubscriptionWebhookHandler et al.). */
|
|
436
|
-
dispatchSystemWrite: (args: {
|
|
437
|
-
readonly handlerQn: string;
|
|
438
|
-
readonly payload: unknown;
|
|
439
|
-
readonly tenantId: TenantId;
|
|
440
|
-
}) => Promise<WriteResult>;
|
|
441
|
-
},
|
|
442
|
-
) => void;
|
|
428
|
+
readonly extraRoutes?: (app: import("hono").Hono, deps: ExtraRoutesSystemDeps) => void;
|
|
443
429
|
/** When true (default), Bun.serve is started before runProdApp resolves —
|
|
444
430
|
* the common case: `await runProdApp({...})` boots the server and the
|
|
445
431
|
* process stays up listening on PORT. Set to false in tests that drive
|
|
@@ -714,6 +700,9 @@ export async function runProdApp(options: RunProdAppOptions): Promise<ProdAppHan
|
|
|
714
700
|
[AuthErrors.invalidCredentials]: 401,
|
|
715
701
|
[AuthErrors.noMembership]: 403,
|
|
716
702
|
},
|
|
703
|
+
...(options.auth.cookieDomain !== undefined && {
|
|
704
|
+
cookieDomain: options.auth.cookieDomain,
|
|
705
|
+
}),
|
|
717
706
|
...sessionAuthFragment,
|
|
718
707
|
...(options.auth.passwordReset && {
|
|
719
708
|
passwordReset: {
|
|
@@ -829,12 +818,7 @@ export async function runProdApp(options: RunProdAppOptions): Promise<ProdAppHan
|
|
|
829
818
|
db,
|
|
830
819
|
redis,
|
|
831
820
|
registry,
|
|
832
|
-
dispatchSystemWrite: (
|
|
833
|
-
entrypoint.dispatcher.write(
|
|
834
|
-
handlerQn,
|
|
835
|
-
payload,
|
|
836
|
-
createSystemUser(tenantId, [ROLES.SystemAdmin]),
|
|
837
|
-
),
|
|
821
|
+
dispatchSystemWrite: makeDispatchSystemWrite(entrypoint.dispatcher),
|
|
838
822
|
});
|
|
839
823
|
}
|
|
840
824
|
|
|
@@ -1027,7 +1011,7 @@ function buildStaticFallback(
|
|
|
1027
1011
|
if (!hostDispatch) return null;
|
|
1028
1012
|
const url = new URL(req.url);
|
|
1029
1013
|
const host = req.headers.get("host") ?? url.host;
|
|
1030
|
-
const result = hostDispatch({ host, path: url.pathname });
|
|
1014
|
+
const result = hostDispatch({ host, path: url.pathname, search: url.search });
|
|
1031
1015
|
if (result.kind === "not-found") {
|
|
1032
1016
|
return new Response("Not Found", { status: 404 });
|
|
1033
1017
|
}
|