houdini 2.0.0-next.27 → 2.0.0-next.29

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 (51) hide show
  1. package/build/cmd/init.js +108 -81
  2. package/build/lib/ast.d.ts +1 -1
  3. package/build/lib/codegen.d.ts +4 -4
  4. package/build/lib/codegen.js +136 -77
  5. package/build/lib/config.d.ts +2 -2
  6. package/build/lib/config.js +2 -1
  7. package/build/lib/constants.js +4 -0
  8. package/build/lib/database.d.ts +3 -3
  9. package/build/lib/database.js +60 -92
  10. package/build/lib/db.d.ts +13 -0
  11. package/build/lib/db.js +134 -0
  12. package/build/lib/fs.d.ts +6 -6
  13. package/build/lib/fs.js +4 -4
  14. package/build/lib/index.d.ts +1 -0
  15. package/build/lib/index.js +1 -0
  16. package/build/lib/logger.js +2 -2
  17. package/build/lib/parse.js +2 -0
  18. package/build/lib/plugins.d.ts +1 -1
  19. package/build/lib/plugins.js +13 -2
  20. package/build/lib/project.js +6 -2
  21. package/build/lib/schema.js +0 -1
  22. package/build/lib/walk.d.ts +7 -3
  23. package/build/node/index.js +28 -38
  24. package/build/package.json +29 -33
  25. package/build/router/conventions.d.ts +4 -4
  26. package/build/router/jwt.js +16 -31
  27. package/build/router/match.d.ts +1 -1
  28. package/build/router/match.js +2 -4
  29. package/build/router/session.js +1 -1
  30. package/build/runtime/cache/gc.js +1 -0
  31. package/build/runtime/cache/index.d.ts +2 -2
  32. package/build/runtime/cache/index.js +22 -1
  33. package/build/runtime/cache/lists.d.ts +5 -5
  34. package/build/runtime/cache/lists.js +9 -0
  35. package/build/runtime/cache/staleManager.js +25 -0
  36. package/build/runtime/cache/storage.js +4 -0
  37. package/build/runtime/cache/subscription.js +1 -0
  38. package/build/runtime/client.js +2 -0
  39. package/build/runtime/config.d.ts +1 -1
  40. package/build/runtime/deepEquals.js +2 -4
  41. package/build/runtime/documentStore.js +30 -4
  42. package/build/runtime/pagination.js +2 -1
  43. package/build/runtime/types.d.ts +1 -0
  44. package/build/runtime/types.js +10 -1
  45. package/build/vite/hmr.js +112 -89
  46. package/build/vite/houdini.js +1 -0
  47. package/build/vite/index.d.ts +2 -2
  48. package/build/vite/index.js +13 -5
  49. package/build/vite/schema.d.ts +1 -1
  50. package/build/vite/schema.js +70 -31
  51. package/package.json +33 -35
