@hatchingpoint/point 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/README.md CHANGED
@@ -13,7 +13,7 @@ point check examples/math.point
13
13
 
14
14
  Pair with the [Point Language](https://marketplace.visualstudio.com/items?itemName=hatchingpoint.point) extension in VS Code or Cursor.
15
15
 
16
- Point's public source language is semantic product logic. The compiler lowers that source into an internal typed core and emits TypeScript for existing JavaScript infrastructure.
16
+ Point's public source language is semantic product logic. The compiler lowers that source into an internal typed core and emits JavaScript by default for Bun and Node. Use `point build-ts` when you need TypeScript for existing typed JavaScript infrastructure.
17
17
 
18
18
  This package is the source of truth for Point. It exposes:
19
19
 
@@ -29,6 +29,6 @@ bun run check
29
29
  bun run build
30
30
  ```
31
31
 
32
- `bun run build` emits TypeScript into `generated/` for React, Vue, Bun, Node, and Vite projects.
32
+ `bun run build` emits JavaScript into `generated/` by default. Use `bun run build:ts` for TypeScript and `bun run build:ast` when debugging compiler output.
33
33
 
34
34
  When Point is extracted, this package can move into a standalone repo with the same package name and public entrypoints.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hatchingpoint/point",
3
- "version": "0.0.11",
3
+ "version": "0.0.13",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Point language compiler and CLI.",
@@ -24,7 +24,9 @@
24
24
  "exports": {
25
25
  ".": "./src/index.ts",
26
26
  "./cli": "./src/cli.ts",
27
- "./core": "./src/core/index.ts"
27
+ "./core": "./src/core/index.ts",
28
+ "./std/json": "./src/std/json.ts",
29
+ "./std/http": "./src/std/http.ts"
28
30
  },
29
31
  "publishConfig": {
30
32
  "access": "public",
package/src/cli.ts CHANGED
File without changes
package/src/core/ast.ts CHANGED
@@ -85,11 +85,31 @@ export interface PointSemanticProgramMetadata {
85
85
  source: "semantic";
86
86
  }
87
87
 
88
+ export interface PointSemanticPageLayout {
89
+ title: PointCoreExpression;
90
+ description?: PointCoreExpression;
91
+ main: PointCoreExpression;
92
+ }
93
+
94
+ export interface PointSemanticViewCheckboxBinding {
95
+ label: string;
96
+ target: PointCoreExpression;
97
+ recordParam: string;
98
+ fieldName: string;
99
+ }
100
+
101
+ export interface PointSemanticViewControls {
102
+ changeCallback: string;
103
+ checkboxes: PointSemanticViewCheckboxBinding[];
104
+ }
105
+
88
106
  export interface PointSemanticDeclarationMetadata {
89
- kind: "record" | "calculation" | "rule" | "label" | "external" | "action" | "policy" | "view" | "route" | "workflow" | "command";
107
+ kind: "record" | "calculation" | "rule" | "label" | "external" | "action" | "policy" | "view" | "page" | "route" | "workflow" | "command";
90
108
  name: string;
91
109
  outputName?: string;
92
110
  effects?: string[];
111
+ pageLayout?: PointSemanticPageLayout;
112
+ viewControls?: PointSemanticViewControls;
93
113
  }
94
114
 
95
115
  export interface PointCoreTypeExpression {
@@ -0,0 +1,155 @@
1
+ import { existsSync } from "node:fs";
2
+ import { dirname, resolve } from "node:path";
3
+ import type { PointCoreDiagnostic } from "./check.ts";
4
+ import { checkPointCore } from "./check.ts";
5
+ import { parsePointSource } from "./parser.ts";
6
+
7
+ const DEFAULT_DOCS_DIR = "docs/site";
8
+ const POINT_FENCE = /```point\r?\n([\s\S]*?)```/g;
9
+ const POINT_FILE_REF = /\b(?:[\w.-]+\/)*[\w.-]+\.point\b/g;
10
+
11
+ export interface DocsCheckItemResult {
12
+ kind: "snippet" | "file";
13
+ source: string;
14
+ label: string;
15
+ line?: number;
16
+ ok: boolean;
17
+ diagnostics: PointCoreDiagnostic[];
18
+ }
19
+
20
+ export interface DocsCheckResult {
21
+ ok: boolean;
22
+ checked: number;
23
+ items: DocsCheckItemResult[];
24
+ }
25
+
26
+ export async function discoverDocsSiteMarkdown(docsDir = DEFAULT_DOCS_DIR, cwd = process.cwd()): Promise<string[]> {
27
+ const root = resolve(cwd, docsDir);
28
+ const glob = new Bun.Glob("**/*.md");
29
+ const files: string[] = [];
30
+ for await (const file of glob.scan({ cwd: root, onlyFiles: true })) {
31
+ files.push(`${docsDir}/${file.replaceAll("\\", "/")}`);
32
+ }
33
+ return files.sort((a, b) => a.localeCompare(b));
34
+ }
35
+
36
+ export function extractPointSnippets(markdown: string, source: string): Array<{ label: string; code: string; line: number }> {
37
+ const snippets: Array<{ label: string; code: string; line: number }> = [];
38
+ let index = 0;
39
+ for (const match of markdown.matchAll(POINT_FENCE)) {
40
+ index += 1;
41
+ const code = match[1]?.replace(/\s+$/, "") ?? "";
42
+ const line = markdown.slice(0, match.index ?? 0).split(/\r?\n/).length;
43
+ snippets.push({ label: `${source} snippet ${index}`, code, line });
44
+ }
45
+ return snippets;
46
+ }
47
+
48
+ export function extractPointFileReferences(markdown: string, markdownPath: string, cwd = process.cwd()): string[] {
49
+ const references = new Set<string>();
50
+ for (const match of markdown.matchAll(POINT_FILE_REF)) {
51
+ const candidate = match[0]!;
52
+ if (candidate.endsWith(".point")) references.add(candidate.replaceAll("\\", "/"));
53
+ }
54
+ const resolved: string[] = [];
55
+ for (const reference of references) {
56
+ const absolute = resolvePointFileReference(reference, markdownPath, cwd);
57
+ if (absolute) resolved.push(absolute);
58
+ }
59
+ return resolved.sort((a, b) => a.localeCompare(b));
60
+ }
61
+
62
+ function resolvePointFileReference(reference: string, markdownPath: string, cwd: string): string | null {
63
+ const candidates = [
64
+ resolve(cwd, reference),
65
+ resolve(cwd, dirname(markdownPath), reference),
66
+ ];
67
+ for (const candidate of candidates) {
68
+ if (!existsSync(candidate)) continue;
69
+ return candidate.replace(resolve(cwd), "").replace(/^[/\\]/, "").replaceAll("\\", "/");
70
+ }
71
+ return null;
72
+ }
73
+
74
+ function checkPointSource(source: string, label: string, kind: "snippet" | "file", markdownSource: string, line?: number): DocsCheckItemResult {
75
+ try {
76
+ const program = parsePointSource(source);
77
+ const diagnostics = checkPointCore(program);
78
+ return { kind, source: markdownSource, label, line, ok: diagnostics.length === 0, diagnostics };
79
+ } catch (error) {
80
+ const message = error instanceof Error ? error.message : String(error);
81
+ return {
82
+ kind,
83
+ source: markdownSource,
84
+ label,
85
+ line,
86
+ ok: false,
87
+ diagnostics: [
88
+ {
89
+ code: "parse-error",
90
+ message,
91
+ path: label,
92
+ ref: `point://docs/${label}`,
93
+ severity: "error",
94
+ span: null,
95
+ repair: "Fix the Point syntax in this docs snippet or referenced file.",
96
+ },
97
+ ],
98
+ };
99
+ }
100
+ }
101
+
102
+ export async function checkDocs(options: { docsDir?: string; cwd?: string } = {}): Promise<DocsCheckResult> {
103
+ const docsDir = options.docsDir ?? DEFAULT_DOCS_DIR;
104
+ const cwd = options.cwd ?? process.cwd();
105
+ const markdownFiles = await discoverDocsSiteMarkdown(docsDir, cwd);
106
+ const items: DocsCheckItemResult[] = [];
107
+ const checkedFiles = new Set<string>();
108
+
109
+ for (const markdownPath of markdownFiles) {
110
+ const absoluteMarkdownPath = resolve(cwd, markdownPath);
111
+ const markdown = await Bun.file(absoluteMarkdownPath).text();
112
+
113
+ for (const snippet of extractPointSnippets(markdown, markdownPath)) {
114
+ items.push(checkPointSource(snippet.code, snippet.label, "snippet", markdownPath, snippet.line));
115
+ }
116
+
117
+ for (const filePath of extractPointFileReferences(markdown, markdownPath, cwd)) {
118
+ if (checkedFiles.has(filePath)) continue;
119
+ checkedFiles.add(filePath);
120
+ const source = await Bun.file(resolve(cwd, filePath)).text();
121
+ items.push(checkPointSource(source, filePath, "file", markdownPath));
122
+ }
123
+ }
124
+
125
+ return { ok: items.every((item) => item.ok), checked: items.length, items };
126
+ }
127
+
128
+ export async function runCheckDocs(options: { docsDir?: string; cwd?: string } = {}): Promise<DocsCheckResult> {
129
+ const result = await checkDocs(options);
130
+ if (result.ok) {
131
+ const snippets = result.items.filter((item) => item.kind === "snippet").length;
132
+ const files = result.items.filter((item) => item.kind === "file").length;
133
+ console.log(`Point docs check passed: ${snippets} snippet(s), ${files} file reference(s)`);
134
+ return result;
135
+ }
136
+
137
+ const failures = result.items.filter((item) => !item.ok);
138
+ console.error(
139
+ JSON.stringify(
140
+ {
141
+ ok: false,
142
+ failures: failures.map((item) => ({
143
+ kind: item.kind,
144
+ source: item.source,
145
+ label: item.label,
146
+ line: item.line,
147
+ diagnostics: item.diagnostics,
148
+ })),
149
+ },
150
+ null,
151
+ 2,
152
+ ),
153
+ );
154
+ process.exit(1);
155
+ }
package/src/core/check.ts CHANGED
@@ -28,7 +28,7 @@ type DiagnosticMetadata = Partial<Pick<PointCoreDiagnostic, "expected" | "actual
28
28
  type ScopeEntry = { type: PointCoreTypeExpression; mutable: boolean };
