lakebed 0.0.11 → 0.0.13

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lakebed",
3
- "version": "0.0.11",
3
+ "version": "0.0.13",
4
4
  "description": "Agent-native CLI and runtime for building and deploying Lakebed capsules.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -52,6 +52,9 @@
52
52
  "publishConfig": {
53
53
  "access": "public"
54
54
  },
55
+ "scripts": {
56
+ "check": "node --check src/cli.js && node --check src/runtime.js && node --check src/server.js && node --check src/client.js && node --check src/source-store.js && node --check src/source-runtime.js && node --check src/source-runtime-worker.js && node --check src/source-runtime-loader.mjs && node --check src/anonymous.js && node --check src/anonymous-server.js && node --check src/auth.js && node --check src/version.js"
57
+ },
55
58
  "dependencies": {
56
59
  "esbuild": "^0.27.1",
57
60
  "pg": "^8.16.3",
@@ -60,8 +63,5 @@
60
63
  },
61
64
  "devDependencies": {
62
65
  "@types/ws": "^8.18.1"
63
- },
64
- "scripts": {
65
- "check": "node --check src/cli.js && node --check src/runtime.js && node --check src/server.js && node --check src/client.js && node --check src/source-store.js && node --check src/source-runtime.js && node --check src/source-runtime-worker.js && node --check src/source-runtime-loader.mjs && node --check src/anonymous.js && node --check src/anonymous-server.js && node --check src/auth.js && node --check src/version.js"
66
66
  }
67
- }
67
+ }
@@ -3804,6 +3804,7 @@ async function serveInspect({ artifact, deploy, route, store, systemPath }, res)
3804
3804
  expiresAt: deploy.expiresAt,
3805
3805
  limits: deploy.limits,
3806
3806
  mutations: Object.keys(artifact.server.mutations ?? {}),
3807
+ name: artifact.name ?? "Lakebed Capsule",
3807
3808
  queries: Object.keys(artifact.server.queries ?? {}),
3808
3809
  schema: artifact.server.schema,
3809
3810
  slug: deploy.slug,
package/src/cli.js CHANGED
@@ -2,8 +2,8 @@
2
2
  import { execFile } from "node:child_process";
3
3
  import { createServer } from "node:http";
4
4
  import { existsSync, realpathSync } from "node:fs";
5
- import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
6
- import { basename, dirname, isAbsolute, join, resolve } from "node:path";
5
+ import { mkdir, readdir, readFile, rm, stat, writeFile } from "node:fs/promises";
6
+ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
7
7
  import { createInterface } from "node:readline/promises";
8
8
  import { promisify } from "node:util";
9
9
  import { fileURLToPath, pathToFileURL } from "node:url";
@@ -371,6 +371,32 @@ function sendJson(ws, message) {
371
371
  ws.send(JSON.stringify(message));
372
372
  }
373
373
 
