@tailorkit/app 0.1.0-beta.29 → 0.1.0-beta.32

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 (53) hide show
  1. package/LICENSE.md +47 -174
  2. package/README.md +37 -38
  3. package/dist/chunk-7ZQPXG7O.js +34 -0
  4. package/dist/chunk-PJBDA2RU.js +415 -0
  5. package/dist/{chunk-STTKSRAI.js → chunk-UDXPIO6D.js} +267 -112
  6. package/dist/{chunk-ANTVMHDZ.js → chunk-YX3JURMG.js} +100 -59
  7. package/dist/client/connection.d.ts +11 -11
  8. package/dist/client/query-store.d.ts +2 -2
  9. package/dist/client/reference.d.ts +4 -4
  10. package/dist/client/session.d.ts +27 -4
  11. package/dist/client/tools.d.ts +11 -5
  12. package/dist/client-connection.js +4 -2
  13. package/dist/client.d.ts +2 -2
  14. package/dist/client.js +3 -3
  15. package/dist/database/definition.d.ts +1 -1
  16. package/dist/database/schema.d.ts +1 -1
  17. package/dist/errors.d.ts +1 -1
  18. package/dist/index.js +6 -6
  19. package/dist/preact.d.ts +5 -5
  20. package/dist/protocol.d.ts +23 -23
  21. package/dist/protocol.js +11 -9
  22. package/dist/routing.d.ts +4 -4
  23. package/dist/server/builder.d.ts +6 -6
  24. package/dist/server/bundle.d.ts +1 -6
  25. package/dist/server/functions.d.ts +19 -19
  26. package/dist/server/instances.d.ts +5 -4
  27. package/dist/server.d.ts +1 -1
  28. package/dist/server.js +4 -4
  29. package/dist/views.d.ts +4 -3
  30. package/package.json +34 -22
  31. package/dist/builder/client-views.d.ts +0 -16
  32. package/dist/builder/database-paths.d.ts +0 -4
  33. package/dist/builder/define-view.d.ts +0 -3
  34. package/dist/builder/drizzle.d.ts +0 -2
  35. package/dist/builder/file-routes.d.ts +0 -34
  36. package/dist/builder/generate-migrations.d.ts +0 -6
  37. package/dist/builder/index.d.ts +0 -11
  38. package/dist/builder/instances.d.ts +0 -19
  39. package/dist/builder/migrations.d.ts +0 -6
  40. package/dist/builder/schema-check.d.ts +0 -2
  41. package/dist/builder/server.d.ts +0 -6
  42. package/dist/builder/upload-manifest.d.ts +0 -20
  43. package/dist/builder.js +0 -1531
  44. package/dist/chunk-DA2TE2FW.js +0 -27
  45. package/dist/chunk-IJBQ77AH.js +0 -35
  46. package/dist/chunk-L5ECQGR7.js +0 -26
  47. package/dist/chunk-SWBQZDWT.js +0 -321
  48. package/dist/config/config.d.ts +0 -86
  49. package/dist/config/index.d.ts +0 -1
  50. package/dist/config/loader.d.ts +0 -7
  51. package/dist/config-loader.js +0 -7
  52. package/dist/config.js +0 -8
  53. package/dist/preact-version.d.ts +0 -7
