houdini 2.0.0-go.2 → 2.0.0-go.20

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.
Files changed (61) hide show
  1. package/LICENSE +21 -0
  2. package/build/cmd/generate.d.ts +3 -0
  3. package/build/cmd/generate.js +31 -3
  4. package/build/cmd/index.js +7 -1
  5. package/build/cmd/init.js +29 -14
  6. package/build/lib/ast.d.ts +1 -1
  7. package/build/lib/codegen.d.ts +3 -3
  8. package/build/lib/codegen.js +240 -48
  9. package/build/lib/config.d.ts +17 -4
  10. package/build/lib/database.d.ts +3 -2
  11. package/build/lib/database.js +85 -45
  12. package/build/lib/error.js +3 -2
  13. package/build/lib/fs.d.ts +2 -1
  14. package/build/lib/fs.js +9 -2
  15. package/build/lib/index.d.ts +1 -0
  16. package/build/lib/index.js +1 -0
  17. package/build/lib/logger.d.ts +12 -0
  18. package/build/lib/logger.js +45 -0
  19. package/build/lib/parse.js +17 -13
  20. package/build/lib/plugins.js +26 -3
  21. package/build/lib/types.d.ts +7 -0
  22. package/build/lib/types.js +7 -0
  23. package/build/node/index.d.ts +37 -0
  24. package/build/node/index.js +270 -0
  25. package/build/node/package.json +1 -0
  26. package/build/package.json +75 -56
  27. package/build/router/conventions.d.ts +6 -4
  28. package/build/router/conventions.js +4 -0
  29. package/build/router/match.d.ts +1 -1
  30. package/build/router/server.d.ts +2 -2
  31. package/build/router/server.js +3 -1
  32. package/build/runtime/cache/index.d.ts +10 -10
  33. package/build/runtime/cache/index.js +14 -14
  34. package/build/runtime/cache/lists.d.ts +7 -7
  35. package/build/runtime/cache/lists.js +2 -2
  36. package/build/runtime/cache/storage.d.ts +1 -1
  37. package/build/runtime/cache/storage.js +1 -1
  38. package/build/runtime/cache/subscription.d.ts +2 -2
  39. package/build/runtime/cache/subscription.js +3 -3
  40. package/build/runtime/client.d.ts +2 -2
  41. package/build/runtime/client.js +0 -1
  42. package/build/runtime/config.d.ts +1 -1
  43. package/build/runtime/documentStore.d.ts +3 -3
  44. package/build/runtime/documentStore.js +4 -4
  45. package/build/runtime/flatten.d.ts +1 -1
  46. package/build/runtime/index.d.ts +12 -12
  47. package/build/runtime/index.js +12 -12
  48. package/build/runtime/pageInfo.d.ts +1 -1
  49. package/build/runtime/pageInfo.js +1 -1
  50. package/build/runtime/pagination.d.ts +2 -2
  51. package/build/runtime/pagination.js +3 -3
  52. package/build/runtime/scalars.d.ts +1 -1
  53. package/build/runtime/scalars.js +3 -3
  54. package/build/runtime/selection.d.ts +1 -1
  55. package/build/runtime/types.d.ts +12 -5
  56. package/build/runtime/types.js +1 -1
  57. package/build/vite/hmr.js +20 -17
  58. package/build/vite/houdini.js +67 -59
  59. package/build/vite/index.js +6 -8
  60. package/build/vite/schema.js +3 -2
  61. package/package.json +191 -180
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Alec Aivazis
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,3 +1,4 @@
1
+ import { type PipelineHook } from '../lib/index.js';
1
2
  export declare function generate(args?: {
2
3
  pullSchema: boolean;
3
4
  persistOutput?: string;
@@ -7,4 +8,6 @@ export declare function generate(args?: {
7
8
  verbose: boolean;
8
9
  mode?: string;
9
10
  preserveDatabase: boolean;
11
+ afterPhase?: PipelineHook;
12
+ beforePhase?: PipelineHook;
10
13
  }): Promise<void>;
@@ -1,7 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { format_error } from "../lib/error.js";
4
- import { codegen_setup, init_db, run_pipeline } from "../lib/index.js";
4
+ import {
5
+ codegen_setup,
6
+ init_db,
7
+ run_pipeline,
8
+ PIPELINE_HOOKS
9
+ } from "../lib/index.js";
5
10
  import { get_config } from "../lib/project.js";
6
11
  import pull_schema from "./pullSchema.js";
7
12
  async function generate(args = {
@@ -30,10 +35,33 @@ async function generate(args = {
30
35
  };
31
36
  process.on("SIGINT", on_close);
32
37
  process.on("SIGTERM", on_close);
33
- await run_pipeline(trigger_hook, { after: "Schema" });
38
+ if (args.afterPhase && !PIPELINE_HOOKS.includes(args.afterPhase)) {
39
+ throw new Error(
40
+ `Invalid --after-phase: ${args.afterPhase}. Valid phases are: ${PIPELINE_HOOKS.join(
41
+ ", "
42
+ )}`
43
+ );
44
+ }
45
+ if (args.beforePhase && !PIPELINE_HOOKS.includes(args.beforePhase)) {
46
+ throw new Error(
47
+ `Invalid --before-phase: ${args.beforePhase}. Valid phases are: ${PIPELINE_HOOKS.join(", ")}`
48
+ );
49
+ }
50
+ const pipelineOptions = {
51
+ after: "Schema"
52
+ };
53
+ if (args.afterPhase) {
54
+ pipelineOptions.after = args.afterPhase;
55
+ }
56
+ if (args.beforePhase) {
57
+ pipelineOptions.through = args.beforePhase;
58
+ }
59
+ const results = await run_pipeline(trigger_hook, pipelineOptions);
60
+ const docCount = Object.values(results.GenerateDocuments ?? {}).flat().length;
61
+ console.log(`\u{1F3A9} Generated ${docCount} ${docCount === 1 ? "document" : "documents"}`);
34
62
  await on_close();
35
63
  } catch (e) {
36
- format_error(e, function(error) {
64
+ format_error(e, (error) => {
37
65
  console.error(error.stack?.split("\n").slice(1).join("\n"));
38
66
  });
39
67
  try {
@@ -8,7 +8,13 @@ const program = new Command();
8
8
  program.command("generate").description("generate the application runtime").option("-p, --pull-schema", "pull the latest schema before generating").option("-r, --preserve-database", "preserve any existing generated logic").option("-o, --output [outputPath]", "persist queries to a queryMap file").option(
9
9
  "-h, --headers <headers...>",
10
10
  "headers to use when pulling your schema. Should be passed as KEY=VALUE"
11
- ).option("-v, --verbose", "verbose error messages").action(generate);
11
+ ).option("-v, --verbose", "verbose error messages").option(
12
+ "--after-phase <phase>",
13
+ "start the pipeline after the specified phase (Config, AfterLoad, Schema, ExtractDocuments, AfterExtract, BeforeValidate, Validate, AfterValidate, BeforeGenerate, GenerateDocuments, GenerateRuntime, AfterGenerate)"
14
+ ).option(
15
+ "--before-phase <phase>",
16
+ "run the pipeline up to and including the specified phase (Config, AfterLoad, Schema, ExtractDocuments, AfterExtract, BeforeValidate, Validate, AfterValidate, BeforeGenerate, GenerateDocuments, GenerateRuntime, AfterGenerate)"
17
+ ).action(generate);
12
18
  program.command("init").arguments("[path]").usage("[path] [options]").description("initialize a new houdini project").option(
13
19
  "-h, --headers <headers...>",
14
20
  "header to use when pulling your schema. Should be passed as KEY=VALUE"
package/build/cmd/init.js CHANGED
@@ -164,12 +164,14 @@ async function init(_path, args) {
164
164
  const configPath = path.join(targetPath, "houdini.config.js");
165
165
  const s = p.spinner();
166
166
  s.start(`\u{1F6A7} Generating houdini's files...`);
167
+ const runtimeDir = ".houdini";
167
168
  await houdiniConfig(
168
169
  configPath,
169
170
  schemaPath,
170
171
  module,
171
172
  frameworkInfo,
172
- is_remote_endpoint ? url : null
173
+ is_remote_endpoint ? url : null,
174
+ runtimeDir
173
175
  );
174
176
  await houdiniClient(sourceDir, typescript, frameworkInfo, url);
175
177
  if (frameworkInfo.framework === "svelte") {
@@ -177,8 +179,11 @@ async function init(_path, args) {
177
179
  } else if (frameworkInfo.framework === "kit") {
178
180
  await svelteConfig(targetPath, typescript);
179
181
  }
180
- await gitIgnore(targetPath);
181
- await graphqlRC(targetPath);
182
+ await gitIgnore({
183
+ targetPath,
184
+ schemaPath: is_remote_endpoint ? schemaPath : void 0
185
+ });
186
+ await graphqlRC(targetPath, runtimeDir);
182
187
  await viteConfig(targetPath, frameworkInfo, typescript);
183
188
  await tjsConfig(targetPath, frameworkInfo);
184
189
  await packageJSON(targetPath, frameworkInfo);
@@ -211,14 +216,14 @@ function finale_logs(package_manager) {
211
216
  )
212
217
  );
213
218
  }
214
- async function houdiniConfig(configPath, schemaPath, module, frameworkInfo, url) {
219
+ async function houdiniConfig(configPath, schemaPath, module, frameworkInfo, url, runtimeDir) {
215
220
  const config = {};
216
221
  if (url !== null) {
217
222
  config.watchSchema = {
218
223
  url
219
224
  };
220
225
  }
221
- config.runtimeDir = ".houdini";
226
+ config.runtimeDir = runtimeDir;
222
227
  if (schemaPath !== "./schema.graphql") {
223
228
  config.schemaPath = schemaPath;
224
229
  }
@@ -244,7 +249,7 @@ const config = ${configObj}`;
244
249
  const content = module === "esm" ? `${content_base}
245
250
 
246
251
  export default config
247
- ` : `${content_base}}
252
+ ` : `${content_base}
248
253
 
249
254
  module.exports = config
250
255
  `;
@@ -264,7 +269,7 @@ export default new HoudiniClient({
264
269
  // fetchParams({ session }) {
265
270
  // return {
266
271
  // headers: {
267
- // Authentication: \`Bearer \${session.token}\`,
272
+ // Authorization: \`Bearer \${session.token}\`,
268
273
  // }
269
274
  // }
270
275
  // }
@@ -323,24 +328,34 @@ export default config;
323
328
  `;
324
329
  await fs.writeFile(svelteConfigPath, typescript ? newContentTs : newContentJs);
325
330
  }
326
- async function gitIgnore(targetPath) {
331
+ async function gitIgnore({ targetPath, schemaPath }) {
327
332
  const filepath = path.join(targetPath, ".gitignore");
328
333
  const existing = await fs.readFile(filepath) || "";
334
+ let newIgnores = "";
329
335
  if (!existing.includes("\n.houdini\n")) {
330
- await fs.writeFile(filepath, existing + "\n.houdini\n");
336
+ newIgnores += ".houdini\n";
337
+ }
338
+ if (schemaPath && !existing.includes(`
339
+ ${schemaPath}
340
+ `)) {
341
+ newIgnores += `${schemaPath}
342
+ `;
343
+ }
344
+ if (newIgnores) {
345
+ await fs.writeFile(filepath, existing + "\n" + newIgnores);
331
346
  }
332
347
  }
333
- async function graphqlRC(targetPath) {
348
+ async function graphqlRC(targetPath, runtimeDir) {
334
349
  const target = path.join(targetPath, ".graphqlrc.yaml");
335
350
  const content = `projects:
336
351
  default:
337
352
  schema:
338
353
  - ./schema.graphql
339
- - ./.houdini/graphql/schema.graphql
354
+ - ./${runtimeDir}/graphql/schema.graphql
340
355
  documents:
341
356
  - '**/*.gql'
342
357
  - '**/*.svelte'
343
- - ./.houdini/graphql/documents.gql
358
+ - ./${runtimeDir}/graphql/documents.gql
344
359
  `;
345
360
  await fs.writeFile(target, content);
346
361
  }
@@ -420,12 +435,12 @@ async function packageJSON(targetPath, frameworkInfo) {
420
435
  }
421
436
  packageJSON2.devDependencies = {
422
437
  ...packageJSON2.devDependencies,
423
- houdini: "^2.0.0-go.2"
438
+ houdini: "^2.0.0-go.20"
424
439
  };
425
440
  if (frameworkInfo.framework === "svelte" || frameworkInfo.framework === "kit") {
426
441
  packageJSON2.devDependencies = {
427
442
  ...packageJSON2.devDependencies,
428
- "houdini-svelte": "^2.0.0-go.2"
443
+ "houdini-svelte": "^3.0.0-go.21"
429
444
  };
430
445
  } else {
431
446
  throw new Error(`Unmanaged framework: "${JSON.stringify(frameworkInfo)}"`);
@@ -12,5 +12,5 @@ export declare function find_exported_fn(body: Statement[], name: string): {
12
12
  declaration: FunctionDeclaration | FunctionExpression | ArrowFunctionExpression | null | Identifier | CallExpression;
13
13
  export: ExportNamedDeclaration;
14
14
  } | null;
15
- export declare function find_exported_id(program: Program, name: string): recast.types.namedTypes.ExportNamedDeclaration | undefined;
15
+ export declare function find_exported_id(program: Program, name: string): recast.types.namedTypes.ExportNamedDeclaration;
16
16
  export {};
@@ -42,9 +42,10 @@ export type CompilerProxy = {
42
42
  database_path: string;
43
43
  run_pipeline: (options: RunPipelineOptions) => Promise<Record<PipelineHook, Record<string, any>>>;
44
44
  };
45
+ export declare function plugin_db_key(name: string): string;
45
46
  export declare function codegen_setup(config: Config, mode: string, db: DatabaseSync, db_file: string): Promise<CompilerProxy>;
46
- declare const PIPELINE_HOOKS: readonly ["Config", "AfterLoad", "Schema", "ExtractDocuments", "AfterExtract", "BeforeValidate", "Validate", "AfterValidate", "BeforeGenerate", "GenerateDocuments", "GenerateRuntime", "AfterGenerate"];
47
- type PipelineHook = (typeof PIPELINE_HOOKS)[number];
47
+ export declare const PIPELINE_HOOKS: readonly ["Config", "AfterLoad", "Schema", "ExtractDocuments", "AfterExtract", "BeforeValidate", "Validate", "AfterValidate", "BeforeGenerate", "GenerateDocuments", "GenerateRuntime", "AfterGenerate"];
48
+ export type PipelineHook = (typeof PIPELINE_HOOKS)[number];
48
49
  export type RunPipelineOptions = {
49
50
  task_id?: string;
50
51
  after?: PipelineHook;
@@ -52,4 +53,3 @@ export type RunPipelineOptions = {
52
53
  through?: PipelineHook;
53
54
  };
54
55
  export declare function run_pipeline(trigger_hook: CompilerProxy['trigger_hook'], options?: RunPipelineOptions): Promise<Record<PipelineHook, Record<string, any>>>;
55
- export {};
@@ -1,11 +1,14 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import path from "node:path";
3
+ import { createInterface } from "node:readline";
3
4
  import sqlite from "node:sqlite";
4
5
  import { WebSocket } from "ws";
5
6
  import * as conventions from "../router/conventions.js";
6
7
  import { create_schema, write_config } from "./database.js";
7
8
  import { format_hook_error } from "./error.js";
8
9
  import * as fs from "./fs.js";
10
+ import { Logger } from "./logger.js";
11
+ import { LogLevel } from "./types.js";
9
12
  function connect_db(config) {
10
13
  const filepath = conventions.db_path(config);
11
14
  const db = new sqlite.DatabaseSync(filepath);
@@ -37,29 +40,43 @@ async function init_db(config, preserve) {
37
40
  }
38
41
  return [connect_db(config)[0], db_file];
39
42
  }
43
+ function plugin_db_key(name) {
44
+ if (!name.startsWith("./") && !name.startsWith("../") && !path.isAbsolute(name)) {
45
+ return name;
46
+ }
47
+ return name.replace(/\./g, "_").replace(/\//g, "__");
48
+ }
40
49
  async function codegen_setup(config, mode, db, db_file) {
50
+ const logger = new Logger(config.config_file.logLevel ?? LogLevel.Summary);
41
51
  await fs.mkdirpSync(conventions.houdini_root(config));
52
+ const rawTransport = config.config_file.pluginTransport ?? "websocket";
53
+ const resolvedTransport = rawTransport.startsWith("env:") ? process.env[rawTransport.slice("env:".length)] ?? "websocket" : rawTransport;
54
+ const useStdio = resolvedTransport === "stdio";
42
55
  const plugins = {};
43
56
  const plugin_specs = [];
44
57
  const spec_results = {};
45
- const wait_for_plugin = (name) => new Promise((resolve, reject) => {
58
+ const stdioStdin = /* @__PURE__ */ new Map();
59
+ const triggerHookRef = { fn: null };
60
+ const wait_for_plugin_db = (configKey, dbKey) => new Promise((resolve, reject) => {
46
61
  const find_plugin = db.prepare("SELECT * FROM plugins WHERE name = ?");
47
62
  const interval = setInterval(() => {
48
- const row = find_plugin.get(name);
63
+ const row = find_plugin.get(dbKey);
49
64
  if (row) {
50
65
  clearInterval(interval);
51
66
  db.prepare("UPDATE plugins set config = ? where name = ?").run(
52
- JSON.stringify(config.plugins.find((p) => p.name === name)?.config ?? {}),
53
- name
67
+ JSON.stringify(
68
+ config.plugins.find((p) => p.name === configKey)?.config ?? {}
69
+ ),
70
+ dbKey
54
71
  );
55
72
  const spec = {
56
73
  name: row.name,
57
74
  port: row.port,
58
75
  hooks: new Set(JSON.parse(row.hooks)),
59
76
  order: row.plugin_order,
60
- directory: config.plugins.find((p) => p.name === name)?.directory || ""
77
+ directory: config.plugins.find((p) => p.name === configKey)?.directory || ""
61
78
  };
62
- spec_results[name] = spec;
79
+ spec_results[configKey] = spec;
63
80
  if (row.config_module) {
64
81
  import(row.config_module).then((module) => {
65
82
  if (module && typeof module.default === "function") {
@@ -70,20 +87,142 @@ async function codegen_setup(config, mode, db, db_file) {
70
87
  } else {
71
88
  resolver(spec);
72
89
  }
73
- resolver(spec);
74
90
  }
75
91
  }, 10);
76
92
  const timeout = setTimeout(() => {
77
93
  clearInterval(interval);
78
- reject(new Error(`Timeout waiting for plugin ${name} to register`));
94
+ reject(new Error(`Timeout waiting for plugin ${configKey} to register`));
79
95
  }, 1e4);
80
96
  const resolver = (spec) => {
81
97
  clearTimeout(timeout);
82
98
  resolve(spec);
83
99
  };
84
100
  });
101
+ const wait_for_plugin_stdio = (name, child) => new Promise((resolve, reject) => {
102
+ const timeout = setTimeout(() => {
103
+ reject(new Error(`Timeout waiting for plugin ${name} to register`));
104
+ }, 1e4);
105
+ const rl = createInterface({ input: child.stdout });
106
+ let registered = false;
107
+ rl.on("line", (line) => {
108
+ try {
109
+ const msg = JSON.parse(line);
110
+ if (!registered) {
111
+ if (msg.type !== "register") {
112
+ clearTimeout(timeout);
113
+ reject(new Error(`Plugin ${name} sent ${msg.type} before registering`));
114
+ return;
115
+ }
116
+ registered = true;
117
+ clearTimeout(timeout);
118
+ const spec = {
119
+ name: msg.name ?? plugin_db_key(name),
120
+ port: msg.port ?? 0,
121
+ hooks: new Set(msg.hooks ?? []),
122
+ order: msg.order,
123
+ directory: config.plugins.find((p) => p.name === name)?.directory || ""
124
+ };
125
+ spec_results[name] = spec;
126
+ db.prepare(
127
+ `INSERT OR IGNORE INTO plugins (name, hooks, port, plugin_order, include_runtime, config_module, client_plugins)
128
+ VALUES (?, ?, ?, ?, ?, ?, ?)`
129
+ ).run(
130
+ spec.name,
131
+ JSON.stringify([...spec.hooks]),
132
+ spec.port,
133
+ spec.order,
134
+ msg.includeRuntime ?? null,
135
+ msg.configModule ?? null,
136
+ msg.clientPlugins ?? null
137
+ );
138
+ db.prepare("UPDATE plugins SET config = ? WHERE name = ?").run(
139
+ JSON.stringify(
140
+ config.plugins.find((p) => p.name === name)?.config ?? {}
141
+ ),
142
+ spec.name
143
+ );
144
+ if (msg.configModule) {
145
+ import(msg.configModule).then((module) => {
146
+ if (module && typeof module.default === "function") {
147
+ config.config_file = module.default(config.config_file);
148
+ }
149
+ resolve(spec);
150
+ }).catch((err) => {
151
+ reject(
152
+ new Error(
153
+ `Failed to load configModule for ${name}: ${err.message}`
154
+ )
155
+ );
156
+ });
157
+ } else {
158
+ resolve(spec);
159
+ }
160
+ return;
161
+ }
162
+ if (msg.type === "response") {
163
+ const pending = pendingRequests.get(msg.id);
164
+ if (!pending)
165
+ return;
166
+ clearTimeout(pending.timeout);
167
+ pendingRequests.delete(msg.id);
168
+ if (msg.error) {
169
+ const errors = Array.isArray(msg.error) ? msg.error : [msg.error];
170
+ errors.forEach(
171
+ (error) => format_hook_error(config.root_dir, error, name, pending.hook)
172
+ );
173
+ pending.reject(new Error(`Failed to call ${name}`));
174
+ } else {
175
+ pending.resolve(msg.result);
176
+ }
177
+ } else if (msg.type === "invoke") {
178
+ if (!triggerHookRef.fn) {
179
+ const reply = JSON.stringify({
180
+ id: msg.id,
181
+ type: "invoke_result",
182
+ error: { message: "orchestrator not ready" }
183
+ }) + "\n";
184
+ child.stdin?.write(reply);
185
+ return;
186
+ }
187
+ triggerHookRef.fn(msg.hook, {
188
+ parallel_safe: msg.parallel,
189
+ payload: msg.payload,
190
+ task_id: msg.taskId
191
+ }).then((result) => {
192
+ const reply = JSON.stringify({ id: msg.id, type: "invoke_result", result }) + "\n";
193
+ child.stdin?.write(reply);
194
+ }).catch((err) => {
195
+ const reply = JSON.stringify({
196
+ id: msg.id,
197
+ type: "invoke_result",
198
+ error: { message: err.message }
199
+ }) + "\n";
200
+ child.stdin?.write(reply);
201
+ });
202
+ }
203
+ } catch (err) {
204
+ if (!registered) {
205
+ reject(err instanceof Error ? err : new Error(String(err)));
206
+ }
207
+ }
208
+ });
209
+ rl.on("close", () => {
210
+ if (!registered) {
211
+ clearTimeout(timeout);
212
+ reject(new Error(`Plugin ${name} stdout closed before registering`));
213
+ } else {
214
+ for (const [id, pending] of pendingRequests.entries()) {
215
+ if (pending.plugin !== name)
216
+ continue;
217
+ clearTimeout(pending.timeout);
218
+ pendingRequests.delete(id);
219
+ pending.reject(new Error(`Plugin ${name} closed unexpectedly`));
220
+ }
221
+ }
222
+ });
223
+ });
85
224
  db.prepare("DELETE FROM plugins").run();
86
- console.time("Start Plugins");
225
+ logger.time("Start Plugins");
87
226
  await Promise.all(
88
227
  config.plugins.map(async (plugin) => {
89
228
  let executable = plugin.executable;
@@ -93,21 +232,35 @@ async function codegen_setup(config, mode, db, db_file) {
93
232
  executable = "node";
94
233
  args.unshift(plugin.executable);
95
234
  }
96
- console.time(`Spawn ${plugin.name}`);
235
+ const dbKey = plugin_db_key(plugin.name);
236
+ args.push("--plugin-key", dbKey);
237
+ if (useStdio) {
238
+ args.push("--transport", "stdio");
239
+ }
240
+ logger.time(`Spawn ${plugin.name}`);
241
+ const child = spawn(executable, args, {
242
+ stdio: useStdio ? ["pipe", "pipe", "inherit"] : ["inherit", "inherit", "inherit"],
243
+ detached: process.platform !== "win32"
244
+ });
245
+ if (useStdio) {
246
+ stdioStdin.set(dbKey, child.stdin);
247
+ child.stdin.on("error", (err) => {
248
+ if (err.code !== "EPIPE") {
249
+ console.error(`[${plugin.name}] stdin error:`, err.message);
250
+ }
251
+ });
252
+ }
97
253
  plugins[plugin.name] = {
98
- process: spawn(executable, args, {
99
- stdio: "inherit",
100
- detached: process.platform !== "win32"
101
- }),
102
- ...await wait_for_plugin(plugin.name)
254
+ process: child,
255
+ ...await (useStdio ? wait_for_plugin_stdio(plugin.name, child) : wait_for_plugin_db(plugin.name, dbKey))
103
256
  };
104
- console.timeEnd(`Spawn ${plugin.name}`);
257
+ logger.timeEnd(`Spawn ${plugin.name}`, LogLevel.Verbose);
105
258
  })
106
259
  );
107
260
  for (const plugin of config.plugins) {
108
261
  plugin_specs.push(spec_results[plugin.name]);
109
262
  }
110
- console.timeEnd("Start Plugins");
263
+ logger.timeEnd("Start Plugins", LogLevel.Summary);
111
264
  const wsConnections = /* @__PURE__ */ new Map();
112
265
  let messageCounter = 0;
113
266
  const pendingRequests = /* @__PURE__ */ new Map();
@@ -171,24 +324,39 @@ async function codegen_setup(config, mode, db, db_file) {
171
324
  throw new Error(`unknown plugin: ${name}`);
172
325
  }
173
326
  const { port, directory } = plugin;
174
- const ws = await getOrCreateWS(name, port);
175
- return new Promise((resolve, reject) => {
176
- const messageId = String(++messageCounter);
177
- const timeout = setTimeout(() => {
178
- pendingRequests.delete(messageId);
179
- reject(new Error(`WebSocket request timeout for ${name}/${hook}`));
180
- }, 3e4);
181
- pendingRequests.set(messageId, { resolve, reject, timeout, hook });
182
- const message = {
183
- id: messageId,
184
- type: "request",
185
- hook,
186
- payload,
187
- taskId: task_id,
188
- pluginDirectory: directory
189
- };
190
- ws.send(JSON.stringify(message));
191
- });
327
+ const messageId = String(++messageCounter);
328
+ const message = {
329
+ id: messageId,
330
+ type: "request",
331
+ hook,
332
+ payload,
333
+ taskId: task_id,
334
+ pluginDirectory: directory
335
+ };
336
+ if (useStdio) {
337
+ const stdin = stdioStdin.get(name);
338
+ if (!stdin) {
339
+ throw new Error(`No stdio channel for plugin ${name}`);
340
+ }
341
+ return new Promise((resolve, reject) => {
342
+ const timeout = setTimeout(() => {
343
+ pendingRequests.delete(messageId);
344
+ reject(new Error(`Request timeout for ${name}/${hook}`));
345
+ }, 3e4);
346
+ pendingRequests.set(messageId, { resolve, reject, timeout, hook, plugin: name });
347
+ stdin.write(JSON.stringify(message) + "\n");
348
+ });
349
+ } else {
350
+ const ws = await getOrCreateWS(name, port);
351
+ return new Promise((resolve, reject) => {
352
+ const timeout = setTimeout(() => {
353
+ pendingRequests.delete(messageId);
354
+ reject(new Error(`WebSocket request timeout for ${name}/${hook}`));
355
+ }, 3e4);
356
+ pendingRequests.set(messageId, { resolve, reject, timeout, hook, plugin: name });
357
+ ws.send(JSON.stringify(message));
358
+ });
359
+ }
192
360
  };
193
361
  const trigger_hook = async (hook, {
194
362
  parallel_safe,
@@ -196,24 +364,28 @@ async function codegen_setup(config, mode, db, db_file) {
196
364
  task_id
197
365
  } = {}) => {
198
366
  const timeName = hook + (task_id ? ` (${task_id})` : "");
199
- console.time(timeName);
367
+ logger.time(timeName);
200
368
  const plugins2 = plugin_specs.filter(({ hooks }) => hooks.has(hook));
201
369
  const result = {};
202
- if (parallel_safe) {
203
- await Promise.all(
204
- plugins2.map(async (plugin) => {
205
- result[plugin.name] = await invoke_hook(plugin.name, hook, payload, task_id);
206
- })
207
- );
208
- } else {
209
- for (const { name } of plugins2) {
210
- result[name] = await invoke_hook(name, hook, payload, task_id);
370
+ try {
371
+ if (parallel_safe) {
372
+ await Promise.all(
373
+ plugins2.map(async (plugin) => {
374
+ result[plugin.name] = await invoke_hook(plugin.name, hook, payload, task_id);
375
+ })
376
+ );
377
+ } else {
378
+ for (const { name } of plugins2) {
379
+ result[name] = await invoke_hook(name, hook, payload, task_id);
380
+ }
211
381
  }
382
+ } finally {
383
+ logger.timeEnd(timeName, task_id ? LogLevel.Verbose : LogLevel.Summary);
212
384
  }
213
- console.timeEnd(timeName);
214
385
  return result;
215
386
  };
216
- await write_config(db, config, invoke_hook, plugin_specs, mode);
387
+ triggerHookRef.fn = trigger_hook;
388
+ await write_config(db, config, invoke_hook, plugin_specs, mode, logger);
217
389
  await trigger_hook("Config");
218
390
  await trigger_hook("AfterLoad");
219
391
  await trigger_hook("Schema");
@@ -232,8 +404,16 @@ async function codegen_setup(config, mode, db, db_file) {
232
404
  }
233
405
  }
234
406
  wsConnections.clear();
235
- for (const [, { timeout }] of pendingRequests.entries()) {
407
+ for (const [, stdin] of stdioStdin.entries()) {
408
+ try {
409
+ stdin.end();
410
+ } catch {
411
+ }
412
+ }
413
+ stdioStdin.clear();
414
+ for (const [, { timeout, reject }] of pendingRequests.entries()) {
236
415
  clearTimeout(timeout);
416
+ reject(new Error("codegen closed"));
237
417
  }
238
418
  pendingRequests.clear();
239
419
  await new Promise((resolve) => setTimeout(resolve, 100));
@@ -315,13 +495,25 @@ async function run_pipeline(trigger_hook, options = {}) {
315
495
  if (hook === "Validate" || hook === "GenerateDocuments") {
316
496
  opts.parallel_safe = true;
317
497
  }
498
+ if (hook === "GenerateDocuments" && i + 1 <= endIndex && PIPELINE_HOOKS[i + 1] === "GenerateRuntime") {
499
+ const [gdResult, grResult] = await Promise.all([
500
+ trigger_hook("GenerateDocuments", { task_id, parallel_safe: true }),
501
+ trigger_hook("GenerateRuntime", { task_id })
502
+ ]);
503
+ results["GenerateDocuments"] = gdResult;
504
+ results["GenerateRuntime"] = grResult;
505
+ i++;
506
+ continue;
507
+ }
318
508
  results[hook] = await trigger_hook(hook, opts);
319
509
  }
320
510
  return results;
321
511
  }
322
512
  export {
513
+ PIPELINE_HOOKS,
323
514
  codegen_setup,
324
515
  connect_db,
325
516
  init_db,
517
+ plugin_db_key,
326
518
  run_pipeline
327
519
  };