@kuratchi/js 0.0.14 → 0.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +135 -68
- package/dist/cli.js +80 -47
- package/dist/compiler/api-route-pipeline.d.ts +8 -0
- package/dist/compiler/api-route-pipeline.js +23 -0
- package/dist/compiler/asset-pipeline.d.ts +7 -0
- package/dist/compiler/asset-pipeline.js +33 -0
- package/dist/compiler/client-module-pipeline.d.ts +25 -0
- package/dist/compiler/client-module-pipeline.js +257 -0
- package/dist/compiler/compiler-shared.d.ts +55 -0
- package/dist/compiler/compiler-shared.js +4 -0
- package/dist/compiler/component-pipeline.d.ts +15 -0
- package/dist/compiler/component-pipeline.js +163 -0
- package/dist/compiler/config-reading.d.ts +11 -0
- package/dist/compiler/config-reading.js +323 -0
- package/dist/compiler/convention-discovery.d.ts +9 -0
- package/dist/compiler/convention-discovery.js +83 -0
- package/dist/compiler/durable-object-pipeline.d.ts +9 -0
- package/dist/compiler/durable-object-pipeline.js +255 -0
- package/dist/compiler/error-page-pipeline.d.ts +1 -0
- package/dist/compiler/error-page-pipeline.js +16 -0
- package/dist/compiler/import-linking.d.ts +36 -0
- package/dist/compiler/import-linking.js +139 -0
- package/dist/compiler/index.d.ts +3 -3
- package/dist/compiler/index.js +137 -3265
- package/dist/compiler/layout-pipeline.d.ts +31 -0
- package/dist/compiler/layout-pipeline.js +155 -0
- package/dist/compiler/page-route-pipeline.d.ts +16 -0
- package/dist/compiler/page-route-pipeline.js +62 -0
- package/dist/compiler/parser.d.ts +4 -0
- package/dist/compiler/parser.js +433 -51
- package/dist/compiler/root-layout-pipeline.d.ts +10 -0
- package/dist/compiler/root-layout-pipeline.js +517 -0
- package/dist/compiler/route-discovery.d.ts +7 -0
- package/dist/compiler/route-discovery.js +87 -0
- package/dist/compiler/route-pipeline.d.ts +57 -0
- package/dist/compiler/route-pipeline.js +296 -0
- package/dist/compiler/route-state-pipeline.d.ts +25 -0
- package/dist/compiler/route-state-pipeline.js +139 -0
- package/dist/compiler/routes-module-feature-blocks.d.ts +2 -0
- package/dist/compiler/routes-module-feature-blocks.js +330 -0
- package/dist/compiler/routes-module-pipeline.d.ts +2 -0
- package/dist/compiler/routes-module-pipeline.js +6 -0
- package/dist/compiler/routes-module-runtime-shell.d.ts +2 -0
- package/dist/compiler/routes-module-runtime-shell.js +81 -0
- package/dist/compiler/routes-module-types.d.ts +44 -0
- package/dist/compiler/routes-module-types.js +1 -0
- package/dist/compiler/script-transform.d.ts +16 -0
- package/dist/compiler/script-transform.js +218 -0
- package/dist/compiler/server-module-pipeline.d.ts +13 -0
- package/dist/compiler/server-module-pipeline.js +124 -0
- package/dist/compiler/template.d.ts +13 -1
- package/dist/compiler/template.js +323 -60
- package/dist/compiler/worker-output-pipeline.d.ts +13 -0
- package/dist/compiler/worker-output-pipeline.js +37 -0
- package/dist/compiler/wrangler-sync.d.ts +14 -0
- package/dist/compiler/wrangler-sync.js +185 -0
- package/dist/runtime/app.js +15 -3
- package/dist/runtime/generated-worker.d.ts +33 -0
- package/dist/runtime/generated-worker.js +412 -0
- package/dist/runtime/index.d.ts +2 -1
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/router.d.ts +2 -1
- package/dist/runtime/router.js +12 -3
- package/dist/runtime/types.d.ts +8 -2
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -16,9 +16,9 @@ cd my-app
|
|
|
16
16
|
bun run dev
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
## How it works
|
|
20
|
-
|
|
21
|
-
`kuratchi build` (or `kuratchi watch`) scans `src/routes/` and generates framework output:
|
|
19
|
+
## How it works
|
|
20
|
+
|
|
21
|
+
`kuratchi build` (or `kuratchi watch`) scans `src/routes/` and generates framework output:
|
|
22
22
|
|
|
23
23
|
| File | Purpose |
|
|
24
24
|
|---|---|
|
|
@@ -26,11 +26,13 @@ bun run dev
|
|
|
26
26
|
| `.kuratchi/worker.js` | Stable wrangler entry - re-exports the fetch handler plus all Durable Object and Agent classes |
|
|
27
27
|
| `.kuratchi/do/*.js` | Generated Durable Object RPC proxy modules for `$durable-objects/*` imports |
|
|
28
28
|
|
|
29
|
-
Point wrangler at the entry and you're done. **No `src/index.ts` needed.**
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
Point wrangler at the entry and you're done. **No `src/index.ts` needed.**
|
|
30
|
+
|
|
31
|
+
For the framework's internal compiler/runtime orchestration and tracked implementation roadmap, see [ARCHITECTURE.md](./ARCHITECTURE.md).
|
|
32
|
+
|
|
33
|
+
```jsonc
|
|
34
|
+
// wrangler.jsonc
|
|
35
|
+
{
|
|
34
36
|
"main": ".kuratchi/worker.js"
|
|
35
37
|
}
|
|
36
38
|
```
|
|
@@ -349,14 +351,31 @@ Navigate to a URL on click (respects `http:`/`https:` only):
|
|
|
349
351
|
|
|
350
352
|
### `data-poll` — polling
|
|
351
353
|
|
|
352
|
-
|
|
354
|
+
Poll and update an element's content at a human-readable interval with automatic exponential backoff:
|
|
353
355
|
|
|
354
356
|
```html
|
|
355
|
-
<div data-
|
|
357
|
+
<div data-poll={getStatus(itemId)} data-interval="2s">
|
|
356
358
|
{status}
|
|
357
359
|
</div>
|
|
358
360
|
```
|
|
359
361
|
|
|
362
|
+
**How it works:**
|
|
363
|
+
1. Client sends a fragment request with `x-kuratchi-fragment` header
|
|
364
|
+
2. Server re-renders the route but returns **only the fragment's innerHTML** — not the full page
|
|
365
|
+
3. Client swaps the element's content — minimal payload, no full page reload
|
|
366
|
+
|
|
367
|
+
This fragment-based architecture is the foundation for partial rendering and scales to Astro-style islands.
|
|
368
|
+
|
|
369
|
+
**Interval formats:**
|
|
370
|
+
- `2s` — 2 seconds
|
|
371
|
+
- `500ms` — 500 milliseconds
|
|
372
|
+
- `1m` — 1 minute
|
|
373
|
+
- Default: `30s` with exponential backoff (30s → 45s → 67s → ... capped at 5 minutes)
|
|
374
|
+
|
|
375
|
+
**Options:**
|
|
376
|
+
- `data-interval` — polling interval (human-readable, default `30s`)
|
|
377
|
+
- `data-backoff="false"` — disable exponential backoff
|
|
378
|
+
|
|
360
379
|
### `data-select-all` / `data-select-item` — checkbox groups
|
|
361
380
|
|
|
362
381
|
Sync a "select all" checkbox with a group of item checkboxes:
|
|
@@ -397,79 +416,87 @@ Durable Object behavior is enabled by filename suffix.
|
|
|
397
416
|
- Any file not ending in `.do.ts` is treated as a normal server module.
|
|
398
417
|
- No required folder name. `src/server/auth.do.ts`, `src/server/foo/bar/sites.do.ts`, etc. all work.
|
|
399
418
|
|
|
400
|
-
###
|
|
419
|
+
### Writing a Durable Object
|
|
401
420
|
|
|
402
|
-
|
|
403
|
-
Use `this.db`, `this.env`, and `this.ctx` inside those functions.
|
|
421
|
+
Extend the native Cloudflare `DurableObject` class. Public methods automatically become RPC-accessible:
|
|
404
422
|
|
|
405
423
|
```ts
|
|
406
|
-
// src/server/
|
|
407
|
-
import {
|
|
408
|
-
import { redirect } from '@kuratchi/js';
|
|
409
|
-
|
|
410
|
-
async function randomPassword(length = 24): Promise<string> {
|
|
411
|
-
const alphabet = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789';
|
|
412
|
-
const bytes = new Uint8Array(length);
|
|
413
|
-
crypto.getRandomValues(bytes);
|
|
414
|
-
let out = '';
|
|
415
|
-
for (let i = 0; i < length; i++) out += alphabet[bytes[i] % alphabet.length];
|
|
416
|
-
return out;
|
|
417
|
-
}
|
|
424
|
+
// src/server/user.do.ts
|
|
425
|
+
import { DurableObject } from 'cloudflare:workers';
|
|
418
426
|
|
|
419
|
-
export
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
}
|
|
427
|
+
export default class UserDO extends DurableObject {
|
|
428
|
+
async getName() {
|
|
429
|
+
return await this.ctx.storage.get('name');
|
|
430
|
+
}
|
|
423
431
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
432
|
+
async setName(name: string) {
|
|
433
|
+
this._validate(name);
|
|
434
|
+
await this.ctx.storage.put('name', name);
|
|
435
|
+
}
|
|
427
436
|
|
|
428
|
-
|
|
429
|
-
|
|
437
|
+
// NOT RPC-accessible (underscore prefix)
|
|
438
|
+
_validate(name: string) {
|
|
439
|
+
if (!name) throw new Error('Name required');
|
|
440
|
+
}
|
|
430
441
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
442
|
+
// NOT RPC-accessible (lifecycle method)
|
|
443
|
+
async alarm() {
|
|
444
|
+
// Handle alarm
|
|
445
|
+
}
|
|
434
446
|
}
|
|
435
447
|
```
|
|
436
448
|
|
|
437
|
-
|
|
449
|
+
**RPC rules:**
|
|
450
|
+
- **Public methods** (`getName`, `setName`) → RPC-accessible
|
|
451
|
+
- **Underscore prefix** (`_validate`) → NOT RPC-accessible
|
|
452
|
+
- **Private/protected** (`private foo()`) → NOT RPC-accessible
|
|
453
|
+
- **Lifecycle methods** (`constructor`, `fetch`, `alarm`, `webSocketMessage`, etc.) → NOT RPC-accessible
|
|
438
454
|
|
|
439
|
-
|
|
440
|
-
- `export async function onAlarm(...args)`
|
|
441
|
-
- `export function onMessage(...args)`
|
|
455
|
+
### Using from routes
|
|
442
456
|
|
|
443
|
-
|
|
457
|
+
Import from `$do/<filename>` (without the `.do` suffix):
|
|
444
458
|
|
|
445
|
-
|
|
459
|
+
```html
|
|
460
|
+
<script server>
|
|
461
|
+
import { getName, setName } from '$do/user';
|
|
446
462
|
|
|
447
|
-
|
|
463
|
+
const name = await getName();
|
|
464
|
+
</script>
|
|
448
465
|
|
|
449
|
-
|
|
450
|
-
|
|
466
|
+
<h1>Hello, {name}</h1>
|
|
467
|
+
```
|
|
451
468
|
|
|
452
|
-
|
|
453
|
-
static binding = 'NOTES_DO';
|
|
469
|
+
The framework handles RPC wiring automatically.
|
|
454
470
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
471
|
+
### Auto-Discovery
|
|
472
|
+
|
|
473
|
+
Durable Objects are auto-discovered from `.do.ts` files. **No config needed.**
|
|
474
|
+
|
|
475
|
+
**Naming convention:**
|
|
476
|
+
- `user.do.ts` → binding `USER_DO`
|
|
477
|
+
- `org-settings.do.ts` → binding `ORG_SETTINGS_DO`
|
|
478
|
+
|
|
479
|
+
**Override binding name** with `static binding`:
|
|
480
|
+
```ts
|
|
481
|
+
export default class UserDO extends DurableObject {
|
|
482
|
+
static binding = 'CUSTOM_BINDING'; // Optional override
|
|
483
|
+
// ...
|
|
458
484
|
}
|
|
459
485
|
```
|
|
460
486
|
|
|
461
|
-
|
|
487
|
+
The framework auto-syncs discovered DOs to `wrangler.jsonc`.
|
|
462
488
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
489
|
+
### Optional: stubId for auth integration
|
|
490
|
+
|
|
491
|
+
If you need automatic stub resolution based on user context, add `stubId` in `kuratchi.config.ts`:
|
|
492
|
+
|
|
493
|
+
```ts
|
|
494
|
+
// kuratchi.config.ts
|
|
495
|
+
export default defineConfig({
|
|
496
|
+
durableObjects: {
|
|
497
|
+
USER_DO: { stubId: 'user.orgId' }, // Only needed for auth integration
|
|
468
498
|
},
|
|
469
|
-
|
|
470
|
-
{ "tag": "v1", "new_sqlite_classes": ["NotesDO"] }
|
|
471
|
-
]
|
|
472
|
-
}
|
|
499
|
+
});
|
|
473
500
|
```
|
|
474
501
|
|
|
475
502
|
## Agents
|
|
@@ -542,6 +569,46 @@ Examples:
|
|
|
542
569
|
- `bond.workflow.ts` → `BOND_WORKFLOW` binding
|
|
543
570
|
- `new-site.workflow.ts` → `NEW_SITE_WORKFLOW` binding
|
|
544
571
|
|
|
572
|
+
### Workflow Status Polling
|
|
573
|
+
|
|
574
|
+
Kuratchi auto-generates status polling RPCs for each discovered workflow. Poll workflow status with zero setup:
|
|
575
|
+
|
|
576
|
+
```html
|
|
577
|
+
<div data-poll={migrationWorkflowStatus(instanceId)} data-interval="2s">
|
|
578
|
+
if (workflowStatus.status === 'running') {
|
|
579
|
+
<div class="spinner">Running...</div>
|
|
580
|
+
} else if (workflowStatus.status === 'complete') {
|
|
581
|
+
<div>✓ Complete</div>
|
|
582
|
+
}
|
|
583
|
+
</div>
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
The element's innerHTML updates automatically when the workflow status changes — no page reload needed.
|
|
587
|
+
|
|
588
|
+
**Auto-generated RPC naming** (camelCase):
|
|
589
|
+
- `migration.workflow.ts` → `migrationWorkflowStatus(instanceId)`
|
|
590
|
+
- `james-bond.workflow.ts` → `jamesBondWorkflowStatus(instanceId)`
|
|
591
|
+
- `site.workflow.ts` → `siteWorkflowStatus(instanceId)`
|
|
592
|
+
|
|
593
|
+
**Multiple workflows on one page:** Each `data-poll` element is independent. You can poll multiple workflow instances without collision:
|
|
594
|
+
|
|
595
|
+
```html
|
|
596
|
+
for (const job of jobs) {
|
|
597
|
+
<div data-poll={migrationWorkflowStatus(job.instanceId)} data-interval="2s">
|
|
598
|
+
{job.name}: polling...
|
|
599
|
+
</div>
|
|
600
|
+
}
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
The status RPC returns the Cloudflare `InstanceStatus` object:
|
|
604
|
+
```ts
|
|
605
|
+
{
|
|
606
|
+
status: 'queued' | 'running' | 'paused' | 'errored' | 'terminated' | 'complete' | 'waiting' | 'unknown';
|
|
607
|
+
error?: { name: string; message: string; };
|
|
608
|
+
output?: unknown;
|
|
609
|
+
}
|
|
610
|
+
```
|
|
611
|
+
|
|
545
612
|
## Containers
|
|
546
613
|
|
|
547
614
|
Kuratchi auto-discovers `.container.ts` files in `src/server/`. **No config needed.**
|
|
@@ -701,7 +768,9 @@ Kuratchi also exposes a framework build-mode flag:
|
|
|
701
768
|
|
|
702
769
|
## `kuratchi.config.ts`
|
|
703
770
|
|
|
704
|
-
Optional. Required only when using framework integrations
|
|
771
|
+
Optional. Required only when using framework integrations (ORM, auth, UI).
|
|
772
|
+
|
|
773
|
+
**Durable Objects are auto-discovered** — no config needed unless you need `stubId` for auth integration.
|
|
705
774
|
|
|
706
775
|
```ts
|
|
707
776
|
import { defineConfig } from '@kuratchi/js';
|
|
@@ -717,16 +786,14 @@ export default defineConfig({
|
|
|
717
786
|
NOTES_DO: { schema: notesSchema, type: 'do' },
|
|
718
787
|
},
|
|
719
788
|
}),
|
|
720
|
-
durableObjects: {
|
|
721
|
-
NOTES_DO: {
|
|
722
|
-
className: 'NotesDO',
|
|
723
|
-
files: ['notes.do.ts'],
|
|
724
|
-
},
|
|
725
|
-
},
|
|
726
789
|
auth: kuratchiAuthConfig({
|
|
727
790
|
cookieName: 'kuratchi_session',
|
|
728
791
|
sessionEnabled: true,
|
|
729
792
|
}),
|
|
793
|
+
// Optional: only needed for auth-based stub resolution
|
|
794
|
+
durableObjects: {
|
|
795
|
+
NOTES_DO: { stubId: 'user.orgId' },
|
|
796
|
+
},
|
|
730
797
|
});
|
|
731
798
|
```
|
|
732
799
|
|
package/dist/cli.js
CHANGED
|
@@ -6,34 +6,41 @@ import { compile } from './compiler/index.js';
|
|
|
6
6
|
import * as path from 'node:path';
|
|
7
7
|
import * as fs from 'node:fs';
|
|
8
8
|
import * as net from 'node:net';
|
|
9
|
+
import { createRequire } from 'node:module';
|
|
9
10
|
import { spawn } from 'node:child_process';
|
|
10
11
|
const args = process.argv.slice(2);
|
|
11
12
|
const command = args[0];
|
|
12
13
|
const projectDir = process.cwd();
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
14
|
+
void main().catch((err) => {
|
|
15
|
+
console.error(`[kuratchi] ${err?.message ?? err}`);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
});
|
|
18
|
+
async function main() {
|
|
19
|
+
switch (command) {
|
|
20
|
+
case 'build':
|
|
21
|
+
runBuild();
|
|
22
|
+
return;
|
|
23
|
+
case 'watch':
|
|
24
|
+
await runWatch(false);
|
|
25
|
+
return;
|
|
26
|
+
case 'dev':
|
|
27
|
+
await runWatch(true);
|
|
28
|
+
return;
|
|
29
|
+
case 'create':
|
|
30
|
+
await runCreate();
|
|
31
|
+
return;
|
|
32
|
+
default:
|
|
33
|
+
console.log(`
|
|
34
|
+
KuratchiJS CLI
|
|
35
|
+
|
|
36
|
+
Usage:
|
|
37
|
+
kuratchi create [name] Scaffold a new KuratchiJS project
|
|
38
|
+
kuratchi build Compile routes once
|
|
39
|
+
kuratchi dev Compile, watch for changes, and start wrangler dev server
|
|
40
|
+
kuratchi watch Compile + watch only (no wrangler — for custom setups)
|
|
35
41
|
`);
|
|
36
|
-
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
37
44
|
}
|
|
38
45
|
async function runCreate() {
|
|
39
46
|
const { create } = await import('./create.js');
|
|
@@ -53,7 +60,7 @@ function runBuild(isDev = false) {
|
|
|
53
60
|
process.exit(1);
|
|
54
61
|
}
|
|
55
62
|
}
|
|
56
|
-
function runWatch(withWrangler = false) {
|
|
63
|
+
async function runWatch(withWrangler = false) {
|
|
57
64
|
runBuild(true);
|
|
58
65
|
const routesDir = path.join(projectDir, 'src', 'routes');
|
|
59
66
|
const serverDir = path.join(projectDir, 'src', 'server');
|
|
@@ -80,11 +87,10 @@ function runWatch(withWrangler = false) {
|
|
|
80
87
|
// `kuratchi dev` also starts the wrangler dev server.
|
|
81
88
|
// `kuratchi watch` is the compiler-only mode for custom setups.
|
|
82
89
|
if (withWrangler) {
|
|
83
|
-
startWranglerDev()
|
|
84
|
-
|
|
85
|
-
process.exit(1);
|
|
86
|
-
});
|
|
90
|
+
await startWranglerDev();
|
|
91
|
+
return;
|
|
87
92
|
}
|
|
93
|
+
await new Promise(() => { });
|
|
88
94
|
}
|
|
89
95
|
function hasPortFlag(inputArgs) {
|
|
90
96
|
for (let i = 0; i < inputArgs.length; i++) {
|
|
@@ -117,25 +123,13 @@ async function findOpenPort(start = 8787, end = 8899) {
|
|
|
117
123
|
}
|
|
118
124
|
async function startWranglerDev() {
|
|
119
125
|
const passthroughArgs = args.slice(1);
|
|
120
|
-
const wranglerArgs = ['
|
|
126
|
+
const wranglerArgs = ['dev', ...passthroughArgs];
|
|
121
127
|
if (!hasPortFlag(passthroughArgs)) {
|
|
122
128
|
const port = await findOpenPort();
|
|
123
129
|
wranglerArgs.push('--port', String(port));
|
|
124
130
|
console.log(`[kuratchi] Starting wrangler dev on port ${port}`);
|
|
125
131
|
}
|
|
126
|
-
|
|
127
|
-
// prematurely when launched via a script runner (e.g. `bun run dev`).
|
|
128
|
-
const isWin = process.platform === 'win32';
|
|
129
|
-
const wrangler = isWin
|
|
130
|
-
? spawn('npx ' + wranglerArgs.join(' '), {
|
|
131
|
-
cwd: projectDir,
|
|
132
|
-
stdio: ['pipe', 'inherit', 'inherit'],
|
|
133
|
-
shell: true,
|
|
134
|
-
})
|
|
135
|
-
: spawn('npx', wranglerArgs, {
|
|
136
|
-
cwd: projectDir,
|
|
137
|
-
stdio: ['pipe', 'inherit', 'inherit'],
|
|
138
|
-
});
|
|
132
|
+
const wrangler = spawnWranglerProcess(wranglerArgs);
|
|
139
133
|
const cleanup = () => {
|
|
140
134
|
if (!wrangler.killed)
|
|
141
135
|
wrangler.kill();
|
|
@@ -143,10 +137,49 @@ async function startWranglerDev() {
|
|
|
143
137
|
process.on('exit', cleanup);
|
|
144
138
|
process.on('SIGINT', () => { cleanup(); process.exit(0); });
|
|
145
139
|
process.on('SIGTERM', () => { cleanup(); process.exit(0); });
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
140
|
+
await new Promise((resolve, reject) => {
|
|
141
|
+
wrangler.on('exit', (code) => {
|
|
142
|
+
if (code !== 0 && code !== null) {
|
|
143
|
+
reject(new Error(`wrangler exited with code ${code}`));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
resolve();
|
|
147
|
+
});
|
|
148
|
+
wrangler.on('error', (err) => {
|
|
149
|
+
reject(err);
|
|
150
|
+
});
|
|
151
|
+
}).catch((err) => {
|
|
152
|
+
console.error(`[kuratchi] Failed to start wrangler dev: ${err?.message ?? err}`);
|
|
153
|
+
process.exit(1);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
function resolveWranglerBin() {
|
|
157
|
+
try {
|
|
158
|
+
const projectPackageJson = path.join(projectDir, 'package.json');
|
|
159
|
+
const projectRequire = createRequire(projectPackageJson);
|
|
160
|
+
return projectRequire.resolve('wrangler/bin/wrangler.js');
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function getNodeExecutable() {
|
|
167
|
+
if (!process.versions.bun)
|
|
168
|
+
return process.execPath;
|
|
169
|
+
return 'node';
|
|
170
|
+
}
|
|
171
|
+
function spawnWranglerProcess(wranglerArgs) {
|
|
172
|
+
const localWranglerBin = resolveWranglerBin();
|
|
173
|
+
const stdio = ['pipe', 'inherit', 'inherit'];
|
|
174
|
+
if (localWranglerBin) {
|
|
175
|
+
return spawn(getNodeExecutable(), [localWranglerBin, ...wranglerArgs], {
|
|
176
|
+
cwd: projectDir,
|
|
177
|
+
stdio,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
const fallbackCommand = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
181
|
+
return spawn(fallbackCommand, ['wrangler', ...wranglerArgs], {
|
|
182
|
+
cwd: projectDir,
|
|
183
|
+
stdio,
|
|
151
184
|
});
|
|
152
185
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
const ALL_API_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'];
|
|
4
|
+
export function compileApiRoute(opts) {
|
|
5
|
+
const outFileDir = path.join(opts.projectDir, '.kuratchi');
|
|
6
|
+
const absRoutePath = opts.transformModule(opts.fullPath);
|
|
7
|
+
let importPath = path.relative(outFileDir, absRoutePath).replace(/\\/g, '/');
|
|
8
|
+
if (!importPath.startsWith('.'))
|
|
9
|
+
importPath = './' + importPath;
|
|
10
|
+
const moduleId = opts.allocateModuleId();
|
|
11
|
+
opts.pushImport(`import * as ${moduleId} from '${importPath}';`);
|
|
12
|
+
const apiSource = fs.readFileSync(opts.fullPath, 'utf-8');
|
|
13
|
+
const exportedMethods = ALL_API_METHODS.filter((method) => {
|
|
14
|
+
const fnPattern = new RegExp(`export\\s+(async\\s+)?function\\s+${method}\\b`);
|
|
15
|
+
const reExportPattern = new RegExp(`export\\s*\\{[^}]*\\b\\w+\\s+as\\s+${method}\\b`);
|
|
16
|
+
const namedExportPattern = new RegExp(`export\\s*\\{[^}]*\\b${method}\\b`);
|
|
17
|
+
return fnPattern.test(apiSource) || reExportPattern.test(apiSource) || namedExportPattern.test(apiSource);
|
|
18
|
+
});
|
|
19
|
+
const methodEntries = exportedMethods
|
|
20
|
+
.map((method) => `${method}: ${moduleId}.${method}`)
|
|
21
|
+
.join(', ');
|
|
22
|
+
return `{ pattern: '${opts.pattern}', __api: true, ${methodEntries} }`;
|
|
23
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as crypto from 'node:crypto';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
const MIME_TYPES = {
|
|
5
|
+
'.css': 'text/css; charset=utf-8',
|
|
6
|
+
'.js': 'text/javascript; charset=utf-8',
|
|
7
|
+
'.json': 'application/json; charset=utf-8',
|
|
8
|
+
'.svg': 'image/svg+xml',
|
|
9
|
+
'.txt': 'text/plain; charset=utf-8',
|
|
10
|
+
};
|
|
11
|
+
export function compileAssets(assetsDir) {
|
|
12
|
+
const compiledAssets = [];
|
|
13
|
+
if (!fs.existsSync(assetsDir))
|
|
14
|
+
return compiledAssets;
|
|
15
|
+
const scanAssets = (dir, prefix) => {
|
|
16
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
17
|
+
if (entry.isDirectory()) {
|
|
18
|
+
scanAssets(path.join(dir, entry.name), prefix ? `${prefix}/${entry.name}` : entry.name);
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
22
|
+
const mime = MIME_TYPES[ext];
|
|
23
|
+
if (!mime)
|
|
24
|
+
continue;
|
|
25
|
+
const content = fs.readFileSync(path.join(dir, entry.name), 'utf-8');
|
|
26
|
+
const etag = '"' + crypto.createHash('md5').update(content).digest('hex').slice(0, 12) + '"';
|
|
27
|
+
const name = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
28
|
+
compiledAssets.push({ name, content, mime, etag });
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
scanAssets(assetsDir, '');
|
|
32
|
+
return compiledAssets;
|
|
33
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { CompiledAsset } from './asset-pipeline.js';
|
|
2
|
+
import { type RouteImportEntry } from './import-linking.js';
|
|
3
|
+
export interface ClientEventRegistration {
|
|
4
|
+
routeId: string;
|
|
5
|
+
handlerId: string;
|
|
6
|
+
argsExpr: string | null;
|
|
7
|
+
}
|
|
8
|
+
export interface ClientModuleCompiler {
|
|
9
|
+
createRegistry(scopeId: string, importEntries: RouteImportEntry[]): ClientRouteRegistry;
|
|
10
|
+
createRouteRegistry(routeIndex: number, importEntries: RouteImportEntry[]): ClientRouteRegistry;
|
|
11
|
+
getCompiledAssets(): CompiledAsset[];
|
|
12
|
+
}
|
|
13
|
+
export interface ClientRouteRegistry {
|
|
14
|
+
hasBindings(): boolean;
|
|
15
|
+
hasBindingReference(expression: string): boolean;
|
|
16
|
+
registerEventHandler(eventName: string, expression: string): ClientEventRegistration | null;
|
|
17
|
+
buildEntryAsset(): {
|
|
18
|
+
assetName: string;
|
|
19
|
+
asset: CompiledAsset;
|
|
20
|
+
} | null;
|
|
21
|
+
}
|
|
22
|
+
export declare function createClientModuleCompiler(opts: {
|
|
23
|
+
projectDir: string;
|
|
24
|
+
srcDir: string;
|
|
25
|
+
}): ClientModuleCompiler;
|