package/dist/builder.js DELETED
@@ -1,1531 +0,0 @@
1
- import {
2
- loadTailorKitConfig
3
- } from "./chunk-L5ECQGR7.js";
4
- import "./chunk-DA2TE2FW.js";
5
-
6
- // src/builder/index.ts
7
- import path9 from "path";
8
- import { createRequire as createRequire2 } from "module";
9
- import { readFile as readFile4, realpath, writeFile as writeFile3 } from "fs/promises";
10
-
11
- // ../asset-delivery/dist/logo-validation.mjs
12
- var maxLogoBytes = 262144;
13
- var maxLogoDimension = 2048;
14
- var readPngDimensions = (content) => {
15
- if (content.length < 33 || ![
16
- 137,
17
- 80,
18
- 78,
19
- 71,
20
- 13,
21
- 10,
22
- 26,
23
- 10
24
- ].every((byte, index) => content[index] === byte)) throw new Error("Logo content is not a valid PNG file.");
25
- const view = new DataView(content.buffer, content.byteOffset, content.byteLength);
26
- const isIhdr = content[12] === 73 && content[13] === 72 && content[14] === 68 && content[15] === 82;
27
- if (view.getUint32(8) !== 13 || !isIhdr) throw new Error("Logo content is not a valid PNG file.");
28
- return {
29
- width: view.getUint32(16),
30
- height: view.getUint32(20)
31
- };
32
- };
33
- var readWebpDimensions = (content) => {
34
- const text = (start, end) => new TextDecoder().decode(content.subarray(start, end));
35
- if (content.length < 30 || text(0, 4) !== "RIFF" || text(8, 12) !== "WEBP") throw new Error("Logo content is not a valid WebP file.");
36
- const view = new DataView(content.buffer, content.byteOffset, content.byteLength);
37
- const chunk = text(12, 16);
38
- if (chunk === "VP8X") return {
39
- width: 1 + view.getUint16(24, true) + (content[26] ?? 0) * 65536,
40
- height: 1 + view.getUint16(27, true) + (content[29] ?? 0) * 65536
41
- };
42
- if (chunk === "VP8L" && content[20] === 47) {
43
- const bits = view.getUint32(21, true);
44
- return {
45
- width: bits % 16384 + 1,
46
- height: Math.floor(bits / 16384) % 16384 + 1
47
- };
48
- }
49
- if (chunk === "VP8 " && content[23] === 157 && content[24] === 1 && content[25] === 42) return {
50
- width: view.getUint16(26, true) % 16384,
51
- height: view.getUint16(28, true) % 16384
52
- };
53
- throw new Error("Logo content is not a supported WebP file.");
54
- };
55
- var validateSvg = (content) => {
56
- const svg = new TextDecoder("utf-8", {
57
- fatal: true,
58
- ignoreBOM: false
59
- }).decode(content);
60
- if (!/<svg(?:\s|>)/iu.test(svg)) throw new Error("Logo content is not a valid SVG file.");
61
- if (/<(?:script|foreignObject|iframe|object|embed)(?:\s|>)/iu.test(svg) || /\son[a-z]+\s*=/iu.test(svg) || /(?:href|src)\s*=\s*["'](?!#)/iu.test(svg) || /(?:@import|url\(\s*(?!["']?#))/iu.test(svg) || /<!DOCTYPE|<!ENTITY|<\?xml-stylesheet/iu.test(svg)) throw new Error("SVG logos cannot contain scripts or external resources.");
62
- };
63
- function validateLogoAsset(content, contentType) {
64
- if (content.byteLength < 1 || content.byteLength > 262144) throw new Error(`Logo must be between 1 and ${maxLogoBytes} bytes.`);
65
- if (contentType === "image/svg+xml") {
66
- validateSvg(content);
67
- return {};
68
- }
69
- const dimensions = contentType === "image/png" ? readPngDimensions(content) : readWebpDimensions(content);
70
- if (dimensions.width < 1 || dimensions.height < 1 || dimensions.width > 2048 || dimensions.height > 2048) throw new Error(`Raster logos cannot exceed ${maxLogoDimension} by ${maxLogoDimension} pixels.`);
71
- return dimensions;
72
- }
73
-
74
- // src/builder/index.ts
75
- import { build as viteBuild } from "vite";
76
-
77
- // src/preact-version.ts
78
- var MINIMUM_PREACT_MAJOR_VERSION = 11;
79
- var parsePreactVersion = (version) => {
80
- const match = /^(\d+)\./u.exec(version);
81
- if (!match?.[1]) {
82
- throw new Error(`Unable to parse Preact version "${version}".`);
83
- }
84
- return {
85
- major: Number.parseInt(match[1], 10),
86
- version
87
- };
88
- };
89
- var assertSupportedPreactVersion = (version) => {
90
- const check = parsePreactVersion(version);
91
- if (check.major < MINIMUM_PREACT_MAJOR_VERSION) {
92
- throw new Error(
93
- `TailorKit requires Preact ${MINIMUM_PREACT_MAJOR_VERSION}.0.0 or newer, but found ${version}.`
94
- );
95
- }
96
- return check;
97
- };
98
-
99
- // src/builder/client-views.ts
100
- import { execFile } from "child_process";
101
- import { promisify } from "util";
102
-
103
- // src/builder/upload-manifest.ts
104
- import { z } from "zod";
105
- var tailorkitUploadManifestSchema = z.object({
106
- assets: z.object({
107
- client: z.literal("client/client.js"),
108
- server: z.literal("server/server.js").optional(),
109
- logos: z.object({
110
- dark: z.string().regex(/^client\/logo-dark\.(?:png|svg|webp)$/u).optional(),
111
- light: z.string().regex(/^client\/logo-light\.(?:png|svg|webp)$/u).optional()
112
- }).optional()
113
- }),
114
- views: z.array(
115
- z.object({
116
- slot: z.string().min(1).max(255),
117
- path: z.string().startsWith("/").max(1024),
118
- instances: z.literal(true).optional(),
119
- disabled: z.literal(true).optional()
120
- })
121
- ).max(1e3).optional(),
122
- version: z.literal(1)
123
- });
124
- var createTailorKitUploadManifest = (logos, server = false, views) => tailorkitUploadManifestSchema.parse({
125
- assets: {
126
- client: "client/client.js",
127
- ...server ? { server: "server/server.js" } : {},
128
- ...logos && Object.keys(logos).length > 0 ? { logos } : {}
129
- },
130
- views,
131
- version: 1
132
- });
133
-
134
- // src/builder/client-views.ts
135
- var inspectClient = `
136
- import { readFile } from "node:fs/promises";
137
- const source = await readFile(process.argv[1], "utf8");
138
- const { default: client } = await import("data:text/javascript;base64," + Buffer.from(source).toString("base64"));
139
- if (!client?.slots || typeof client.slots !== "object") throw new Error("Invalid generated app client: missing slots");
140
- const instanceResolvers = [];
141
- const views = Object.entries(client.slots).flatMap(([slot, definitions]) =>
142
- Object.entries(definitions).flatMap(([path, view]) => {
143
- if (view === false) return [{ slot, path, disabled: true }];
144
- const resolver = view?.instances?.resolver;
145
- if (resolver !== undefined) instanceResolvers.push({ slot, path, resolver });
146
- return [{ slot, path, ...(resolver !== undefined ? { instances: true } : {}) }];
147
- })
148
- );
149
- process.stdout.write("\\nTAILORKIT_VIEWS=" + JSON.stringify({ views, instanceResolvers }));
150
- process.exit(0);
151
- `;
152
- async function readClientManifest(filename) {
153
- try {
154
- const { stdout } = await promisify(execFile)(
155
- process.execPath,
156
- ["--input-type=module", "--eval", inspectClient, filename],
157
- { timeout: 1e4, maxBuffer: 2 * 1024 * 1024 }
158
- );
159
- const result = stdout.slice(
160
- stdout.lastIndexOf("\nTAILORKIT_VIEWS=") + "\nTAILORKIT_VIEWS=".length
161
- );
162
- const manifest = JSON.parse(result);
163
- const views = tailorkitUploadManifestSchema.shape.views.unwrap().parse(manifest.views);
164
- if (!Array.isArray(manifest.instanceResolvers) || manifest.instanceResolvers.some(
165
- (ref) => !ref || typeof ref !== "object" || typeof ref.slot !== "string" || typeof ref.path !== "string" || typeof ref.resolver !== "string" || !/^i[0-9a-f]{24}$/u.test(ref.resolver)
166
- )) {
167
- throw new Error("Invalid instance resolver references");
168
- }
169
- return { views, instanceResolvers: manifest.instanceResolvers };
170
- } catch (error) {
171
- throw new Error(
172
- "Unable to read app views during the build. Keep browser-only side effects inside components or effects. Check the default exports of your roots, layouts and .view.tsx files.",
173
- { cause: error }
174
- );
175
- }
176
- }
177
-
178
- // src/builder/server.ts
179
- import { mkdir as mkdir2, mkdtemp as mkdtemp3, writeFile as writeFile2, cp, rm as rm3 } from "fs/promises";
180
- import path7 from "path";
181
- import { build } from "vite";
182
-
183
- // src/builder/migrations.ts
184
- import { createHash } from "crypto";
185
- import { readdir, readFile } from "fs/promises";
186
- import path from "path";
187
- async function readAppMigrations(directory, optional = false) {
188
- const entries = await readdir(directory, { withFileTypes: true }).catch((error) => {
189
- if (error.code === "ENOENT") {
190
- if (optional) return [];
191
- throw new Error(
192
- `App migrations directory is missing: ${directory}. Run tailorkit db generate and commit migrations before building.`,
193
- { cause: error }
194
- );
195
- }
196
- throw error;
197
- });
198
- const migrations = [];
199
- const files = [directory];
200
- const names = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
201
- for (const name of names) {
202
- if (!/^\d{14}_[\w-]+$/u.test(name)) {
203
- throw new Error(`Unsupported app migration folder: ${name}. Use Drizzle Kit v1 migrations.`);
204
- }
205
- const file = path.join(directory, name, "migration.sql");
206
- const sql = await readFile(file, "utf8");
207
- const statements = sql.split("--> statement-breakpoint").map((part) => part.trim()).filter(Boolean);
208
- if (!statements.length) throw new Error(`App migration is empty: ${name}`);
209
- migrations.push({
210
- id: name,
211
- hash: createHash("sha256").update(sql).digest("hex"),
212
- statements
213
- });
214
- files.push(file);
215
- }
216
- return { migrations, files };
217
- }
218
-
219
- // src/server/bundle.ts
220
- var runtimeManifest = {
221
- apiVersion: 1,
222
- requires: ["database", "actions", "database-relations", "tools"]
223
- };
224
-
225
- // src/builder/schema-check.ts
226
- import { execFile as execFile2 } from "child_process";
227
- import { access as access2, mkdtemp, readFile as readFile2, readdir as readdir2, rm } from "fs/promises";
228
- import { tmpdir } from "os";
229
- import path4 from "path";
230
- import { promisify as promisify2 } from "util";
231
-
232
- // src/builder/drizzle.ts
233
- import { access } from "fs/promises";
234
- import { createRequire } from "module";
235
- import path3 from "path";
236
-
237
- // src/builder/database-paths.ts
238
- import path2 from "path";
239
- function appDatabasePaths(root, migrations) {
240
- return {
241
- schema: path2.join(root, "src/db/schema.ts"),
242
- migrations: path2.resolve(root, migrations ?? "./src/db/migrations")
243
- };
244
- }
245
-
246
- // src/builder/drizzle.ts
247
- async function drizzleGenerateArguments(root, output, name) {
248
- const require2 = createRequire(path3.join(root, "package.json"));
249
- for (const directory of require2.resolve.paths("drizzle-kit") ?? []) {
250
- const cli = path3.join(directory, "drizzle-kit/bin.cjs");
251
- if (await access(cli).then(() => true).catch(() => false)) {
252
- return [
253
- "--preserve-symlinks-main",
254
- cli,
255
- "generate",
256
- "--dialect",
257
- "sqlite",
258
- "--schema",
259
- appDatabasePaths(root).schema,
260
- "--out",
261
- output,
262
- ...name === void 0 ? [] : ["--name", name]
263
- ];
264
- }
265
- }
266
- throw new Error(
267
- "Install drizzle-kit and drizzle-orm as dev dependencies, then run tailorkit db generate."
268
- );
269
- }
270
-
271
- // src/builder/schema-check.ts
272
- var run = promisify2(execFile2);
273
- async function snapshot(directory) {
274
- const entries = await readdir2(directory, { withFileTypes: true }).catch((error) => {
275
- if (error.code === "ENOENT") return [];
276
- throw error;
277
- });
278
- const latest = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort().at(-1);
279
- if (!latest) return;
280
- const filename = path4.join(directory, latest, "snapshot.json");
281
- const value = JSON.parse(await readFile2(filename, "utf8"));
282
- if (value.version !== "7" || value.dialect !== "sqlite" || !Array.isArray(value.ddl)) {
283
- throw new Error(
284
- `Unsupported app schema snapshot: ${filename}. Use Drizzle Kit v1 SQLite migrations.`
285
- );
286
- }
287
- return value;
288
- }
289
- function canonical(value) {
290
- if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
291
- if (value && typeof value === "object") {
292
- return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${canonical(item)}`).join(",")}}`;
293
- }
294
- return JSON.stringify(value);
295
- }
296
- async function checkAppSchema(root, migrationsDirectory) {
297
- const schema = appDatabasePaths(root).schema;
298
- try {
299
- await access2(schema);
300
- } catch (error) {
301
- if (error.code === "ENOENT") return;
302
- throw error;
303
- }
304
- const temporary = await mkdtemp(path4.join(tmpdir(), "tailorkit-schema-check-"));
305
- try {
306
- const { stdout } = await run(
307
- process.execPath,
308
- await drizzleGenerateArguments(root, temporary),
309
- {
310
- cwd: root,
311
- timeout: 3e4,
312
- maxBuffer: 1024 * 1024,
313
- env: { ...process.env, CI: "true" }
314
- }
315
- ).catch((error) => {
316
- throw new Error(
317
- `Could not check the app database schema with Drizzle Kit. Check ${path4.relative(root, schema)} and your drizzle-kit/drizzle-orm dependencies.`,
318
- { cause: error }
319
- );
320
- });
321
- const current = await snapshot(temporary);
322
- if (!current && !stdout.includes("No schema changes")) {
323
- throw new Error(
324
- `Drizzle Kit did not produce a schema snapshot. Check ${path4.relative(root, schema)} and run tailorkit db generate.`
325
- );
326
- }
327
- const saved = await snapshot(migrationsDirectory);
328
- const expected = (current?.ddl ?? []).map(canonical).sort();
329
- const actual = (saved?.ddl ?? []).map(canonical).sort();
330
- if (canonical(expected) !== canonical(actual)) {
331
- throw new Error(
332
- `App database migrations are missing or out of sync with ${path4.relative(root, schema)}. Run tailorkit db generate, commit the generated files in ${path4.relative(root, migrationsDirectory)}, then build again.`
333
- );
334
- }
335
- } finally {
336
- await rm(temporary, { recursive: true, force: true });
337
- }
338
- }
339
-
340
- // src/builder/instances.ts
341
- import { createHash as createHash2 } from "crypto";
342
- import { readFile as readFile3 } from "fs/promises";
343
- import path6 from "path";
344
- import { parseSync as parseSync2, transformWithOxc } from "vite";
345
-
346
- // src/builder/file-routes.ts
347
- import { readdir as readdir3, mkdir, mkdtemp as mkdtemp2, writeFile, rm as rm2 } from "fs/promises";
348
- import { existsSync, watch } from "fs";
349
- import path5 from "path";
350
- import { parseSync } from "vite";
351
-
352
- // src/builder/define-view.ts
353
- var clientImports = /* @__PURE__ */ new Set([
354
- "tailorkit/client",
355
- "tailorkit/app",
356
- "tailorkit/app/client",
357
- "@tailorkit/app/client",
358
- "@tailorkit/app"
359
- ]);
360
- function createScope(parent, functionScope = false) {
361
- return { parent, functionScope, bindings: /* @__PURE__ */ new Map() };
362
- }
363
- function bindingNames(node) {
364
- if (!node) {
365
- return [];
366
- }
367
- switch (node.type) {
368
- case "Identifier": {
369
- return [node.name];
370
- }
371
- case "RestElement": {
372
- return bindingNames(node.argument);
373
- }
374
- case "AssignmentPattern": {
375
- return bindingNames(node.left);
376
- }
377
- case "ArrayPattern": {
378
- return node.elements.flatMap(bindingNames);
379
- }
380
- case "ObjectPattern": {
381
- return node.properties.flatMap(
382
- (property2) => bindingNames(property2.type === "Property" ? property2.value : property2.argument)
383
- );
384
- }
385
- case "TSParameterProperty": {
386
- return bindingNames(node.parameter);
387
- }
388
- default: {
389
- return [];
390
- }
391
- }
392
- }
393
- function bind(node, scope) {
394
- for (const name of bindingNames(node)) {
395
- scope.bindings.set(name, "local");
396
- }
397
- }
398
- function classScope(node, parent) {
399
- if (node.type === "ClassDeclaration") {
400
- bind(node.id, parent);
401
- }
402
- const scope = createScope(parent);
403
- bind(node.id, scope);
404
- return scope;
405
- }
406
- function resolve(scope, name) {
407
- for (let current = scope; current; current = current.parent) {
408
- if (current.bindings.has(name)) {
409
- return current.bindings.get(name);
410
- }
411
- }
412
- }
413
- function childNodes(node) {
414
- return Object.values(node).flatMap((value) => {
415
- const children2 = Array.isArray(value) ? value : [value];
416
- return children2.filter(
417
- (child) => !!child && typeof child === "object" && "type" in child
418
- );
419
- });
420
- }
421
- function bindImports(node, scope) {
422
- const sdk = node.importKind !== "type" && clientImports.has(String(node.source.value));
423
- for (const specifier of node.specifiers) {
424
- let binding = "local";
425
- if (sdk && specifier.type === "ImportNamespaceSpecifier") {
426
- binding = "namespace";
427
- } else if (sdk && specifier.type === "ImportSpecifier" && specifier.importKind !== "type" && specifier.imported.type === "Identifier" && specifier.imported.name === "defineView") {
428
- binding = "view";
429
- }
430
- scope.bindings.set(specifier.local.name, binding);
431
- }
432
- }
433
- function callScopes(program) {
434
- const scopes = /* @__PURE__ */ new WeakMap();
435
- function visitFunction(node, scope) {
436
- if (node.type === "FunctionDeclaration") {
437
- bind(node.id, scope);
438
- }
439
- const parameters = createScope(scope, true);
440
- if (node.type !== "ArrowFunctionExpression") {
441
- bind(node.id, parameters);
442
- }
443
- for (const parameter of node.params) {
444
- bind(parameter, parameters);
445
- }
446
- for (const parameter of node.params) {
447
- visit(parameter, parameters);
448
- }
449
- if (node.body) {
450
- visit(node.body, createScope(parameters, true));
451
- }
452
- }
453
- function bindVariables(node, scope) {
454
- let target = scope;
455
- if (node.kind === "var") {
456
- while (!target.functionScope && target.parent) {
457
- target = target.parent;
458
- }
459
- }
460
- for (const declaration of node.declarations) {
461
- bind(declaration.id, target);
462
- }
463
- }
464
- function visit(node, scope) {
465
- scopes.set(node, scope);
466
- switch (node.type) {
467
- case "ImportDeclaration": {
468
- bindImports(node, scope);
469
- return;
470
- }
471
- case "VariableDeclaration": {
472
- bindVariables(node, scope);
473
- break;
474
- }
475
- case "FunctionDeclaration":
476
- case "FunctionExpression":
477
- case "ArrowFunctionExpression": {
478
- visitFunction(node, scope);
479
- return;
480
- }
481
- case "ClassDeclaration":
482
- case "ClassExpression": {
483
- scope = classScope(node, scope);
484
- break;
485
- }
486
- case "CatchClause": {
487
- scope = createScope(scope);
488
- bind(node.param, scope);
489
- break;
490
- }
491
- case "SwitchStatement": {
492
- visit(node.discriminant, scope);
493
- scope = createScope(scope);
494
- for (const branch of node.cases) {
495
- visit(branch, scope);
496
- }
497
- return;
498
- }
499
- case "BlockStatement":
500
- case "ForStatement":
501
- case "ForInStatement":
502
- case "ForOfStatement": {
503
- scope = createScope(scope);
504
- break;
505
- }
506
- case "StaticBlock":
507
- case "TSModuleBlock": {
508
- scope = createScope(scope, true);
509
- break;
510
- }
511
- case "TSEnumDeclaration":
512
- case "TSModuleDeclaration": {
513
- bind(node.id, scope);
514
- break;
515
- }
516
- default: {
517
- break;
518
- }
519
- }
520
- for (const child of childNodes(node)) {
521
- visit(child, scope);
522
- }
523
- }
524
- visit(program, createScope(void 0, true));
525
- return scopes;
526
- }
527
- function createDefineViewMatcher(program) {
528
- const scopes = callScopes(program);
529
- return (node) => {
530
- if (node.type !== "CallExpression") {
531
- return false;
532
- }
533
- const { callee } = node;
534
- const scope = scopes.get(node);
535
- if (callee.type === "Identifier") {
536
- return resolve(scope, callee.name) === "view";
537
- }
538
- if (callee.type !== "MemberExpression" || callee.object.type !== "Identifier" || resolve(scope, callee.object.name) !== "namespace") {
539
- return false;
540
- }
541
- return callee.computed ? callee.property.type === "Literal" && callee.property.value === "defineView" : callee.property.type === "Identifier" && callee.property.name === "defineView";
542
- };
543
- }
544
-
545
- // src/builder/file-routes.ts
546
- var clientRuntimeImport = "tailorkit:file-client-runtime";
547
- function fileViewLocation(filename, root) {
548
- const relative = path5.relative(path5.join(root, "src/slots"), filename).split(path5.sep);
549
- if (relative.length < 2 || relative[0] === ".." || !filename.endsWith(".view.tsx")) {
550
- return;
551
- }
552
- const [slot, ...routeSegments] = relative;
553
- if (!slot) {
554
- return;
555
- }
556
- const basename = routeSegments.join("/").slice(0, -".view.tsx".length);
557
- return { slot, path: routePath(basename, filename) };
558
- }
559
- function routePath(name, filename) {
560
- const segments = name.split(/[./]/u);
561
- if (segments.some((segment) => !segment || segment === "..")) {
562
- throw new Error(`${filename} has an empty route segment. Use route.nested-route.view.tsx.`);
563
- }
564
- return `/${segments.join("/")}`;
565
- }
566
- function inferFileView(source, filename, root) {
567
- const location = fileViewLocation(filename, root);
568
- if (!location) {
569
- return source;
570
- }
571
- const { slot, path: viewPath } = location;
572
- const parsed = parseSync(filename, source);
573
- if (parsed.errors.length) {
574
- throw new Error(`${filename}: ${parsed.errors[0]?.message}`);
575
- }
576
- const isDefineViewCall = createDefineViewMatcher(parsed.program);
577
- const edits = [];
578
- function visit(node) {
579
- if (isDefineViewCall(node)) {
580
- const options = node.arguments[0];
581
- if (options?.type !== "ObjectExpression") {
582
- throw new Error(
583
- `${filename}: file views require defineView with an inline options object.`
584
- );
585
- }
586
- const properties = new Set(
587
- options.properties.flatMap((property2) => {
588
- if (property2.type !== "Property" || property2.computed) {
589
- return [];
590
- }
591
- if (property2.key.type === "Identifier") {
592
- return [property2.key.name];
593
- }
594
- if (property2.key.type === "Literal") {
595
- return [String(property2.key.value)];
596
- }
597
- return [];
598
- })
599
- );
600
- const defaults = [
601
- !properties.has("slot") ? `slot: ${JSON.stringify(slot)},` : "",
602
- !properties.has("view") ? `view: ${JSON.stringify(viewPath)},` : ""
603
- ].join("");
604
- if (defaults) {
605
- edits.push({ position: options.start + 1, text: defaults });
606
- }
607
- }
608
- for (const value of Object.values(node)) {
609
- const children2 = Array.isArray(value) ? value : [value];
610
- for (const child of children2) {
611
- if (child && typeof child === "object" && "type" in child) {
612
- visit(child);
613
- }
614
- }
615
- }
616
- }
617
- visit(parsed.program);
618
- for (const edit of edits.toSorted((a, b) => b.position - a.position)) {
619
- source = source.slice(0, edit.position) + edit.text + source.slice(edit.position);
620
- }
621
- return source;
622
- }
623
- async function discoverFileRoutes(root) {
624
- const views = [];
625
- const roots = [];
626
- const layouts = [];
627
- const slotsDir = path5.join(root, "src/slots");
628
- async function scan(directory, slot, prefix = "") {
629
- const entries = await readdir3(directory, { withFileTypes: true }).catch(
630
- (error) => {
631
- if (error.code === "ENOENT") {
632
- return [];
633
- }
634
- throw error;
635
- }
636
- );
637
- for (const entry of entries.toSorted((a, b) => a.name.localeCompare(b.name))) {
638
- const filename = path5.join(directory, entry.name);
639
- if (entry.isDirectory()) {
640
- await scan(filename, slot ?? entry.name, slot ? `${prefix}${entry.name}/` : "");
641
- } else if (entry.isFile() && slot) {
642
- if (entry.name.endsWith(".view.tsx")) {
643
- fileViewLocation(filename, root);
644
- views.push({ filename, slot });
645
- } else if (entry.name === "root.tsx" && !prefix) {
646
- roots.push({ filename, slot });
647
- } else if (entry.name === "layout.tsx" || entry.name.endsWith(".layout.tsx")) {
648
- const name = entry.name === "layout.tsx" ? prefix.replace(/\/$/u, "") : prefix + entry.name.slice(0, -".layout.tsx".length);
649
- layouts.push({ filename, slot, path: name ? routePath(name, filename) : "/" });
650
- }
651
- }
652
- }
653
- }
654
- await scan(slotsDir);
655
- const rootFile = path5.join(root, "src/root.tsx");
656
- return {
657
- root: existsSync(rootFile) ? { filename: rootFile } : void 0,
658
- roots,
659
- layouts,
660
- views
661
- };
662
- }
663
- async function getClientSourceFiles(root) {
664
- const discovered = await discoverFileRoutes(root);
665
- const bindings = path5.join(root, "src/tailorkit.gen.ts");
666
- return [
667
- ...discovered.root ? [discovered.root.filename] : [],
668
- ...discovered.roots.map((module) => module.filename),
669
- ...discovered.layouts.map((module) => module.filename),
670
- ...discovered.views.map((module) => module.filename),
671
- ...existsSync(bindings) ? [bindings] : []
672
- ];
673
- }
674
- async function fileClientSource(root) {
675
- const discovered = await discoverFileRoutes(root);
676
- const imports = [`import { createFileClient } from ${JSON.stringify(clientRuntimeImport)};`];
677
- let index = 0;
678
- function module(entry) {
679
- const binding = `module${index++}`;
680
- imports.push(`import ${binding} from ${JSON.stringify(entry.filename)};`);
681
- return `{ filename: ${JSON.stringify(path5.relative(root, entry.filename))}, definition: ${binding}`;
682
- }
683
- const rootModule = discovered.root ? `${module(discovered.root)} }` : "undefined";
684
- const roots = discovered.roots.map(
685
- (entry) => `${module(entry)}, slot: ${JSON.stringify(entry.slot)} }`
686
- );
687
- const layouts = discovered.layouts.map(
688
- (entry) => `${module(entry)}, slot: ${JSON.stringify(entry.slot)}, path: ${JSON.stringify(entry.path)} }`
689
- );
690
- const views = discovered.views.map(
691
- (entry) => `${module(entry)}, slot: ${JSON.stringify(entry.slot)} }`
692
- );
693
- return `${imports.join("\n")}
694
- export default createFileClient({ root: ${rootModule}, roots: [${roots.join(",")}], layouts: [${layouts.join(",")}], views: [${views.join(",")}] });`;
695
- }
696
- async function createFileRouteEntry(root, output, watching) {
697
- let source = await fileClientSource(root);
698
- const temporaryRoot = path5.join(output, "tmp");
699
- await mkdir(temporaryRoot, { recursive: true });
700
- const directory = await mkdtemp2(path5.join(temporaryRoot, "client-"));
701
- const entry = path5.join(directory, "entry.ts");
702
- await writeFile(entry, source);
703
- let pending = Promise.resolve();
704
- let closed = false;
705
- let timer;
706
- async function refresh() {
707
- if (closed) {
708
- return;
709
- }
710
- let next;
711
- try {
712
- next = await fileClientSource(root);
713
- } catch (error) {
714
- next = `throw new Error(${JSON.stringify(error instanceof Error ? error.message : String(error))});`;
715
- }
716
- if (next === source) {
717
- return;
718
- }
719
- source = next;
720
- await writeFile(entry, next);
721
- }
722
- const watcher = watching ? watch(path5.join(root, "src"), { recursive: true }, () => {
723
- if (timer) {
724
- clearTimeout(timer);
725
- }
726
- timer = setTimeout(() => {
727
- pending = pending.then(refresh);
728
- }, 25);
729
- }) : void 0;
730
- return {
731
- entry,
732
- async close() {
733
- closed = true;
734
- if (timer) {
735
- clearTimeout(timer);
736
- }
737
- watcher?.close();
738
- try {
739
- await pending;
740
- } finally {
741
- await rm2(directory, { recursive: true, force: true });
742
- }
743
- }
744
- };
745
- }
746
- function fileRoutesPlugin(root) {
747
- return {
748
- name: "tailorkit-file-routes",
749
- enforce: "pre",
750
- async resolveId(id) {
751
- if (id !== clientRuntimeImport) {
752
- return;
753
- }
754
- const importer = path5.join(root, "src/root.tsx");
755
- return await this.resolve("tailorkit/client", importer, { skipSelf: true }) ?? this.resolve("@tailorkit/app/client", importer, { skipSelf: true });
756
- },
757
- transform(source, filename) {
758
- if (!filename.endsWith(".view.tsx")) {
759
- return;
760
- }
761
- const code = inferFileView(source, filename, root);
762
- return code === source ? void 0 : { code, map: null };
763
- }
764
- };
765
- }
766
-
767
- // src/builder/instances.ts
768
- var suffix = "?tailorkit-instances";
769
- var browserGlobals = /* @__PURE__ */ new Set([
770
- "window",
771
- "document",
772
- "navigator",
773
- "localStorage",
774
- "sessionStorage"
775
- ]);
776
- function isNode(value) {
777
- return !!value && typeof value === "object" && "type" in value;
778
- }
779
- function children(node) {
780
- return Object.values(node).flatMap(
781
- (value) => Array.isArray(value) ? value.filter(isNode) : isNode(value) ? [value] : []
782
- );
783
- }
784
- function walk(node, visit, ancestors = []) {
785
- visit(node, ancestors);
786
- for (const child of children(node)) walk(child, visit, [...ancestors, node]);
787
- }
788
- function boundNames(node) {
789
- if (!node) return [];
790
- switch (node.type) {
791
- case "Identifier":
792
- return [node.name];
793
- case "RestElement":
794
- return boundNames(node.argument);
795
- case "AssignmentPattern":
796
- return boundNames(node.left);
797
- case "ArrayPattern":
798
- return node.elements.flatMap(boundNames);
799
- case "ObjectPattern":
800
- return node.properties.flatMap(
801
- (p) => boundNames(p.type === "Property" ? p.value : p.argument)
802
- );
803
- default:
804
- return [];
805
- }
806
- }
807
- function declarations(node) {
808
- if (node.type === "VariableDeclaration")
809
- return node.declarations.flatMap((d) => boundNames(d.id));
810
- if (node.type === "FunctionDeclaration" || node.type === "ClassDeclaration")
811
- return boundNames(node.id);
812
- return [];
813
- }
814
- function isFunction(node) {
815
- return ["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"].includes(
816
- node.type
817
- );
818
- }
819
- function freeNames(root) {
820
- const references = /* @__PURE__ */ new Set();
821
- function pattern(node, scope) {
822
- if (node.type === "AssignmentPattern") {
823
- visit(node.right, scope);
824
- pattern(node.left, scope);
825
- } else if (node.type === "ObjectPattern") {
826
- for (const p of node.properties) {
827
- if (p.type === "Property") {
828
- if (p.computed) visit(p.key, scope);
829
- pattern(p.value, scope);
830
- } else pattern(p.argument, scope);
831
- }
832
- } else if (node.type === "ArrayPattern") {
833
- for (const element of node.elements) if (element) pattern(element, scope);
834
- } else if (node.type === "RestElement") pattern(node.argument, scope);
835
- }
836
- function visit(node, parent) {
837
- if (node.type === "Identifier") {
838
- if (!parent.has(node.name)) references.add(node.name);
839
- return;
840
- }
841
- if (node.type === "ImportDeclaration") return;
842
- if (node.type === "ExportSpecifier") {
843
- visit(node.local, parent);
844
- return;
845
- }
846
- if (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") {
847
- let hoisted2 = function(body) {
848
- if (isFunction(body)) return;
849
- if (body.type === "VariableDeclaration" && body.kind === "var") {
850
- for (const name of declarations(body)) scope.add(name);
851
- }
852
- for (const child of children(body)) hoisted2(child);
853
- };
854
- var hoisted = hoisted2;
855
- const scope = /* @__PURE__ */ new Set([
856
- ...parent,
857
- ...boundNames(node.id),
858
- ...node.params.flatMap(boundNames)
859
- ]);
860
- if (node.body) hoisted2(node.body);
861
- for (const p of node.params) pattern(p, scope);
862
- if (node.body) visit(node.body, scope);
863
- return;
864
- }
865
- if (node.type === "BlockStatement" || node.type === "Program") {
866
- const scope = /* @__PURE__ */ new Set([...parent, ...node.body.flatMap(declarations)]);
867
- for (const statement of node.body) visit(statement, scope);
868
- return;
869
- }
870
- if (node.type === "VariableDeclarator") {
871
- pattern(node.id, parent);
872
- if (node.init) visit(node.init, parent);
873
- return;
874
- }
875
- if (node.type === "Property" || node.type === "MethodDefinition" || node.type === "PropertyDefinition") {
876
- if (node.computed) visit(node.key, parent);
877
- if (node.value) visit(node.value, parent);
878
- return;
879
- }
880
- if (node.type === "MemberExpression") {
881
- visit(node.object, parent);
882
- if (node.computed) visit(node.property, parent);
883
- return;
884
- }
885
- if (node.type === "CatchClause") {
886
- const scope = /* @__PURE__ */ new Set([...parent, ...boundNames(node.param)]);
887
- if (node.param) pattern(node.param, scope);
888
- visit(node.body, scope);
889
- return;
890
- }
891
- if (node.type === "ForStatement" || node.type === "ForInStatement" || node.type === "ForOfStatement") {
892
- const declaration = node.type === "ForStatement" ? node.init : node.left;
893
- const scope = /* @__PURE__ */ new Set([...parent, ...declaration ? declarations(declaration) : []]);
894
- for (const child of children(node)) visit(child, scope);
895
- return;
896
- }
897
- if (node.type === "ClassDeclaration" || node.type === "ClassExpression") {
898
- const scope = /* @__PURE__ */ new Set([...parent, ...boundNames(node.id)]);
899
- if (node.superClass) visit(node.superClass, scope);
900
- visit(node.body, scope);
901
- return;
902
- }
903
- if (node.type === "LabeledStatement") {
904
- visit(node.body, parent);
905
- return;
906
- }
907
- if (node.type === "BreakStatement" || node.type === "ContinueStatement") return;
908
- for (const child of children(node)) visit(child, parent);
909
- }
910
- visit(root, /* @__PURE__ */ new Set());
911
- return references;
912
- }
913
- function property(node, name) {
914
- return node.properties.find(
915
- (p) => p.type === "Property" && !p.computed && (p.key.type === "Identifier" ? p.key.name === name : p.key.type === "Literal" && p.key.value === name)
916
- );
917
- }
918
- function parse(filename, source) {
919
- const result = parseSync2(filename, source);
920
- if (result.errors.length)
921
- throw new Error(`Unable to parse ${filename}: ${result.errors[0]?.message}`);
922
- return result.program;
923
- }
924
- function units(program, source) {
925
- const imports = [];
926
- const statements = [];
927
- for (const statement of program.body) {
928
- if (statement.type === "ImportDeclaration") {
929
- imports.push(statement);
930
- continue;
931
- }
932
- const exported = statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration";
933
- const node = statement.type === "ExportNamedDeclaration" && statement.declaration ? statement.declaration : statement;
934
- if (node.type === "VariableDeclaration") {
935
- for (const d of node.declarations)
936
- statements.push({
937
- node: d,
938
- names: boundNames(d.id),
939
- code: `${exported ? "export " : ""}${node.kind} ${source.slice(d.start, d.end)};`,
940
- exported
941
- });
942
- } else
943
- statements.push({
944
- node,
945
- names: declarations(node),
946
- code: source.slice(statement.start, statement.end),
947
- exported
948
- });
949
- }
950
- return { imports, statements };
951
- }
952
- function closure(roots, statements) {
953
- const bindings = new Map(statements.flatMap((u) => u.names.map((name) => [name, u])));
954
- const names = /* @__PURE__ */ new Set();
955
- const selected = /* @__PURE__ */ new Set();
956
- function add(name) {
957
- if (names.has(name)) return;
958
- names.add(name);
959
- const unit = bindings.get(name);
960
- if (!unit || selected.has(unit)) return;
961
- selected.add(unit);
962
- for (const ref of freeNames(unit.node)) add(ref);
963
- }
964
- for (const root of roots) add(root);
965
- return { names, selected };
966
- }
967
- function renderImports(imports, needed, source, sideEffects) {
968
- return imports.flatMap((i) => {
969
- if (!i.specifiers.length) return sideEffects ? [source.slice(i.start, i.end)] : [];
970
- const parts = i.specifiers.filter((s) => needed(s.local.name));
971
- if (!parts.length) return [];
972
- const defaultImport = parts.find((s) => s.type === "ImportDefaultSpecifier");
973
- const namespace = parts.find((s) => s.type === "ImportNamespaceSpecifier");
974
- const named = parts.filter((s) => s.type === "ImportSpecifier");
975
- const clauses = [
976
- defaultImport?.local.name,
977
- namespace ? `* as ${namespace.local.name}` : void 0,
978
- named.length ? `{ ${named.map((s) => `${source.slice(s.imported.start, s.imported.end)} as ${s.local.name}`).join(", ")} }` : void 0
979
- ].filter(Boolean);
980
- return [`import ${clauses.join(", ")} from ${JSON.stringify(i.source.value)};`];
981
- }).join("\n");
982
- }
983
- async function extractInstances(filename, source, root, includedNames) {
984
- source = inferFileView(source, filename, root);
985
- const transformed = await transformWithOxc(source, filename, {
986
- jsx: { importSource: "preact" },
987
- sourcemap: false
988
- });
989
- source = transformed.code;
990
- const program = parse(filename.replace(/\.[^.]+$/u, ".js"), source);
991
- const isDefineViewCall = createDefineViewMatcher(program);
992
- let resolvers = [];
993
- walk(program, (node, ancestors) => {
994
- if (!isDefineViewCall(node)) {
995
- return;
996
- }
997
- const options = node.arguments[0];
998
- if (options?.type !== "ObjectExpression") return;
999
- const instances = property(options, "instances");
1000
- if (!instances) return;
1001
- const fail = (message) => {
1002
- throw new Error(`${filename}: ${message}`);
1003
- };
1004
- if (ancestors.some(isFunction))
1005
- fail("defineView with instances must be declared at module scope.");
1006
- const owner = ancestors.findLast((n) => n.type === "VariableDeclarator");
1007
- const viewName = owner?.type === "VariableDeclarator" && owner.id.type === "Identifier" ? owner.id.name : "default";
1008
- const view = property(options, "view")?.value;
1009
- if (view?.type !== "Literal" || typeof view.value !== "string")
1010
- fail("Instance view paths must be string literals.");
1011
- if (instances.value.type !== "ObjectExpression")
1012
- fail("instances must be an inline object with dataSchema and resolve.");
1013
- const config = instances.value;
1014
- if (config.properties.some((p) => p.type !== "Property" || p.computed || p.kind !== "init"))
1015
- fail("Instance options cannot contain spreads, getters or computed properties.");
1016
- const resolve2 = property(config, "resolve");
1017
- const schema = property(config, "dataSchema");
1018
- if (!resolve2 || !schema || resolve2.method || !["ArrowFunctionExpression", "FunctionExpression"].includes(resolve2.value.type))
1019
- fail("instances requires dataSchema and an inline resolve function.");
1020
- const name = `i${createHash2("sha256").update(
1021
- `${path6.relative(root, filename).split(path6.sep).join("/")}\0${viewName}\0${view.value}`
1022
- ).digest("hex").slice(0, 24)}`;
1023
- if (resolvers.some((r) => r.name === name))
1024
- fail("Instance resolvers must have distinct module-level variable names.");
1025
- resolvers.push({
1026
- name,
1027
- view: view.value,
1028
- call: node,
1029
- instances,
1030
- resolve: resolve2,
1031
- schema
1032
- });
1033
- });
1034
- if (includedNames) {
1035
- resolvers = resolvers.filter((r) => includedNames.includes(r.name));
1036
- if (resolvers.length !== includedNames.length)
1037
- throw new Error(`${filename}: Instance definitions changed; rebuild the app client.`);
1038
- }
1039
- if (!resolvers.length) return;
1040
- const original = units(program, source);
1041
- const server = closure(
1042
- resolvers.flatMap((r) => [...freeNames(r.resolve.value), ...freeNames(r.schema.value)]),
1043
- original.statements
1044
- );
1045
- for (const name of server.names) {
1046
- if (browserGlobals.has(name))
1047
- throw new Error(`${filename}: Instance resolvers cannot access browser global ${name}.`);
1048
- }
1049
- for (const unit of server.selected) {
1050
- if (resolvers.some((r) => unit.node.start <= r.call.start && unit.node.end >= r.call.end))
1051
- throw new Error(
1052
- `${filename}: Instance resolvers cannot capture a view definition. Use the supplied context and queries.`
1053
- );
1054
- }
1055
- const serverSource = [
1056
- renderImports(original.imports, (name) => server.names.has(name), source, false),
1057
- ...original.statements.filter((u) => server.selected.has(u)).map((u) => u.code.replace(/^export /u, "")),
1058
- `export const resolvers = { ${resolvers.map((r) => `${r.name}: { dataSchema: ${source.slice(r.schema.value.start, r.schema.value.end)}, resolve: ${source.slice(r.resolve.value.start, r.resolve.value.end)} }`).join(", ")} };`
1059
- ].join("\n");
1060
- let browserSource = source;
1061
- for (const r of [...resolvers].sort(
1062
- (a, b) => b.instances.value.start - a.instances.value.start
1063
- )) {
1064
- browserSource = browserSource.slice(0, r.instances.value.start) + `{ resolver: ${JSON.stringify(r.name)} }` + browserSource.slice(r.instances.value.end);
1065
- }
1066
- const browser = units(parse("browser.js", browserSource), browserSource);
1067
- const serverBindings = new Set([...server.selected].flatMap((u) => u.names));
1068
- const browserRoots = browser.statements.filter(
1069
- (u) => u.exported || !u.names.length || !u.names.some((name) => serverBindings.has(name))
1070
- );
1071
- const browserNeeded = closure(
1072
- browserRoots.flatMap((u) => [...freeNames(u.node), ...u.names]),
1073
- browser.statements
1074
- );
1075
- return {
1076
- names: resolvers.map((r) => r.name),
1077
- server: serverSource,
1078
- browser: [
1079
- renderImports(
1080
- browser.imports,
1081
- (name) => !server.names.has(name) || browserNeeded.names.has(name),
1082
- browserSource,
1083
- true
1084
- ),
1085
- ...browser.statements.filter((u) => browserRoots.includes(u) || browserNeeded.selected.has(u)).map((u) => u.code)
1086
- ].join("\n")
1087
- };
1088
- }
1089
- function instanceExtractionPlugin(root, serverConfigured, modules) {
1090
- return {
1091
- name: "tailorkit-instance-extraction",
1092
- enforce: "pre",
1093
- async transform(source, filename) {
1094
- if (filename.includes("node_modules") || !/\.[cm]?[jt]sx?$/u.test(filename) || !source.includes("instances") && !modules.has(filename))
1095
- return;
1096
- const result = await extractInstances(filename, source, root);
1097
- if (!result) {
1098
- modules.delete(filename);
1099
- return;
1100
- }
1101
- if (!serverConfigured)
1102
- throw new Error("View instances require server configuration in tailorkit.config.ts.");
1103
- modules.set(filename, { filename, names: result.names });
1104
- return { code: result.browser, map: null };
1105
- }
1106
- };
1107
- }
1108
- function instanceServerPlugin(root, modules) {
1109
- return {
1110
- name: "tailorkit-instance-server-modules",
1111
- enforce: "pre",
1112
- resolveId(id) {
1113
- if (id.endsWith(suffix)) return id;
1114
- },
1115
- async load(id) {
1116
- if (!id.endsWith(suffix)) return;
1117
- const filename = id.slice(0, -suffix.length);
1118
- this.addWatchFile(filename);
1119
- const names = modules.find((module) => module.filename === filename)?.names;
1120
- const result = await extractInstances(
1121
- filename,
1122
- await readFile3(filename, "utf8"),
1123
- root,
1124
- names
1125
- );
1126
- if (!result)
1127
- throw new Error(`Instance resolver missing from ${filename}; rebuild the app client.`);
1128
- return result.server;
1129
- }
1130
- };
1131
- }
1132
- function instanceServerImports(modules) {
1133
- return modules.map(
1134
- (m, i) => `import { resolvers as instances${i} } from ${JSON.stringify(m.filename + suffix)};`
1135
- ).join("\n");
1136
- }
1137
- function instanceServerBindings(modules, registrations) {
1138
- return `[${registrations.map(({ slot, path: viewPath, resolver }) => {
1139
- const index = modules.findIndex((module) => module.names.includes(resolver));
1140
- if (index < 0) throw new Error("Instance resolver was not extracted by the app build.");
1141
- return `{ slot: ${JSON.stringify(slot)}, path: ${JSON.stringify(viewPath)}, ...instances${index}[${JSON.stringify(resolver)}] }`;
1142
- }).join(", ")}]`;
1143
- }
1144
-
1145
- // src/builder/server.ts
1146
- async function buildServer(loaded, watch2 = false, buildOutDir, instances = [], registrations = []) {
1147
- const config = loaded.config.server;
1148
- if (!config) {
1149
- return;
1150
- }
1151
- const output = buildOutDir ?? path7.resolve(loaded.root, loaded.config.build?.outDir ?? ".tailorkit");
1152
- const directory = path7.join(output, "server");
1153
- const migrationsDirectory = appDatabasePaths(loaded.root, config.migrations).migrations;
1154
- const migrationsOutput = path7.join(output, "migrations");
1155
- const temporaryRoot = path7.join(output, "tmp");
1156
- validateServerBuildOutput(loaded, output);
1157
- await rm3(directory, { recursive: true, force: true });
1158
- await mkdir2(directory, { recursive: true });
1159
- await mkdir2(temporaryRoot, { recursive: true });
1160
- const temporary = await mkdtemp3(path7.join(temporaryRoot, "server-"));
1161
- const entry = path7.join(temporary, "entry.mjs");
1162
- let watching = false;
1163
- let ready;
1164
- const initialBuild = watch2 ? new Promise((resolve2) => {
1165
- ready = resolve2;
1166
- }) : void 0;
1167
- try {
1168
- await writeFile2(
1169
- entry,
1170
- `${instances.length ? `import app from ${JSON.stringify(path7.resolve(loaded.root, config.entry ?? "src/server.ts"))};
1171
- import { withInstanceResolvers } from "tailorkit/server";
1172
- ${instanceServerImports(instances)}
1173
- export default withInstanceResolvers(app, ${instanceServerBindings(instances, registrations)});` : `export { default } from ${JSON.stringify(path7.resolve(loaded.root, config.entry ?? "src/server.ts"))};`}
1174
- export { default as migrations } from "tailorkit:migrations";
1175
- export const runtimeManifest = ${JSON.stringify(runtimeManifest)};
1176
- `
1177
- );
1178
- const result = await build({
1179
- root: loaded.root,
1180
- configFile: false,
1181
- plugins: [
1182
- instanceServerPlugin(loaded.root, instances),
1183
- {
1184
- name: "tailorkit-app-migrations",
1185
- closeWatcher: () => rm3(temporary, { recursive: true, force: true }),
1186
- resolveId(id) {
1187
- if (id === "tailorkit:migrations") {
1188
- return "\0tailorkit:migrations";
1189
- }
1190
- },
1191
- async load(id) {
1192
- if (id !== "\0tailorkit:migrations") {
1193
- return;
1194
- }
1195
- const { migrations, files } = await readAppMigrations(
1196
- migrationsDirectory,
1197
- config.migrations === void 0
1198
- );
1199
- await checkAppSchema(loaded.root, migrationsDirectory);
1200
- this.addWatchFile(appDatabasePaths(loaded.root).schema);
1201
- for (const file of files) {
1202
- this.addWatchFile(file);
1203
- }
1204
- await rm3(migrationsOutput, { recursive: true, force: true });
1205
- await mkdir2(migrationsOutput, { recursive: true });
1206
- await cp(migrationsDirectory, migrationsOutput, { recursive: true }).catch(
1207
- (error) => {
1208
- if (config.migrations === void 0 && error.code === "ENOENT") {
1209
- return;
1210
- }
1211
- throw error;
1212
- }
1213
- );
1214
- return `export default ${JSON.stringify(migrations)};`;
1215
- }
1216
- }
1217
- ],
1218
- build: {
1219
- ssr: entry,
1220
- outDir: directory,
1221
- emptyOutDir: false,
1222
- target: "es2022",
1223
- watch: watch2 ? {} : null,
1224
- minify: "oxc",
1225
- rollupOptions: {
1226
- external: [],
1227
- output: {
1228
- entryFileNames: "server.js",
1229
- inlineDynamicImports: true,
1230
- minify: true,
1231
- minifyInternalExports: true
1232
- }
1233
- }
1234
- },
1235
- ssr: { noExternal: true }
1236
- });
1237
- watching = watch2;
1238
- if (watch2 && "on" in result && initialBuild) {
1239
- try {
1240
- await Promise.race([
1241
- initialBuild,
1242
- new Promise(
1243
- (_, reject) => result.on("event", (event) => {
1244
- if (event.code === "ERROR") reject(event.error);
1245
- if (event.code === "END") ready?.();
1246
- })
1247
- )
1248
- ]);
1249
- } catch (error) {
1250
- await result.close();
1251
- throw error;
1252
- }
1253
- }
1254
- return result;
1255
- } finally {
1256
- if (!watching) await rm3(temporary, { recursive: true, force: true });
1257
- }
1258
- }
1259
- function validateServerBuildOutput(loaded, output) {
1260
- if (!loaded.config.server) return;
1261
- const migrationsDirectory = appDatabasePaths(
1262
- loaded.root,
1263
- loaded.config.server.migrations
1264
- ).migrations;
1265
- for (const name of ["server", "client", "migrations", "tmp"]) {
1266
- if (overlaps(migrationsDirectory, path7.join(output, name))) {
1267
- throw new Error("App build output must not overlap migration source");
1268
- }
1269
- }
1270
- }
1271
- function overlaps(source, destination) {
1272
- const contains = (parent, child) => {
1273
- const relative = path7.relative(parent, child);
1274
- return relative === "" || !path7.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path7.sep}`);
1275
- };
1276
- return contains(source, destination) || contains(destination, source);
1277
- }
1278
-
1279
- // src/builder/generate-migrations.ts
1280
- import { spawn } from "child_process";
1281
- import { access as access3 } from "fs/promises";
1282
- import path8 from "path";
1283
- async function generateAppMigrations(options = {}) {
1284
- const loaded = await loadTailorKitConfig(options.configPath, options.cwd);
1285
- if (!loaded.config.server) {
1286
- throw new Error(
1287
- "Enable server: {} in tailorkit.config.ts before generating database migrations."
1288
- );
1289
- }
1290
- const paths = appDatabasePaths(loaded.root, loaded.config.server.migrations);
1291
- await access3(paths.schema).catch((error) => {
1292
- throw new Error(
1293
- `Define the app database schema in ${path8.relative(loaded.root, paths.schema)} before generating migrations.`,
1294
- { cause: error }
1295
- );
1296
- });
1297
- const directory = paths.migrations;
1298
- const args = await drizzleGenerateArguments(loaded.root, directory, options.name);
1299
- await new Promise((resolve2, reject) => {
1300
- const child = spawn(process.execPath, args, { cwd: loaded.root, stdio: "inherit" });
1301
- child.once("error", reject);
1302
- child.once("close", (code, signal) => {
1303
- if (code === 0) resolve2();
1304
- else reject(new Error(`Drizzle migration generation failed (${signal ?? code}).`));
1305
- });
1306
- });
1307
- await checkAppSchema(loaded.root, directory);
1308
- return directory;
1309
- }
1310
-
1311
- // src/builder/index.ts
1312
- var preactPackageJson = "preact/package.json";
1313
- var preactPackageJsonModuleId = "\0tailorkit-preact-package-json";
1314
- var buildApp = async (options = {}) => {
1315
- const loaded = await loadTailorKitConfig(options.configPath, options.cwd);
1316
- loaded.root = await realpath(loaded.root);
1317
- const serverEntry = path9.resolve(loaded.root, loaded.config.server?.entry ?? "src/server.ts");
1318
- const outDir = options.outDir ?? loaded.config.build?.outDir ?? ".tailorkit";
1319
- const preactVersion = getInstalledPreactVersion(loaded.root);
1320
- assertSupportedPreactVersion(preactVersion);
1321
- const resolvedOutDir = path9.resolve(loaded.root, outDir);
1322
- validateServerBuildOutput(loaded, resolvedOutDir);
1323
- const clientOutDir = path9.join(resolvedOutDir, "client");
1324
- const fileEntry = await createFileRouteEntry(loaded.root, resolvedOutDir, Boolean(options.watch));
1325
- const entry = fileEntry.entry;
1326
- const instanceModules = /* @__PURE__ */ new Map();
1327
- let serverWatcher;
1328
- const writeBuildExtras = async () => {
1329
- const { views, instanceResolvers } = await readClientManifest(
1330
- path9.join(clientOutDir, "client.js")
1331
- );
1332
- const resolverNames = new Set(instanceResolvers.map((registration) => registration.resolver));
1333
- const activeModules = [...instanceModules.values()].map((module) => ({
1334
- ...module,
1335
- names: module.names.filter((name) => resolverNames.has(name))
1336
- })).filter((module) => module.names.length).sort((a, b) => a.filename.localeCompare(b.filename));
1337
- if (activeModules.reduce((count, module) => count + module.names.length, 0) !== resolverNames.size) {
1338
- throw new Error(
1339
- "Client references an instance resolver that was not extracted by the app build."
1340
- );
1341
- }
1342
- if (serverWatcher && "close" in serverWatcher) await serverWatcher.close();
1343
- serverWatcher = await buildServer(
1344
- loaded,
1345
- options.watch,
1346
- resolvedOutDir,
1347
- activeModules,
1348
- instanceResolvers
1349
- );
1350
- const logoManifest = {};
1351
- for (const variant of ["light", "dark"]) {
1352
- const configuredPath = loaded.config.logos?.[variant];
1353
- if (!configuredPath) {
1354
- continue;
1355
- }
1356
- const extension = path9.extname(configuredPath).toLowerCase().slice(1);
1357
- const contentType = { png: "image/png", svg: "image/svg+xml", webp: "image/webp" }[extension];
1358
- if (!contentType) {
1359
- throw new Error(`The ${variant} logo must be an SVG, PNG, or WebP file.`);
1360
- }
1361
- const content = await readFile4(path9.resolve(loaded.root, configuredPath));
1362
- validateLogoAsset(content, contentType);
1363
- const filename = `logo-${variant}.${extension}`;
1364
- await writeFile3(path9.join(clientOutDir, filename), content);
1365
- logoManifest[variant] = `client/${filename}`;
1366
- }
1367
- await writeFile3(path9.join(clientOutDir, "views.json"), `${JSON.stringify(views)}
1368
- `);
1369
- await writeFile3(
1370
- path9.join(resolvedOutDir, "tailorkit-upload.json"),
1371
- `${JSON.stringify(createTailorKitUploadManifest(logoManifest, Boolean(loaded.config.server), views), null, 2)}
1372
- `,
1373
- "utf-8"
1374
- );
1375
- };
1376
- let firstBuildDone;
1377
- let firstBuildFailed;
1378
- const firstBuild = new Promise((resolve2, reject) => {
1379
- firstBuildDone = resolve2;
1380
- firstBuildFailed = reject;
1381
- });
1382
- if (!options.watch) {
1383
- void firstBuild.catch(() => {
1384
- });
1385
- }
1386
- const completeBuild = async () => {
1387
- try {
1388
- await writeBuildExtras();
1389
- firstBuildDone?.();
1390
- } catch (error) {
1391
- firstBuildFailed?.(error instanceof Error ? error : new Error(String(error)));
1392
- throw error;
1393
- }
1394
- };
1395
- const result = await viteBuild({
1396
- build: {
1397
- emptyOutDir: true,
1398
- lib: {
1399
- entry,
1400
- fileName: "client",
1401
- formats: ["es"]
1402
- },
1403
- outDir: clientOutDir,
1404
- watch: options.watch ? {} : null,
1405
- minify: "oxc",
1406
- rollupOptions: {
1407
- output: {
1408
- comments: {
1409
- annotation: false,
1410
- jsdoc: false,
1411
- legal: false
1412
- },
1413
- minify: true,
1414
- minifyInternalExports: true
1415
- }
1416
- }
1417
- },
1418
- configFile: false,
1419
- mode: options.mode,
1420
- oxc: { jsx: { importSource: "preact" } },
1421
- plugins: [
1422
- fileRoutesPlugin(loaded.root),
1423
- instanceExtractionPlugin(loaded.root, Boolean(loaded.config.server), instanceModules),
1424
- {
1425
- name: "tailorkit-browser-server-boundary",
1426
- enforce: "pre",
1427
- async resolveId(id, importer) {
1428
- if (/^(?:@tailorkit\/app\/server$|tailorkit\/(?:server|app\/server)$|effect(?:\/|$))/u.test(
1429
- id
1430
- ) || importer && path9.resolve(path9.dirname(importer), id) === serverEntry) {
1431
- throw new Error(
1432
- "App server implementations cannot be imported into a browser bundle. Import api from #tailorkit and server types with import type."
1433
- );
1434
- }
1435
- if (importer) {
1436
- const resolved = await this.resolve(id, importer, { skipSelf: true });
1437
- if (resolved?.id.split("?")[0] === serverEntry) {
1438
- throw new Error(
1439
- "App server implementations cannot be imported into a browser bundle. Import api from #tailorkit and server types with import type."
1440
- );
1441
- }
1442
- }
1443
- return null;
1444
- }
1445
- },
1446
- {
1447
- name: "tailorkit-preview-build-ready",
1448
- async writeBundle() {
1449
- await completeBuild();
1450
- }
1451
- },
1452
- {
1453
- name: "tailorkit-preact-package-json",
1454
- enforce: "pre",
1455
- resolveId(id) {
1456
- if (id === preactPackageJson) {
1457
- return preactPackageJsonModuleId;
1458
- }
1459
- return null;
1460
- },
1461
- load(id) {
1462
- if (id === preactPackageJsonModuleId) {
1463
- const version = JSON.stringify(preactVersion);
1464
- return `export const version = ${version}; export default { version: ${version} };`;
1465
- }
1466
- return null;
1467
- }
1468
- }
1469
- ],
1470
- root: loaded.root
1471
- }).catch(async (error) => {
1472
- await fileEntry.close();
1473
- if (serverWatcher && "close" in serverWatcher) await serverWatcher.close();
1474
- throw error;
1475
- });
1476
- if (options.watch) {
1477
- if (result && typeof result === "object" && "on" in result) {
1478
- result.on("event", (event) => {
1479
- if (event.code === "ERROR") {
1480
- firstBuildFailed?.(event.error ?? new Error("Initial app build failed."));
1481
- }
1482
- });
1483
- }
1484
- try {
1485
- await firstBuild;
1486
- } catch (error) {
1487
- if (result && typeof result === "object" && "close" in result) {
1488
- await result.close();
1489
- }
1490
- if (serverWatcher && "close" in serverWatcher) await serverWatcher.close();
1491
- await fileEntry.close();
1492
- throw error;
1493
- }
1494
- if (result && "close" in result) {
1495
- const clientWatcher = result;
1496
- return {
1497
- on: clientWatcher.on.bind(clientWatcher),
1498
- async close() {
1499
- await clientWatcher.close();
1500
- if (serverWatcher && "close" in serverWatcher) await serverWatcher.close();
1501
- await fileEntry.close();
1502
- }
1503
- };
1504
- }
1505
- }
1506
- await fileEntry.close();
1507
- return result;
1508
- };
1509
- function getInstalledPreactVersion(root) {
1510
- const require2 = createRequire2(path9.join(root, "package.json"));
1511
- try {
1512
- const packageJsonPath = require2.resolve("preact/package.json");
1513
- const packageJson = require2(packageJsonPath);
1514
- if (typeof packageJson.version === "string") {
1515
- return packageJson.version;
1516
- }
1517
- } catch (error) {
1518
- throw new Error(
1519
- `TailorKit requires Preact to build an app. Install preact@^11 and try again.`,
1520
- { cause: error }
1521
- );
1522
- }
1523
- throw new Error("Unable to read the installed Preact version.");
1524
- }
1525
- export {
1526
- buildApp,
1527
- createTailorKitUploadManifest,
1528
- generateAppMigrations,
1529
- getClientSourceFiles,
1530
- tailorkitUploadManifestSchema
1531
- };