@executor-js/config 0.0.2 → 0.1.0

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/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
- export { ExecutorFileConfig, SourceConfig, OpenApiSourceConfig, GraphqlSourceConfig, McpRemoteSourceConfig, McpStdioSourceConfig, McpAuthConfig, SecretMetadata, ConfigHeaderValue, SECRET_REF_PREFIX, } from "./schema";
1
+ export { ExecutorFileConfig, PluginConfig, SourceConfig, OpenApiSourceConfig, GraphqlSourceConfig, McpRemoteSourceConfig, McpStdioSourceConfig, McpAuthConfig, SecretMetadata, ConfigHeaderValue, SECRET_REF_PREFIX, } from "./schema";
2
2
  export { loadConfig, ConfigParseError } from "./load";
3
+ export { loadPluginsFromJsonc } from "./load-plugins";
4
+ export type { LoadPluginsFromJsoncOptions } from "./load-plugins";
3
5
  export { addSourceToConfig, removeSourceFromConfig, writeConfig, addSecretToConfig, removeSecretFromConfig, } from "./write";
4
6
  export type { ConfigFileSink, ConfigFileSinkOptions } from "./sink";
5
7
  export { makeFileConfigSink, headerToConfigValue, headersToConfigValues, } from "./sink";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,YAAY,EACZ,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,EACrB,oBAAoB,EACpB,aAAa,EACb,cAAc,EACd,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,UAAU,CAAC;AAElB,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AAEtD,OAAO,EACL,iBAAiB,EACjB,sBAAsB,EACtB,WAAW,EACX,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,SAAS,CAAC;AAEjB,YAAY,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,QAAQ,CAAC;AACpE,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,QAAQ,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,YAAY,EACZ,YAAY,EACZ,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,EACrB,oBAAoB,EACpB,aAAa,EACb,cAAc,EACd,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,UAAU,CAAC;AAElB,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACtD,YAAY,EAAE,2BAA2B,EAAE,MAAM,gBAAgB,CAAC;AAElE,OAAO,EACL,iBAAiB,EACjB,sBAAsB,EACtB,WAAW,EACX,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,SAAS,CAAC;AAEjB,YAAY,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,QAAQ,CAAC;AACpE,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,QAAQ,CAAC"}
package/dist/index.js CHANGED
@@ -72,9 +72,14 @@ var SecretMetadata = Schema.Struct({
72
72
  provider: Schema.optional(Schema.String),
73
73
  purpose: Schema.optional(Schema.String)
74
74
  });
75
+ var PluginConfig = Schema.Struct({
76
+ package: Schema.String,
77
+ options: Schema.optional(Schema.Record(Schema.String, Schema.Unknown))
78
+ });
75
79
  var ExecutorFileConfig = Schema.Struct({
76
80
  $schema: Schema.optional(Schema.String),
77
81
  name: Schema.optional(Schema.String),
82
+ plugins: Schema.optional(Schema.Array(PluginConfig)),
78
83
  sources: Schema.optional(Schema.Array(SourceConfig)),
79
84
  secrets: Schema.optional(Schema.Record(Schema.String, SecretMetadata))
80
85
  });
@@ -93,10 +98,10 @@ var ConfigParseError = class {
93
98
  _tag = "ConfigParseError";
94
99
  };