29
29
  type Scope = Map<string, ScopeEntry>;
30
30
 
31
- const PRIMITIVE_TYPES = new Set(["Text", "Int", "Float", "Bool", "Void", "List", "Maybe", "Error", "Or"]);
31
+ const PRIMITIVE_TYPES = new Set(["Text", "Int", "Float", "Bool", "Void", "List", "Maybe", "Error", "Or", "Page", "Handler"]);
32
32
 
33
33
  export function checkPointCore(program: PointCoreProgram): PointCoreDiagnostic[] {
34
34
  const checker = new CoreChecker(program);
@@ -125,6 +125,7 @@ class CoreChecker {
125
125
  }
126
126
  return;
127
127
  }
128
+ if (fn.semantic?.kind === "page" || fn.semantic?.kind === "view") return;
128
129
  this.checkExpressionAssignable(statement.value, fn.returnType, `fn.${fn.name}.return`, locals);
129
130
  return;
130
131
  }
@@ -531,7 +532,14 @@ class CoreChecker {
531
532
  repair: "Use syntax such as User or Error.",
532
533
  });
533
534
  }
534
- if (type.name !== "List" && type.name !== "Maybe" && type.name !== "Or" && type.args.length > 0 && !this.typeDeclarations.has(String(type.name))) {
535
+ if (type.name === "Handler" && type.args.length !== 1) {
536
+ this.push("invalid-type-arity", "Handler requires one type argument", path, type.span, {
537
+ expected: "Handler T",
538
+ actual: formatType(type),
539
+ repair: "Use Handler Listing Signals or another record type.",
540
+ });
541
+ }
542
+ if (type.name !== "List" && type.name !== "Maybe" && type.name !== "Or" && type.name !== "Handler" && type.args.length > 0 && !this.typeDeclarations.has(String(type.name))) {
535
543
  this.push("invalid-type-arity", `${type.name} does not accept type arguments`, path, type.span, {
536
544
  expected: String(type.name),
537
545
  actual: formatType(type),
package/src/core/cli.ts CHANGED
@@ -6,14 +6,19 @@ import { createPointCoreIndex, createPointCoreRepairPlan, explainPointCoreRef }
6
6
  import { createSemanticIndex, explainSemanticRef, mapPublicDiagnostics } from "../semantic/context.ts";
7
7
  import { emitPointCoreTypeScript } from "./emit-typescript.ts";
8
8
  import { emitPointCoreJavaScript } from "./emit-javascript.ts";
9
+ import { emitPointCorePython, isPureLogicProgram } from "./emit-python.ts";
9
10
  import { formatPointSource } from "./format.ts";
10
11
  import { isCacheHit, isIncrementalEnabled, readBuildCache, recordCacheEntry, writeBuildCache } from "./incremental.ts";
11
12
  import { parsePointSource } from "./parser.ts";
13
+ import { runCheckDocs } from "./check-docs.ts";
14
+ import { addPointDependency, modulePathFromLock, POINT_LOCK, POINT_MANIFEST, readPointLock } from "./packages.ts";
12
15
  import { runPointLspServer } from "../lsp/server.ts";
13
16
 
14
17
  const DEFAULT_INPUT = "examples/math.point";
15
18
  const DEFAULT_OUTPUT = "generated/math.ast.json";
19
+ const DEFAULT_JS_OUTPUT = "generated/math.js";
16
20
  const DEFAULT_TS_OUTPUT = "generated/math.ts";
21
+ const DEFAULT_PY_OUTPUT = "generated/math.py";
17
22
  const DEFAULT_PATTERNS = ["examples/**/*.point", "std/**/*.point", "compiler/**/*.point"];
18
23
  const GENERATED_DIR = "generated";
19
24
 
@@ -34,6 +39,23 @@ export async function main() {
34
39
  return;
35
40
  }
36
41
 
42
+ if (command === "check-docs") {
43
+ await runCheckDocs();
44
+ return;
45
+ }
46
+
47
+ if (command === "add") {
48
+ const dependencyName = input;
49
+ const spec = output;
50
+ if (!dependencyName || !spec) {
51
+ throw new Error("Usage: point add <name> <spec> (spec: workspace:<path> | file:<path> | npm:<package>)");
52
+ }
53
+ const { manifest, lock } = await addPointDependency(dependencyName, spec);
54
+ console.log(`Point add updated ${POINT_MANIFEST} and ${POINT_LOCK}: ${dependencyName} -> ${spec}`);
55
+ console.log(JSON.stringify({ name: manifest.name, dependencies: manifest.dependencies, lockPackages: Object.keys(lock.packages) }, null, 2));
56
+ return;
57
+ }
58
+
37
59
  const inputPath = resolve(process.cwd(), input);
38
60
  const source = await Bun.file(inputPath).text();
39
61
  const program = parsePointSource(source);
@@ -101,15 +123,27 @@ export async function main() {
101
123
  return;
102
124
  }
103
125
 
104
- if (command === "build") {
126
+ if (command === "build" || command === "build-js") {
127
+ if (diagnostics.length > 0) {
128
+ console.error(JSON.stringify({ ok: false, diagnostics }, null, 2));
129
+ process.exit(1);
130
+ }
131
+ const outputPath = resolve(process.cwd(), output === DEFAULT_OUTPUT ? DEFAULT_JS_OUTPUT : output);
132
+ await Bun.$`mkdir -p ${dirname(outputPath)}`.quiet();
133
+ await Bun.write(outputPath, emitPointCoreJavaScript(program));
134
+ console.log(`Point core JavaScript build wrote ${outputPath.replaceAll("\\", "/")}`);
135
+ return;
136
+ }
137
+
138
+ if (command === "build-ast") {
105
139
  if (diagnostics.length > 0) {
106
140
  console.error(JSON.stringify({ ok: false, diagnostics }, null, 2));
107
141
  process.exit(1);
108
142
  }
109
- const outputPath = resolve(process.cwd(), output);
143
+ const outputPath = resolve(process.cwd(), output === DEFAULT_OUTPUT ? DEFAULT_OUTPUT : output);
110
144
  await Bun.$`mkdir -p ${dirname(outputPath)}`.quiet();
111
145
  await Bun.write(outputPath, `${JSON.stringify(program, null, 2)}\n`);
112
- console.log(`Point core build wrote ${output}`);
146
+ console.log(`Point core AST build wrote ${outputPath.replaceAll("\\", "/")}`);
113
147
  return;
114
148
  }
115
149
 
@@ -125,15 +159,15 @@ export async function main() {
125
159
  return;
126
160
  }
127
161
 
128
- if (command === "build-js") {
162
+ if (command === "build-py") {
129
163
  if (diagnostics.length > 0) {
130
164
  console.error(JSON.stringify({ ok: false, diagnostics }, null, 2));
131
165
  process.exit(1);
132
166
  }
133
- const outputPath = resolve(process.cwd(), output === DEFAULT_OUTPUT ? DEFAULT_TS_OUTPUT.replace(/\.ts$/, ".js") : output);
167
+ const outputPath = resolve(process.cwd(), output === DEFAULT_OUTPUT ? DEFAULT_PY_OUTPUT : output);
134
168
  await Bun.$`mkdir -p ${dirname(outputPath)}`.quiet();
135
- await Bun.write(outputPath, emitPointCoreJavaScript(program));
136
- console.log(`Point core JavaScript build wrote ${outputPath.replaceAll("\\", "/")}`);
169
+ await Bun.write(outputPath, emitPointCorePython(program));
170
+ console.log(`Point core Python build wrote ${outputPath.replaceAll("\\", "/")}`);
137
171
  return;
138
172
  }
139
173
 
@@ -142,8 +176,8 @@ export async function main() {
142
176
  console.error(JSON.stringify({ ok: false, diagnostics }, null, 2));
143
177
  process.exit(1);
144
178
  }
145
- const runOutput = resolve(tmpdir(), `point-run-${Date.now()}.ts`);
146
- await Bun.write(runOutput, emitPointCoreTypeScript(program));
179
+ const runOutput = resolve(tmpdir(), `point-run-${Date.now()}.js`);
180
+ await Bun.write(runOutput, emitPointCoreJavaScript(program));
147
181
  let entryName: string | null = null;
148
182
  try {
149
183
  const mod = await import(pathToFileUrl(runOutput));
@@ -177,8 +211,9 @@ export async function main() {
177
211
  async function runProjectCommand(command: string) {
178
212
  const inputs = await discoverInputs();
179
213
  if (inputs.length === 0) throw new Error(`No Point core files matched ${DEFAULT_PATTERNS.join(", ")}`);
180
- const results = await Promise.all(inputs.map((input) => loadCoreFile(input)));
181
- const graph = createModuleGraph(results);
214
+ const lock = await readPointLock();
215
+ const results = await Promise.all(inputs.map((input) => loadCoreFile(input, lock)));
216
+ const graph = createModuleGraph(results, lock);
182
217
  const orderedResults = orderByDependencies(results, graph);
183
218
 
184
219
  if (command === "fmt-all") {
@@ -223,7 +258,7 @@ async function runProjectCommand(command: string) {
223
258
  return;
224
259
  }
225
260
 
226
- if (command === "build-all") {
261
+ if (command === "build-all" || command === "build-js-all") {
227
262
  const diagnostics = orderedResults.flatMap((result) =>
228
263
  checkPointCore(programWithDependencyDeclarations(result, graph)).map((diagnostic) => ({ ...diagnostic, file: result.input })),
229
264
  );
@@ -232,12 +267,30 @@ async function runProjectCommand(command: string) {
232
267
  process.exit(1);
233
268
  }
234
269
  for (const result of orderedResults) {
235
- const output = outputFor(result.input);
270
+ const output = jsOutputFor(result.input);
271
+ const outputPath = resolve(process.cwd(), output);
272
+ await Bun.$`mkdir -p ${dirname(outputPath)}`.quiet();
273
+ await Bun.write(outputPath, emitPointCoreJavaScript(programWithTypeScriptImports(result, graph)));
274
+ }
275
+ console.log(`Point core JavaScript build wrote ${results.length} files`);
276
+ return;
277
+ }
278
+
279
+ if (command === "build-ast-all") {
280
+ const diagnostics = orderedResults.flatMap((result) =>
281
+ checkPointCore(programWithDependencyDeclarations(result, graph)).map((diagnostic) => ({ ...diagnostic, file: result.input })),
282
+ );
283
+ if (diagnostics.length > 0) {
284
+ console.error(JSON.stringify({ ok: false, diagnostics }, null, 2));
285
+ process.exit(1);
286
+ }
287
+ for (const result of orderedResults) {
288
+ const output = astOutputFor(result.input);
236
289
  const outputPath = resolve(process.cwd(), output);
237
290
  await Bun.$`mkdir -p ${dirname(outputPath)}`.quiet();
238
291
  await Bun.write(outputPath, `${JSON.stringify(result.program, null, 2)}\n`);
239
292
  }
240
- console.log(`Point core build wrote ${results.length} files`);
293
+ console.log(`Point core AST build wrote ${results.length} files`);
241
294
  return;
242
295
  }
243
296
 
@@ -259,7 +312,7 @@ async function runProjectCommand(command: string) {
259
312
  return;
260
313
  }
261
314
 
262
- if (command === "build-js-all") {
315
+ if (command === "build-py-all") {
263
316
  const diagnostics = orderedResults.flatMap((result) =>
264
317
  checkPointCore(programWithDependencyDeclarations(result, graph)).map((diagnostic) => ({ ...diagnostic, file: result.input })),
265
318
  );
@@ -267,13 +320,15 @@ async function runProjectCommand(command: string) {
267
320
  console.error(JSON.stringify({ ok: false, diagnostics }, null, 2));
268
321
  process.exit(1);
269
322
  }
270
- for (const result of orderedResults) {
271
- const output = jsOutputFor(result.input);
323
+ const pureLogicResults = orderedResults.filter((result) => isPureLogicProgram(result.program));
324
+ for (const result of pureLogicResults) {
325
+ const output = pyOutputFor(result.input);
272
326
  const outputPath = resolve(process.cwd(), output);
273
327
  await Bun.$`mkdir -p ${dirname(outputPath)}`.quiet();
274
- await Bun.write(outputPath, emitPointCoreJavaScript(programWithTypeScriptImports(result, graph)));
328
+ await Bun.write(outputPath, emitPointCorePython(programWithTypeScriptImports(result, graph)));
275
329
  }
276
- console.log(`Point core JavaScript build wrote ${results.length} files`);
330
+ const skipped = results.length - pureLogicResults.length;
331
+ console.log(`Point core Python build wrote ${pureLogicResults.length} files${skipped ? ` (${skipped} skipped)` : ""}`);
277
332
  return;
278
333
  }
279
334
 
@@ -358,8 +413,8 @@ async function runPointTests(program: PointCoreProgram, input: string): Promise<
358
413
  (declaration.semantic?.name.startsWith("test") || declaration.name.startsWith("test")),
359
414
  );
360
415
  if (tests.length === 0) return { file: input, ok: true, tests: [] };
361
- const testOutput = resolve(tmpdir(), `point-test-${Date.now()}-${Math.random().toString(16).slice(2)}.ts`);
362
- await Bun.write(testOutput, emitPointCoreTypeScript(program));
416
+ const testOutput = resolve(tmpdir(), `point-test-${Date.now()}-${Math.random().toString(16).slice(2)}.js`);
417
+ await Bun.write(testOutput, emitPointCoreJavaScript(program));
363
418
  const mod = await import(pathToFileUrl(testOutput));
364
419
  const results = [];
365
420
  for (const test of tests) {
@@ -388,9 +443,9 @@ export function findRunEntryName(program: PointCoreProgram): string | null {
388
443
  return preferred?.name ?? null;
389
444
  }
390
445
 
391
- async function loadCoreFile(input: string) {
446
+ async function loadCoreFile(input: string, lock: Awaited<ReturnType<typeof readPointLock>>) {
392
447
  const source = await Bun.file(resolve(process.cwd(), input)).text();
393
- return { input, source, program: parsePointSource(source), uses: parseUseDeclarations(source, input) };
448
+ return { input, source, program: parsePointSource(source), uses: parseUseDeclarations(source, input, lock) };
394
449
  }
395
450
 
396
451
  type CoreFile = Awaited<ReturnType<typeof loadCoreFile>>;
@@ -402,15 +457,15 @@ interface UseDeclaration {
402
457
  input: string;
403
458
  }
404
459
 
405
- function parseUseDeclarations(source: string, input: string): UseDeclaration[] {
460
+ function parseUseDeclarations(source: string, input: string, lock: Awaited<ReturnType<typeof readPointLock>>): UseDeclaration[] {
406
461
  return source
407
462
  .split(/\r?\n/)
408
463
  .map((line) => line.trim().match(/^use\s+([A-Za-z][A-Za-z0-9]*(?:\.[A-Za-z][A-Za-z0-9]*)*)(?:\s+from\s+"([^"]+)")?$/))
409
464
  .filter((match): match is RegExpMatchArray => Boolean(match))
410
- .map((match) => ({ moduleName: match[1]!, from: match[2] ?? stdPathFor(match[1]!), input }));
465
+ .map((match) => ({ moduleName: match[1]!, from: match[2] ?? modulePathFromLock(lock, match[1]!), input }));
411
466
  }
412
467
 
413
- function createModuleGraph(results: CoreFile[]): ModuleGraph {
468
+ function createModuleGraph(results: CoreFile[], lock: Awaited<ReturnType<typeof readPointLock>>): ModuleGraph {
414
469
  const byInput = new Map(results.map((result) => [normalizeInput(result.input), result]));
415
470
  const graph: ModuleGraph = new Map();
416
471
  for (const result of results) {
@@ -474,11 +529,6 @@ function resolveDependencyInput(input: string, from: string): string {
474
529
  return resolve(base, from).replace(resolve(process.cwd()), "").replace(/^[/\\]/, "");
475
530
  }
476
531
 
477
- function stdPathFor(moduleName: string): string {
478
- if (!moduleName.startsWith("std.")) throw new Error(`Use declarations without from must target std modules: ${moduleName}`);
479
- return `${moduleName.replace(/^std\./, "std/").replaceAll(".", "/")}.point`;
480
- }
481
-
482
532
  function normalizeInput(input: string): string {
483
533
  return input.replaceAll("\\", "/");
484
534
  }
@@ -498,6 +548,15 @@ function jsOutputFor(input: string): string {
498
548
  return `${GENERATED_DIR}/${name}.js`;
499
549
  }
500
550
 
551
+ function astOutputFor(input: string): string {
552
+ return outputFor(input);
553
+ }
554
+
555
+ function pyOutputFor(input: string): string {
556
+ const name = outputBaseName(input);
557
+ return `${GENERATED_DIR}/${name}.py`;
558
+ }
559
+
501
560
  function outputBaseName(input: string): string {
502
561
  return normalizeInput(input).split("/").pop()?.replace(/\.point$/, "") ?? "program";
503
562
  }
@@ -27,6 +27,7 @@ export type PointCoreSymbolKind =
27
27
  | "action"
28
28
  | "policy"
29
29
  | "view"
30
+ | "page"
30
31
  | "route"
31
32
  | "workflow"
32
33
  | "command";
@@ -325,6 +326,7 @@ function relatedRefsFor(symbol: PointCoreSymbol, index: PointCoreIndex): string[
325
326
  symbol.kind === "action" ||
326
327
  symbol.kind === "policy" ||
327
328
  symbol.kind === "view" ||
329
+ symbol.kind === "page" ||
328
330
  symbol.kind === "route" ||
329
331
  symbol.kind === "workflow" ||
330
332
  symbol.kind === "command"
@@ -351,6 +353,7 @@ function summaryFor(symbol: PointCoreSymbol): string {
351
353
  if (symbol.kind === "action") return `Semantic action ${symbol.name} returns ${symbol.type}; effects: ${(symbol.effects ?? []).join(", ") || "none"}.`;
352
354
  if (symbol.kind === "policy") return `Semantic policy ${symbol.name} returns ${symbol.type}.`;
353
355
  if (symbol.kind === "view") return `Semantic view ${symbol.name} returns React JSX.`;
356
+ if (symbol.kind === "page") return `Semantic page ${symbol.name} returns a Next.js page shell as React JSX.`;
354
357
  if (symbol.kind === "route") return `Semantic route ${symbol.name} returns ${symbol.type}.`;
355
358
  if (symbol.kind === "workflow") return `Semantic workflow ${symbol.name} returns ${symbol.type}.`;
356
359
  if (symbol.kind === "command") return `Semantic command ${symbol.name} returns ${symbol.type}.`;
@@ -8,6 +8,8 @@ import type {
8
8
  PointCoreTypeDeclaration,
9
9
  PointCoreValueDeclaration,
10
10
  } from "./ast.ts";
11
+ import type { PointSemanticRouteDeclaration } from "../semantic/ast.ts";
12
+ import { semanticFunctionName } from "../semantic/naming.ts";
11
13
 
12
14
  const BINARY_OPERATORS: Record<string, string> = {
13
15
  and: "&&",
@@ -16,13 +18,22 @@ const BINARY_OPERATORS: Record<string, string> = {
16
18
 
17
19
  /** Emit JavaScript from a core AST program (no type syntax). Production path: parsePointSource → check → emit. */
18
20
  export function emitPointCoreJavaScript(program: PointCoreProgram): string {
21
+ const routes = program.semanticSource?.declarations.filter((declaration): declaration is PointSemanticRouteDeclaration => declaration.kind === "route") ?? [];
22
+ const routeServeCommand = program.declarations.find((declaration) => declaration.kind === "function" && isRouteServeCommand(declaration));
19
23
  const lines: string[] = [];
20
24
  lines.push("// Generated by Point. Do not edit directly.");
21
25
  if (program.module) lines.push(`// Point module: ${program.module}`);
22
26
  lines.push("");
23
27
  for (const declaration of program.declarations) {
28
+ if (declaration.kind === "function" && declaration.semantic?.kind === "command" && routes.length > 0 && isRouteServeCommand(declaration)) {
29
+ lines.push(...emitRouteServeCommand(declaration), "");
30
+ continue;
31
+ }
24
32
  lines.push(...emitDeclaration(declaration), "");
25
33
  }
34
+ if (routes.length > 0 && routeServeCommand) {
35
+ lines.push(...emitRouteServerRuntime(routes), "");
36
+ }
26
37
  return `${trimTrailingBlankLines(lines).join("\n")}\n`;
27
38
  }
28
39
 
@@ -41,9 +52,13 @@ function emitDeclaration(declaration: PointCoreDeclaration): string[] {
41
52
 
42
53
  function emitFunction(declaration: PointCoreFunctionDeclaration): string[] {
43
54
  const asyncPrefix = declaration.semantic?.kind === "action" || declaration.semantic?.kind === "workflow" || declaration.semantic?.kind === "command" ? "async " : "";
55
+ const bodyLines =
56
+ declaration.semantic?.kind === "page" && declaration.semantic.pageLayout
57
+ ? [`return ${emitExpression(declaration.semantic.pageLayout.main)};`]
58
+ : declaration.body.flatMap((statement) => emitStatement(statement, declaration.semantic?.kind));
44
59
  return [
45
60
  `export ${asyncPrefix}function ${declaration.name}(${declaration.params.map(emitParam).join(", ")}) {`,
46
- ...indentLines(declaration.body.flatMap((statement) => emitStatement(statement, declaration.semantic?.kind))),
61
+ ...indentLines(bodyLines),
47
62
  "}",
48
63
  ];
49
64
  }
@@ -122,3 +137,54 @@ function trimTrailingBlankLines(lines: string[]): string[] {
122
137
  while (lines.at(-1) === "") lines.pop();
123
138
  return lines;
124
139
  }
140
+
141
+ function isRouteServeCommand(declaration: PointCoreFunctionDeclaration): boolean {
142
+ return declaration.semantic?.name === "serve store readiness" || declaration.name === "serveStoreReadinessCommand";
143
+ }
144
+
145
+ function emitRouteServeCommand(declaration: PointCoreFunctionDeclaration): string[] {
146
+ const asyncPrefix = declaration.semantic?.kind === "command" ? "async " : "";
147
+ return [
148
+ `export ${asyncPrefix}function ${declaration.name}(${declaration.params.map(emitParam).join(", ")}) {`,
149
+ " const port = Number(process.env.PORT ?? 3456);",
150
+ " const server = Bun.serve({ port, fetch: createPointRouteFetchHandler() });",
151
+ " console.log(`Store readiness listening on http://localhost:${server.port}`);",
152
+ " await new Promise(() => {});",
153
+ "}",
154
+ ];
155
+ }
156
+
157
+ function emitRouteServerRuntime(routes: PointSemanticRouteDeclaration[]): string[] {
158
+ const matchers = routes.map((route) => {
159
+ const handlerName = semanticFunctionName(route.name, "route", "route");
160
+ const pattern = route.path.replace(/:[A-Za-z][A-Za-z0-9_]*/g, "([^/]+)").replace(/\//g, "\\/");
161
+ const argExpressions = route.inputs.map((_input, index) => `match[${index + 1}]`);
162
+ return {
163
+ method: route.method.toUpperCase(),
164
+ pattern,
165
+ handlerName,
166
+ argExpressions,
167
+ };
168
+ });
169
+ const matchLines = matchers.flatMap((matcher) => [
170
+ ` if (req.method === ${JSON.stringify(matcher.method)} && new RegExp(${JSON.stringify(`^${matcher.pattern}$`)}).test(url.pathname)) {`,
171
+ ` const match = url.pathname.match(new RegExp(${JSON.stringify(`^${matcher.pattern}$`)}));`,
172
+ ` const body = ${matcher.handlerName}(${matcher.argExpressions.join(", ")});`,
173
+ ' return new Response(typeof body === "string" ? body : body, { headers: { "content-type": "application/json" } });',
174
+ " }",
175
+ ]);
176
+ return [
177
+ "export function createPointRouteFetchHandler() {",
178
+ " return async (req) => {",
179
+ " const url = new URL(req.url);",
180
+ ...matchLines,
181
+ ' return new Response(JSON.stringify({ error: "Not found" }), { status: 404, headers: { "content-type": "application/json" } });',
182
+ " };",
183
+ "}",
184
+ "",
185
+ "export function startRoutesServer() {",
186
+ " const port = Number(process.env.PORT ?? 3456);",
187
+ " return Bun.serve({ port, fetch: createPointRouteFetchHandler() });",
188
+ "}",
189
+ ];
190
+ }