@malloy-publisher/server 0.0.198-dev4 → 0.0.198-dev6

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.
@@ -1,270 +0,0 @@
1
- /**
2
- * Wire protocol between the main thread (CompileWorkerPool) and the
3
- * compile worker threads. Messages flow in both directions over the
4
- * worker_threads MessagePort:
5
- *
6
- * main ──▶ worker: CompileJobRequest (start a compile)
7
- * worker ──▶ main: CompileJobResult (success)
8
- * worker ──▶ main: CompileJobError (failure)
9
- *
10
- * worker ──▶ main: SchemaForTablesRequest (proxy schema fetch)
11
- * worker ──▶ main: SchemaForSqlRequest (proxy SQL block schema)
12
- * main ──▶ worker: SchemaForTablesResponse / SchemaForSqlResponse
13
- *
14
- * main ──▶ worker: ShutdownRequest (graceful drain & exit)
15
- *
16
- * The protocol intentionally uses plain structured-clonable POJOs so
17
- * `parentPort.postMessage` and `worker.postMessage` can transfer them
18
- * via V8's structured clone — much cheaper than JSON.stringify for
19
- * the multi-MB `modelDef` payloads that come back from compile.
20
- *
21
- * All requests are correlated by an opaque `requestId` string so the
22
- * receiver can match responses without relying on FIFO ordering.
23
- */
24
-
25
- import type {
26
- Annotation,
27
- SQLSourceDef,
28
- TableSourceDef,
29
- } from "@malloydata/malloy";
30
-
31
- // ──────────────────────────────────────────────────────────────────────
32
- // Direction: main ──▶ worker (compile job)
33
- // ──────────────────────────────────────────────────────────────────────
34
-
35
- /**
36
- * Connection metadata the worker needs to construct a stub
37
- * `InfoConnection`. Resolved lazily — the worker asks the main thread
38
- * for these on the first `lookupConnection(name)` call (see
39
- * {@link ConnectionMetadataRequest}). We don't ship the full list
40
- * upfront because the caller layer doesn't always know it; Malloy
41
- * sees connection names only as `connection.table('...')`
42
- * references inside the model.
43
- */
44
- export interface ConnectionMetadata {
45
- name: string;
46
- dialectName: string;
47
- digest: string;
48
- }
49
-
50
- export interface CompileJobRequest {
51
- type: "compile";
52
- requestId: string;
53
- /** Absolute path to the package directory on disk. */
54
- packagePath: string;
55
- /**
56
- * Path of the model file relative to `packagePath`. Required for
57
- * file-backed compiles. Omit when supplying {@link inlineSource}.
58
- */
59
- modelPath?: string;
60
- /**
61
- * Inline Malloy source string to compile in place of reading a file.
62
- * Used by call sites that synthesize Malloy on the fly (e.g. the
63
- * per-database schema probe in {@link Package.getDatabaseInfo}). When
64
- * set, the worker calls `runtime.loadModel(inlineSource, {…})` instead
65
- * of resolving a file:// URL. `modelPath` should be omitted; the
66
- * worker will use a synthetic in-memory model id derived from
67
- * `requestId` for source-info display purposes.
68
- */
69
- inlineSource?: string;
70
- /**
71
- * Base URL used to resolve `import "…"` statements inside the model.
72
- * Only meaningful with {@link inlineSource}. For file-backed compiles
73
- * the worker derives the importBaseURL from the modelPath.
74
- */
75
- importBaseURL?: string;
76
- /** Name of the default connection (e.g. "duckdb"), or null. */
77
- defaultConnectionName: string | null;
78
- /** Optional row-build manifest passed through to the Runtime. */
79
- buildManifest?: unknown;
80
- }
81
-
82
- // ──────────────────────────────────────────────────────────────────────
83
- // Direction: worker ──▶ main (compile result)
84
- // ──────────────────────────────────────────────────────────────────────
85
-
86
- /**
87
- * Wire shape of a successful compile. Mirrors the fields the
88
- * server's `Model` constructor needs to fully describe a `.malloy`
89
- * file without holding a `ModelMaterializer` reference.
90
- *
91
- * The materializer itself is intentionally NOT shipped back — it
92
- * binds to a Runtime that holds live native connection handles and
93
- * cannot cross a worker_threads boundary. The main thread builds
94
- * its own materializer lazily on the first query (see
95
- * `Model.ensureMaterializer`).
96
- */
97
- export interface CompileJobResult {
98
- type: "compile-result";
99
- requestId: string;
100
- /** Whatever `await modelMaterializer.getModel()`._modelDef returned. */
101
- modelDef: unknown;
102
- /** Source-info entries (from imports + local sources). */
103
- sourceInfos: unknown[];
104
- /** Pre-extracted API source descriptors. */
105
- sources: unknown[];
106
- /** Pre-extracted API query descriptors. */
107
- queries: unknown[];
108
- /** Parsed `#(filter)` map, keyed by source name. */
109
- filterMap: Array<[string, unknown[]]>;
110
- /** Givens declared on the model, already in API shape so the main
111
- * thread can stash them on the `Model` without further conversion. */
112
- givens?: unknown[];
113
- /** Accumulated dataStyles (from HackyDataStylesAccumulator). */
114
- dataStyles: unknown;
115
- /** Wall-clock ms inside the worker for the actual compile. */
116
- compileDurationMs: number;
117
- }
118
-
119
- export interface CompileJobError {
120
- type: "compile-error";
121
- requestId: string;
122
- /** Serialized error — the main thread reconstructs an Error. */
123
- error: SerializedError;
124
- }
125
-
126
- /**
127
- * Error wire-shape. We cannot transfer Error instances directly
128
- * across postMessage cleanly (Bun/Node behaviour diverges on stack
129
- * propagation), so we ship a structured payload and reconstitute on
130
- * the main thread.
131
- */
132
- export interface SerializedError {
133
- name: string;
134
- message: string;
135
- stack?: string;
136
- /** Set when the error originated as a Malloy `MalloyError`. */
137
- malloyProblems?: unknown[];
138
- /** Set when the error originated as `ModelCompilationError`. */
139
- isCompilationError?: boolean;
140
- }
141
-
142
- // ──────────────────────────────────────────────────────────────────────
143
- // Direction: worker ──▶ main (proxy connection metadata)
144
- // ──────────────────────────────────────────────────────────────────────
145
-
146
- export interface ConnectionMetadataRequest {
147
- type: "connection-metadata";
148
- requestId: string;
149
- jobId: string;
150
- connectionName: string;
151
- }
152
-
153
- export interface ConnectionMetadataResponse {
154
- type: "connection-metadata-response";
155
- requestId: string;
156
- ok: true;
157
- metadata: ConnectionMetadata;
158
- }
159
-
160
- // ──────────────────────────────────────────────────────────────────────
161
- // Direction: worker ──▶ main (proxy schema fetches)
162
- // ──────────────────────────────────────────────────────────────────────
163
-
164
- export interface SchemaForTablesRequest {
165
- type: "schema-for-tables";
166
- requestId: string;
167
- /** Job this RPC belongs to (so main routes to the right config). */
168
- jobId: string;
169
- connectionName: string;
170
- tables: Record<string, string>;
171
- options: {
172
- refreshTimestamp?: number;
173
- modelAnnotation?: Annotation;
174
- };
175
- }
176
-
177
- export interface SchemaForTablesResponse {
178
- type: "schema-for-tables-response";
179
- requestId: string;
180
- ok: true;
181
- schemas: Record<string, TableSourceDef>;
182
- errors: Record<string, string>;
183
- }
184
-
185
- export interface SchemaForSqlRequest {
186
- type: "schema-for-sql";
187
- requestId: string;
188
- jobId: string;
189
- connectionName: string;
190
- sentence: unknown;
191
- options: {
192
- refreshTimestamp?: number;
193
- modelAnnotation?: Annotation;
194
- };
195
- }
196
-
197
- export interface SchemaForSqlResponse {
198
- type: "schema-for-sql-response";
199
- requestId: string;
200
- ok: true;
201
- structDef?: SQLSourceDef;
202
- error?: string;
203
- }
204
-
205
- export interface RpcErrorResponse {
206
- type: "rpc-error";
207
- requestId: string;
208
- ok: false;
209
- error: SerializedError;
210
- }
211
-
212
- // ──────────────────────────────────────────────────────────────────────
213
- // Direction: worker ──▶ main (file read for imports)
214
- // ──────────────────────────────────────────────────────────────────────
215
-
216
- /**
217
- * Workers read most files directly via fs (they run in the same
218
- * filesystem namespace). This RPC exists for the rare case where the
219
- * package URL reader has host-specific behaviour (e.g. virtual files,
220
- * remote URLs) — we delegate back to the main thread's URL reader so
221
- * compile semantics stay identical to the in-process path.
222
- */
223
- export interface ReadUrlRequest {
224
- type: "read-url";
225
- requestId: string;
226
- jobId: string;
227
- url: string;
228
- }
229
-
230
- export interface ReadUrlResponse {
231
- type: "read-url-response";
232
- requestId: string;
233
- ok: true;
234
- contents: string;
235
- invalidationKey?: string | number | null;
236
- }
237
-
238
- // ──────────────────────────────────────────────────────────────────────
239
- // Lifecycle
240
- // ──────────────────────────────────────────────────────────────────────
241
-
242
- export interface ShutdownRequest {
243
- type: "shutdown";
244
- }
245
-
246
- export interface ReadyMessage {
247
- type: "ready";
248
- }
249
-
250
- // ──────────────────────────────────────────────────────────────────────
251
- // Union types for routing
252
- // ──────────────────────────────────────────────────────────────────────
253
-
254
- export type MainToWorkerMessage =
255
- | CompileJobRequest
256
- | ConnectionMetadataResponse
257
- | SchemaForTablesResponse
258
- | SchemaForSqlResponse
259
- | ReadUrlResponse
260
- | RpcErrorResponse
261
- | ShutdownRequest;
262
-
263
- export type WorkerToMainMessage =
264
- | CompileJobResult
265
- | CompileJobError
266
- | ConnectionMetadataRequest
267
- | SchemaForTablesRequest
268
- | SchemaForSqlRequest
269
- | ReadUrlRequest
270
- | ReadyMessage;
@@ -1,133 +0,0 @@
1
- /**
2
- * Integration test: exercise `Model.create` with the worker pool
3
- * enabled (MALLOY_COMPILE_WORKERS=1).
4
- *
5
- * Validates that the worker-compile path:
6
- * - produces a Model with a populated modelDef + sources + queries
7
- * - defers materializer construction (none until first query)
8
- * - falls back to in-process compile for notebooks
9
- * - falls through to in-process compile when the worker pool fails
10
- *
11
- * Kept separate from `model.spec.ts` so the existing tests keep
12
- * running on the in-process path without paying worker startup cost.
13
- */
14
- import { afterAll, afterEach, beforeAll, describe, expect, it } from "bun:test";
15
- import * as fs from "fs";
16
- import * as os from "os";
17
- import * as path from "path";
18
- import { __setCompilePoolForTests } from "../compile/compile_pool";
19
- import { Model } from "./model";
20
-
21
- const ORIGINAL_ENV = process.env.MALLOY_COMPILE_WORKERS;
22
-
23
- describe("Model.create via worker pool", () => {
24
- let tempDir: string;
25
-
26
- beforeAll(() => {
27
- process.env.MALLOY_COMPILE_WORKERS = "1";
28
- });
29
-
30
- afterAll(async () => {
31
- if (ORIGINAL_ENV === undefined) {
32
- delete process.env.MALLOY_COMPILE_WORKERS;
33
- } else {
34
- process.env.MALLOY_COMPILE_WORKERS = ORIGINAL_ENV;
35
- }
36
- await __setCompilePoolForTests(null);
37
- });
38
-
39
- afterEach(() => {
40
- if (tempDir) {
41
- fs.rmSync(tempDir, { recursive: true, force: true });
42
- tempDir = "";
43
- }
44
- });
45
-
46
- it("compiles a .malloy file via worker and returns a usable Model", async () => {
47
- const { DuckDBConnection } = await import("@malloydata/db-duckdb");
48
- tempDir = fs.mkdtempSync(
49
- path.join(os.tmpdir(), "publisher-model-worker-"),
50
- );
51
- fs.writeFileSync(
52
- path.join(tempDir, "trivial.malloy"),
53
- `source: nums is duckdb.sql("select 1 as a") extend {
54
- measure: total is a.sum()
55
- }`,
56
- );
57
-
58
- const duckdb = new DuckDBConnection("duckdb", ":memory:");
59
- try {
60
- const model = await Model.create(
61
- "test-pkg",
62
- tempDir,
63
- "trivial.malloy",
64
- new Map([["duckdb", duckdb]]),
65
- );
66
-
67
- expect(model).toBeInstanceOf(Model);
68
- // The API type narrows to the public CompiledModel shape; the
69
- // private modelDef/type fields are set behind the `as`-cast
70
- // in getStandardModel, so we widen here to peek at them.
71
- const apiModel = (await model.getModel()) as {
72
- type?: string;
73
- modelDef?: string;
74
- sources?: { name?: string }[];
75
- modelInfo?: string;
76
- };
77
- expect(apiModel.type).toBe("source");
78
- expect(apiModel.modelDef).toBeDefined();
79
- expect(apiModel.modelDef!.length).toBeGreaterThan(10);
80
- // Single source `nums` from the worker-extracted ApiSource[]
81
- expect(apiModel.sources?.[0]?.name).toBe("nums");
82
- } finally {
83
- await duckdb.close();
84
- }
85
- });
86
-
87
- it("propagates compilation errors as ModelCompilationError", async () => {
88
- const { DuckDBConnection } = await import("@malloydata/db-duckdb");
89
- const { ModelCompilationError } = await import("../errors");
90
- tempDir = fs.mkdtempSync(
91
- path.join(os.tmpdir(), "publisher-model-worker-"),
92
- );
93
- fs.writeFileSync(
94
- path.join(tempDir, "broken.malloy"),
95
- `source: nums is duckdb.sql("select 1 as a") extend {
96
- measure: total is THIS_FUNC_DOES_NOT_EXIST(a)
97
- }`,
98
- );
99
-
100
- const duckdb = new DuckDBConnection("duckdb", ":memory:");
101
- try {
102
- const model = await Model.create(
103
- "test-pkg",
104
- tempDir,
105
- "broken.malloy",
106
- new Map([["duckdb", duckdb]]),
107
- );
108
- // Either the Model surfaces with `compilationError` populated
109
- // (returned by the worker, re-wrapped on the main thread) or
110
- // getModel() throws — both are equivalent under the existing
111
- // error contract; we accept either.
112
- try {
113
- await model.getModel();
114
- // If getModel didn't throw, the compile error should be
115
- // visible via the Model's `compilationError` field.
116
- expect(
117
- (model as unknown as { compilationError?: Error })
118
- .compilationError,
119
- ).toBeDefined();
120
- } catch (err) {
121
- expect(err).toBeInstanceOf(Error);
122
- // Compile errors come back as ModelCompilationError
123
- // (worker serializes MalloyError with
124
- // isCompilationError=true; pool re-wraps).
125
- expect(
126
- err instanceof ModelCompilationError || err instanceof Error,
127
- ).toBe(true);
128
- }
129
- } finally {
130
- await duckdb.close();
131
- }
132
- });
133
- });