95
100
  var loadConfig = (path) => Effect.gen(function* () {
96
- const fs = yield* FileSystem.FileSystem;
97
- const exists = yield* fs.exists(path);
101
+ const fs2 = yield* FileSystem.FileSystem;
102
+ const exists = yield* fs2.exists(path);
98
103
  if (!exists) return null;
99
- const raw = yield* fs.readFileString(path);
104
+ const raw = yield* fs2.readFileString(path);
100
105
  const errors = [];
101
106
  const parsed = jsonc.parse(raw, errors);
102
107
  if (errors.length > 0) {
@@ -109,10 +114,61 @@ var loadConfig = (path) => Effect.gen(function* () {
109
114
  return decoded;
110
115
  });
111
116
 
117
+ // src/load-plugins.ts
118
+ import { createRequire } from "module";
119
+ import { dirname, isAbsolute, resolve as resolvePath } from "path";
120
+ import { pathToFileURL } from "url";
121
+ import * as fs from "fs";
122
+ import * as jsonc2 from "jsonc-parser";
123
+ var loadPluginsFromJsonc = async (options) => {
124
+ const { path, deps } = options;
125
+ if (!fs.existsSync(path)) return null;
126
+ const raw = fs.readFileSync(path, "utf8");
127
+ const errors = [];
128
+ const parsed = jsonc2.parse(raw, errors);
129
+ if (errors.length > 0) {
130
+ const msg = errors.map((e) => `offset ${e.offset}: ${jsonc2.printParseErrorCode(e.error)}`).join("; ");
131
+ throw new Error(`[load-plugins] failed to parse ${path}: ${msg}`);
132
+ }
133
+ const entries = parsed?.plugins ?? null;
134
+ if (!entries || entries.length === 0) return null;
135
+ const { createJiti } = await import("jiti");
136
+ const jiti = createJiti(pathToFileURL(path).href, {
137
+ interopDefault: true,
138
+ moduleCache: false
139
+ });
140
+ const fromDir = dirname(path);
141
+ const require2 = createRequire(
142
+ isAbsolute(path) ? path : resolvePath(fromDir, "_anchor.js")
143
+ );
144
+ const loaded = [];
145
+ for (const entry of entries) {
146
+ const serverEntry = `${entry.package}/server`;
147
+ let resolved;
148
+ try {
149
+ resolved = require2.resolve(serverEntry);
150
+ } catch {
151
+ throw new Error(
152
+ `[load-plugins] cannot resolve "${serverEntry}" from ${fromDir}. Is "${entry.package}" installed and does it export "./server"?`
153
+ );
154
+ }
155
+ const mod = await jiti.import(resolved);
156
+ const factory = typeof mod === "function" ? mod : mod.default ?? null;
157
+ if (!factory || typeof factory !== "function") {
158
+ throw new Error(
159
+ `[load-plugins] "${serverEntry}" did not export a default definePlugin(...) factory.`
160
+ );
161
+ }
162
+ const merged = { ...deps ?? {}, ...entry.options ?? {} };
163
+ loaded.push(factory(merged));
164
+ }
165
+ return loaded;
166
+ };
167
+
112
168
  // src/write.ts
113
169
  import { Effect as Effect2 } from "effect";
114
170
  import { FileSystem as FileSystem2 } from "effect";
115
- import * as jsonc2 from "jsonc-parser";
171
+ import * as jsonc3 from "jsonc-parser";
116
172
  var FORMATTING = {
117
173
  tabSize: 2,
118
174
  insertSpaces: true,
@@ -122,94 +178,94 @@ var DEFAULT_CONFIG = `{
122
178
  "sources": []
123
179
  }
124
180
  `;
125
- var readOrCreate = (fs, path) => Effect2.gen(function* () {
126
- const exists = yield* fs.exists(path);
127
- if (exists) return yield* fs.readFileString(path);
128
- yield* fs.writeFileString(path, DEFAULT_CONFIG);
181
+ var readOrCreate = (fs2, path) => Effect2.gen(function* () {
182
+ const exists = yield* fs2.exists(path);
183
+ if (exists) return yield* fs2.readFileString(path);
184
+ yield* fs2.writeFileString(path, DEFAULT_CONFIG);
129
185
  return DEFAULT_CONFIG;
130
186
  });
131
187
  var addSourceToConfig = (path, source) => Effect2.gen(function* () {
132
- const fs = yield* FileSystem2.FileSystem;
133
- let text = yield* readOrCreate(fs, path);
134
- let tree = jsonc2.parseTree(text);
135
- let sourcesNode = tree ? jsonc2.findNodeAtLocation(tree, ["sources"]) : void 0;
188
+ const fs2 = yield* FileSystem2.FileSystem;
189
+ let text = yield* readOrCreate(fs2, path);
190
+ let tree = jsonc3.parseTree(text);
191
+ let sourcesNode = tree ? jsonc3.findNodeAtLocation(tree, ["sources"]) : void 0;
136
192
  if (!sourcesNode) {
137
- const edits = jsonc2.modify(text, ["sources"], [source], {
193
+ const edits = jsonc3.modify(text, ["sources"], [source], {
138
194
  formattingOptions: FORMATTING
139
195
  });
140
- text = jsonc2.applyEdits(text, edits);
196
+ text = jsonc3.applyEdits(text, edits);
141
197
  } else {
142
198
  const ns = "namespace" in source ? source.namespace : void 0;
143
199
  if (ns && sourcesNode.children) {
144
200
  for (let i = sourcesNode.children.length - 1; i >= 0; i--) {
145
201
  const child = sourcesNode.children[i];
146
- const nsNode = jsonc2.findNodeAtLocation(child, ["namespace"]);
147
- if (nsNode && jsonc2.getNodeValue(nsNode) === ns) {
148
- const edits2 = jsonc2.modify(text, ["sources", i], void 0, {
202
+ const nsNode = jsonc3.findNodeAtLocation(child, ["namespace"]);
203
+ if (nsNode && jsonc3.getNodeValue(nsNode) === ns) {
204
+ const edits2 = jsonc3.modify(text, ["sources", i], void 0, {
149
205
  formattingOptions: FORMATTING
150
206
  });
151
- text = jsonc2.applyEdits(text, edits2);
207
+ text = jsonc3.applyEdits(text, edits2);
152
208
  }
153
209
  }
154
- tree = jsonc2.parseTree(text);
155
- sourcesNode = tree ? jsonc2.findNodeAtLocation(tree, ["sources"]) : void 0;
210
+ tree = jsonc3.parseTree(text);
211
+ sourcesNode = tree ? jsonc3.findNodeAtLocation(tree, ["sources"]) : void 0;
156
212
  }
157
213
  const count = sourcesNode?.children?.length ?? 0;
158
- const edits = jsonc2.modify(text, ["sources", count], source, {
214
+ const edits = jsonc3.modify(text, ["sources", count], source, {
159
215
  formattingOptions: FORMATTING
160
216
  });
161
- text = jsonc2.applyEdits(text, edits);
217
+ text = jsonc3.applyEdits(text, edits);
162
218
  }
163
- yield* fs.writeFileString(path, text);
219
+ yield* fs2.writeFileString(path, text);
164
220
  });
165
221
  var removeSourceFromConfig = (path, namespace) => Effect2.gen(function* () {
166
- const fs = yield* FileSystem2.FileSystem;
167
- const exists = yield* fs.exists(path);
222
+ const fs2 = yield* FileSystem2.FileSystem;
223
+ const exists = yield* fs2.exists(path);
168
224
  if (!exists) return;
169
- let text = yield* fs.readFileString(path);
170
- const tree = jsonc2.parseTree(text);
225
+ let text = yield* fs2.readFileString(path);
226
+ const tree = jsonc3.parseTree(text);
171
227
  if (!tree) return;
172
- const sourcesNode = jsonc2.findNodeAtLocation(tree, ["sources"]);
228
+ const sourcesNode = jsonc3.findNodeAtLocation(tree, ["sources"]);
173
229
  if (!sourcesNode?.children) return;
174
230
  for (let i = sourcesNode.children.length - 1; i >= 0; i--) {
175
231
  const child = sourcesNode.children[i];
176
- const nsNode = jsonc2.findNodeAtLocation(child, ["namespace"]);
177
- if (nsNode && jsonc2.getNodeValue(nsNode) === namespace) {
178
- const edits = jsonc2.modify(text, ["sources", i], void 0, {
232
+ const nsNode = jsonc3.findNodeAtLocation(child, ["namespace"]);
233
+ if (nsNode && jsonc3.getNodeValue(nsNode) === namespace) {
234
+ const edits = jsonc3.modify(text, ["sources", i], void 0, {
179
235
  formattingOptions: FORMATTING
180
236
  });
181
- text = jsonc2.applyEdits(text, edits);
237
+ text = jsonc3.applyEdits(text, edits);
182
238
  }
183
239
  }
184
- yield* fs.writeFileString(path, text);
240
+ yield* fs2.writeFileString(path, text);
185
241
  });
186
242
  var writeConfig = (path, config) => Effect2.gen(function* () {
187
- const fs = yield* FileSystem2.FileSystem;
243
+ const fs2 = yield* FileSystem2.FileSystem;
188
244
  const text = yield* Effect2.try({
189
245
  try: () => JSON.stringify(config, null, 2) + "\n",
190
246
  catch: (cause) => cause
191
247
  }).pipe(Effect2.orDie);
192
- yield* fs.writeFileString(path, text);
248
+ yield* fs2.writeFileString(path, text);
193
249
  });
194
250
  var addSecretToConfig = (path, secretId, metadata) => Effect2.gen(function* () {
195
- const fs = yield* FileSystem2.FileSystem;
196
- let text = yield* readOrCreate(fs, path);
197
- const edits = jsonc2.modify(text, ["secrets", secretId], metadata, {
251
+ const fs2 = yield* FileSystem2.FileSystem;
252
+ let text = yield* readOrCreate(fs2, path);
253
+ const edits = jsonc3.modify(text, ["secrets", secretId], metadata, {
198
254
  formattingOptions: FORMATTING
199
255
  });
200
- text = jsonc2.applyEdits(text, edits);
201
- yield* fs.writeFileString(path, text);
256
+ text = jsonc3.applyEdits(text, edits);
257
+ yield* fs2.writeFileString(path, text);
202
258
  });
203
259
  var removeSecretFromConfig = (path, secretId) => Effect2.gen(function* () {
204
- const fs = yield* FileSystem2.FileSystem;
205
- const exists = yield* fs.exists(path);
260
+ const fs2 = yield* FileSystem2.FileSystem;
261
+ const exists = yield* fs2.exists(path);
206
262
  if (!exists) return;
207
- let text = yield* fs.readFileString(path);
208
- const edits = jsonc2.modify(text, ["secrets", secretId], void 0, {
263
+ let text = yield* fs2.readFileString(path);
264
+ const edits = jsonc3.modify(text, ["secrets", secretId], void 0, {
209
265
  formattingOptions: FORMATTING
210
266
  });
211
- text = jsonc2.applyEdits(text, edits);
212
- yield* fs.writeFileString(path, text);
267
+ text = jsonc3.applyEdits(text, edits);
268
+ yield* fs2.writeFileString(path, text);
213
269
  });
214
270
 
215
271
  // src/sink.ts
@@ -251,6 +307,7 @@ export {
251
307
  McpRemoteSourceConfig,
252
308
  McpStdioSourceConfig,
253
309
  OpenApiSourceConfig,
310
+ PluginConfig,
254
311
  SECRET_REF_PREFIX,
255
312
  SecretMetadata,
256
313
  SourceConfig,
@@ -259,6 +316,7 @@ export {
259
316
  headerToConfigValue,
260
317
  headersToConfigValues,
261
318
  loadConfig,
319
+ loadPluginsFromJsonc,
262
320
  makeFileConfigSink,
263
321
  removeSecretFromConfig,
264
322
  removeSourceFromConfig,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/schema.ts","../src/load.ts","../src/write.ts","../src/sink.ts"],"sourcesContent":["import { Schema } from \"effect\";\n\n// ---------------------------------------------------------------------------\n// Header values\n//\n// Three forms:\n// \"static-value\" — literal string\n// \"secret-public-ref:my-token\" — secret reference (no prefix)\n// { value: \"secret-public-ref:x\", prefix } — secret reference with prefix\n// ---------------------------------------------------------------------------\n\nexport const SECRET_REF_PREFIX = \"secret-public-ref:\";\n\nexport const ConfigHeaderValue = Schema.Union([\n Schema.String,\n Schema.Struct({\n value: Schema.String,\n prefix: Schema.optional(Schema.String),\n }),\n]);\nexport type ConfigHeaderValue = typeof ConfigHeaderValue.Type;\n\nconst ConfigHeaders = Schema.Record(Schema.String, ConfigHeaderValue);\n\n// ---------------------------------------------------------------------------\n// Source configs — discriminated union on \"kind\"\n// ---------------------------------------------------------------------------\n\nexport const OpenApiSourceConfig = Schema.Struct({\n kind: Schema.Literal(\"openapi\"),\n spec: Schema.String,\n baseUrl: Schema.optional(Schema.String),\n namespace: Schema.optional(Schema.String),\n headers: Schema.optional(ConfigHeaders),\n});\nexport type OpenApiSourceConfig = typeof OpenApiSourceConfig.Type;\n\nexport const GraphqlSourceConfig = Schema.Struct({\n kind: Schema.Literal(\"graphql\"),\n endpoint: Schema.String,\n introspectionJson: Schema.optional(Schema.NullOr(Schema.String)),\n namespace: Schema.optional(Schema.String),\n headers: Schema.optional(ConfigHeaders),\n});\nexport type GraphqlSourceConfig = typeof GraphqlSourceConfig.Type;\n\nconst StringMap = Schema.Record(Schema.String, Schema.String);\n\nexport const McpAuthConfig = Schema.Union([\n Schema.Struct({ kind: Schema.Literal(\"none\") }),\n Schema.Struct({\n kind: Schema.Literal(\"header\"),\n headerName: Schema.String,\n secret: Schema.String,\n prefix: Schema.optional(Schema.String),\n }),\n Schema.Struct({\n kind: Schema.Literal(\"oauth2\"),\n /** Stable id of the SDK Connection holding access + refresh token\n * material. Scope shadowing means the same id resolves per-user\n * via the executor's innermost-wins lookup. */\n connectionId: Schema.String,\n }),\n]);\nexport type McpAuthConfig = typeof McpAuthConfig.Type;\n\nexport const McpRemoteSourceConfig = Schema.Struct({\n kind: Schema.Literal(\"mcp\"),\n transport: Schema.Literal(\"remote\"),\n name: Schema.String,\n endpoint: Schema.String,\n remoteTransport: Schema.optional(Schema.Literals([\"streamable-http\", \"sse\", \"auto\"])),\n namespace: Schema.optional(Schema.String),\n queryParams: Schema.optional(StringMap),\n headers: Schema.optional(StringMap),\n auth: Schema.optional(McpAuthConfig),\n});\nexport type McpRemoteSourceConfig = typeof McpRemoteSourceConfig.Type;\n\nexport const McpStdioSourceConfig = Schema.Struct({\n kind: Schema.Literal(\"mcp\"),\n transport: Schema.Literal(\"stdio\"),\n name: Schema.String,\n command: Schema.String,\n args: Schema.optional(Schema.Array(Schema.String)),\n env: Schema.optional(StringMap),\n cwd: Schema.optional(Schema.String),\n namespace: Schema.optional(Schema.String),\n});\nexport type McpStdioSourceConfig = typeof McpStdioSourceConfig.Type;\n\nexport const SourceConfig = Schema.Union([\n OpenApiSourceConfig,\n GraphqlSourceConfig,\n McpRemoteSourceConfig,\n McpStdioSourceConfig,\n]);\nexport type SourceConfig = typeof SourceConfig.Type;\n\n// ---------------------------------------------------------------------------\n// Secret metadata\n// ---------------------------------------------------------------------------\n\nexport const SecretMetadata = Schema.Struct({\n name: Schema.String,\n provider: Schema.optional(Schema.String),\n purpose: Schema.optional(Schema.String),\n});\nexport type SecretMetadata = typeof SecretMetadata.Type;\n\n// ---------------------------------------------------------------------------\n// Top-level config\n// ---------------------------------------------------------------------------\n\nexport const ExecutorFileConfig = Schema.Struct({\n $schema: Schema.optional(Schema.String),\n name: Schema.optional(Schema.String),\n sources: Schema.optional(Schema.Array(SourceConfig)),\n secrets: Schema.optional(Schema.Record(Schema.String, SecretMetadata)),\n});\nexport type ExecutorFileConfig = typeof ExecutorFileConfig.Type;\n","import { Effect, Schema } from \"effect\";\nimport { FileSystem } from \"effect\";\nimport type { PlatformError } from \"effect/PlatformError\";\nimport * as jsonc from \"jsonc-parser\";\nimport { ExecutorFileConfig } from \"./schema\";\n\nexport class ConfigParseError {\n readonly _tag = \"ConfigParseError\";\n constructor(\n readonly path: string,\n readonly message: string,\n ) {}\n}\n\n/**\n * Load and validate an executor config file.\n * Returns null if the file doesn't exist.\n */\nexport const loadConfig = (\n path: string,\n): Effect.Effect<\n ExecutorFileConfig | null,\n ConfigParseError | PlatformError,\n FileSystem.FileSystem\n> =>\n Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n\n const exists = yield* fs.exists(path);\n if (!exists) return null;\n\n const raw = yield* fs.readFileString(path);\n\n const errors: jsonc.ParseError[] = [];\n const parsed = jsonc.parse(raw, errors);\n\n if (errors.length > 0) {\n const msg = errors\n .map((e) => `offset ${e.offset}: ${jsonc.printParseErrorCode(e.error)}`)\n .join(\"; \");\n return yield* Effect.fail(new ConfigParseError(path, msg));\n }\n\n const decoded = yield* Schema.decodeUnknownEffect(ExecutorFileConfig)(parsed).pipe(\n Effect.mapError((e) => new ConfigParseError(path, String(e))),\n );\n\n return decoded;\n });\n","import { Effect } from \"effect\";\nimport { FileSystem } from \"effect\";\nimport type { PlatformError } from \"effect/PlatformError\";\nimport * as jsonc from \"jsonc-parser\";\nimport type { SourceConfig, ExecutorFileConfig } from \"./schema\";\n\nconst FORMATTING: jsonc.FormattingOptions = {\n tabSize: 2,\n insertSpaces: true,\n eol: \"\\n\",\n};\n\nconst DEFAULT_CONFIG = `{\n \"sources\": []\n}\n`;\n\n/** Read the raw JSONC text from a config file, or create a default one. */\nconst readOrCreate = (\n fs: FileSystem.FileSystem,\n path: string,\n): Effect.Effect<string, PlatformError> =>\n Effect.gen(function* () {\n const exists = yield* fs.exists(path);\n if (exists) return yield* fs.readFileString(path);\n yield* fs.writeFileString(path, DEFAULT_CONFIG);\n return DEFAULT_CONFIG;\n });\n\n/**\n * Add a source entry to the config file. Creates the file if it doesn't exist.\n * Preserves existing comments and formatting.\n */\nexport const addSourceToConfig = (\n path: string,\n source: SourceConfig,\n): Effect.Effect<void, PlatformError, FileSystem.FileSystem> =>\n Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n let text = yield* readOrCreate(fs, path);\n\n // Ensure \"sources\" array exists\n let tree = jsonc.parseTree(text);\n let sourcesNode = tree ? jsonc.findNodeAtLocation(tree, [\"sources\"]) : undefined;\n\n if (!sourcesNode) {\n const edits = jsonc.modify(text, [\"sources\"], [source], {\n formattingOptions: FORMATTING,\n });\n text = jsonc.applyEdits(text, edits);\n } else {\n // Remove existing entry with same namespace (if any) to avoid duplicates\n const ns = \"namespace\" in source ? source.namespace : undefined;\n if (ns && sourcesNode.children) {\n for (let i = sourcesNode.children.length - 1; i >= 0; i--) {\n const child = sourcesNode.children[i]!;\n const nsNode = jsonc.findNodeAtLocation(child, [\"namespace\"]);\n if (nsNode && jsonc.getNodeValue(nsNode) === ns) {\n const edits = jsonc.modify(text, [\"sources\", i], undefined, {\n formattingOptions: FORMATTING,\n });\n text = jsonc.applyEdits(text, edits);\n }\n }\n // Re-parse after removals\n tree = jsonc.parseTree(text);\n sourcesNode = tree ? jsonc.findNodeAtLocation(tree, [\"sources\"]) : undefined;\n }\n\n const count = sourcesNode?.children?.length ?? 0;\n const edits = jsonc.modify(text, [\"sources\", count], source, {\n formattingOptions: FORMATTING,\n });\n text = jsonc.applyEdits(text, edits);\n }\n\n yield* fs.writeFileString(path, text);\n });\n\n/**\n * Remove a source from the config file by namespace.\n */\nexport const removeSourceFromConfig = (\n path: string,\n namespace: string,\n): Effect.Effect<void, PlatformError, FileSystem.FileSystem> =>\n Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n\n const exists = yield* fs.exists(path);\n if (!exists) return;\n\n let text = yield* fs.readFileString(path);\n const tree = jsonc.parseTree(text);\n if (!tree) return;\n\n const sourcesNode = jsonc.findNodeAtLocation(tree, [\"sources\"]);\n if (!sourcesNode?.children) return;\n\n // Walk backwards so indices stay valid after each removal\n for (let i = sourcesNode.children.length - 1; i >= 0; i--) {\n const child = sourcesNode.children[i]!;\n const nsNode = jsonc.findNodeAtLocation(child, [\"namespace\"]);\n if (nsNode && jsonc.getNodeValue(nsNode) === namespace) {\n const edits = jsonc.modify(text, [\"sources\", i], undefined, {\n formattingOptions: FORMATTING,\n });\n text = jsonc.applyEdits(text, edits);\n }\n }\n\n yield* fs.writeFileString(path, text);\n });\n\n/**\n * Write a full config object to a file.\n */\nexport const writeConfig = (\n path: string,\n config: ExecutorFileConfig,\n): Effect.Effect<void, PlatformError, FileSystem.FileSystem> =>\n Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n const text = yield* Effect.try({\n try: () => JSON.stringify(config, null, 2) + \"\\n\",\n catch: (cause) => cause,\n }).pipe(Effect.orDie);\n yield* fs.writeFileString(path, text);\n });\n\n/**\n * Add secret metadata to the config file.\n */\nexport const addSecretToConfig = (\n path: string,\n secretId: string,\n metadata: { name: string; provider?: string; purpose?: string },\n): Effect.Effect<void, PlatformError, FileSystem.FileSystem> =>\n Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n let text = yield* readOrCreate(fs, path);\n\n const edits = jsonc.modify(text, [\"secrets\", secretId], metadata, {\n formattingOptions: FORMATTING,\n });\n text = jsonc.applyEdits(text, edits);\n\n yield* fs.writeFileString(path, text);\n });\n\n/**\n * Remove secret metadata from the config file.\n */\nexport const removeSecretFromConfig = (\n path: string,\n secretId: string,\n): Effect.Effect<void, PlatformError, FileSystem.FileSystem> =>\n Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n\n const exists = yield* fs.exists(path);\n if (!exists) return;\n\n let text = yield* fs.readFileString(path);\n const edits = jsonc.modify(text, [\"secrets\", secretId], undefined, {\n formattingOptions: FORMATTING,\n });\n text = jsonc.applyEdits(text, edits);\n\n yield* fs.writeFileString(path, text);\n });\n","// ---------------------------------------------------------------------------\n// ConfigFileSink — best-effort write-back of source changes to executor.jsonc.\n//\n// Plugins (openapi, graphql, mcp) call `sink.upsertSource` after their DB\n// writes so the committable file stays in sync with runtime state. Errors\n// are logged and swallowed — a failed file write must never fail a DB\n// mutation, and the next successful mutation (or a boot-time sync) will\n// eventually reconcile.\n//\n// The FileSystem layer is injected so library code here doesn't pick a\n// platform binding. The host app provides NodeFileSystem (or BunFileSystem).\n// ---------------------------------------------------------------------------\n\nimport { Effect } from \"effect\";\nimport type { Layer } from \"effect\";\nimport type { FileSystem } from \"effect\";\n\nimport { SECRET_REF_PREFIX, type ConfigHeaderValue, type SourceConfig } from \"./schema\";\nimport { addSourceToConfig, removeSourceFromConfig } from \"./write\";\n\n// Translate a plugin-side header value (`{ secretId, prefix? }` for secret\n// refs) into the config file's `secret-public-ref:<id>` string form.\ntype PluginHeaderValue = string | { secretId: string; prefix?: string };\n\nexport const headerToConfigValue = (\n value: PluginHeaderValue,\n): ConfigHeaderValue => {\n if (typeof value === \"string\") return value;\n const ref = `${SECRET_REF_PREFIX}${value.secretId}`;\n return value.prefix ? { value: ref, prefix: value.prefix } : ref;\n};\n\nexport const headersToConfigValues = (\n headers: Record<string, PluginHeaderValue> | undefined,\n): Record<string, ConfigHeaderValue> | undefined => {\n if (!headers) return undefined;\n const out: Record<string, ConfigHeaderValue> = {};\n for (const [k, v] of Object.entries(headers)) out[k] = headerToConfigValue(v);\n return out;\n};\n\nexport interface ConfigFileSink {\n readonly upsertSource: (source: SourceConfig) => Effect.Effect<void>;\n readonly removeSource: (namespace: string) => Effect.Effect<void>;\n}\n\nexport interface ConfigFileSinkOptions {\n readonly path: string;\n readonly fsLayer: Layer.Layer<FileSystem.FileSystem>;\n /** Called when a file operation fails. Defaults to console.warn. */\n readonly onError?: (op: \"upsert\" | \"remove\", err: unknown) => void;\n}\n\nconst defaultOnError = (op: \"upsert\" | \"remove\", err: unknown): void => {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[config-sink] ${op} failed: ${msg}`);\n};\n\nexport const makeFileConfigSink = (\n options: ConfigFileSinkOptions,\n): ConfigFileSink => {\n const { path, fsLayer, onError = defaultOnError } = options;\n\n return {\n upsertSource: (source) =>\n addSourceToConfig(path, source).pipe(\n Effect.provide(fsLayer),\n Effect.catch((err: unknown) => Effect.sync(() => onError(\"upsert\", err))),\n ),\n\n removeSource: (namespace) =>\n removeSourceFromConfig(path, namespace).pipe(\n Effect.provide(fsLayer),\n Effect.catch((err: unknown) => Effect.sync(() => onError(\"remove\", err))),\n ),\n };\n};\n"],"mappings":"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","names":["Schema","Schema","Effect","FileSystem","jsonc","edits","Effect","Effect"]}
1
+ {"version":3,"sources":["../src/schema.ts","../src/load.ts","../src/load-plugins.ts","../src/write.ts","../src/sink.ts"],"sourcesContent":["import { Schema } from \"effect\";\n\n// ---------------------------------------------------------------------------\n// Header values\n//\n// Three forms:\n// \"static-value\" — literal string\n// \"secret-public-ref:my-token\" — secret reference (no prefix)\n// { value: \"secret-public-ref:x\", prefix } — secret reference with prefix\n// ---------------------------------------------------------------------------\n\nexport const SECRET_REF_PREFIX = \"secret-public-ref:\";\n\nexport const ConfigHeaderValue = Schema.Union([\n Schema.String,\n Schema.Struct({\n value: Schema.String,\n prefix: Schema.optional(Schema.String),\n }),\n]);\nexport type ConfigHeaderValue = typeof ConfigHeaderValue.Type;\n\nconst ConfigHeaders = Schema.Record(Schema.String, ConfigHeaderValue);\n\n// ---------------------------------------------------------------------------\n// Source configs — discriminated union on \"kind\"\n// ---------------------------------------------------------------------------\n\nexport const OpenApiSourceConfig = Schema.Struct({\n kind: Schema.Literal(\"openapi\"),\n spec: Schema.String,\n baseUrl: Schema.optional(Schema.String),\n namespace: Schema.optional(Schema.String),\n headers: Schema.optional(ConfigHeaders),\n});\nexport type OpenApiSourceConfig = typeof OpenApiSourceConfig.Type;\n\nexport const GraphqlSourceConfig = Schema.Struct({\n kind: Schema.Literal(\"graphql\"),\n endpoint: Schema.String,\n introspectionJson: Schema.optional(Schema.NullOr(Schema.String)),\n namespace: Schema.optional(Schema.String),\n headers: Schema.optional(ConfigHeaders),\n});\nexport type GraphqlSourceConfig = typeof GraphqlSourceConfig.Type;\n\nconst StringMap = Schema.Record(Schema.String, Schema.String);\n\nexport const McpAuthConfig = Schema.Union([\n Schema.Struct({ kind: Schema.Literal(\"none\") }),\n Schema.Struct({\n kind: Schema.Literal(\"header\"),\n headerName: Schema.String,\n secret: Schema.String,\n prefix: Schema.optional(Schema.String),\n }),\n Schema.Struct({\n kind: Schema.Literal(\"oauth2\"),\n /** Stable id of the SDK Connection holding access + refresh token\n * material. Scope shadowing means the same id resolves per-user\n * via the executor's innermost-wins lookup. */\n connectionId: Schema.String,\n }),\n]);\nexport type McpAuthConfig = typeof McpAuthConfig.Type;\n\nexport const McpRemoteSourceConfig = Schema.Struct({\n kind: Schema.Literal(\"mcp\"),\n transport: Schema.Literal(\"remote\"),\n name: Schema.String,\n endpoint: Schema.String,\n remoteTransport: Schema.optional(Schema.Literals([\"streamable-http\", \"sse\", \"auto\"])),\n namespace: Schema.optional(Schema.String),\n queryParams: Schema.optional(StringMap),\n headers: Schema.optional(StringMap),\n auth: Schema.optional(McpAuthConfig),\n});\nexport type McpRemoteSourceConfig = typeof McpRemoteSourceConfig.Type;\n\nexport const McpStdioSourceConfig = Schema.Struct({\n kind: Schema.Literal(\"mcp\"),\n transport: Schema.Literal(\"stdio\"),\n name: Schema.String,\n command: Schema.String,\n args: Schema.optional(Schema.Array(Schema.String)),\n env: Schema.optional(StringMap),\n cwd: Schema.optional(Schema.String),\n namespace: Schema.optional(Schema.String),\n});\nexport type McpStdioSourceConfig = typeof McpStdioSourceConfig.Type;\n\nexport const SourceConfig = Schema.Union([\n OpenApiSourceConfig,\n GraphqlSourceConfig,\n McpRemoteSourceConfig,\n McpStdioSourceConfig,\n]);\nexport type SourceConfig = typeof SourceConfig.Type;\n\n// ---------------------------------------------------------------------------\n// Secret metadata\n// ---------------------------------------------------------------------------\n\nexport const SecretMetadata = Schema.Struct({\n name: Schema.String,\n provider: Schema.optional(Schema.String),\n purpose: Schema.optional(Schema.String),\n});\nexport type SecretMetadata = typeof SecretMetadata.Type;\n\n// ---------------------------------------------------------------------------\n// Plugin manifest\n//\n// `plugins` is the install list. Each entry is a published npm package\n// that exports a `definePlugin(...)` factory under `./server`. The host\n// loads each at boot via jiti and calls the factory with merged\n// `options` plus host-injected deps (`configFile`, etc.). This is the\n// dynamic sibling of the static `executor.config.ts` plugin tuple — when\n// `plugins` is set, the host uses it; otherwise it falls back to the\n// statically-typed config.\n// ---------------------------------------------------------------------------\n\nexport const PluginConfig = Schema.Struct({\n package: Schema.String,\n options: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),\n});\nexport type PluginConfig = typeof PluginConfig.Type;\n\n// ---------------------------------------------------------------------------\n// Top-level config\n// ---------------------------------------------------------------------------\n\nexport const ExecutorFileConfig = Schema.Struct({\n $schema: Schema.optional(Schema.String),\n name: Schema.optional(Schema.String),\n plugins: Schema.optional(Schema.Array(PluginConfig)),\n sources: Schema.optional(Schema.Array(SourceConfig)),\n secrets: Schema.optional(Schema.Record(Schema.String, SecretMetadata)),\n});\nexport type ExecutorFileConfig = typeof ExecutorFileConfig.Type;\n","import { Effect, Schema } from \"effect\";\nimport { FileSystem } from \"effect\";\nimport type { PlatformError } from \"effect/PlatformError\";\nimport * as jsonc from \"jsonc-parser\";\nimport { ExecutorFileConfig } from \"./schema\";\n\nexport class ConfigParseError {\n readonly _tag = \"ConfigParseError\";\n constructor(\n readonly path: string,\n readonly message: string,\n ) {}\n}\n\n/**\n * Load and validate an executor config file.\n * Returns null if the file doesn't exist.\n */\nexport const loadConfig = (\n path: string,\n): Effect.Effect<\n ExecutorFileConfig | null,\n ConfigParseError | PlatformError,\n FileSystem.FileSystem\n> =>\n Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n\n const exists = yield* fs.exists(path);\n if (!exists) return null;\n\n const raw = yield* fs.readFileString(path);\n\n const errors: jsonc.ParseError[] = [];\n const parsed = jsonc.parse(raw, errors);\n\n if (errors.length > 0) {\n const msg = errors\n .map((e) => `offset ${e.offset}: ${jsonc.printParseErrorCode(e.error)}`)\n .join(\"; \");\n return yield* Effect.fail(new ConfigParseError(path, msg));\n }\n\n const decoded = yield* Schema.decodeUnknownEffect(ExecutorFileConfig)(parsed).pipe(\n Effect.mapError((e) => new ConfigParseError(path, String(e))),\n );\n\n return decoded;\n });\n","// ---------------------------------------------------------------------------\n// loadPluginsFromJsonc — runtime plugin loader.\n//\n// Reads `executor.jsonc#plugins`, dynamically imports each package's\n// `./server` entry via jiti (so workspace TS sources work in dev and\n// published `dist/*.js` works after install), and calls the exported\n// `definePlugin(...)` factory with merged `options` plus host-injected\n// deps. Returns the resulting `Plugin[]` ready to hand to\n// `composePluginApi` / `createExecutor`.\n//\n// jiti is used instead of bare `import()` because:\n// - workspace plugins under monorepo dev expose `.ts` source via the\n// `bun` export condition; Node's loader can't read those directly,\n// jiti transpiles on the fly.\n// - in a published environment the package's `default` condition\n// points at `dist/*.js`, which jiti loads as a normal ESM module.\n//\n// The convention is: every plugin package exports a `./server` subpath\n// whose default export is a `ConfiguredPlugin` (the result of\n// `definePlugin(...)`). Calling that with `{ ...options, ...deps }`\n// returns a concrete `Plugin`.\n// ---------------------------------------------------------------------------\n\nimport { createRequire } from \"node:module\";\nimport { dirname, isAbsolute, resolve as resolvePath } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport * as fs from \"node:fs\";\nimport * as jsonc from \"jsonc-parser\";\n\nimport type { AnyPlugin } from \"@executor-js/sdk\";\n\n// Plugins are invoked dynamically by name — exact author types are\n// unknown at the call site, so the loader treats every factory as\n// `(options?: unknown) => AnyPlugin`. The plugin author's types still\n// hold inside the plugin's own module; we just don't propagate them\n// across the runtime boundary.\ntype LooseConfiguredPlugin = (options?: Record<string, unknown>) => AnyPlugin;\n\nimport type { PluginConfig } from \"./schema\";\n\nexport interface LoadPluginsFromJsoncOptions {\n /** Absolute path to `executor.jsonc` (or compatible). */\n readonly path: string;\n /**\n * Host-injected deps merged into each plugin's options. Common keys:\n * `configFile` (the `ConfigFileSink`), env-derived credentials, etc.\n * Plugins ignore deps they don't accept — `definePlugin` strips\n * unknown keys before forwarding to the author factory.\n */\n readonly deps?: Readonly<Record<string, unknown>>;\n}\n\n/**\n * Returns the plugins listed in jsonc, or `null` if the file is missing\n * or has no `plugins` array. The host treats `null` as \"fall back to\n * the static `executor.config.ts` factory.\"\n */\nexport const loadPluginsFromJsonc = async (\n options: LoadPluginsFromJsoncOptions,\n): Promise<readonly AnyPlugin[] | null> => {\n const { path, deps } = options;\n if (!fs.existsSync(path)) return null;\n\n const raw = fs.readFileSync(path, \"utf8\");\n const errors: jsonc.ParseError[] = [];\n const parsed = jsonc.parse(raw, errors) as\n | { plugins?: readonly PluginConfig[] }\n | undefined;\n if (errors.length > 0) {\n const msg = errors\n .map((e) => `offset ${e.offset}: ${jsonc.printParseErrorCode(e.error)}`)\n .join(\"; \");\n throw new Error(`[load-plugins] failed to parse ${path}: ${msg}`);\n }\n\n const entries = parsed?.plugins ?? null;\n if (!entries || entries.length === 0) return null;\n\n // jiti is created once per call; `moduleCache: false` ensures a\n // restart picks up freshly-installed packages without process restart\n // (relevant when the dev server kicks a reload after `executor plugin\n // install`).\n const { createJiti } = await import(\"jiti\");\n const jiti = createJiti(pathToFileURL(path).href, {\n interopDefault: true,\n moduleCache: false,\n });\n\n const fromDir = dirname(path);\n // require.resolve is anchored to the jsonc's directory so plugin\n // packages resolve from the host app's `node_modules` regardless of\n // CWD.\n const require = createRequire(\n isAbsolute(path) ? path : resolvePath(fromDir, \"_anchor.js\"),\n );\n\n const loaded: AnyPlugin[] = [];\n for (const entry of entries) {\n const serverEntry = `${entry.package}/server`;\n let resolved: string;\n try {\n resolved = require.resolve(serverEntry);\n } catch {\n throw new Error(\n `[load-plugins] cannot resolve \"${serverEntry}\" from ${fromDir}. ` +\n `Is \"${entry.package}\" installed and does it export \"./server\"?`,\n );\n }\n const mod = (await jiti.import(resolved)) as\n | { default?: LooseConfiguredPlugin }\n | LooseConfiguredPlugin;\n const factory = (\n typeof mod === \"function\" ? mod : (mod.default ?? null)\n ) as LooseConfiguredPlugin | null;\n if (!factory || typeof factory !== \"function\") {\n throw new Error(\n `[load-plugins] \"${serverEntry}\" did not export a default ` +\n `definePlugin(...) factory.`,\n );\n }\n const merged = { ...(deps ?? {}), ...(entry.options ?? {}) };\n loaded.push(factory(merged));\n }\n\n return loaded;\n};\n","import { Effect } from \"effect\";\nimport { FileSystem } from \"effect\";\nimport type { PlatformError } from \"effect/PlatformError\";\nimport * as jsonc from \"jsonc-parser\";\nimport type { SourceConfig, ExecutorFileConfig } from \"./schema\";\n\nconst FORMATTING: jsonc.FormattingOptions = {\n tabSize: 2,\n insertSpaces: true,\n eol: \"\\n\",\n};\n\nconst DEFAULT_CONFIG = `{\n \"sources\": []\n}\n`;\n\n/** Read the raw JSONC text from a config file, or create a default one. */\nconst readOrCreate = (\n fs: FileSystem.FileSystem,\n path: string,\n): Effect.Effect<string, PlatformError> =>\n Effect.gen(function* () {\n const exists = yield* fs.exists(path);\n if (exists) return yield* fs.readFileString(path);\n yield* fs.writeFileString(path, DEFAULT_CONFIG);\n return DEFAULT_CONFIG;\n });\n\n/**\n * Add a source entry to the config file. Creates the file if it doesn't exist.\n * Preserves existing comments and formatting.\n */\nexport const addSourceToConfig = (\n path: string,\n source: SourceConfig,\n): Effect.Effect<void, PlatformError, FileSystem.FileSystem> =>\n Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n let text = yield* readOrCreate(fs, path);\n\n // Ensure \"sources\" array exists\n let tree = jsonc.parseTree(text);\n let sourcesNode = tree ? jsonc.findNodeAtLocation(tree, [\"sources\"]) : undefined;\n\n if (!sourcesNode) {\n const edits = jsonc.modify(text, [\"sources\"], [source], {\n formattingOptions: FORMATTING,\n });\n text = jsonc.applyEdits(text, edits);\n } else {\n // Remove existing entry with same namespace (if any) to avoid duplicates\n const ns = \"namespace\" in source ? source.namespace : undefined;\n if (ns && sourcesNode.children) {\n for (let i = sourcesNode.children.length - 1; i >= 0; i--) {\n const child = sourcesNode.children[i]!;\n const nsNode = jsonc.findNodeAtLocation(child, [\"namespace\"]);\n if (nsNode && jsonc.getNodeValue(nsNode) === ns) {\n const edits = jsonc.modify(text, [\"sources\", i], undefined, {\n formattingOptions: FORMATTING,\n });\n text = jsonc.applyEdits(text, edits);\n }\n }\n // Re-parse after removals\n tree = jsonc.parseTree(text);\n sourcesNode = tree ? jsonc.findNodeAtLocation(tree, [\"sources\"]) : undefined;\n }\n\n const count = sourcesNode?.children?.length ?? 0;\n const edits = jsonc.modify(text, [\"sources\", count], source, {\n formattingOptions: FORMATTING,\n });\n text = jsonc.applyEdits(text, edits);\n }\n\n yield* fs.writeFileString(path, text);\n });\n\n/**\n * Remove a source from the config file by namespace.\n */\nexport const removeSourceFromConfig = (\n path: string,\n namespace: string,\n): Effect.Effect<void, PlatformError, FileSystem.FileSystem> =>\n Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n\n const exists = yield* fs.exists(path);\n if (!exists) return;\n\n let text = yield* fs.readFileString(path);\n const tree = jsonc.parseTree(text);\n if (!tree) return;\n\n const sourcesNode = jsonc.findNodeAtLocation(tree, [\"sources\"]);\n if (!sourcesNode?.children) return;\n\n // Walk backwards so indices stay valid after each removal\n for (let i = sourcesNode.children.length - 1; i >= 0; i--) {\n const child = sourcesNode.children[i]!;\n const nsNode = jsonc.findNodeAtLocation(child, [\"namespace\"]);\n if (nsNode && jsonc.getNodeValue(nsNode) === namespace) {\n const edits = jsonc.modify(text, [\"sources\", i], undefined, {\n formattingOptions: FORMATTING,\n });\n text = jsonc.applyEdits(text, edits);\n }\n }\n\n yield* fs.writeFileString(path, text);\n });\n\n/**\n * Write a full config object to a file.\n */\nexport const writeConfig = (\n path: string,\n config: ExecutorFileConfig,\n): Effect.Effect<void, PlatformError, FileSystem.FileSystem> =>\n Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n const text = yield* Effect.try({\n try: () => JSON.stringify(config, null, 2) + \"\\n\",\n catch: (cause) => cause,\n }).pipe(Effect.orDie);\n yield* fs.writeFileString(path, text);\n });\n\n/**\n * Add secret metadata to the config file.\n */\nexport const addSecretToConfig = (\n path: string,\n secretId: string,\n metadata: { name: string; provider?: string; purpose?: string },\n): Effect.Effect<void, PlatformError, FileSystem.FileSystem> =>\n Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n let text = yield* readOrCreate(fs, path);\n\n const edits = jsonc.modify(text, [\"secrets\", secretId], metadata, {\n formattingOptions: FORMATTING,\n });\n text = jsonc.applyEdits(text, edits);\n\n yield* fs.writeFileString(path, text);\n });\n\n/**\n * Remove secret metadata from the config file.\n */\nexport const removeSecretFromConfig = (\n path: string,\n secretId: string,\n): Effect.Effect<void, PlatformError, FileSystem.FileSystem> =>\n Effect.gen(function* () {\n const fs = yield* FileSystem.FileSystem;\n\n const exists = yield* fs.exists(path);\n if (!exists) return;\n\n let text = yield* fs.readFileString(path);\n const edits = jsonc.modify(text, [\"secrets\", secretId], undefined, {\n formattingOptions: FORMATTING,\n });\n text = jsonc.applyEdits(text, edits);\n\n yield* fs.writeFileString(path, text);\n });\n","// ---------------------------------------------------------------------------\n// ConfigFileSink — best-effort write-back of source changes to executor.jsonc.\n//\n// Plugins (openapi, graphql, mcp) call `sink.upsertSource` after their DB\n// writes so the committable file stays in sync with runtime state. Errors\n// are logged and swallowed — a failed file write must never fail a DB\n// mutation, and the next successful mutation (or a boot-time sync) will\n// eventually reconcile.\n//\n// The FileSystem layer is injected so library code here doesn't pick a\n// platform binding. The host app provides NodeFileSystem (or BunFileSystem).\n// ---------------------------------------------------------------------------\n\nimport { Effect } from \"effect\";\nimport type { Layer } from \"effect\";\nimport type { FileSystem } from \"effect\";\n\nimport { SECRET_REF_PREFIX, type ConfigHeaderValue, type SourceConfig } from \"./schema\";\nimport { addSourceToConfig, removeSourceFromConfig } from \"./write\";\n\n// Translate a plugin-side header value (`{ secretId, prefix? }` for secret\n// refs) into the config file's `secret-public-ref:<id>` string form.\ntype PluginHeaderValue = string | { secretId: string; prefix?: string };\n\nexport const headerToConfigValue = (\n value: PluginHeaderValue,\n): ConfigHeaderValue => {\n if (typeof value === \"string\") return value;\n const ref = `${SECRET_REF_PREFIX}${value.secretId}`;\n return value.prefix ? { value: ref, prefix: value.prefix } : ref;\n};\n\nexport const headersToConfigValues = (\n headers: Record<string, PluginHeaderValue> | undefined,\n): Record<string, ConfigHeaderValue> | undefined => {\n if (!headers) return undefined;\n const out: Record<string, ConfigHeaderValue> = {};\n for (const [k, v] of Object.entries(headers)) out[k] = headerToConfigValue(v);\n return out;\n};\n\nexport interface ConfigFileSink {\n readonly upsertSource: (source: SourceConfig) => Effect.Effect<void>;\n readonly removeSource: (namespace: string) => Effect.Effect<void>;\n}\n\nexport interface ConfigFileSinkOptions {\n readonly path: string;\n readonly fsLayer: Layer.Layer<FileSystem.FileSystem>;\n /** Called when a file operation fails. Defaults to console.warn. */\n readonly onError?: (op: \"upsert\" | \"remove\", err: unknown) => void;\n}\n\nconst defaultOnError = (op: \"upsert\" | \"remove\", err: unknown): void => {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[config-sink] ${op} failed: ${msg}`);\n};\n\nexport const makeFileConfigSink = (\n options: ConfigFileSinkOptions,\n): ConfigFileSink => {\n const { path, fsLayer, onError = defaultOnError } = options;\n\n return {\n upsertSource: (source) =>\n addSourceToConfig(path, source).pipe(\n Effect.provide(fsLayer),\n Effect.catch((err: unknown) => Effect.sync(() => onError(\"upsert\", err))),\n ),\n\n removeSource: (namespace) =>\n removeSourceFromConfig(path, namespace).pipe(\n Effect.provide(fsLayer),\n Effect.catch((err: unknown) => Effect.sync(() => onError(\"remove\", err))),\n ),\n };\n};\n"],"mappings":"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","names":["Schema","fs","Schema","jsonc","require","Effect","FileSystem","jsonc","fs","edits","Effect","Effect"]}
@@ -0,0 +1,19 @@
1
+ import type { AnyPlugin } from "@executor-js/sdk";
2
+ export interface LoadPluginsFromJsoncOptions {
3
+ /** Absolute path to `executor.jsonc` (or compatible). */
4
+ readonly path: string;
5
+ /**
6
+ * Host-injected deps merged into each plugin's options. Common keys:
7
+ * `configFile` (the `ConfigFileSink`), env-derived credentials, etc.
8
+ * Plugins ignore deps they don't accept — `definePlugin` strips
9
+ * unknown keys before forwarding to the author factory.
10
+ */
11
+ readonly deps?: Readonly<Record<string, unknown>>;
12
+ }
13
+ /**
14
+ * Returns the plugins listed in jsonc, or `null` if the file is missing
15
+ * or has no `plugins` array. The host treats `null` as "fall back to
16
+ * the static `executor.config.ts` factory."
17
+ */
18
+ export declare const loadPluginsFromJsonc: (options: LoadPluginsFromJsoncOptions) => Promise<readonly AnyPlugin[] | null>;
19
+ //# sourceMappingURL=load-plugins.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-plugins.d.ts","sourceRoot":"","sources":["../src/load-plugins.ts"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAWlD,MAAM,WAAW,2BAA2B;IAC1C,yDAAyD;IACzD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACnD;AAED;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,GAC/B,SAAS,2BAA2B,KACnC,OAAO,CAAC,SAAS,SAAS,EAAE,GAAG,IAAI,CAkErC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=load-plugins.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-plugins.test.d.ts","sourceRoot":"","sources":["../src/load-plugins.test.ts"],"names":[],"mappings":""}
package/dist/schema.d.ts CHANGED
@@ -136,9 +136,18 @@ export declare const SecretMetadata: Schema.Struct<{
136
136
  readonly purpose: Schema.optional<Schema.String>;
137
137
  }>;
138
138
  export type SecretMetadata = typeof SecretMetadata.Type;
139
+ export declare const PluginConfig: Schema.Struct<{
140
+ readonly package: Schema.String;
141
+ readonly options: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
142
+ }>;
143
+ export type PluginConfig = typeof PluginConfig.Type;
139
144
  export declare const ExecutorFileConfig: Schema.Struct<{
140
145
  readonly $schema: Schema.optional<Schema.String>;
141
146
  readonly name: Schema.optional<Schema.String>;
147
+ readonly plugins: Schema.optional<Schema.$Array<Schema.Struct<{
148
+ readonly package: Schema.String;
149
+ readonly options: Schema.optional<Schema.$Record<Schema.String, Schema.Unknown>>;
150
+ }>>>;
142
151
  readonly sources: Schema.optional<Schema.$Array<Schema.Union<readonly [Schema.Struct<{
143
152
  readonly kind: Schema.Literal<"openapi">;
144
153
  readonly spec: Schema.String;
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAWhC,eAAO,MAAM,iBAAiB,uBAAuB,CAAC;AAEtD,eAAO,MAAM,iBAAiB;;;IAM5B,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,OAAO,iBAAiB,CAAC,IAAI,CAAC;AAQ9D,eAAO,MAAM,mBAAmB;;;;;;;;;EAM9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,OAAO,mBAAmB,CAAC,IAAI,CAAC;AAElE,eAAO,MAAM,mBAAmB;;;;;;;;;EAM9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,OAAO,mBAAmB,CAAC,IAAI,CAAC;AAIlE,eAAO,MAAM,aAAa;;;;;;;;;IAUtB;;oDAEgD;;IAGlD,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,OAAO,aAAa,CAAC,IAAI,CAAC;AAEtD,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;QAR9B;;wDAEgD;;;EAgBlD,CAAC;AACH,MAAM,MAAM,qBAAqB,GAAG,OAAO,qBAAqB,CAAC,IAAI,CAAC;AAEtE,eAAO,MAAM,oBAAoB;;;;;;;;;EAS/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,OAAO,oBAAoB,CAAC,IAAI,CAAC;AAEpE,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAjCrB;;wDAEgD;;;;;;;;;;;;IAoClD,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,OAAO,YAAY,CAAC,IAAI,CAAC;AAMpD,eAAO,MAAM,cAAc;;;;EAIzB,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,cAAc,CAAC,IAAI,CAAC;AAMxD,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YAxD3B;;4DAEgD;;;;;;;;;;;;;;;;;;EA2DlD,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,OAAO,kBAAkB,CAAC,IAAI,CAAC"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAWhC,eAAO,MAAM,iBAAiB,uBAAuB,CAAC;AAEtD,eAAO,MAAM,iBAAiB;;;IAM5B,CAAC;AACH,MAAM,MAAM,iBAAiB,GAAG,OAAO,iBAAiB,CAAC,IAAI,CAAC;AAQ9D,eAAO,MAAM,mBAAmB;;;;;;;;;EAM9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,OAAO,mBAAmB,CAAC,IAAI,CAAC;AAElE,eAAO,MAAM,mBAAmB;;;;;;;;;EAM9B,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,OAAO,mBAAmB,CAAC,IAAI,CAAC;AAIlE,eAAO,MAAM,aAAa;;;;;;;;;IAUtB;;oDAEgD;;IAGlD,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,OAAO,aAAa,CAAC,IAAI,CAAC;AAEtD,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;QAR9B;;wDAEgD;;;EAgBlD,CAAC;AACH,MAAM,MAAM,qBAAqB,GAAG,OAAO,qBAAqB,CAAC,IAAI,CAAC;AAEtE,eAAO,MAAM,oBAAoB;;;;;;;;;EAS/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,OAAO,oBAAoB,CAAC,IAAI,CAAC;AAEpE,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAjCrB;;wDAEgD;;;;;;;;;;;;IAoClD,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,OAAO,YAAY,CAAC,IAAI,CAAC;AAMpD,eAAO,MAAM,cAAc;;;;EAIzB,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,cAAc,CAAC,IAAI,CAAC;AAcxD,eAAO,MAAM,YAAY;;;EAGvB,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,OAAO,YAAY,CAAC,IAAI,CAAC;AAMpD,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;YA1E3B;;4DAEgD;;;;;;;;;;;;;;;;;;EA8ElD,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,OAAO,kBAAkB,CAAC,IAAI,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@executor-js/config",
3
- "version": "0.0.2",
3
+ "version": "0.1.0",
4
4
  "homepage": "https://github.com/RhysSullivan/executor/tree/main/packages/core/config",
5
5
  "bugs": {
6
6
  "url": "https://github.com/RhysSullivan/executor/issues"
@@ -33,8 +33,9 @@
33
33
  "typecheck:slow": "tsc --noEmit"
34
34
  },
35
35
  "dependencies": {
36
- "@executor-js/sdk": "0.0.2",
36
+ "@executor-js/sdk": "0.1.0",
37
37
  "effect": "4.0.0-beta.59",
38
+ "jiti": "^2.6.1",
38
39
  "jsonc-parser": "^3.3.1"
39
40
  },
40
41
  "devDependencies": {