package/build/cmd/init.js CHANGED
@@ -72,62 +72,99 @@ async function init(_path, args) {
72
72
  let schemaPath = is_remote_endpoint ? "./schema.graphql" : "path/to/src/lib/**/*.graphql";
73
73
  let pullSchema_content = null;
74
74
  if (is_remote_endpoint && !args.yes) {
75
- let number_of_round = 0;
76
- let url_and_headers = "";
77
- while (pullSchema_content === null && number_of_round < 10) {
78
- number_of_round++;
79
- const answer = await p.group(
80
- {
81
- url_and_headers: async () => p.text({
82
- message: `What's the URL for your api? ${number_of_round === 1 ? "" : `(attempt ${number_of_round})`}`,
83
- placeholder: `http://localhost:4000/graphql ${number_of_round === 1 ? "" : "Authorization=Bearer MyToken"}`,
84
- validate: (value) => {
85
- if (value === "") {
86
- return;
87
- }
88
- if (!value.startsWith("http")) {
89
- return "Please enter a valid URL";
75
+ const { api_running } = await p.group(
76
+ {
77
+ api_running: () => p.confirm({
78
+ message: "Is your API currently running?",
79
+ initialValue: true
80
+ })
81
+ },
82
+ { onCancel: () => pCancel() }
83
+ );
84
+ if (api_running) {
85
+ let number_of_round = 0;
86
+ let url_and_headers = "";
87
+ while (pullSchema_content === null && number_of_round < 10) {
88
+ number_of_round++;
89
+ const answer = await p.group(
90
+ {
91
+ url_and_headers: async () => p.text({
92
+ message: `What's the URL for your api? ${number_of_round === 1 ? "" : `(attempt ${number_of_round})`}`,
93
+ placeholder: `http://localhost:4000/graphql ${number_of_round === 1 ? "" : "Authorization=Bearer MyToken"}`,
94
+ validate: (value) => {
95
+ if (value && !value.startsWith("http")) {
96
+ return "Please enter a valid URL";
97
+ }
90
98
  }
91
- }
99
+ })
100
+ },
101
+ {
102
+ onCancel: () => pCancel()
103
+ }
104
+ );
105
+ url_and_headers = answer.url_and_headers;
106
+ const value_splited = url_and_headers.split(" ");
107
+ const local_url = value_splited[0];
108
+ const local_headers = value_splited.length > 1 ? extractHeadersStr(value_splited.slice(1).join(" ")) : headers;
109
+ const fetchTimeout = 3e4;
110
+ pullSchema_content = await pull_schema(
111
+ local_url,
112
+ fetchTimeout,
113
+ schemaPath,
114
+ local_headers,
115
+ true
116
+ );
117
+ if (pullSchema_content === null) {
118
+ const msg = `If you need to pass headers, add them after the URL (eg: '${green(
119
+ `http://myurl.com/graphql Authorization=Bearer MyToken`
120
+ )}')`;
121
+ p.log.error(msg);
122
+ }
123
+ url = url_and_headers === "" ? "http://localhost:4000/graphql" : local_url;
124
+ }
125
+ if (pullSchema_content === null) {
126
+ pCancel("We couldn't pull the schema. Please check your URL/headers and try again.");
127
+ }
128
+ } else {
129
+ const { has_schema_file } = await p.group(
130
+ {
131
+ has_schema_file: () => p.confirm({
132
+ message: "Do you have a schema file on disk we can use?",
133
+ initialValue: false
92
134
  })
93
135
  },
94
- {
95
- onCancel: () => pCancel()
96
- }
97
- );
98
- url_and_headers = answer.url_and_headers;
99
- const value_splited = url_and_headers.split(" ");
100
- const local_url = value_splited[0];
101
- const local_headers = value_splited.length > 1 ? extractHeadersStr(value_splited.slice(1).join(" ")) : headers;
102
- const fetchTimeout = 3e4;
103
- pullSchema_content = await pull_schema(
104
- local_url,
105
- fetchTimeout,
106
- schemaPath,
107
- local_headers,
108
- true
136
+ { onCancel: () => pCancel() }
109
137
  );
110
- if (pullSchema_content === null) {
111
- const msg = `If you need to pass headers, add them after the URL (eg: '${green(
112
- `http://myurl.com/graphql Authorization=Bearer MyToken`
113
- )}')`;
114
- p.log.error(msg);
138
+ if (has_schema_file) {
139
+ const { schema_file_path } = await p.group(
140
+ {
141
+ schema_file_path: () => p.path({
142
+ message: "Where is the schema file?",
143
+ initialValue: "./schema.graphql",
144
+ validate: (value) => {
145
+ if (!value) return "Please enter a valid path";
146
+ try {
147
+ fs.statSync(path.resolve(value));
148
+ } catch {
149
+ return "File not found";
150
+ }
151
+ }
152
+ })
153
+ },
154
+ { onCancel: () => pCancel() }
155
+ );
156
+ pullSchema_content = await fs.readFile(path.resolve(schema_file_path)) ?? null;
115
157
  }
116
- url = url_and_headers === "" ? "http://localhost:4000/graphql" : local_url;
117
- }
118
- if (pullSchema_content === null) {
119
- pCancel("We couldn't pull the schema. Please check your URL/headers and try again.");
158
+ url = "API_URL";
120
159
  }
121
160
  } else if (!args.yes) {
122
161
  const answers = await p.group(
123
162
  {
124
- schema_path: () => p.text({
163
+ schema_path: () => p.path({
125
164
  message: "Where is your schema located?",
126
- placeholder: schemaPath,
165
+ initialValue: schemaPath,
127
166
  validate: (value) => {
128
- if (value === "") {
129
- return "Please enter a valid schemaPath";
130
- }
167
+ if (!value) return "Please enter a valid schemaPath";
131
168
  }
132
169
  })
133
170
  },
@@ -173,17 +210,13 @@ async function init(_path, args) {
173
210
  is_remote_endpoint ? url : null,
174
211
  runtimeDir
175
212
  );
176
- await houdiniClient(sourceDir, typescript, frameworkInfo, url);
213
+ await houdiniClient(sourceDir, typescript, frameworkInfo, is_remote_endpoint ? url : null);
177
214
  if (frameworkInfo.framework === "svelte") {
178
215
  await svelteKitConfig(targetPath, typescript);
179
216
  } else if (frameworkInfo.framework === "kit") {
180
217
  await svelteConfig(targetPath, typescript);
181
218
  }
182
- await gitIgnore({
183
- targetPath,
184
- runtimeDir,
185
- schemaPath: is_remote_endpoint ? schemaPath : void 0
186
- });
219
+ await gitIgnore({ targetPath, runtimeDir });
187
220
  await graphqlRC(targetPath, runtimeDir);
188
221
  await viteConfig(targetPath, frameworkInfo, typescript);
189
222
  await tjsConfig(targetPath, frameworkInfo);
@@ -225,7 +258,7 @@ async function houdiniConfig(configPath, schemaPath, module, frameworkInfo, url,
225
258
  };
226
259
  }
227
260
  config.runtimeDir = runtimeDir;
228
- if (schemaPath !== "./schema.graphql") {
261
+ if (url !== null) {
229
262
  config.schemaPath = schemaPath;
230
263
  }
231
264
  if (module !== "esm") {
@@ -247,24 +280,26 @@ async function houdiniConfig(configPath, schemaPath, module, frameworkInfo, url,
247
280
 
248
281
  /** @type {import('houdini').ConfigFile} */
249
282
  const config = ${configObj}`;
250
- const content = module === "esm" ? `${content_base}
283
+ const content = module === "esm" ? (
284
+ // ESM default config
285
+ `${content_base}
251
286
 
252
287
  export default config
253
- ` : `${content_base}
288
+ `
289
+ ) : (
290
+ // CommonJS default config
291
+ `${content_base}
254
292
 
255
293
  module.exports = config
256
- `;
294
+ `
295
+ );
257
296
  await fs.writeFile(configPath, content);
258
297
  return false;
259
298
  }
260
299
  async function houdiniClient(targetPath, typescript, _frameworkInfo, url) {
261
300
  const houdiniClientExt = typescript ? `ts` : `js`;
262
301
  const houdiniClientPath = path.join(targetPath, `client.${houdiniClientExt}`);
263
- const content = `import { HoudiniClient } from '$houdini';
264
-
265
- export default new HoudiniClient({
266
- url: '${url}'
267
-
302
+ const comment = `
268
303
  // uncomment this to configure the network call (for things like authentication)
269
304
  // for more information, please visit here: https://www.houdinigraphql.com/guides/authentication
270
305
  // fetchParams({ session }) {
@@ -273,8 +308,14 @@ export default new HoudiniClient({
273
308
  // Authorization: \`Bearer \${session.token}\`,
274
309
  // }
275
310
  // }
276
- // }
277
- })
311
+ // }`;
312
+ const urlLine = url ? `{
313
+ url: '${url}',${comment}
314
+ }` : `{${comment}
315
+ }`;
316
+ const content = `import { HoudiniClient } from '$houdini';
317
+
318
+ export default new HoudiniClient(${urlLine})
278
319
  `;
279
320
  await fs.writeFile(houdiniClientPath, content);
280
321
  }
@@ -329,29 +370,15 @@ export default config;
329
370
  `;
330
371
  await fs.writeFile(svelteConfigPath, typescript ? newContentTs : newContentJs);
331
372
  }
332
- async function gitIgnore({
333
- targetPath,
334
- runtimeDir,
335
- schemaPath
336
- }) {
373
+ async function gitIgnore({ targetPath, runtimeDir }) {
337
374
  const filepath = path.join(targetPath, ".gitignore");
338
375
  const existing = await fs.readFile(filepath) || "";
339
- let newIgnores = "";
340
376
  if (!existing.includes(`
341
377
  ${runtimeDir}
342
378
  `)) {
343
- newIgnores += `${runtimeDir}
344
- `;
345
- }
346
- if (schemaPath && !existing.includes(`
347
- ${schemaPath}
348
- `)) {
349
- newIgnores += `${schemaPath}
350
- `;
351
- }
352
- if (newIgnores) {
353
379
  await fs.writeFile(filepath, `${existing}
354
- ${newIgnores}`);
380
+ ${runtimeDir}
381
+ `);
355
382
  }
356
383
  }
357
384
  async function graphqlRC(targetPath, runtimeDir) {
@@ -445,12 +472,12 @@ async function packageJSON(targetPath, frameworkInfo) {
445
472
  }
446
473
  packageJSON2.devDependencies = {
447
474
  ...packageJSON2.devDependencies,
448
- houdini: "^2.0.0-next.27"
475
+ houdini: "^2.0.0-next.29"
449
476
  };
450
477
  if (frameworkInfo.framework === "svelte" || frameworkInfo.framework === "kit") {
451
478
  packageJSON2.devDependencies = {
452
479
  ...packageJSON2.devDependencies,
453
- "houdini-svelte": "^3.0.0-next.28"
480
+ "houdini-svelte": "^3.0.0-next.30"
454
481
  };
455
482
  } else {
456
483
  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;
15
+ export declare function find_exported_id(program: Program, name: string): recast.types.namedTypes.ExportNamedDeclaration | undefined;
16
16
  export {};
@@ -1,6 +1,6 @@
1
- import { type DatabaseSync } from 'node:sqlite';
2
1
  import * as conventions from '../router/conventions.js';
3
2
  import type { Config } from './config.js';
3
+ import { type Db } from './db.js';
4
4
  import type { ProjectManifest } from './types.js';
5
5
  export type PluginSpec = {
6
6
  name: string;
@@ -30,8 +30,8 @@ export type Adapter = ((args: {
30
30
  outDir: string;
31
31
  }) => Promise<void> | void;
32
32
  };
33
- export declare function connect_db(config: Config): [DatabaseSync, string];
34
- export declare function init_db(config: Config, preserve: boolean): Promise<[DatabaseSync, string]>;
33
+ export declare function connect_db(config: Config): Promise<[Db, string]>;
34
+ export declare function init_db(config: Config, preserve: boolean): Promise<[Db, string]>;
35
35
  export type CompilerProxy = {
36
36
  close: () => Promise<void>;
37
37
  trigger_hook: (name: PipelineHook, opts?: {
@@ -43,7 +43,7 @@ export type CompilerProxy = {
43
43
  run_pipeline: (options: RunPipelineOptions) => Promise<Record<PipelineHook, Record<string, any>>>;
44
44
  };
45
45
  export declare function plugin_db_key(name: string): string;
46
- export declare function codegen_setup(config: Config, mode: string, db: DatabaseSync, db_file: string): Promise<CompilerProxy>;
46
+ export declare function codegen_setup(config: Config, mode: string, db: Db, db_file: string): Promise<CompilerProxy>;
47
47
  export declare const PIPELINE_HOOKS: readonly ["Config", "AfterLoad", "Schema", "ExtractDocuments", "AfterExtract", "BeforeValidate", "Validate", "AfterValidate", "BeforeGenerate", "GenerateDocuments", "GenerateRuntime", "AfterGenerate"];
48
48
  export type PipelineHook = (typeof PIPELINE_HOOKS)[number];
49
49
  export type RunPipelineOptions = {
@@ -1,25 +1,66 @@
1
1
  import { spawn } from "node:child_process";
2
+ import { writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
2
4
  import path from "node:path";
3
5
  import { createInterface } from "node:readline";
4
- import sqlite from "node:sqlite";
5
6
  import { WebSocket } from "ws";
7
+ const wasiRunnerPath = path.join(tmpdir(), "houdini-wasi-runner.mjs");
8
+ writeFileSync(
9
+ wasiRunnerPath,
10
+ `import{WASI}from'node:wasi';
11
+ import{readFileSync}from'node:fs';
12
+ import{Worker,isMainThread,workerData,MessageChannel,receiveMessageOnPort}from'worker_threads';
13
+ const[,,w,...r]=process.argv;
14
+ if(isMainThread){
15
+ const{port1:p1,port2:p2}=new MessageChannel();
16
+ const sb=new Int32Array(new SharedArrayBuffer(4));
17
+ process.stdin.on('error',()=>{});
18
+ process.stdin.on('data',d=>{p1.postMessage(d);Atomics.add(sb,0,1);Atomics.notify(sb,0);});
19
+ process.stdin.on('end',()=>{p1.postMessage(null);Atomics.add(sb,0,1);Atomics.notify(sb,0);});
20
+ const wk=new Worker(new URL(import.meta.url),{workerData:{w,r,p:p2,sb},transferList:[p2]});
21
+ wk.on('exit',c=>process.exit(c??0));
22
+ }else{
23
+ const{w:wb,r:args,p:port,sb}=workerData;
24
+ const wasi=new WASI({version:'preview1',args:[wb,...args],env:process.env,preopens:{'/':'/'}});
25
+ let mem=null;
26
+ const io=wasi.getImportObject();
27
+ const rfr=io.wasi_snapshot_preview1.fd_read;
28
+ io.wasi_snapshot_preview1.fd_read=(fd,iovs,il,nr)=>{
29
+ if(fd!==0||!mem)return rfr(fd,iovs,il,nr);
30
+ while(Atomics.load(sb,0)===0)Atomics.wait(sb,0,0);
31
+ const m=receiveMessageOnPort(port);
32
+ Atomics.sub(sb,0,1);
33
+ const v=new DataView(mem.buffer);
34
+ if(!m||m.message===null){v.setUint32(nr,0,true);return 0;}
35
+ const b=Buffer.isBuffer(m.message)?m.message:Buffer.from(m.message);
36
+ let wn=0;
37
+ for(let i=0;i<il;i++){
38
+ const p=v.getUint32(iovs+i*8,true),l=v.getUint32(iovs+i*8+4,true);
39
+ const n=Math.min(l,b.length-wn);
40
+ if(n<=0)break;
41
+ new Uint8Array(mem.buffer,p,n).set(b.subarray(wn,wn+n));
42
+ wn+=n;}
43
+ v.setUint32(nr,wn,true);
44
+ return 0;};
45
+ const mod=new WebAssembly.Module(readFileSync(wb));
46
+ const inst=new WebAssembly.Instance(mod,io);
47
+ mem=inst.exports.memory;
48
+ wasi.start(inst);
49
+ process.exit(0);}
50
+ `
51
+ );
6
52
  import * as conventions from "../router/conventions.js";
53
+ import { openDb } from "./db.js";
7
54
  import { create_schema, write_config } from "./database.js";
8
55
  import { format_hook_error } from "./error.js";
9
56
  import * as fs from "./fs.js";
10
57
  import { Logger } from "./logger.js";
11
58
  import { LogLevel } from "./types.js";
12
- function connect_db(config) {
59
+ async function connect_db(config) {
13
60
  const filepath = conventions.db_path(config);
14
- const db = new sqlite.DatabaseSync(filepath);
15
- db.exec("PRAGMA journal_mode = WAL");
16
- db.exec("PRAGMA synchronous = off");
17
- db.exec("PRAGMA cache_size = 10000");
18
- db.exec("PRAGMA temp_store = memory");
19
- db.exec("PRAGMA busy_timeout = 5000");
20
- db.exec("PRAGMA foreign_key = ON");
21
- db.exec("PRAGMA defer_foreign_keys = ON");
61
+ const db = await openDb(filepath);
22
62
  db.exec(create_schema);
63
+ db.flush();
23
64
  return [db, filepath];
24
65
  }
25
66
  async function init_db(config, preserve) {
@@ -38,7 +79,7 @@ async function init_db(config, preserve) {
38
79
  } catch (_e) {
39
80
  }
40
81
  }
41
- return [connect_db(config)[0], db_file];
82
+ return connect_db(config);
42
83
  }
43
84
  function plugin_db_key(name) {
44
85
  if (!name.startsWith("./") && !name.startsWith("../") && !path.isAbsolute(name)) {
@@ -47,6 +88,7 @@ function plugin_db_key(name) {
47
88
  return name.replace(/\./g, "_").replace(/\//g, "__");
48
89
  }
49
90
  async function codegen_setup(config, mode, db, db_file) {
91
+ let _db = db;
50
92
  const logger = new Logger(config.config_file.logLevel ?? LogLevel.Summary);
51
93
  await fs.mkdirpSync(conventions.houdini_root(config));
52
94
  const rawTransport = config.config_file.pluginTransport ?? "websocket";
@@ -57,47 +99,51 @@ async function codegen_setup(config, mode, db, db_file) {
57
99
  const spec_results = {};
58
100
  const stdioStdin = /* @__PURE__ */ new Map();
59
101
  const triggerHookRef = { fn: null };
60
- const wait_for_plugin_db = (configKey, dbKey) => new Promise((resolve, reject) => {
61
- const find_plugin = db.prepare("SELECT * FROM plugins WHERE name = ?");
62
- const interval = setInterval(() => {
63
- const row = find_plugin.get(dbKey);
64
- if (row) {
65
- clearInterval(interval);
66
- db.prepare("UPDATE plugins set config = ? where name = ?").run(
67
- JSON.stringify(
68
- config.plugins.find((p) => p.name === configKey)?.config ?? {}
69
- ),
70
- dbKey
71
- );
72
- const spec = {
73
- name: row.name,
74
- port: row.port,
75
- hooks: new Set(JSON.parse(row.hooks)),
76
- order: row.plugin_order,
77
- directory: config.plugins.find((p) => p.name === configKey)?.directory || ""
78
- };
79
- spec_results[configKey] = spec;
80
- if (row.config_module) {
81
- import(row.config_module).then((module) => {
82
- if (module && typeof module.default === "function") {
83
- config.config_file = module.default(config.config_file);
84
- }
85
- resolver(spec);
86
- });
87
- } else {
88
- resolver(spec);
102
+ const pendingRequests = /* @__PURE__ */ new Map();
103
+ const wait_for_plugin_db = async (configKey, dbKey) => {
104
+ const pollDb = await openDb(db_file);
105
+ return new Promise((resolve, reject) => {
106
+ const interval = setInterval(() => {
107
+ pollDb.reload();
108
+ const row = pollDb.get("SELECT * FROM plugins WHERE name = ?", [dbKey]);
109
+ if (row) {
110
+ clearInterval(interval);
111
+ clearTimeout(timeout);
112
+ pollDb.run("UPDATE plugins SET config = ? WHERE name = ?", [
113
+ JSON.stringify(
114
+ config.plugins.find((p) => p.name === configKey)?.config ?? {}
115
+ ),
116
+ dbKey
117
+ ]);
118
+ pollDb.flush();
119
+ pollDb.close();
120
+ const spec = {
121
+ name: row.name,
122
+ port: row.port,
123
+ hooks: new Set(JSON.parse(row.hooks)),
124
+ order: row.plugin_order,
125
+ directory: config.plugins.find((p) => p.name === configKey)?.directory || ""
126
+ };
127
+ spec_results[configKey] = spec;
128
+ if (row.config_module) {
129
+ import(row.config_module).then((module) => {
130
+ if (module && typeof module.default === "function") {
131
+ config.config_file = module.default(config.config_file);
132
+ }
133
+ resolve(spec);
134
+ });
135
+ } else {
136
+ resolve(spec);
137
+ }
89
138
  }
90
- }
91
- }, 10);
92
- const timeout = setTimeout(() => {
93
- clearInterval(interval);
94
- reject(new Error(`Timeout waiting for plugin ${configKey} to register`));
95
- }, 1e4);
96
- const resolver = (spec) => {
97
- clearTimeout(timeout);
98
- resolve(spec);
99
- };
100
- });
139
+ }, 10);
140
+ const timeout = setTimeout(() => {
141
+ clearInterval(interval);
142
+ pollDb.close();
143
+ reject(new Error(`Timeout waiting for plugin ${configKey} to register`));
144
+ }, 1e4);
145
+ });
146
+ };
101
147
  const wait_for_plugin_stdio = (name, child) => new Promise((resolve, reject) => {
102
148
  const timeout = setTimeout(() => {
103
149
  reject(new Error(`Timeout waiting for plugin ${name} to register`));
@@ -123,24 +169,26 @@ async function codegen_setup(config, mode, db, db_file) {
123
169
  directory: config.plugins.find((p) => p.name === name)?.directory || ""
124
170
  };
125
171
  spec_results[name] = spec;
126
- db.prepare(
172
+ _db.run(
127
173
  `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
174
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
175
+ [
176
+ spec.name,
177
+ JSON.stringify([...spec.hooks]),
178
+ spec.port,
179
+ spec.order,
180
+ msg.includeRuntime ?? null,
181
+ msg.configModule ?? null,
182
+ msg.clientPlugins ?? null
183
+ ]
137
184
  );
138
- db.prepare("UPDATE plugins SET config = ? WHERE name = ?").run(
185
+ _db.run("UPDATE plugins SET config = ? WHERE name = ?", [
139
186
  JSON.stringify(
140
187
  config.plugins.find((p) => p.name === name)?.config ?? {}
141
188
  ),
142
189
  spec.name
143
- );
190
+ ]);
191
+ _db.flush();
144
192
  if (msg.configModule) {
145
193
  import(msg.configModule).then((module) => {
146
194
  if (module && typeof module.default === "function") {
@@ -161,8 +209,7 @@ async function codegen_setup(config, mode, db, db_file) {
161
209
  }
162
210
  if (msg.type === "response") {
163
211
  const pending = pendingRequests.get(msg.id);
164
- if (!pending)
165
- return;
212
+ if (!pending) return;
166
213
  clearTimeout(pending.timeout);
167
214
  pendingRequests.delete(msg.id);
168
215
  if (msg.error) {
@@ -214,8 +261,7 @@ async function codegen_setup(config, mode, db, db_file) {
214
261
  reject(new Error(`Plugin ${name} stdout closed before registering`));
215
262
  } else {
216
263
  for (const [id, pending] of pendingRequests.entries()) {
217
- if (pending.plugin !== name)
218
- continue;
264
+ if (pending.plugin !== name) continue;
219
265
  clearTimeout(pending.timeout);
220
266
  pendingRequests.delete(id);
221
267
  pending.reject(new Error(`Plugin ${name} closed unexpectedly`));
@@ -223,7 +269,8 @@ async function codegen_setup(config, mode, db, db_file) {
223
269
  }
224
270
  });
225
271
  });
226
- db.prepare("DELETE FROM plugins").run();
272
+ _db.run("DELETE FROM plugins");
273
+ _db.flush();
227
274
  logger.time("Start Plugins");
228
275
  await Promise.all(
229
276
  config.plugins.map(async (plugin) => {
@@ -233,18 +280,24 @@ async function codegen_setup(config, mode, db, db_file) {
233
280
  if (jsExtensions.includes(path.extname(plugin.executable))) {
234
281
  executable = "node";
235
282
  args.unshift(plugin.executable);
283
+ } else if (path.extname(plugin.executable) === ".wasm") {
284
+ executable = "node";
285
+ args.unshift(wasiRunnerPath, plugin.executable);
236
286
  }
237
287
  const dbKey = plugin_db_key(plugin.name);
238
288
  args.push("--plugin-key", dbKey);
239
- if (useStdio) {
289
+ const pluginUsesStdio = useStdio || path.extname(plugin.executable) === ".wasm";
290
+ if (pluginUsesStdio) {
240
291
  args.push("--transport", "stdio");
241
292
  }
242
293
  logger.time(`Spawn ${plugin.name}`);
243
294
  const child = spawn(executable, args, {
244
- stdio: useStdio ? ["pipe", "pipe", "inherit"] : ["inherit", "inherit", "inherit"],
295
+ // [stdin, stdout, stderr]: stdio plugins need piped stdin/stdout for the
296
+ // message protocol; stderr is always inherited so plugin logs reach the terminal.
297
+ stdio: pluginUsesStdio ? ["pipe", "pipe", "inherit"] : ["inherit", "inherit", "inherit"],
245
298
  detached: process.platform !== "win32"
246
299
  });
247
- if (useStdio) {
300
+ if (pluginUsesStdio) {
248
301
  stdioStdin.set(dbKey, child.stdin);
249
302
  child.stdin.on("error", (err) => {
250
303
  if (err.code !== "EPIPE") {
@@ -254,7 +307,7 @@ async function codegen_setup(config, mode, db, db_file) {
254
307
  }
255
308
  plugins[plugin.name] = {
256
309
  process: child,
257
- ...await (useStdio ? wait_for_plugin_stdio(plugin.name, child) : wait_for_plugin_db(plugin.name, dbKey))
310
+ ...await (pluginUsesStdio ? wait_for_plugin_stdio(plugin.name, child) : wait_for_plugin_db(plugin.name, dbKey))
258
311
  };
259
312
  logger.timeEnd(`Spawn ${plugin.name}`, LogLevel.Verbose);
260
313
  })
@@ -265,7 +318,6 @@ async function codegen_setup(config, mode, db, db_file) {
265
318
  logger.timeEnd("Start Plugins", LogLevel.Summary);
266
319
  const wsConnections = /* @__PURE__ */ new Map();
267
320
  let messageCounter = 0;
268
- const pendingRequests = /* @__PURE__ */ new Map();
269
321
  async function getOrCreateWS(name, port) {
270
322
  const existing = wsConnections.get(name);
271
323
  if (existing && existing.readyState === WebSocket.OPEN) {
@@ -282,8 +334,7 @@ async function codegen_setup(config, mode, db, db_file) {
282
334
  try {
283
335
  const response = JSON.parse(data.toString());
284
336
  const pending = pendingRequests.get(response.id);
285
- if (!pending)
286
- return;
337
+ if (!pending) return;
287
338
  clearTimeout(pending.timeout);
288
339
  pendingRequests.delete(response.id);
289
340
  switch (response.type) {
@@ -361,7 +412,8 @@ async function codegen_setup(config, mode, db, db_file) {
361
412
  });
362
413
  }
363
414
  };
364
- const trigger_hook = async (hook, {
415
+ _db.reload();
416
+ const _fireHook = async (hook, {
365
417
  parallel_safe,
366
418
  payload,
367
419
  task_id
@@ -387,8 +439,15 @@ async function codegen_setup(config, mode, db, db_file) {
387
439
  }
388
440
  return result;
389
441
  };
442
+ const trigger_hook = async (hook, opts) => {
443
+ _db.flush();
444
+ const result = await _fireHook(hook, opts);
445
+ _db.reload();
446
+ return result;
447
+ };
390
448
  triggerHookRef.fn = trigger_hook;
391
- await write_config(db, config, invoke_hook, plugin_specs, mode, logger);
449
+ await write_config(_db, config, invoke_hook, plugin_specs, mode, logger);
450
+ _db.flush();
392
451
  await trigger_hook("Config");
393
452
  await trigger_hook("AfterLoad");
394
453
  await trigger_hook("Schema");
@@ -214,7 +214,7 @@ export declare class Config {
214
214
  });
215
215
  schema_path(): string;
216
216
  get localApiDir(): string;
217
- api_url(): Promise<string>;
217
+ api_url(): Promise<string | undefined>;
218
218
  get include(): Array<string>;
219
219
  includeFile(filepath: string, { root }?: {
220
220
  root?: string;
@@ -225,7 +225,7 @@ export declare class Config {
225
225
  root?: string;
226
226
  }): boolean;
227
227
  schema_pull_headers(): Promise<any>;
228
- process_env_values(env: Record<string, string | undefined>, value: string | ((env: any) => string)): string;
228
+ process_env_values(env: Record<string, string | undefined>, value: string | ((env: any) => string)): string | undefined;
229
229
  get artifact_dir(): string;
230
230
  get routes_dir(): string;
231
231
  artifactPath(document: graphql.DocumentNode): string;