374
+ async function capsuleFileFingerprint(rootDir, dir = rootDir, entries = []) {
375
+ const dirEntries = await readdir(dir, { withFileTypes: true });
376
+
377
+ for (const entry of dirEntries) {
378
+ if (entry.name === "node_modules" || entry.name === ".lakebed" || entry.name === ".DS_Store") {
379
+ continue;
380
+ }
381
+
382
+ const absolutePath = join(dir, entry.name);
383
+ if (entry.isDirectory()) {
384
+ await capsuleFileFingerprint(rootDir, absolutePath, entries);
385
+ continue;
386
+ }
387
+
388
+ if (!entry.isFile()) {
389
+ continue;
390
+ }
391
+
392
+ const info = await stat(absolutePath);
393
+ const path = relative(rootDir, absolutePath).split(sep).join("/");
394
+ entries.push(`${path}:${info.size}:${info.mtimeMs}`);
395
+ }
396
+
397
+ return entries.sort().join("\n");
398
+ }
399
+
374
400
  function createContext({ stateCell, auth, logs, env }) {
375
401
  return {
376
402
  auth,
@@ -417,25 +443,53 @@ export async function startDevServer({
417
443
  shooBaseUrl = shooBaseUrlFromEnv()
418
444
  } = {}) {
419
445
  const resolvedCapsuleDir = resolveCapsuleDir(capsuleDir);
420
- const built = await buildCapsule({ capsuleDir: resolvedCapsuleDir, sourceStore, capsuleId });
446
+ let currentBuild = await buildCapsule({ capsuleDir: resolvedCapsuleDir, sourceStore, capsuleId });
421
447
  const defaultAuth = await readAuth();
422
- const stateCell = new StateCell(built.app.schema);
448
+ const stateCell = new StateCell(currentBuild.app.schema);
423
449
  const logs = new LogBuffer();
424
450
  const subscriptions = new Map();
451
+ let fileFingerprint = sourceStore ? "" : await capsuleFileFingerprint(resolvedCapsuleDir);
452
+ let rebuildTimer = null;
453
+ let rebuildPromise = Promise.resolve();
454
+
455
+ async function rebuild() {
456
+ try {
457
+ const nextBuild = await buildCapsule({ capsuleDir: resolvedCapsuleDir, sourceStore, capsuleId });
458
+ currentBuild = nextBuild;
459
+ stateCell.updateSchema(nextBuild.app.schema);
460
+ logs.append("info", "dev server rebuilt capsule", { capsuleDir: resolvedCapsuleDir });
461
+ for (const client of wss.clients) {
462
+ sendJson(client, { op: "refresh" });
463
+ }
464
+ } catch (error) {
465
+ logs.append("error", "dev server rebuild failed", { error: error instanceof Error ? error.message : String(error) });
466
+ }
467
+ }
468
+
469
+ function scheduleRebuild() {
470
+ if (rebuildTimer) {
471
+ clearTimeout(rebuildTimer);
472
+ }
473
+
474
+ rebuildTimer = setTimeout(() => {
475
+ rebuildTimer = null;
476
+ rebuildPromise = rebuildPromise.then(rebuild, rebuild);
477
+ }, 100);
478
+ }
425
479
 
426
480
  const server = createServer(async (req, res) => {
427
481
  try {
428
482
  const requestUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
429
483
 
430
484
  if (requestUrl.pathname === "/" || requestUrl.pathname === "/index.html" || requestUrl.pathname === "/auth/callback") {
431
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
432
- res.end(html(built.app.name ?? "Lakebed Capsule", { shooBaseUrl }));
485
+ res.writeHead(200, { "Cache-Control": "no-store", "Content-Type": "text/html; charset=utf-8" });
486
+ res.end(html(currentBuild.app.name ?? "Lakebed Capsule", { shooBaseUrl }));
433
487
  return;
434
488
  }
435
489
 
436
490
  if (requestUrl.pathname === "/client.js") {
437
- res.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8" });
438
- res.end(await readFile(built.clientOut, "utf8"));
491
+ res.writeHead(200, { "Cache-Control": "no-store", "Content-Type": "application/javascript; charset=utf-8" });
492
+ res.end(await readFile(currentBuild.clientOut, "utf8"));
439
493
  return;
440
494
  }
441
495
 
@@ -472,11 +526,11 @@ export async function startDevServer({
472
526
  for (const name of subscription.queries) {
473
527
  try {
474
528
  const data = await runQuery({
475
- app: built.app,
529
+ app: currentBuild.app,
476
530
  stateCell,
477
531
  auth: subscription.auth,
478
532
  logs,
479
- env: built.env,
533
+ env: currentBuild.env,
480
534
  name
481
535
  });
482
536
  sendJson(ws, { op: "query.result", name, data });
@@ -508,11 +562,11 @@ export async function startDevServer({
508
562
  if (message.op === "query.subscribe") {
509
563
  subscription.queries.add(message.name);
510
564
  const data = await runQuery({
511
- app: built.app,
565
+ app: currentBuild.app,
512
566
  stateCell,
513
567
  auth: subscription.auth,
514
568
  logs,
515
- env: built.env,
569
+ env: currentBuild.env,
516
570
  name: message.name
517
571
  });
518
572
  sendJson(ws, { id: message.id, op: "query.result", ok: true, name: message.name, data });
@@ -521,11 +575,11 @@ export async function startDevServer({
521
575
 
522
576
  if (message.op === "mutation.run") {
523
577
  const { result } = await runMutation({
524
- app: built.app,
578
+ app: currentBuild.app,
525
579
  stateCell,
526
580
  auth: subscription.auth,
527
581
  logs,
528
- env: built.env,
582
+ env: currentBuild.env,
529
583
  name: message.name,
530
584
  args: message.args ?? []
531
585
  });
@@ -585,15 +639,42 @@ export async function startDevServer({
585
639
  console.log(`Auth: ${defaultAuth.userId}`);
586
640
  }
587
641
 
642
+ const watchInterval = sourceStore
643
+ ? null
644
+ : setInterval(async () => {
645
+ try {
646
+ const nextFingerprint = await capsuleFileFingerprint(resolvedCapsuleDir);
647
+ if (nextFingerprint === fileFingerprint) {
648
+ return;
649
+ }
650
+
651
+ fileFingerprint = nextFingerprint;
652
+ scheduleRebuild();
653
+ } catch (error) {
654
+ logs.append("error", "dev server file watch failed", { error: error instanceof Error ? error.message : String(error) });
655
+ }
656
+ }, 300);
657
+
588
658
  return {
589
- app: built.app,
590
- buildDir: built.buildDir,
659
+ get app() {
660
+ return currentBuild.app;
661
+ },
662
+ get buildDir() {
663
+ return currentBuild.buildDir;
664
+ },
591
665
  capsuleDir: resolvedCapsuleDir,
592
666
  logs,
593
667
  port,
594
668
  stateCell,
595
669
  url: `http://localhost:${port}`,
596
670
  async close() {
671
+ if (watchInterval) {
672
+ clearInterval(watchInterval);
673
+ }
674
+ if (rebuildTimer) {
675
+ clearTimeout(rebuildTimer);
676
+ }
677
+ await rebuildPromise.catch(() => {});
597
678
  for (const client of wss.clients) {
598
679
  client.close();
599
680
  }
@@ -1220,6 +1301,8 @@ function todoTemplate(name) {
1220
1301
  import { cleanTodoText } from "../shared/todo";
1221
1302
 
1222
1303
  export default capsule({
1304
+ name: ${JSON.stringify(title)},
1305
+
1223
1306
  schema: {
1224
1307
  todos: table({
1225
1308
  text: string(),
package/src/runtime.js CHANGED
@@ -187,6 +187,22 @@ export class StateCell {
187
187
  }
188
188
  }
189
189
 
190
+ updateSchema(schema) {
191
+ this.schema = schema;
192
+
193
+ for (const tableName of Object.keys(schema ?? {})) {
194
+ if (!this.tables.has(tableName)) {
195
+ this.tables.set(tableName, new Map());
196
+ }
197
+ }
198
+
199
+ for (const tableName of this.tables.keys()) {
200
+ if (!schema?.[tableName]) {
201
+ this.tables.delete(tableName);
202
+ }
203
+ }
204
+ }
205
+
190
206
  createDb() {
191
207
  const db = {};
192
208
  for (const tableName of this.tables.keys()) {
package/src/version.js CHANGED
@@ -1 +1 @@
1
- export const LAKEBED_VERSION = "0.0.11";
1
+ export const LAKEBED_VERSION = "0.0.13";