@montytools/cli 0.2.6 → 0.2.8

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/bin/monty.mjs CHANGED
@@ -489,6 +489,7 @@ async function dev() {
489
489
  let hbTimer = null;
490
490
  let publishing = false;
491
491
  let ended = false;
492
+ const sessionId = `dev_${randomBytes(16).toString("hex")}`;
492
493
  const buildFile = join(appDir, ".monty", "build");
493
494
  const buildId = existsSync(buildFile) ? readFileSync(buildFile, "utf8").trim() : undefined;
494
495
  // The DEV schema channel: heartbeats carry the compiled schema, and edits
@@ -519,7 +520,7 @@ async function dev() {
519
520
  await fetch(`${host}/api/dev-session`, {
520
521
  method: "POST",
521
522
  headers: { authorization: `Bearer ${cfg.key}`, "content-type": "application/json" },
522
- body: JSON.stringify({ slug: meta.slug, end: true }),
523
+ body: JSON.stringify({ slug: meta.slug, sessionId, end: true }),
523
524
  signal: AbortSignal.timeout(timeoutMs),
524
525
  });
525
526
  } catch { /* best effort */ }
@@ -534,7 +535,17 @@ async function dev() {
534
535
  await clearDevSession();
535
536
  }
536
537
 
537
- async function heartbeat(originUrl) {
538
+ function stopSuperseded(fix) {
539
+ if (ended) return;
540
+ ended = true;
541
+ if (hbTimer) clearInterval(hbTimer);
542
+ try { tunnelChild?.kill(); } catch { /* already gone */ }
543
+ try { child.kill(); } catch { /* already gone */ }
544
+ console.log(`dev-session: superseded — ${fix}`);
545
+ setTimeout(() => process.exit(0), 50);
546
+ }
547
+
548
+ async function heartbeat(originUrl, { claim = false } = {}) {
538
549
  await refreshSchemaIfChanged();
539
550
  try {
540
551
  const r = await fetch(`${host}/api/dev-session`, {
@@ -543,6 +554,8 @@ async function dev() {
543
554
  body: JSON.stringify({
544
555
  slug: meta.slug,
545
556
  tunnelUrl: originUrl,
557
+ sessionId,
558
+ claim,
546
559
  name: currentMeta.name,
547
560
  icon: currentMeta.icon,
548
561
  buildId,
@@ -552,6 +565,10 @@ async function dev() {
552
565
  });
553
566
  const data = await r.json().catch(() => null);
554
567
  if (!r.ok) {
568
+ if (data?.code === "DEV_SESSION_SUPERSEDED") {
569
+ stopSuperseded(data.fix ?? "A newer `monty dev` session is active for this app.");
570
+ return false;
571
+ }
555
572
  console.log(`dev-session: ${data?.code ?? r.status}${data?.fix ? ` — ${data.fix}` : ""}`);
556
573
  return false;
557
574
  }
@@ -633,7 +650,7 @@ async function dev() {
633
650
  console.log("tunnel: unavailable — dev mode registered on localhost (visible on this machine's browser only)");
634
651
  }
635
652
  }
636
- const registered = await heartbeat(originUrl);
653
+ const registered = await heartbeat(originUrl, { claim: true });
637
654
  console.log(
638
655
  registered
639
656
  ? `studio: ${host}/studio/${meta.slug} — your app is live there while this runs; click Publish to ship`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@montytools/cli",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "monty": "./bin/monty.mjs"
@@ -39,6 +39,9 @@ Folders are managed for you: this app lives in `~/Monty/<slug>`. `monty current`
39
39
 
40
40
  ```ts
41
41
  // monty.config.ts
42
+ import { defineApp, montyFileSchema } from "@montytools/sdk";
43
+ import { z } from "zod";
44
+
42
45
  export const app = defineApp({
43
46
  slug: "expenses",
44
47
  tables: {
@@ -47,13 +50,14 @@ export const app = defineApp({
47
50
  amount: z.number().positive(),
48
51
  status: z.enum(["draft", "submitted", "approved"]).default("draft"),
49
52
  assigneeId: z.string().optional(), // userId from useMembers()
53
+ receipt: montyFileSchema.optional(),
50
54
  }),
51
55
  },
52
56
  });
53
57
  ```
54
58
 
55
59
  ```tsx
56
- import { useList, useRecord, useInsert, useUpdate, useRemove, useMembers } from "@montytools/sdk/react";
60
+ import { useList, useRecord, useInsert, useUpdate, useRemove, useMembers, useUploadFile, useFileUrl } from "@montytools/sdk/react";
57
61
  import { app } from "../../monty.config";
58
62
 
59
63
  const { data, status, loadMore } = useList(app, "expenses", {
@@ -81,6 +85,11 @@ await remove(row._id);
81
85
 
82
86
  const one = useRecord(app, "expenses", idOrNull); // row | null(missing or no selection) | undefined(loading)
83
87
  const members = useMembers(); // [{ userId, name, email, imageUrl, role }]
88
+
89
+ const uploadFile = useUploadFile(app); // File/Blob -> MontyFile descriptor
90
+ const receipt = await uploadFile(input.files[0]);
91
+ await update(row._id, { receipt }); // store descriptor, not bytes
92
+ const { url } = useFileUrl(app, row.receipt); // authenticated blob: URL for previews/downloads
84
93
  ```
85
94
 
86
95
  ## Errors are instructions
@@ -97,6 +106,7 @@ Every platform error is one line shaped like:
97
106
  | `NOT_FOUND` | Stale, foreign, or WRONG-TABLE record id — ids come from `useList`/`useRecord` on the same table; never hard-code or mix them. |
98
107
  | `INVALID_SCHEMA` | A table field uses a reserved name (`_*`, `updatedAt`, `createdBy`) — rename it. |
99
108
  | `SCHEMA_DRIFT` (warning) | Stored rows predate your latest schema change; nothing crashes, but make changed fields `.optional()`/`.default(...)`. |
109
+ | `FILE_TOO_LARGE` | Platform file uploads are currently capped at 10 MiB per file. |
100
110
  | `UNAUTHENTICATED` / `NO_ACTIVE_WORKSPACE` | App isn't running through the Monty host/dev shell. |
101
111
  | `MISSING_ENV` / `NO_PROVIDER` | `.env.local` or the `<MontyProvider>` in `main.tsx` was removed. |
102
112
 
@@ -136,6 +146,8 @@ confirms it.
136
146
 
137
147
  - Rows, not embedded arrays: a comments thread is a `comments` table with a
138
148
  `parentId` field, not an array inside a record (records cap at 1 MiB).
149
+ - Files are `montyFileSchema` descriptors in records; bytes live in platform
150
+ storage and are previewed with `useFileUrl(app, file)`.
139
151
  - People are `z.string()` userIds from `useMembers()`; render names/avatars
140
152
  from the member list.
141
153
  - Timestamps are `z.number()` epochs or `z.iso.datetime()` strings — never
@@ -10,7 +10,7 @@
10
10
  },
11
11
  "dependencies": {
12
12
  "@fontsource-variable/roboto": "^5.2.10",
13
- "@montytools/sdk": "^0.1.1",
13
+ "@montytools/sdk": "^0.1.2",
14
14
  "@tanstack/react-router": "1.170.17",
15
15
  "class-variance-authority": "^0.7.1",
16
16
  "clsx": "^2.1.1",