@executor-js/cli 0.0.1-beta.2 → 0.0.1-beta.3
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.js +267 -1
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -8,7 +8,273 @@ import { existsSync as existsSync3 } from "fs";
|
|
|
8
8
|
import fs from "fs/promises";
|
|
9
9
|
import path2 from "path";
|
|
10
10
|
import { Command } from "commander";
|
|
11
|
-
|
|
11
|
+
|
|
12
|
+
// ../storage-core/src/factory.ts
|
|
13
|
+
import { Effect } from "effect";
|
|
14
|
+
|
|
15
|
+
// ../sdk/src/ids.ts
|
|
16
|
+
import { Schema } from "effect";
|
|
17
|
+
var ScopeId = Schema.String.pipe(Schema.brand("ScopeId"));
|
|
18
|
+
var ToolId = Schema.String.pipe(Schema.brand("ToolId"));
|
|
19
|
+
var SecretId = Schema.String.pipe(Schema.brand("SecretId"));
|
|
20
|
+
var PolicyId = Schema.String.pipe(Schema.brand("PolicyId"));
|
|
21
|
+
|
|
22
|
+
// ../sdk/src/scope.ts
|
|
23
|
+
import { Schema as Schema2 } from "effect";
|
|
24
|
+
var Scope = class extends Schema2.Class("Scope")({
|
|
25
|
+
id: ScopeId,
|
|
26
|
+
name: Schema2.String,
|
|
27
|
+
createdAt: Schema2.DateFromNumber
|
|
28
|
+
}) {
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// ../sdk/src/errors.ts
|
|
32
|
+
import { Data, Schema as Schema3 } from "effect";
|
|
33
|
+
var ToolNotFoundError = class extends Schema3.TaggedError()(
|
|
34
|
+
"ToolNotFoundError",
|
|
35
|
+
{ toolId: ToolId }
|
|
36
|
+
) {
|
|
37
|
+
};
|
|
38
|
+
var ToolInvocationError = class extends Data.TaggedError("ToolInvocationError") {
|
|
39
|
+
};
|
|
40
|
+
var PluginNotLoadedError = class extends Schema3.TaggedError()(
|
|
41
|
+
"PluginNotLoadedError",
|
|
42
|
+
{
|
|
43
|
+
pluginId: Schema3.String,
|
|
44
|
+
toolId: ToolId
|
|
45
|
+
}
|
|
46
|
+
) {
|
|
47
|
+
};
|
|
48
|
+
var NoHandlerError = class extends Schema3.TaggedError()(
|
|
49
|
+
"NoHandlerError",
|
|
50
|
+
{
|
|
51
|
+
toolId: ToolId,
|
|
52
|
+
pluginId: Schema3.String
|
|
53
|
+
}
|
|
54
|
+
) {
|
|
55
|
+
};
|
|
56
|
+
var SourceNotFoundError = class extends Schema3.TaggedError()(
|
|
57
|
+
"SourceNotFoundError",
|
|
58
|
+
{ sourceId: Schema3.String }
|
|
59
|
+
) {
|
|
60
|
+
};
|
|
61
|
+
var SourceRemovalNotAllowedError = class extends Schema3.TaggedError()(
|
|
62
|
+
"SourceRemovalNotAllowedError",
|
|
63
|
+
{ sourceId: Schema3.String }
|
|
64
|
+
) {
|
|
65
|
+
};
|
|
66
|
+
var SecretNotFoundError = class extends Schema3.TaggedError()(
|
|
67
|
+
"SecretNotFoundError",
|
|
68
|
+
{ secretId: SecretId }
|
|
69
|
+
) {
|
|
70
|
+
};
|
|
71
|
+
var SecretResolutionError = class extends Schema3.TaggedError()(
|
|
72
|
+
"SecretResolutionError",
|
|
73
|
+
{
|
|
74
|
+
secretId: SecretId,
|
|
75
|
+
message: Schema3.String
|
|
76
|
+
}
|
|
77
|
+
) {
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// ../sdk/src/types.ts
|
|
81
|
+
import { Schema as Schema4 } from "effect";
|
|
82
|
+
var ToolSchema = class extends Schema4.Class("ToolSchema")({
|
|
83
|
+
id: ToolId,
|
|
84
|
+
name: Schema4.optional(Schema4.String),
|
|
85
|
+
description: Schema4.optional(Schema4.String),
|
|
86
|
+
inputSchema: Schema4.optional(Schema4.Unknown),
|
|
87
|
+
outputSchema: Schema4.optional(Schema4.Unknown),
|
|
88
|
+
inputTypeScript: Schema4.optional(Schema4.String),
|
|
89
|
+
outputTypeScript: Schema4.optional(Schema4.String),
|
|
90
|
+
typeScriptDefinitions: Schema4.optional(
|
|
91
|
+
Schema4.Record({ key: Schema4.String, value: Schema4.String })
|
|
92
|
+
)
|
|
93
|
+
}) {
|
|
94
|
+
};
|
|
95
|
+
var SourceDetectionResult = class extends Schema4.Class(
|
|
96
|
+
"SourceDetectionResult"
|
|
97
|
+
)({
|
|
98
|
+
/** Plugin id that recognized the URL (e.g. "openapi", "graphql"). */
|
|
99
|
+
kind: Schema4.String,
|
|
100
|
+
/** Confidence tier — UI uses this to pick a winner when multiple
|
|
101
|
+
* plugins claim a URL. */
|
|
102
|
+
confidence: Schema4.Literal("high", "medium", "low"),
|
|
103
|
+
/** The (possibly normalized) endpoint the plugin will use. */
|
|
104
|
+
endpoint: Schema4.String,
|
|
105
|
+
/** Human-readable name suggestion, typically derived from spec title
|
|
106
|
+
* or URL hostname. */
|
|
107
|
+
name: Schema4.String,
|
|
108
|
+
/** Namespace suggestion — the plugin's recommendation for the source
|
|
109
|
+
* id. UI may override. */
|
|
110
|
+
namespace: Schema4.String
|
|
111
|
+
}) {
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// ../sdk/src/core-schema.ts
|
|
115
|
+
var coreSchema = {
|
|
116
|
+
source: {
|
|
117
|
+
fields: {
|
|
118
|
+
id: { type: "string", required: true },
|
|
119
|
+
scope_id: { type: "string", required: true, index: true },
|
|
120
|
+
plugin_id: { type: "string", required: true, index: true },
|
|
121
|
+
kind: { type: "string", required: true },
|
|
122
|
+
name: { type: "string", required: true },
|
|
123
|
+
url: { type: "string", required: false },
|
|
124
|
+
can_remove: {
|
|
125
|
+
type: "boolean",
|
|
126
|
+
required: true,
|
|
127
|
+
defaultValue: true
|
|
128
|
+
},
|
|
129
|
+
can_refresh: {
|
|
130
|
+
type: "boolean",
|
|
131
|
+
required: true,
|
|
132
|
+
defaultValue: false
|
|
133
|
+
},
|
|
134
|
+
can_edit: {
|
|
135
|
+
type: "boolean",
|
|
136
|
+
required: true,
|
|
137
|
+
defaultValue: false
|
|
138
|
+
},
|
|
139
|
+
created_at: { type: "date", required: true },
|
|
140
|
+
updated_at: { type: "date", required: true }
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
tool: {
|
|
144
|
+
fields: {
|
|
145
|
+
id: { type: "string", required: true },
|
|
146
|
+
scope_id: { type: "string", required: true, index: true },
|
|
147
|
+
source_id: { type: "string", required: true, index: true },
|
|
148
|
+
plugin_id: { type: "string", required: true, index: true },
|
|
149
|
+
name: { type: "string", required: true },
|
|
150
|
+
description: { type: "string", required: true },
|
|
151
|
+
input_schema: { type: "json", required: false },
|
|
152
|
+
output_schema: { type: "json", required: false },
|
|
153
|
+
// NOTE: tool annotations (requiresApproval, approvalDescription,
|
|
154
|
+
// mayElicit) are NOT stored on this row. They're derived at read
|
|
155
|
+
// time from plugin-owned data via `plugin.resolveAnnotations`,
|
|
156
|
+
// because the source of truth already lives in each plugin's own
|
|
157
|
+
// storage (openapi's OperationBinding, etc.) and duplicating it
|
|
158
|
+
// here would just mean bulk-rewriting rows every time the
|
|
159
|
+
// derivation logic changes.
|
|
160
|
+
created_at: { type: "date", required: true },
|
|
161
|
+
updated_at: { type: "date", required: true }
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
// Shared JSON-schema `$defs` stored once per source. Tool input/output
|
|
165
|
+
// schemas carry `$ref: "#/$defs/X"` pointers; the read path attaches
|
|
166
|
+
// matching defs under `$defs` before returning. Keyed by synthetic id
|
|
167
|
+
// `${source_id}.${name}` so cleanup on source removal is a single
|
|
168
|
+
// deleteMany by source_id.
|
|
169
|
+
definition: {
|
|
170
|
+
fields: {
|
|
171
|
+
id: { type: "string", required: true },
|
|
172
|
+
scope_id: { type: "string", required: true, index: true },
|
|
173
|
+
source_id: { type: "string", required: true, index: true },
|
|
174
|
+
plugin_id: { type: "string", required: true, index: true },
|
|
175
|
+
name: { type: "string", required: true },
|
|
176
|
+
schema: { type: "json", required: true },
|
|
177
|
+
created_at: { type: "date", required: true }
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
// Secrets live in the core surface as metadata (id, display name,
|
|
181
|
+
// provider key). Actual values never touch this table — they live in
|
|
182
|
+
// the secret provider (keychain, 1password, file, etc.) and are
|
|
183
|
+
// resolved on demand via `ctx.secrets.get(id)`.
|
|
184
|
+
secret: {
|
|
185
|
+
fields: {
|
|
186
|
+
id: { type: "string", required: true },
|
|
187
|
+
scope_id: { type: "string", required: true, index: true },
|
|
188
|
+
name: { type: "string", required: true },
|
|
189
|
+
provider: { type: "string", required: true, index: true },
|
|
190
|
+
created_at: { type: "date", required: true }
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// ../sdk/src/secrets.ts
|
|
196
|
+
import { Schema as Schema5 } from "effect";
|
|
197
|
+
var SecretRef = class extends Schema5.Class("SecretRef")({
|
|
198
|
+
id: SecretId,
|
|
199
|
+
scopeId: ScopeId,
|
|
200
|
+
/** Human-readable label (e.g. "Cloudflare API Token") */
|
|
201
|
+
name: Schema5.String,
|
|
202
|
+
/** Which provider holds the value */
|
|
203
|
+
provider: Schema5.String,
|
|
204
|
+
createdAt: Schema5.DateFromNumber
|
|
205
|
+
}) {
|
|
206
|
+
};
|
|
207
|
+
var SetSecretInput = class extends Schema5.Class(
|
|
208
|
+
"SetSecretInput"
|
|
209
|
+
)({
|
|
210
|
+
id: SecretId,
|
|
211
|
+
/** Display name shown in secret-list UI. */
|
|
212
|
+
name: Schema5.String,
|
|
213
|
+
/** The secret value itself — never persisted outside the provider. */
|
|
214
|
+
value: Schema5.String,
|
|
215
|
+
/** Optional provider routing. If unset the executor picks the first
|
|
216
|
+
* writable provider in registration order. */
|
|
217
|
+
provider: Schema5.optional(Schema5.String)
|
|
218
|
+
}) {
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// ../sdk/src/elicitation.ts
|
|
222
|
+
import { Schema as Schema6 } from "effect";
|
|
223
|
+
var FormElicitation = class extends Schema6.TaggedClass()("FormElicitation", {
|
|
224
|
+
message: Schema6.String,
|
|
225
|
+
/** JSON Schema describing the fields to collect */
|
|
226
|
+
requestedSchema: Schema6.Record({ key: Schema6.String, value: Schema6.Unknown })
|
|
227
|
+
}) {
|
|
228
|
+
};
|
|
229
|
+
var UrlElicitation = class extends Schema6.TaggedClass()("UrlElicitation", {
|
|
230
|
+
message: Schema6.String,
|
|
231
|
+
url: Schema6.String,
|
|
232
|
+
/** Unique ID so the host can correlate the callback */
|
|
233
|
+
elicitationId: Schema6.String
|
|
234
|
+
}) {
|
|
235
|
+
};
|
|
236
|
+
var ElicitationAction = Schema6.Literal("accept", "decline", "cancel");
|
|
237
|
+
var ElicitationResponse = class extends Schema6.Class("ElicitationResponse")({
|
|
238
|
+
action: ElicitationAction,
|
|
239
|
+
/** Present when action is "accept" — the data the user provided */
|
|
240
|
+
content: Schema6.optional(Schema6.Record({ key: Schema6.String, value: Schema6.Unknown }))
|
|
241
|
+
}) {
|
|
242
|
+
};
|
|
243
|
+
var ElicitationDeclinedError = class extends Schema6.TaggedError()(
|
|
244
|
+
"ElicitationDeclinedError",
|
|
245
|
+
{
|
|
246
|
+
toolId: ToolId,
|
|
247
|
+
action: Schema6.Literal("decline", "cancel")
|
|
248
|
+
}
|
|
249
|
+
) {
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
// ../sdk/src/blob.ts
|
|
253
|
+
import { Effect as Effect4 } from "effect";
|
|
254
|
+
|
|
255
|
+
// ../sdk/src/executor.ts
|
|
256
|
+
import { Effect as Effect5, FiberRef } from "effect";
|
|
257
|
+
var collectSchemas = (plugins) => {
|
|
258
|
+
const merged = { ...coreSchema };
|
|
259
|
+
for (const plugin of plugins) {
|
|
260
|
+
if (!plugin.schema) continue;
|
|
261
|
+
for (const [modelKey, model] of Object.entries(plugin.schema)) {
|
|
262
|
+
if (merged[modelKey]) {
|
|
263
|
+
throw new Error(
|
|
264
|
+
`Duplicate model "${modelKey}" contributed by plugin "${plugin.id}" (reserved by core or another plugin)`
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
merged[modelKey] = model;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return merged;
|
|
271
|
+
};
|
|
272
|
+
var activeAdapterRef = FiberRef.unsafeMake(
|
|
273
|
+
null
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
// ../storage-core/src/testing/memory.ts
|
|
277
|
+
import { Effect as Effect6 } from "effect";
|
|
12
278
|
|
|
13
279
|
// src/utils/get-config.ts
|
|
14
280
|
import { existsSync } from "fs";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/commands/generate.ts","../src/utils/get-config.ts","../src/generators/drizzle.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { Command } from \"commander\";\nimport { generate } from \"./commands/generate.js\";\n\nprocess.on(\"SIGINT\", () => process.exit(0));\nprocess.on(\"SIGTERM\", () => process.exit(0));\n\nconst program = new Command(\"executor\")\n .version(\"0.0.1\")\n .description(\"Executor CLI\")\n .addCommand(generate)\n .action(() => program.help());\n\nprogram.parse();\n","import { existsSync } from \"node:fs\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { Command } from \"commander\";\nimport { collectSchemas } from \"@executor/sdk/core\";\nimport { getConfig } from \"../utils/get-config.js\";\nimport { generateDrizzleSchema } from \"../generators/drizzle.js\";\n\nasync function generateAction(opts: {\n cwd: string;\n config?: string;\n output?: string;\n}) {\n const cwd = path.resolve(opts.cwd);\n if (!existsSync(cwd)) {\n console.error(`The directory \"${cwd}\" does not exist.`);\n process.exit(1);\n }\n\n const config = await getConfig({ cwd, configPath: opts.config });\n if (!config) {\n console.error(\n \"No configuration file found. Add an `executor.config.ts` file to \" +\n \"your project or pass the path using the `--config` flag.\",\n );\n process.exit(1);\n }\n\n const schema = collectSchemas(config.plugins);\n\n const result = await generateDrizzleSchema({\n schema,\n dialect: config.dialect,\n file: opts.output,\n });\n\n if (!result.code) {\n console.log(\"Schema is already up to date.\");\n process.exit(0);\n }\n\n const outPath = path.resolve(cwd, result.fileName);\n const outDir = path.dirname(outPath);\n if (!existsSync(outDir)) {\n await fs.mkdir(outDir, { recursive: true });\n }\n\n await fs.writeFile(outPath, result.code);\n console.log(`Schema generated: ${path.relative(cwd, outPath)}`);\n}\n\nexport const generate = new Command(\"generate\")\n .description(\"Generate a drizzle schema file from the executor config\")\n .option(\n \"-c, --cwd <cwd>\",\n \"the working directory\",\n process.cwd(),\n )\n .option(\n \"--config <config>\",\n \"path to the executor config file\",\n )\n .option(\n \"--output <output>\",\n \"output file path for the generated schema\",\n )\n .action(generateAction);\n","import { existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { createJiti } from \"jiti\";\nimport type { ExecutorCliConfig } from \"@executor/sdk/core\";\n\nconst defaultPaths = [\n \"executor.config.ts\",\n \"executor.config.js\",\n \"src/executor.config.ts\",\n \"src/executor.config.js\",\n];\n\nexport const getConfig = async (opts: {\n cwd: string;\n configPath?: string;\n}): Promise<ExecutorCliConfig | null> => {\n const { cwd, configPath } = opts;\n\n let resolvedPath: string | undefined;\n\n if (configPath) {\n resolvedPath = path.resolve(cwd, configPath);\n if (!existsSync(resolvedPath)) {\n console.error(`Config file not found: ${resolvedPath}`);\n return null;\n }\n } else {\n for (const p of defaultPaths) {\n const candidate = path.resolve(cwd, p);\n if (existsSync(candidate)) {\n resolvedPath = candidate;\n break;\n }\n }\n }\n\n if (!resolvedPath) return null;\n\n const jiti = createJiti(cwd, {\n interopDefault: true,\n moduleCache: false,\n });\n\n const mod = await jiti.import(resolvedPath);\n const config = (mod as { default?: ExecutorCliConfig }).default ?? mod;\n return config as ExecutorCliConfig;\n};\n","// ---------------------------------------------------------------------------\n// Drizzle schema generator — DBSchema → drizzle-orm TS source.\n//\n// Ported from better-auth (packages/cli/src/generators/drizzle.ts) under\n// MIT. Adapted for executor:\n// - Reads our DBSchema shape (modelName optional, key = default)\n// - No auth-specific logic (uuid/serial id modes, usePlural, camelCase)\n// - Always emits text primary keys\n// - Dialect from ExecutorCliConfig, not from adapter.options.provider\n// ---------------------------------------------------------------------------\n\nimport { existsSync } from \"node:fs\";\nimport type { DBSchema, DBFieldAttribute } from \"@executor/storage-core\";\nimport type { SchemaGenerator } from \"./types.js\";\n\ntype Dialect = \"pg\" | \"sqlite\" | \"mysql\";\n\nconst getModelName = (key: string, def: DBSchema[string]): string =>\n def.modelName ?? key;\n\nconst getType = (\n name: string,\n field: DBFieldAttribute,\n dialect: Dialect,\n): string => {\n if (field.references?.field === \"id\") {\n return `text('${name}')`;\n }\n\n const type = field.type;\n\n if (typeof type !== \"string\") {\n // Enum array — e.g. [\"active\", \"inactive\"]\n if (Array.isArray(type) && type.every((x) => typeof x === \"string\")) {\n return {\n sqlite: `text({ enum: [${type.map((x) => `'${x}'`).join(\", \")}] })`,\n pg: `text('${name}', { enum: [${type.map((x) => `'${x}'`).join(\", \")}] })`,\n mysql: `mysqlEnum([${type.map((x) => `'${x}'`).join(\", \")}])`,\n }[dialect];\n }\n throw new TypeError(\n `Invalid field type for field ${name}`,\n );\n }\n\n const typeMap: Record<string, Record<Dialect, string>> = {\n string: {\n sqlite: `text('${name}')`,\n pg: `text('${name}')`,\n mysql: field.unique\n ? `varchar('${name}', { length: 255 })`\n : field.references\n ? `varchar('${name}', { length: 36 })`\n : field.sortable\n ? `varchar('${name}', { length: 255 })`\n : field.index\n ? `varchar('${name}', { length: 255 })`\n : `text('${name}')`,\n },\n boolean: {\n sqlite: `integer('${name}', { mode: 'boolean' })`,\n pg: `boolean('${name}')`,\n mysql: `boolean('${name}')`,\n },\n number: {\n sqlite: `integer('${name}')`,\n pg: field.bigint\n ? `bigint('${name}', { mode: 'number' })`\n : `integer('${name}')`,\n mysql: field.bigint\n ? `bigint('${name}', { mode: 'number' })`\n : `int('${name}')`,\n },\n date: {\n sqlite: `integer('${name}', { mode: 'timestamp_ms' })`,\n pg: `timestamp('${name}')`,\n mysql: `timestamp('${name}', { fsp: 3 })`,\n },\n \"number[]\": {\n sqlite: `text('${name}', { mode: \"json\" })`,\n pg: field.bigint\n ? `bigint('${name}', { mode: 'number' }).array()`\n : `integer('${name}').array()`,\n mysql: `text('${name}', { mode: 'json' })`,\n },\n \"string[]\": {\n sqlite: `text('${name}', { mode: \"json\" })`,\n pg: `text('${name}').array()`,\n mysql: `text('${name}', { mode: \"json\" })`,\n },\n json: {\n sqlite: `text('${name}', { mode: \"json\" })`,\n pg: `jsonb('${name}')`,\n mysql: `json('${name}', { mode: \"json\" })`,\n },\n };\n\n const dbTypeMap = typeMap[type as string];\n if (!dbTypeMap) {\n throw new Error(\n `Unsupported field type '${field.type}' for field '${name}'.`,\n );\n }\n return dbTypeMap[dialect];\n};\n\n// ---------------------------------------------------------------------------\n// Generator\n// ---------------------------------------------------------------------------\n\nexport const generateDrizzleSchema: SchemaGenerator = async ({\n schema,\n dialect,\n file,\n}) => {\n const filePath = file || \"./executor-schema.ts\";\n const fileExist = existsSync(filePath);\n\n let code = generateImport({ dialect, schema });\n\n for (const [tableKey, tableDef] of Object.entries(schema)) {\n const modelName = getModelName(tableKey, tableDef);\n const fields = tableDef.fields;\n\n // Scoped tables get a composite `(scope_id, id)` primary key so two\n // tenants can register rows with the same user-facing id without\n // colliding on a globally-unique PK. Single-column PK stays for\n // unscoped tables (conformance fixtures, the blob store, etc.).\n const hasScopeId = Object.prototype.hasOwnProperty.call(fields, \"scope_id\");\n const id = hasScopeId ? `text('id').notNull()` : `text('id').primaryKey()`;\n\n type TableExtra =\n | { kind: \"uniqueIndex\" | \"index\"; name: string; on: string }\n | { kind: \"primaryKey\"; columns: readonly string[] };\n const extras: TableExtra[] = [];\n\n const assignExtras = (items: TableExtra[]): string => {\n if (!items.length) return \"\";\n const lines: string[] = [`, (table) => [`];\n for (const item of items) {\n if (item.kind === \"primaryKey\") {\n const cols = item.columns.map((c) => `table.${c}`).join(\", \");\n lines.push(` primaryKey({ columns: [${cols}] }),`);\n } else {\n lines.push(` ${item.kind}(\"${item.name}\").on(table.${item.on}),`);\n }\n }\n lines.push(`]`);\n return lines.join(\"\\n\");\n };\n\n if (hasScopeId) {\n extras.push({ kind: \"primaryKey\", columns: [\"scope_id\", \"id\"] });\n }\n\n const tableSchema = `export const ${tableKey} = ${dialect}Table(\"${modelName}\", {\n id: ${id},\n ${Object.entries(fields)\n .filter(([fieldName]) => fieldName !== \"id\")\n .map(([fieldName, attr]) => {\n const physical = attr.fieldName ?? fieldName;\n\n if (attr.index && !attr.unique) {\n extras.push({\n kind: \"index\",\n name: `${tableKey}_${physical}_idx`,\n on: physical,\n });\n } else if (attr.index && attr.unique) {\n extras.push({\n kind: \"uniqueIndex\",\n name: `${tableKey}_${physical}_uidx`,\n on: physical,\n });\n }\n\n let col = getType(physical, attr, dialect);\n\n if (\n attr.defaultValue !== null &&\n typeof attr.defaultValue !== \"undefined\"\n ) {\n if (typeof attr.defaultValue === \"function\") {\n if (\n attr.type === \"date\" &&\n attr.defaultValue.toString().includes(\"new Date()\")\n ) {\n if (dialect === \"sqlite\") {\n col += `.default(sql\\`(cast(unixepoch('subsecond') * 1000 as integer))\\`)`;\n } else {\n col += `.defaultNow()`;\n }\n }\n } else if (typeof attr.defaultValue === \"string\") {\n col += `.default(\"${attr.defaultValue}\")`;\n } else {\n col += `.default(${attr.defaultValue})`;\n }\n }\n\n if (attr.onUpdate && attr.type === \"date\") {\n if (typeof attr.onUpdate === \"function\") {\n col += `.$onUpdate(${attr.onUpdate})`;\n }\n }\n\n return `${physical}: ${col}${attr.required !== false ? \".notNull()\" : \"\"}${\n attr.unique ? \".unique()\" : \"\"\n }${\n attr.references\n ? `.references(()=> ${attr.references.model}.${attr.references.field ?? \"id\"}, { onDelete: '${\n attr.references.onDelete || \"cascade\"\n }' })`\n : \"\"\n }`;\n })\n .join(\",\\n \")}\n}${assignExtras(extras)});`;\n\n code += `\\n${tableSchema}\\n`;\n }\n\n // ---------------------------------------------------------------------------\n // Relations — scan FKs in both directions\n // ---------------------------------------------------------------------------\n\n let relationsString = \"\";\n for (const [tableKey, tableDef] of Object.entries(schema)) {\n const modelName = tableKey;\n\n type Relation = {\n key: string;\n model: string;\n type: \"one\" | \"many\";\n reference?: {\n field: string;\n references: string;\n fieldName: string;\n };\n };\n\n const oneRelations: Relation[] = [];\n const manyRelations: Relation[] = [];\n const manyRelationsSet = new Set<string>();\n\n // Find all FKs in THIS table → \"one\" relations\n for (const [fieldName, field] of Object.entries(tableDef.fields)) {\n if (!field.references) continue;\n const referencedModel = field.references.model;\n const physical = field.fieldName ?? fieldName;\n const fieldRef = `${tableKey}.${physical}`;\n const referenceRef = `${referencedModel}.${field.references.field || \"id\"}`;\n\n oneRelations.push({\n key: referencedModel,\n model: referencedModel,\n type: \"one\",\n reference: {\n field: fieldRef,\n references: referenceRef,\n fieldName,\n },\n });\n }\n\n // Find all OTHER tables that reference THIS table → \"many\" relations\n for (const [otherKey, otherDef] of Object.entries(schema)) {\n if (otherKey === tableKey) continue;\n const hasFK = Object.values(otherDef.fields).some(\n (field) => field.references?.model === tableKey,\n );\n if (!hasFK) continue;\n\n const relationKey = `${otherKey}s`;\n if (!manyRelationsSet.has(relationKey)) {\n manyRelationsSet.add(relationKey);\n manyRelations.push({\n key: relationKey,\n model: otherKey,\n type: \"many\",\n });\n }\n }\n\n // Detect duplicates\n const relationsByModel = new Map<string, Relation[]>();\n for (const rel of oneRelations) {\n if (!rel.reference) continue;\n const arr = relationsByModel.get(rel.key) ?? [];\n arr.push(rel);\n relationsByModel.set(rel.key, arr);\n }\n\n const duplicateRelations: Relation[] = [];\n const singleRelations: Relation[] = [];\n\n for (const [, rels] of relationsByModel.entries()) {\n if (rels.length > 1) {\n duplicateRelations.push(...rels);\n } else {\n singleRelations.push(rels[0]!);\n }\n }\n\n // Duplicate relations get field-specific exports\n for (const rel of duplicateRelations) {\n if (!rel.reference) continue;\n const relExportName = `${modelName}${rel.reference.fieldName.charAt(0).toUpperCase() + rel.reference.fieldName.slice(1)}Relations`;\n const block = `export const ${relExportName} = relations(${modelName}, ({ one }) => ({\n ${rel.key}: one(${rel.model}, {\n fields: [${rel.reference.field}],\n references: [${rel.reference.references}],\n })\n}))`;\n relationsString += `\\n${block}\\n`;\n }\n\n // Combined single relations\n const hasOne = singleRelations.length > 0;\n const hasMany = manyRelations.length > 0;\n\n if (hasOne || hasMany) {\n const destructured = [\n hasOne ? \"one\" : \"\",\n hasMany ? \"many\" : \"\",\n ]\n .filter(Boolean)\n .join(\", \");\n\n const body = [\n ...singleRelations\n .filter((r) => r.reference)\n .map(\n (r) =>\n ` ${r.key}: one(${r.model}, {\\n fields: [${r.reference!.field}],\\n references: [${r.reference!.references}],\\n })`,\n ),\n ...manyRelations.map(\n ({ key, model }) => ` ${key}: many(${model})`,\n ),\n ].join(\",\\n\");\n\n const block = `export const ${modelName}Relations = relations(${modelName}, ({ ${destructured} }) => ({\n${body}\n}))`;\n relationsString += `\\n${block}\\n`;\n }\n }\n\n code += `\\n${relationsString}`;\n\n return {\n code,\n fileName: filePath,\n overwrite: fileExist,\n };\n};\n\n// ---------------------------------------------------------------------------\n// Import generation — only emit what's actually used\n// ---------------------------------------------------------------------------\n\nfunction generateImport({\n dialect,\n schema,\n}: {\n dialect: Dialect;\n schema: DBSchema;\n}) {\n const rootImports: string[] = [];\n const coreImports: string[] = [];\n\n let hasBigint = false;\n let hasJson = false;\n let hasBoolean = false;\n let hasNumber = false;\n let hasDate = false;\n let hasIndex = false;\n let hasUniqueIndex = false;\n let hasReferences = false;\n let hasCompositePrimaryKey = false;\n\n for (const [tableKey, table] of Object.entries(schema)) {\n for (const field of Object.values(table.fields)) {\n if (field.bigint) hasBigint = true;\n if (field.type === \"json\") hasJson = true;\n if (field.type === \"boolean\") hasBoolean = true;\n if (field.type === \"number\" || field.type === \"number[]\") hasNumber = true;\n if (field.type === \"date\") hasDate = true;\n if (field.index && !field.unique) hasIndex = true;\n if (field.index && field.unique) hasUniqueIndex = true;\n if (field.references) hasReferences = true;\n }\n // Scoped tables get a composite (scope_id, id) PK — see generator\n // body where `primaryKey({ columns: [...] })` is emitted.\n if (Object.prototype.hasOwnProperty.call(table.fields, \"scope_id\")) {\n hasCompositePrimaryKey = true;\n }\n // Keep the generator silent about tableKey in this pass — we only\n // need the existence check above. Referenced here to satisfy lint.\n void tableKey;\n }\n\n coreImports.push(`${dialect}Table`);\n coreImports.push(\"text\");\n\n if (hasBoolean && dialect !== \"sqlite\") coreImports.push(\"boolean\");\n if (hasDate) {\n if (dialect === \"pg\") coreImports.push(\"timestamp\");\n // sqlite uses integer for timestamps, pg uses timestamp\n }\n if (hasNumber || dialect === \"sqlite\") {\n if (dialect === \"pg\") coreImports.push(\"integer\");\n else if (dialect === \"mysql\") coreImports.push(\"int\");\n else coreImports.push(\"integer\");\n }\n if (hasBigint && dialect !== \"sqlite\") coreImports.push(\"bigint\");\n if (hasJson) {\n if (dialect === \"pg\") coreImports.push(\"jsonb\");\n else if (dialect === \"mysql\") coreImports.push(\"json\");\n // sqlite uses text for JSON\n }\n if (hasIndex) coreImports.push(\"index\");\n if (hasUniqueIndex) coreImports.push(\"uniqueIndex\");\n if (hasCompositePrimaryKey) coreImports.push(\"primaryKey\");\n\n // sqlite needs integer for boolean + date\n if (dialect === \"sqlite\" && (hasBoolean || hasDate)) {\n if (!coreImports.includes(\"integer\")) coreImports.push(\"integer\");\n }\n // sqlite needs real for number\n if (dialect === \"sqlite\" && hasNumber) {\n // better-auth uses integer for numbers on sqlite; we use real()\n // for floating-point fidelity.\n }\n\n // Has any timestamp with defaultNow function?\n const hasSqliteTimestamp =\n dialect === \"sqlite\" &&\n Object.values(schema).some((table) =>\n Object.values(table.fields).some(\n (field) =>\n field.type === \"date\" &&\n field.defaultValue &&\n typeof field.defaultValue === \"function\" &&\n field.defaultValue.toString().includes(\"new Date()\"),\n ),\n );\n\n if (hasSqliteTimestamp) {\n rootImports.push(\"sql\");\n }\n\n if (hasReferences || dialect === \"mysql\") {\n // mysql might need varchar for FK fields\n }\n\n // `relations` is only imported when the schema has any references that\n // produce relation blocks (see relationsString generation).\n if (hasReferences) rootImports.push(\"relations\");\n\n const filteredCore = coreImports\n .map((x) => x.trim())\n .filter((x) => x !== \"\");\n\n // Deduplicate\n const uniqueCore = [...new Set(filteredCore)];\n const uniqueRoot = [...new Set(rootImports)];\n\n return `${uniqueRoot.length > 0 ? `import { ${uniqueRoot.join(\", \")} } from \"drizzle-orm\";\\n` : \"\"}import { ${uniqueCore.join(\", \")} } from \"drizzle-orm/${dialect}-core\";\\n`;\n}\n"],"mappings":";;;AAEA,SAAS,WAAAA,gBAAe;;;ACFxB,SAAS,cAAAC,mBAAkB;AAC3B,OAAO,QAAQ;AACf,OAAOC,WAAU;AACjB,SAAS,eAAe;AACxB,SAAS,sBAAsB;;;ACJ/B,SAAS,kBAAkB;AAC3B,OAAO,UAAU;AACjB,SAAS,kBAAkB;AAG3B,IAAM,eAAe;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,YAAY,OAAO,SAGS;AACvC,QAAM,EAAE,KAAK,WAAW,IAAI;AAE5B,MAAI;AAEJ,MAAI,YAAY;AACd,mBAAe,KAAK,QAAQ,KAAK,UAAU;AAC3C,QAAI,CAAC,WAAW,YAAY,GAAG;AAC7B,cAAQ,MAAM,0BAA0B,YAAY,EAAE;AACtD,aAAO;AAAA,IACT;AAAA,EACF,OAAO;AACL,eAAW,KAAK,cAAc;AAC5B,YAAM,YAAY,KAAK,QAAQ,KAAK,CAAC;AACrC,UAAI,WAAW,SAAS,GAAG;AACzB,uBAAe;AACf;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,MAAI,CAAC,aAAc,QAAO;AAE1B,QAAM,OAAO,WAAW,KAAK;AAAA,IAC3B,gBAAgB;AAAA,IAChB,aAAa;AAAA,EACf,CAAC;AAED,QAAM,MAAM,MAAM,KAAK,OAAO,YAAY;AAC1C,QAAM,SAAU,IAAwC,WAAW;AACnE,SAAO;AACT;;;ACnCA,SAAS,cAAAC,mBAAkB;AAM3B,IAAM,eAAe,CAAC,KAAa,QACjC,IAAI,aAAa;AAEnB,IAAM,UAAU,CACd,MACA,OACA,YACW;AACX,MAAI,MAAM,YAAY,UAAU,MAAM;AACpC,WAAO,SAAS,IAAI;AAAA,EACtB;AAEA,QAAM,OAAO,MAAM;AAEnB,MAAI,OAAO,SAAS,UAAU;AAE5B,QAAI,MAAM,QAAQ,IAAI,KAAK,KAAK,MAAM,CAAC,MAAM,OAAO,MAAM,QAAQ,GAAG;AACnE,aAAO;AAAA,QACL,QAAQ,iBAAiB,KAAK,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,KAAK,IAAI,CAAC;AAAA,QAC7D,IAAI,SAAS,IAAI,eAAe,KAAK,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,KAAK,IAAI,CAAC;AAAA,QACpE,OAAO,cAAc,KAAK,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,KAAK,IAAI,CAAC;AAAA,MAC3D,EAAE,OAAO;AAAA,IACX;AACA,UAAM,IAAI;AAAA,MACR,gCAAgC,IAAI;AAAA,IACtC;AAAA,EACF;AAEA,QAAM,UAAmD;AAAA,IACvD,QAAQ;AAAA,MACN,QAAQ,SAAS,IAAI;AAAA,MACrB,IAAI,SAAS,IAAI;AAAA,MACjB,OAAO,MAAM,SACT,YAAY,IAAI,wBAChB,MAAM,aACJ,YAAY,IAAI,uBAChB,MAAM,WACJ,YAAY,IAAI,wBAChB,MAAM,QACJ,YAAY,IAAI,wBAChB,SAAS,IAAI;AAAA,IACzB;AAAA,IACA,SAAS;AAAA,MACP,QAAQ,YAAY,IAAI;AAAA,MACxB,IAAI,YAAY,IAAI;AAAA,MACpB,OAAO,YAAY,IAAI;AAAA,IACzB;AAAA,IACA,QAAQ;AAAA,MACN,QAAQ,YAAY,IAAI;AAAA,MACxB,IAAI,MAAM,SACN,WAAW,IAAI,2BACf,YAAY,IAAI;AAAA,MACpB,OAAO,MAAM,SACT,WAAW,IAAI,2BACf,QAAQ,IAAI;AAAA,IAClB;AAAA,IACA,MAAM;AAAA,MACJ,QAAQ,YAAY,IAAI;AAAA,MACxB,IAAI,cAAc,IAAI;AAAA,MACtB,OAAO,cAAc,IAAI;AAAA,IAC3B;AAAA,IACA,YAAY;AAAA,MACV,QAAQ,SAAS,IAAI;AAAA,MACrB,IAAI,MAAM,SACN,WAAW,IAAI,mCACf,YAAY,IAAI;AAAA,MACpB,OAAO,SAAS,IAAI;AAAA,IACtB;AAAA,IACA,YAAY;AAAA,MACV,QAAQ,SAAS,IAAI;AAAA,MACrB,IAAI,SAAS,IAAI;AAAA,MACjB,OAAO,SAAS,IAAI;AAAA,IACtB;AAAA,IACA,MAAM;AAAA,MACJ,QAAQ,SAAS,IAAI;AAAA,MACrB,IAAI,UAAU,IAAI;AAAA,MAClB,OAAO,SAAS,IAAI;AAAA,IACtB;AAAA,EACF;AAEA,QAAM,YAAY,QAAQ,IAAc;AACxC,MAAI,CAAC,WAAW;AACd,UAAM,IAAI;AAAA,MACR,2BAA2B,MAAM,IAAI,gBAAgB,IAAI;AAAA,IAC3D;AAAA,EACF;AACA,SAAO,UAAU,OAAO;AAC1B;AAMO,IAAM,wBAAyC,OAAO;AAAA,EAC3D;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,WAAW,QAAQ;AACzB,QAAM,YAAYA,YAAW,QAAQ;AAErC,MAAI,OAAO,eAAe,EAAE,SAAS,OAAO,CAAC;AAE7C,aAAW,CAAC,UAAU,QAAQ,KAAK,OAAO,QAAQ,MAAM,GAAG;AACzD,UAAM,YAAY,aAAa,UAAU,QAAQ;AACjD,UAAM,SAAS,SAAS;AAMxB,UAAM,aAAa,OAAO,UAAU,eAAe,KAAK,QAAQ,UAAU;AAC1E,UAAM,KAAK,aAAa,yBAAyB;AAKjD,UAAM,SAAuB,CAAC;AAE9B,UAAM,eAAe,CAAC,UAAgC;AACpD,UAAI,CAAC,MAAM,OAAQ,QAAO;AAC1B,YAAM,QAAkB,CAAC,gBAAgB;AACzC,iBAAW,QAAQ,OAAO;AACxB,YAAI,KAAK,SAAS,cAAc;AAC9B,gBAAM,OAAO,KAAK,QAAQ,IAAI,CAAC,MAAM,SAAS,CAAC,EAAE,EAAE,KAAK,IAAI;AAC5D,gBAAM,KAAK,4BAA4B,IAAI,OAAO;AAAA,QACpD,OAAO;AACL,gBAAM,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,IAAI,eAAe,KAAK,EAAE,IAAI;AAAA,QACnE;AAAA,MACF;AACA,YAAM,KAAK,GAAG;AACd,aAAO,MAAM,KAAK,IAAI;AAAA,IACxB;AAEA,QAAI,YAAY;AACd,aAAO,KAAK,EAAE,MAAM,cAAc,SAAS,CAAC,YAAY,IAAI,EAAE,CAAC;AAAA,IACjE;AAEA,UAAM,cAAc,gBAAgB,QAAQ,MAAM,OAAO,UAAU,SAAS;AAAA,QACxE,EAAE;AAAA,IACN,OAAO,QAAQ,MAAM,EACpB,OAAO,CAAC,CAAC,SAAS,MAAM,cAAc,IAAI,EAC1C,IAAI,CAAC,CAAC,WAAW,IAAI,MAAM;AAC1B,YAAM,WAAW,KAAK,aAAa;AAEnC,UAAI,KAAK,SAAS,CAAC,KAAK,QAAQ;AAC9B,eAAO,KAAK;AAAA,UACV,MAAM;AAAA,UACN,MAAM,GAAG,QAAQ,IAAI,QAAQ;AAAA,UAC7B,IAAI;AAAA,QACN,CAAC;AAAA,MACH,WAAW,KAAK,SAAS,KAAK,QAAQ;AACpC,eAAO,KAAK;AAAA,UACV,MAAM;AAAA,UACN,MAAM,GAAG,QAAQ,IAAI,QAAQ;AAAA,UAC7B,IAAI;AAAA,QACN,CAAC;AAAA,MACH;AAEA,UAAI,MAAM,QAAQ,UAAU,MAAM,OAAO;AAEzC,UACE,KAAK,iBAAiB,QACtB,OAAO,KAAK,iBAAiB,aAC7B;AACA,YAAI,OAAO,KAAK,iBAAiB,YAAY;AAC3C,cACE,KAAK,SAAS,UACd,KAAK,aAAa,SAAS,EAAE,SAAS,YAAY,GAClD;AACA,gBAAI,YAAY,UAAU;AACxB,qBAAO;AAAA,YACT,OAAO;AACL,qBAAO;AAAA,YACT;AAAA,UACF;AAAA,QACF,WAAW,OAAO,KAAK,iBAAiB,UAAU;AAChD,iBAAO,aAAa,KAAK,YAAY;AAAA,QACvC,OAAO;AACL,iBAAO,YAAY,KAAK,YAAY;AAAA,QACtC;AAAA,MACF;AAEA,UAAI,KAAK,YAAY,KAAK,SAAS,QAAQ;AACzC,YAAI,OAAO,KAAK,aAAa,YAAY;AACvC,iBAAO,cAAc,KAAK,QAAQ;AAAA,QACpC;AAAA,MACF;AAEA,aAAO,GAAG,QAAQ,KAAK,GAAG,GAAG,KAAK,aAAa,QAAQ,eAAe,EAAE,GACtE,KAAK,SAAS,cAAc,EAC9B,GACE,KAAK,aACD,oBAAoB,KAAK,WAAW,KAAK,IAAI,KAAK,WAAW,SAAS,IAAI,kBACxE,KAAK,WAAW,YAAY,SAC9B,SACA,EACN;AAAA,IACF,CAAC,EACA,KAAK,OAAO,CAAC;AAAA,GACf,aAAa,MAAM,CAAC;AAEnB,YAAQ;AAAA,EAAK,WAAW;AAAA;AAAA,EAC1B;AAMA,MAAI,kBAAkB;AACtB,aAAW,CAAC,UAAU,QAAQ,KAAK,OAAO,QAAQ,MAAM,GAAG;AACzD,UAAM,YAAY;AAalB,UAAM,eAA2B,CAAC;AAClC,UAAM,gBAA4B,CAAC;AACnC,UAAM,mBAAmB,oBAAI,IAAY;AAGzC,eAAW,CAAC,WAAW,KAAK,KAAK,OAAO,QAAQ,SAAS,MAAM,GAAG;AAChE,UAAI,CAAC,MAAM,WAAY;AACvB,YAAM,kBAAkB,MAAM,WAAW;AACzC,YAAM,WAAW,MAAM,aAAa;AACpC,YAAM,WAAW,GAAG,QAAQ,IAAI,QAAQ;AACxC,YAAM,eAAe,GAAG,eAAe,IAAI,MAAM,WAAW,SAAS,IAAI;AAEzE,mBAAa,KAAK;AAAA,QAChB,KAAK;AAAA,QACL,OAAO;AAAA,QACP,MAAM;AAAA,QACN,WAAW;AAAA,UACT,OAAO;AAAA,UACP,YAAY;AAAA,UACZ;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAGA,eAAW,CAAC,UAAU,QAAQ,KAAK,OAAO,QAAQ,MAAM,GAAG;AACzD,UAAI,aAAa,SAAU;AAC3B,YAAM,QAAQ,OAAO,OAAO,SAAS,MAAM,EAAE;AAAA,QAC3C,CAAC,UAAU,MAAM,YAAY,UAAU;AAAA,MACzC;AACA,UAAI,CAAC,MAAO;AAEZ,YAAM,cAAc,GAAG,QAAQ;AAC/B,UAAI,CAAC,iBAAiB,IAAI,WAAW,GAAG;AACtC,yBAAiB,IAAI,WAAW;AAChC,sBAAc,KAAK;AAAA,UACjB,KAAK;AAAA,UACL,OAAO;AAAA,UACP,MAAM;AAAA,QACR,CAAC;AAAA,MACH;AAAA,IACF;AAGA,UAAM,mBAAmB,oBAAI,IAAwB;AACrD,eAAW,OAAO,cAAc;AAC9B,UAAI,CAAC,IAAI,UAAW;AACpB,YAAM,MAAM,iBAAiB,IAAI,IAAI,GAAG,KAAK,CAAC;AAC9C,UAAI,KAAK,GAAG;AACZ,uBAAiB,IAAI,IAAI,KAAK,GAAG;AAAA,IACnC;AAEA,UAAM,qBAAiC,CAAC;AACxC,UAAM,kBAA8B,CAAC;AAErC,eAAW,CAAC,EAAE,IAAI,KAAK,iBAAiB,QAAQ,GAAG;AACjD,UAAI,KAAK,SAAS,GAAG;AACnB,2BAAmB,KAAK,GAAG,IAAI;AAAA,MACjC,OAAO;AACL,wBAAgB,KAAK,KAAK,CAAC,CAAE;AAAA,MAC/B;AAAA,IACF;AAGA,eAAW,OAAO,oBAAoB;AACpC,UAAI,CAAC,IAAI,UAAW;AACpB,YAAM,gBAAgB,GAAG,SAAS,GAAG,IAAI,UAAU,UAAU,OAAO,CAAC,EAAE,YAAY,IAAI,IAAI,UAAU,UAAU,MAAM,CAAC,CAAC;AACvH,YAAM,QAAQ,gBAAgB,aAAa,gBAAgB,SAAS;AAAA,IACtE,IAAI,GAAG,SAAS,IAAI,KAAK;AAAA,eACd,IAAI,UAAU,KAAK;AAAA,mBACf,IAAI,UAAU,UAAU;AAAA;AAAA;AAGrC,yBAAmB;AAAA,EAAK,KAAK;AAAA;AAAA,IAC/B;AAGA,UAAM,SAAS,gBAAgB,SAAS;AACxC,UAAM,UAAU,cAAc,SAAS;AAEvC,QAAI,UAAU,SAAS;AACrB,YAAM,eAAe;AAAA,QACnB,SAAS,QAAQ;AAAA,QACjB,UAAU,SAAS;AAAA,MACrB,EACG,OAAO,OAAO,EACd,KAAK,IAAI;AAEZ,YAAM,OAAO;AAAA,QACX,GAAG,gBACA,OAAO,CAAC,MAAM,EAAE,SAAS,EACzB;AAAA,UACC,CAAC,MACC,KAAK,EAAE,GAAG,SAAS,EAAE,KAAK;AAAA,eAAqB,EAAE,UAAW,KAAK;AAAA,mBAAwB,EAAE,UAAW,UAAU;AAAA;AAAA,QACpH;AAAA,QACF,GAAG,cAAc;AAAA,UACf,CAAC,EAAE,KAAK,MAAM,MAAM,KAAK,GAAG,UAAU,KAAK;AAAA,QAC7C;AAAA,MACF,EAAE,KAAK,KAAK;AAEZ,YAAM,QAAQ,gBAAgB,SAAS,yBAAyB,SAAS,QAAQ,YAAY;AAAA,EACjG,IAAI;AAAA;AAEA,yBAAmB;AAAA,EAAK,KAAK;AAAA;AAAA,IAC/B;AAAA,EACF;AAEA,UAAQ;AAAA,EAAK,eAAe;AAE5B,SAAO;AAAA,IACL;AAAA,IACA,UAAU;AAAA,IACV,WAAW;AAAA,EACb;AACF;AAMA,SAAS,eAAe;AAAA,EACtB;AAAA,EACA;AACF,GAGG;AACD,QAAM,cAAwB,CAAC;AAC/B,QAAM,cAAwB,CAAC;AAE/B,MAAI,YAAY;AAChB,MAAI,UAAU;AACd,MAAI,aAAa;AACjB,MAAI,YAAY;AAChB,MAAI,UAAU;AACd,MAAI,WAAW;AACf,MAAI,iBAAiB;AACrB,MAAI,gBAAgB;AACpB,MAAI,yBAAyB;AAE7B,aAAW,CAAC,UAAU,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACtD,eAAW,SAAS,OAAO,OAAO,MAAM,MAAM,GAAG;AAC/C,UAAI,MAAM,OAAQ,aAAY;AAC9B,UAAI,MAAM,SAAS,OAAQ,WAAU;AACrC,UAAI,MAAM,SAAS,UAAW,cAAa;AAC3C,UAAI,MAAM,SAAS,YAAY,MAAM,SAAS,WAAY,aAAY;AACtE,UAAI,MAAM,SAAS,OAAQ,WAAU;AACrC,UAAI,MAAM,SAAS,CAAC,MAAM,OAAQ,YAAW;AAC7C,UAAI,MAAM,SAAS,MAAM,OAAQ,kBAAiB;AAClD,UAAI,MAAM,WAAY,iBAAgB;AAAA,IACxC;AAGA,QAAI,OAAO,UAAU,eAAe,KAAK,MAAM,QAAQ,UAAU,GAAG;AAClE,+BAAyB;AAAA,IAC3B;AAGA,SAAK;AAAA,EACP;AAEA,cAAY,KAAK,GAAG,OAAO,OAAO;AAClC,cAAY,KAAK,MAAM;AAEvB,MAAI,cAAc,YAAY,SAAU,aAAY,KAAK,SAAS;AAClE,MAAI,SAAS;AACX,QAAI,YAAY,KAAM,aAAY,KAAK,WAAW;AAAA,EAEpD;AACA,MAAI,aAAa,YAAY,UAAU;AACrC,QAAI,YAAY,KAAM,aAAY,KAAK,SAAS;AAAA,aACvC,YAAY,QAAS,aAAY,KAAK,KAAK;AAAA,QAC/C,aAAY,KAAK,SAAS;AAAA,EACjC;AACA,MAAI,aAAa,YAAY,SAAU,aAAY,KAAK,QAAQ;AAChE,MAAI,SAAS;AACX,QAAI,YAAY,KAAM,aAAY,KAAK,OAAO;AAAA,aACrC,YAAY,QAAS,aAAY,KAAK,MAAM;AAAA,EAEvD;AACA,MAAI,SAAU,aAAY,KAAK,OAAO;AACtC,MAAI,eAAgB,aAAY,KAAK,aAAa;AAClD,MAAI,uBAAwB,aAAY,KAAK,YAAY;AAGzD,MAAI,YAAY,aAAa,cAAc,UAAU;AACnD,QAAI,CAAC,YAAY,SAAS,SAAS,EAAG,aAAY,KAAK,SAAS;AAAA,EAClE;AAEA,MAAI,YAAY,YAAY,WAAW;AAAA,EAGvC;AAGA,QAAM,qBACJ,YAAY,YACZ,OAAO,OAAO,MAAM,EAAE;AAAA,IAAK,CAAC,UAC1B,OAAO,OAAO,MAAM,MAAM,EAAE;AAAA,MAC1B,CAAC,UACC,MAAM,SAAS,UACf,MAAM,gBACN,OAAO,MAAM,iBAAiB,cAC9B,MAAM,aAAa,SAAS,EAAE,SAAS,YAAY;AAAA,IACvD;AAAA,EACF;AAEF,MAAI,oBAAoB;AACtB,gBAAY,KAAK,KAAK;AAAA,EACxB;AAEA,MAAI,iBAAiB,YAAY,SAAS;AAAA,EAE1C;AAIA,MAAI,cAAe,aAAY,KAAK,WAAW;AAE/C,QAAM,eAAe,YAClB,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,CAAC,MAAM,MAAM,EAAE;AAGzB,QAAM,aAAa,CAAC,GAAG,IAAI,IAAI,YAAY,CAAC;AAC5C,QAAM,aAAa,CAAC,GAAG,IAAI,IAAI,WAAW,CAAC;AAE3C,SAAO,GAAG,WAAW,SAAS,IAAI,YAAY,WAAW,KAAK,IAAI,CAAC;AAAA,IAA6B,EAAE,YAAY,WAAW,KAAK,IAAI,CAAC,wBAAwB,OAAO;AAAA;AACpK;;;AF7cA,eAAe,eAAe,MAI3B;AACD,QAAM,MAAMC,MAAK,QAAQ,KAAK,GAAG;AACjC,MAAI,CAACC,YAAW,GAAG,GAAG;AACpB,YAAQ,MAAM,kBAAkB,GAAG,mBAAmB;AACtD,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,SAAS,MAAM,UAAU,EAAE,KAAK,YAAY,KAAK,OAAO,CAAC;AAC/D,MAAI,CAAC,QAAQ;AACX,YAAQ;AAAA,MACN;AAAA,IAEF;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,SAAS,eAAe,OAAO,OAAO;AAE5C,QAAM,SAAS,MAAM,sBAAsB;AAAA,IACzC;AAAA,IACA,SAAS,OAAO;AAAA,IAChB,MAAM,KAAK;AAAA,EACb,CAAC;AAED,MAAI,CAAC,OAAO,MAAM;AAChB,YAAQ,IAAI,+BAA+B;AAC3C,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,UAAUD,MAAK,QAAQ,KAAK,OAAO,QAAQ;AACjD,QAAM,SAASA,MAAK,QAAQ,OAAO;AACnC,MAAI,CAACC,YAAW,MAAM,GAAG;AACvB,UAAM,GAAG,MAAM,QAAQ,EAAE,WAAW,KAAK,CAAC;AAAA,EAC5C;AAEA,QAAM,GAAG,UAAU,SAAS,OAAO,IAAI;AACvC,UAAQ,IAAI,qBAAqBD,MAAK,SAAS,KAAK,OAAO,CAAC,EAAE;AAChE;AAEO,IAAM,WAAW,IAAI,QAAQ,UAAU,EAC3C,YAAY,yDAAyD,EACrE;AAAA,EACC;AAAA,EACA;AAAA,EACA,QAAQ,IAAI;AACd,EACC;AAAA,EACC;AAAA,EACA;AACF,EACC;AAAA,EACC;AAAA,EACA;AACF,EACC,OAAO,cAAc;;;AD7DxB,QAAQ,GAAG,UAAU,MAAM,QAAQ,KAAK,CAAC,CAAC;AAC1C,QAAQ,GAAG,WAAW,MAAM,QAAQ,KAAK,CAAC,CAAC;AAE3C,IAAM,UAAU,IAAIE,SAAQ,UAAU,EACnC,QAAQ,OAAO,EACf,YAAY,cAAc,EAC1B,WAAW,QAAQ,EACnB,OAAO,MAAM,QAAQ,KAAK,CAAC;AAE9B,QAAQ,MAAM;","names":["Command","existsSync","path","existsSync","path","existsSync","Command"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/commands/generate.ts","../../storage-core/src/factory.ts","../../sdk/src/ids.ts","../../sdk/src/scope.ts","../../sdk/src/errors.ts","../../sdk/src/types.ts","../../sdk/src/core-schema.ts","../../sdk/src/secrets.ts","../../sdk/src/elicitation.ts","../../sdk/src/blob.ts","../../sdk/src/executor.ts","../../storage-core/src/testing/memory.ts","../src/utils/get-config.ts","../src/generators/drizzle.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { Command } from \"commander\";\nimport { generate } from \"./commands/generate.js\";\n\nprocess.on(\"SIGINT\", () => process.exit(0));\nprocess.on(\"SIGTERM\", () => process.exit(0));\n\nconst program = new Command(\"executor\")\n .version(\"0.0.1\")\n .description(\"Executor CLI\")\n .addCommand(generate)\n .action(() => program.help());\n\nprogram.parse();\n","import { existsSync } from \"node:fs\";\nimport fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { Command } from \"commander\";\nimport { collectSchemas } from \"@executor/sdk/core\";\nimport { getConfig } from \"../utils/get-config.js\";\nimport { generateDrizzleSchema } from \"../generators/drizzle.js\";\n\nasync function generateAction(opts: {\n cwd: string;\n config?: string;\n output?: string;\n}) {\n const cwd = path.resolve(opts.cwd);\n if (!existsSync(cwd)) {\n console.error(`The directory \"${cwd}\" does not exist.`);\n process.exit(1);\n }\n\n const config = await getConfig({ cwd, configPath: opts.config });\n if (!config) {\n console.error(\n \"No configuration file found. Add an `executor.config.ts` file to \" +\n \"your project or pass the path using the `--config` flag.\",\n );\n process.exit(1);\n }\n\n const schema = collectSchemas(config.plugins);\n\n const result = await generateDrizzleSchema({\n schema,\n dialect: config.dialect,\n file: opts.output,\n });\n\n if (!result.code) {\n console.log(\"Schema is already up to date.\");\n process.exit(0);\n }\n\n const outPath = path.resolve(cwd, result.fileName);\n const outDir = path.dirname(outPath);\n if (!existsSync(outDir)) {\n await fs.mkdir(outDir, { recursive: true });\n }\n\n await fs.writeFile(outPath, result.code);\n console.log(`Schema generated: ${path.relative(cwd, outPath)}`);\n}\n\nexport const generate = new Command(\"generate\")\n .description(\"Generate a drizzle schema file from the executor config\")\n .option(\n \"-c, --cwd <cwd>\",\n \"the working directory\",\n process.cwd(),\n )\n .option(\n \"--config <config>\",\n \"path to the executor config file\",\n )\n .option(\n \"--output <output>\",\n \"output file path for the generated schema\",\n )\n .action(generateAction);\n","// ---------------------------------------------------------------------------\n// createAdapter — factory that wraps a CustomAdapter into a DBAdapter.\n//\n// Vendored from better-auth (packages/core/src/db/adapter/factory.ts) under\n// MIT. Adapted for executor:\n// - Promise/async → Effect.Effect<T, Error>\n// - Stripped auth-specific concerns (numeric serial ids, joins, telemetry\n// spans, logger, plural model name resolution, BetterAuthOptions)\n// - Contract matches our CustomAdapter + DBAdapterFactoryConfig in\n// ./adapter.ts (simpler than better-auth's equivalents)\n//\n// Responsibilities:\n// - id generation (auto + customIdGenerator + forceAllowId)\n// - transformInput: apply defaultValue / onUpdate / transform.input,\n// map logical field names → physical column names, serialize JSON /\n// dates / booleans / arrays based on supports* flags\n// - transformOutput: map physical column names → logical field names,\n// deserialize JSON / dates / booleans / arrays, apply transform.output,\n// filter by `returned: false`\n// - transformWhereClause: fill in CleanedWhere defaults, rename field,\n// re-encode RHS to match the write path\n// - createMany fallback: loop create when the CustomAdapter doesn't\n// implement it natively\n// - transaction: delegate to config.transaction when provided; fall back\n// to running the callback against the current adapter\n// ---------------------------------------------------------------------------\n\nimport { Effect } from \"effect\";\n\nimport type {\n CleanedWhere,\n CustomAdapter,\n DBAdapter,\n DBAdapterFactoryConfig,\n DBTransactionAdapter,\n JoinConfig,\n JoinOption,\n Where,\n} from \"./adapter\";\nimport type { DBFieldAttribute, DBSchema, DBPrimitive } from \"./schema\";\n\n// ---------------------------------------------------------------------------\n// Id generation\n// ---------------------------------------------------------------------------\n\nconst defaultGenerateId = (): string =>\n typeof crypto !== \"undefined\" && \"randomUUID\" in crypto\n ? crypto.randomUUID()\n : `id_${Math.random().toString(36).slice(2)}_${Date.now().toString(36)}`;\n\n// ---------------------------------------------------------------------------\n// Default value helpers — mirrors better-auth's `withApplyDefault`.\n// ---------------------------------------------------------------------------\n\nconst withApplyDefault = (\n value: unknown,\n field: DBFieldAttribute,\n action: \"create\" | \"update\",\n): unknown => {\n if (action === \"update\") {\n // Only apply onUpdate when the caller DID NOT supply a value. An explicit\n // `updatedAt: someDate` in the update payload should win over the\n // plugin's onUpdate hook — matches upstream.\n if (value === undefined && field.onUpdate !== undefined) {\n return field.onUpdate();\n }\n return value;\n }\n // Create: apply defaultValue only when the caller omitted the field, OR\n // when they passed null for a required field (upstream convention —\n // explicit null on an optional/nullable field is preserved). Without the\n // `required` gate we'd silently overwrite legitimate null writes.\n const triggerDefault =\n value === undefined || (field.required === true && value === null);\n if (triggerDefault && field.defaultValue !== undefined) {\n return typeof field.defaultValue === \"function\"\n ? (field.defaultValue as () => DBPrimitive)()\n : field.defaultValue;\n }\n return value;\n};\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\nexport interface CreateAdapterOptions {\n readonly schema: DBSchema;\n readonly config: DBAdapterFactoryConfig;\n readonly adapter: CustomAdapter;\n}\n\n/**\n * Wrap a CustomAdapter into a full DBAdapter that applies schema-driven\n * transforms. This is the single codepath every backend shares.\n */\nexport const createAdapter = (\n options: CreateAdapterOptions,\n): DBAdapter => {\n const { schema, adapter: inner } = options;\n const config: Required<\n Pick<\n DBAdapterFactoryConfig,\n | \"adapterId\"\n | \"supportsJSON\"\n | \"supportsDates\"\n | \"supportsBooleans\"\n | \"supportsArrays\"\n | \"disableIdGeneration\"\n >\n > & DBAdapterFactoryConfig = {\n ...options.config,\n supportsJSON: options.config.supportsJSON ?? false,\n supportsDates: options.config.supportsDates ?? true,\n supportsBooleans: options.config.supportsBooleans ?? true,\n supportsArrays: options.config.supportsArrays ?? false,\n disableIdGeneration: options.config.disableIdGeneration ?? false,\n };\n\n const idGen = (model: string): string => {\n if (config.customIdGenerator) {\n return config.customIdGenerator({ model });\n }\n return defaultGenerateId();\n };\n\n const getModelDef = (model: string): Effect.Effect<DBSchema[string], Error> =>\n Effect.gen(function* () {\n const def = schema[model];\n if (!def) {\n return yield* Effect.fail(\n new Error(`[storage-core] unknown model \"${model}\"`),\n );\n }\n return def;\n });\n\n // Sync accessor for call sites that can't sit inside Effect.gen (cleanWhere,\n // getModelName, getPhysicalField). These are all fed model names that have\n // already been validated upstream by the typed API, so unknown-model throws\n // here are a caller bug, not a runtime failure channel.\n const getModelDefSync = (model: string): DBSchema[string] => {\n const def = schema[model];\n if (!def) throw new Error(`[storage-core] unknown model \"${model}\"`);\n return def;\n };\n\n // Map physical table name → logical model key, for renaming incoming model\n // arg in mapKeysTransformInput/Output when callers pass physical names.\n // We deliberately *don't* support plural or physical-name inputs — our\n // plugins always pass the logical key — so getModelName is identity.\n const getModelName = (model: string): string =>\n getModelDefSync(model).modelName ?? model;\n\n // Field name (logical → physical). Honors mapKeysTransformInput override.\n const getPhysicalField = (model: string, logical: string): string => {\n if (logical === \"id\") return config.mapKeysTransformInput?.[\"id\"] ?? \"id\";\n const override = config.mapKeysTransformInput?.[logical];\n if (override) return override;\n const attr = getModelDefSync(model).fields[logical];\n return attr?.fieldName ?? logical;\n };\n\n // Inverse of mapKeysTransformOutput: on the output path we may need to\n // rename a logical field to a different output key for the caller (symmetric\n // to mapKeysTransformInput on the write path). Upstream better-auth wires\n // this in the same place.\n const getOutputKey = (logical: string): string =>\n config.mapKeysTransformOutput?.[logical] ?? logical;\n\n // ---------------------------------------------------------------------------\n // Value encode / decode based on supports* flags.\n // ---------------------------------------------------------------------------\n\n const encodeValue = (\n attr: DBFieldAttribute | undefined,\n value: unknown,\n ): unknown => {\n if (value === undefined) return undefined;\n if (value === null) return null;\n if (!attr) return value;\n const type = attr.type;\n if (type === \"json\") {\n if (!config.supportsJSON) return JSON.stringify(value);\n return value;\n }\n if (type === \"date\") {\n if (value instanceof Date) {\n return config.supportsDates ? value : value.toISOString();\n }\n if (typeof value === \"string\" && !config.supportsDates) {\n // Keep ISO strings as-is\n return value;\n }\n return value;\n }\n if (type === \"boolean\") {\n if (config.supportsBooleans) return value;\n return value ? 1 : 0;\n }\n if (\n (type === \"string[]\" || type === \"number[]\") &&\n Array.isArray(value) &&\n !config.supportsArrays\n ) {\n return JSON.stringify(value);\n }\n return value;\n };\n\n const decodeValue = (\n attr: DBFieldAttribute | undefined,\n value: unknown,\n ): unknown => {\n if (value === undefined || value === null) return value;\n if (!attr) return value;\n const type = attr.type;\n if (type === \"json\" && typeof value === \"string\") {\n try {\n return JSON.parse(value);\n } catch {\n return value;\n }\n }\n if (type === \"date\") {\n if (value instanceof Date) return value;\n if (typeof value === \"string\" || typeof value === \"number\") {\n return new Date(value);\n }\n return value;\n }\n if (type === \"boolean\" && typeof value === \"number\") {\n return value === 1;\n }\n if (\n (type === \"string[]\" || type === \"number[]\") &&\n typeof value === \"string\"\n ) {\n try {\n return JSON.parse(value);\n } catch {\n return value;\n }\n }\n if (type === \"number\" && typeof value === \"string\") {\n const n = Number(value);\n return Number.isNaN(n) ? value : n;\n }\n return value;\n };\n\n // ---------------------------------------------------------------------------\n // transformInput — logical row → physical row, with defaults + id gen\n // ---------------------------------------------------------------------------\n\n const transformInput = (\n model: string,\n data: Record<string, unknown>,\n action: \"create\" | \"update\",\n forceAllowId: boolean,\n ): Effect.Effect<Record<string, unknown>, Error> =>\n Effect.gen(function* () {\n const def = yield* getModelDef(model);\n const out: Record<string, unknown> = {};\n\n // id handling on create\n if (action === \"create\") {\n if (forceAllowId && \"id\" in data && data.id !== undefined && data.id !== null) {\n out[getPhysicalField(model, \"id\")] = data.id;\n } else if (!config.disableIdGeneration) {\n out[getPhysicalField(model, \"id\")] = idGen(model);\n }\n }\n\n for (const [logical, attr] of Object.entries(def.fields)) {\n if (logical === \"id\") continue;\n if (attr.input === false) continue;\n let value: unknown = data[logical];\n\n // Date coercion from string\n if (\n attr.type === \"date\" &&\n value !== undefined &&\n value !== null &&\n !(value instanceof Date) &&\n typeof value === \"string\"\n ) {\n try {\n value = new Date(value);\n } catch {\n // leave as-is\n }\n }\n\n // defaultValue / onUpdate\n value = withApplyDefault(value, attr, action);\n\n // transform.input\n if (attr.transform?.input) {\n const res = attr.transform.input(value as DBPrimitive);\n // Sync only in executor path; if a plugin returns a Promise we\n // await it via tryPromise to keep the Effect pure.\n if (res && typeof (res as { then?: unknown }).then === \"function\") {\n value = yield* Effect.tryPromise({\n try: () => res as Promise<DBPrimitive>,\n catch: (e) =>\n new Error(\n `[storage-core] transform.input for \"${model}.${logical}\" failed: ${String(e)}`,\n ),\n });\n } else {\n value = res;\n }\n }\n\n if (value === undefined) continue;\n\n const physical = getPhysicalField(model, logical);\n let encoded = encodeValue(attr, value);\n\n // customTransformInput — user-land per-field hook, runs after the\n // built-in encode step. Effect-ified from upstream's sync `any`\n // return — plugins that don't need async work can wrap with\n // `Effect.succeed`.\n if (config.customTransformInput) {\n encoded = yield* config.customTransformInput({\n data: encoded,\n fieldAttributes: attr,\n field: physical,\n action,\n model: getModelName(model),\n schema,\n });\n }\n\n out[physical] = encoded;\n }\n\n return out;\n });\n\n // ---------------------------------------------------------------------------\n // transformOutput — physical row → logical row, filter `returned: false`\n // ---------------------------------------------------------------------------\n\n const transformOutput = (\n model: string,\n row: Record<string, unknown> | null,\n select?: string[],\n ): Effect.Effect<Record<string, unknown> | null, Error> =>\n Effect.gen(function* () {\n if (row === null || row === undefined) return null;\n const def = yield* getModelDef(model);\n const out: Record<string, unknown> = {};\n\n // id always returned\n const idPhysical = getPhysicalField(model, \"id\");\n const idOutputKey = getOutputKey(\"id\");\n if (idPhysical in row && row[idPhysical] !== undefined) {\n out[idOutputKey] = row[idPhysical];\n } else if (\"id\" in row) {\n out[idOutputKey] = row[\"id\"];\n }\n\n for (const [logical, attr] of Object.entries(def.fields)) {\n if (logical === \"id\") continue;\n if (attr.returned === false) continue;\n if (select && select.length > 0 && !select.includes(logical)) continue;\n\n const physical = getPhysicalField(model, logical);\n if (!(physical in row)) continue;\n\n let value: unknown = decodeValue(attr, row[physical]);\n\n if (attr.transform?.output) {\n const res = attr.transform.output(value as DBPrimitive);\n if (res && typeof (res as { then?: unknown }).then === \"function\") {\n value = yield* Effect.tryPromise({\n try: () => res as Promise<DBPrimitive>,\n catch: (e) =>\n new Error(\n `[storage-core] transform.output for \"${model}.${logical}\" failed: ${String(e)}`,\n ),\n });\n } else {\n value = res;\n }\n }\n\n // customTransformOutput — user-land per-field hook, runs after the\n // built-in decode step. Mirrors upstream threading.\n if (config.customTransformOutput) {\n value = yield* config.customTransformOutput({\n data: value,\n fieldAttributes: attr,\n field: logical,\n select: select ?? [],\n model: getModelName(model),\n schema,\n });\n }\n\n out[getOutputKey(logical)] = value;\n }\n return out;\n });\n\n // ---------------------------------------------------------------------------\n // transformWhereClause — Where[] → CleanedWhere[]\n //\n // Fills in defaults, renames logical → physical, re-encodes RHS so that\n // filter comparisons line up with the wire representation produced by\n // transformInput.\n // ---------------------------------------------------------------------------\n\n // ---------------------------------------------------------------------------\n // Join resolver — JoinOption (caller) → JoinConfig (custom adapter)\n //\n // For each requested join target `T` (keyed by logical model name), we\n // look at both sides of the schema:\n //\n // - If the *base* model has a field whose `references.model === T`,\n // this is a \"child → parent\" lookup: relation = one-to-one,\n // `on.from` = the base field, `on.to` = the referenced field on T.\n // - Otherwise, if model T has a field whose `references.model === base`,\n // this is a \"parent → children\" lookup: relation = one-to-many,\n // `on.from` = the referenced field on base (usually \"id\"),\n // `on.to` = the field on T carrying the FK.\n //\n // If neither side declares a reference we throw — a caller asking for a\n // join that the schema can't resolve is a bug, not a runtime state.\n // ---------------------------------------------------------------------------\n\n const resolveJoin = (base: string, join: JoinOption): JoinConfig => {\n const baseDef = getModelDefSync(base);\n const out: JoinConfig = {};\n for (const [target, raw] of Object.entries(join)) {\n if (raw === false) continue;\n const targetDef = getModelDefSync(target);\n const limit =\n typeof raw === \"object\" && raw.limit !== undefined ? raw.limit : undefined;\n\n // child → parent\n let found: JoinConfig[string] | undefined;\n for (const [fieldName, attr] of Object.entries(baseDef.fields)) {\n if (attr.references?.model === target) {\n found = {\n on: {\n from: getPhysicalField(base, fieldName),\n to:\n getPhysicalField(target, attr.references.field) ||\n attr.references.field,\n },\n relation: \"one-to-one\",\n ...(limit !== undefined ? { limit } : {}),\n };\n break;\n }\n }\n // parent → children\n if (!found) {\n for (const [fieldName, attr] of Object.entries(targetDef.fields)) {\n if (attr.references?.model === base) {\n found = {\n on: {\n from:\n getPhysicalField(base, attr.references.field) ||\n attr.references.field,\n to: getPhysicalField(target, fieldName),\n },\n relation: \"one-to-many\",\n ...(limit !== undefined ? { limit } : {}),\n };\n break;\n }\n }\n }\n if (!found) {\n throw new Error(\n `[storage-core] cannot resolve join \"${base}\" → \"${target}\": neither model declares a \\`references\\` for the other`,\n );\n }\n out[target] = found;\n }\n return out;\n };\n\n const cleanWhere = (\n model: string,\n where: readonly Where[] | undefined,\n ): CleanedWhere[] | undefined => {\n if (!where) return undefined;\n const def = getModelDefSync(model);\n return where.map((w) => {\n const operator = w.operator ?? \"eq\";\n const connector = w.connector ?? \"AND\";\n const mode = w.mode ?? \"sensitive\";\n const logical = w.field;\n const attr =\n logical === \"id\" ? undefined : def.fields[logical];\n const physical = getPhysicalField(model, logical);\n\n let value: Where[\"value\"] = w.value;\n if (attr) {\n if (Array.isArray(value)) {\n value = (value as unknown[]).map((v) =>\n encodeValue(attr, v),\n ) as typeof value;\n } else {\n value = encodeValue(attr, value) as typeof value;\n }\n }\n\n return {\n operator,\n connector,\n mode,\n field: physical,\n value,\n } satisfies CleanedWhere;\n });\n };\n\n // ---------------------------------------------------------------------------\n // Transform skip helpers — disableTransformInput/Output let backend authors\n // bypass the factory's built-in transform when they want the raw shape\n // passed through. Matches upstream's `if (!config.disableTransform*)`.\n // ---------------------------------------------------------------------------\n\n const maybeTransformInput = (\n model: string,\n data: Record<string, unknown>,\n action: \"create\" | \"update\",\n forceAllowId: boolean,\n ): Effect.Effect<Record<string, unknown>, Error> =>\n config.disableTransformInput\n ? Effect.succeed(data)\n : transformInput(model, data, action, forceAllowId);\n\n // ---------------------------------------------------------------------------\n // attachJoinedRows — re-decode nested join payloads.\n //\n // `transformOutput` only knows about the base model's fields and drops\n // anything else. If the caller asked for `join: { tag: true }` we need\n // to run the nested rows through `transformOutput` keyed on the target\n // model and then stick the decoded payload onto the base row under the\n // logical join key. Mirrors the upstream factory's nested-result path.\n // ---------------------------------------------------------------------------\n\n const attachJoinedRows = (\n base: Record<string, unknown> | null,\n raw: Record<string, unknown> | null,\n join: JoinOption | undefined,\n ): Effect.Effect<Record<string, unknown> | null, Error> =>\n Effect.gen(function* () {\n if (!base || !raw || !join) return base;\n const merged: Record<string, unknown> = { ...base };\n for (const [target, flag] of Object.entries(join)) {\n if (flag === false) continue;\n const nested = raw[target];\n if (nested === undefined) continue;\n if (nested === null) {\n merged[target] = null;\n continue;\n }\n if (Array.isArray(nested)) {\n const decoded: unknown[] = [];\n for (const n of nested) {\n if (n && typeof n === \"object\") {\n const t = yield* transformOutput(\n target,\n n as Record<string, unknown>,\n );\n decoded.push(t);\n } else {\n decoded.push(n);\n }\n }\n merged[target] = decoded;\n } else if (typeof nested === \"object\") {\n merged[target] = yield* transformOutput(\n target,\n nested as Record<string, unknown>,\n );\n } else {\n merged[target] = nested;\n }\n }\n return merged;\n });\n\n const maybeTransformOutput = (\n model: string,\n row: Record<string, unknown> | null,\n select?: string[],\n ): Effect.Effect<Record<string, unknown> | null, Error> =>\n config.disableTransformOutput\n ? Effect.succeed(row)\n : transformOutput(model, row, select);\n\n // ---------------------------------------------------------------------------\n // DBAdapter surface\n // ---------------------------------------------------------------------------\n\n const self: DBAdapter = {\n id: config.adapterId,\n\n create: <T extends Record<string, unknown>, R = T>(data: {\n model: string;\n data: Omit<T, \"id\">;\n select?: string[] | undefined;\n forceAllowId?: boolean | undefined;\n }) =>\n Effect.gen(function* () {\n const input = yield* maybeTransformInput(\n data.model,\n data.data as Record<string, unknown>,\n \"create\",\n data.forceAllowId === true,\n );\n const res = yield* inner.create({\n model: getModelName(data.model),\n data: input,\n select: data.select,\n });\n const out = yield* maybeTransformOutput(\n data.model,\n res as Record<string, unknown>,\n data.select,\n );\n return out as unknown as R;\n }),\n\n createMany: <T extends Record<string, unknown>, R = T>(data: {\n model: string;\n data: ReadonlyArray<Omit<T, \"id\">>;\n forceAllowId?: boolean | undefined;\n }) =>\n Effect.gen(function* () {\n // Delegates straight to the backend's native bulk insert — no\n // per-row fallback, because over a real network connection\n // (Hyperdrive, etc.) N round-trips would blow the request\n // budget for specs with thousands of operations. Transforms\n // still run per-row so JSON / dates / booleans serialize the\n // same as single `create`.\n const inputs: Record<string, unknown>[] = [];\n for (const row of data.data) {\n inputs.push(\n yield* maybeTransformInput(\n data.model,\n row as Record<string, unknown>,\n \"create\",\n data.forceAllowId === true,\n ),\n );\n }\n const res = yield* inner.createMany({\n model: getModelName(data.model),\n data: inputs,\n });\n const out: R[] = [];\n for (const row of res) {\n out.push(\n (yield* maybeTransformOutput(\n data.model,\n row as Record<string, unknown>,\n undefined,\n )) as unknown as R,\n );\n }\n return out as unknown as readonly R[];\n }),\n\n findOne: <T>(data: {\n model: string;\n where: Where[];\n select?: string[] | undefined;\n join?: JoinOption | undefined;\n }) =>\n Effect.gen(function* () {\n const where = cleanWhere(data.model, data.where) ?? [];\n const join = data.join ? resolveJoin(data.model, data.join) : undefined;\n const res = yield* inner.findOne<Record<string, unknown>>({\n model: getModelName(data.model),\n where,\n select: data.select,\n join,\n });\n const out = yield* maybeTransformOutput(data.model, res, data.select);\n const merged = yield* attachJoinedRows(out, res, data.join);\n return merged as unknown as T | null;\n }),\n\n findMany: <T>(data: {\n model: string;\n where?: Where[] | undefined;\n limit?: number | undefined;\n select?: string[] | undefined;\n sortBy?: { field: string; direction: \"asc\" | \"desc\" } | undefined;\n offset?: number | undefined;\n join?: JoinOption | undefined;\n }) =>\n Effect.gen(function* () {\n const where = cleanWhere(data.model, data.where);\n const sortBy = data.sortBy\n ? {\n field: getPhysicalField(data.model, data.sortBy.field),\n direction: data.sortBy.direction,\n }\n : undefined;\n const join = data.join ? resolveJoin(data.model, data.join) : undefined;\n const res = yield* inner.findMany<Record<string, unknown>>({\n model: getModelName(data.model),\n where,\n limit: data.limit,\n select: data.select,\n sortBy,\n offset: data.offset,\n join,\n });\n const out: unknown[] = [];\n for (const r of res) {\n const t = yield* maybeTransformOutput(data.model, r, data.select);\n const merged = yield* attachJoinedRows(t, r, data.join);\n out.push(merged);\n }\n return out as readonly T[];\n }),\n\n count: (data: { model: string; where?: Where[] | undefined }) =>\n Effect.gen(function* () {\n const where = cleanWhere(data.model, data.where);\n return yield* inner.count({\n model: getModelName(data.model),\n where,\n });\n }),\n\n update: <T>(data: {\n model: string;\n where: Where[];\n update: Record<string, unknown>;\n }) =>\n Effect.gen(function* () {\n const where = cleanWhere(data.model, data.where) ?? [];\n const update = yield* maybeTransformInput(\n data.model,\n data.update,\n \"update\",\n false,\n );\n const res = yield* inner.update<Record<string, unknown>>({\n model: getModelName(data.model),\n where,\n update,\n });\n const out = yield* maybeTransformOutput(data.model, res);\n return out as unknown as T | null;\n }),\n\n updateMany: (data: {\n model: string;\n where: Where[];\n update: Record<string, unknown>;\n }) =>\n Effect.gen(function* () {\n const where = cleanWhere(data.model, data.where) ?? [];\n const update = yield* maybeTransformInput(\n data.model,\n data.update,\n \"update\",\n false,\n );\n return yield* inner.updateMany({\n model: getModelName(data.model),\n where,\n update,\n });\n }),\n\n delete: (data: { model: string; where: Where[] }) =>\n Effect.gen(function* () {\n const where = cleanWhere(data.model, data.where) ?? [];\n yield* inner.delete({\n model: getModelName(data.model),\n where,\n });\n }),\n\n deleteMany: (data: { model: string; where: Where[] }) =>\n Effect.gen(function* () {\n const where = cleanWhere(data.model, data.where) ?? [];\n return yield* inner.deleteMany({\n model: getModelName(data.model),\n where,\n });\n }),\n\n transaction: <R, E>(\n callback: (trx: DBTransactionAdapter) => Effect.Effect<R, E>,\n ) => {\n const txFn = config.transaction;\n if (!txFn) {\n // No real transaction support — just run the callback against self.\n return callback(self);\n }\n return txFn(callback);\n },\n\n // Forward the backend's createSchema verbatim. Upstream better-auth\n // mutates the `tables` set here to drop session when secondaryStorage\n // is set; we intentionally don't replicate that auth-specific concern.\n createSchema: inner.createSchema\n ? (props) => inner.createSchema!(props)\n : undefined,\n\n // Expose the full factory config + the inner adapter's own options to\n // plugin authors at runtime. Mirrors upstream's `options` field on\n // DBAdapter.\n options: {\n adapterConfig: options.config,\n ...(inner.options ?? {}),\n },\n };\n\n return self;\n};\n","import { Schema } from \"effect\";\n\nexport const ScopeId = Schema.String.pipe(Schema.brand(\"ScopeId\"));\nexport type ScopeId = typeof ScopeId.Type;\n\nexport const ToolId = Schema.String.pipe(Schema.brand(\"ToolId\"));\nexport type ToolId = typeof ToolId.Type;\n\nexport const SecretId = Schema.String.pipe(Schema.brand(\"SecretId\"));\nexport type SecretId = typeof SecretId.Type;\n\nexport const PolicyId = Schema.String.pipe(Schema.brand(\"PolicyId\"));\nexport type PolicyId = typeof PolicyId.Type;\n","import { Schema } from \"effect\";\n\nimport { ScopeId } from \"./ids\";\n\nexport class Scope extends Schema.Class<Scope>(\"Scope\")({\n id: ScopeId,\n name: Schema.String,\n createdAt: Schema.DateFromNumber,\n}) {}\n","import { Data, Schema } from \"effect\";\n\nimport { ToolId, SecretId } from \"./ids\";\n\n// ---------------------------------------------------------------------------\n// Tool lifecycle\n// ---------------------------------------------------------------------------\n\nexport class ToolNotFoundError extends Schema.TaggedError<ToolNotFoundError>()(\n \"ToolNotFoundError\",\n { toolId: ToolId },\n) {}\n\nexport class ToolInvocationError extends Data.TaggedError(\"ToolInvocationError\")<{\n readonly toolId: ToolId;\n readonly message: string;\n readonly cause?: unknown;\n}> {}\n\n/** Tool row exists in the DB but its owning plugin isn't loaded. Means\n * the tool was registered by a plugin that's no longer present in the\n * current executor config — usually a stale row from an older session. */\nexport class PluginNotLoadedError extends Schema.TaggedError<PluginNotLoadedError>()(\n \"PluginNotLoadedError\",\n {\n pluginId: Schema.String,\n toolId: ToolId,\n },\n) {}\n\n/** Tool was found but its owning plugin has no `invokeTool` handler —\n * the plugin only declares static tools and this one's id matched\n * dynamically somehow. Shouldn't happen in practice; guards against\n * programmer error. */\nexport class NoHandlerError extends Schema.TaggedError<NoHandlerError>()(\n \"NoHandlerError\",\n {\n toolId: ToolId,\n pluginId: Schema.String,\n },\n) {}\n\n// ---------------------------------------------------------------------------\n// Source lifecycle\n// ---------------------------------------------------------------------------\n\nexport class SourceNotFoundError extends Schema.TaggedError<SourceNotFoundError>()(\n \"SourceNotFoundError\",\n { sourceId: Schema.String },\n) {}\n\n/** `executor.sources.remove(id)` was called on a source with\n * `canRemove: false` — typically a static source declared by a plugin\n * at startup. Removing static sources is a bug in the caller. */\nexport class SourceRemovalNotAllowedError extends Schema.TaggedError<SourceRemovalNotAllowedError>()(\n \"SourceRemovalNotAllowedError\",\n { sourceId: Schema.String },\n) {}\n\n// ---------------------------------------------------------------------------\n// Secrets\n// ---------------------------------------------------------------------------\n\nexport class SecretNotFoundError extends Schema.TaggedError<SecretNotFoundError>()(\n \"SecretNotFoundError\",\n { secretId: SecretId },\n) {}\n\nexport class SecretResolutionError extends Schema.TaggedError<SecretResolutionError>()(\n \"SecretResolutionError\",\n {\n secretId: SecretId,\n message: Schema.String,\n },\n) {}\n\n// ---------------------------------------------------------------------------\n// Union type for convenience in signatures.\n// ---------------------------------------------------------------------------\n\nexport type ExecutorError =\n | ToolNotFoundError\n | ToolInvocationError\n | PluginNotLoadedError\n | NoHandlerError\n | SourceNotFoundError\n | SourceRemovalNotAllowedError\n | SecretNotFoundError\n | SecretResolutionError;\n","// ---------------------------------------------------------------------------\n// Public projections — what consumers see when they call\n// `executor.sources.list()` / `executor.tools.list()`. Deliberately leaner\n// than the row shapes in core-schema.ts: no audit columns, no raw JSON.\n// ---------------------------------------------------------------------------\n\nimport { Schema } from \"effect\";\n\nimport type { ToolAnnotations } from \"./core-schema\";\nimport { ToolId } from \"./ids\";\n\nexport interface Source {\n readonly id: string;\n readonly kind: string;\n readonly name: string;\n readonly url?: string;\n /** Which plugin owns this source. */\n readonly pluginId: string;\n /** Whether the user can remove this source via\n * `executor.sources.remove(id)`. `false` for static / built-in\n * sources declared by plugins at startup. */\n readonly canRemove: boolean;\n /** Whether the plugin supports `executor.sources.refresh(id)`. */\n readonly canRefresh: boolean;\n /** Whether the source has editable config (headers, base url, etc.).\n * Editing is done via plugin-specific extension methods\n * (`executor.openapi.updateSource(id, patch)` etc.) — this flag is\n * just a UI signal. */\n readonly canEdit: boolean;\n /** True if the source was declared statically by a plugin at startup\n * (in-memory only, no DB row). False if it was added at runtime via\n * `ctx.core.sources.register(...)`. UI differentiates built-in vs\n * user-added with this. */\n readonly runtime: boolean;\n}\n\nexport interface Tool {\n readonly id: string;\n readonly sourceId: string;\n /** Which plugin owns this tool. Matches the owning source's `pluginId`. */\n readonly pluginId: string;\n readonly name: string;\n readonly description: string;\n readonly inputSchema?: unknown;\n readonly outputSchema?: unknown;\n readonly annotations?: ToolAnnotations;\n}\n\n// ---------------------------------------------------------------------------\n// ToolSchema — the full schema-side view of a tool, returned by\n// `executor.tools.schema(toolId)`. Includes JSON schemas with `$defs`\n// attached at read time AND TypeScript preview strings rendered from\n// them via `schemaToTypeScriptPreview`. The UI uses the TS previews to\n// show \"calling this tool looks like this\" code samples.\n// ---------------------------------------------------------------------------\n\nexport class ToolSchema extends Schema.Class<ToolSchema>(\"ToolSchema\")({\n id: ToolId,\n name: Schema.optional(Schema.String),\n description: Schema.optional(Schema.String),\n inputSchema: Schema.optional(Schema.Unknown),\n outputSchema: Schema.optional(Schema.Unknown),\n inputTypeScript: Schema.optional(Schema.String),\n outputTypeScript: Schema.optional(Schema.String),\n typeScriptDefinitions: Schema.optional(\n Schema.Record({ key: Schema.String, value: Schema.String }),\n ),\n}) {}\n\n// ---------------------------------------------------------------------------\n// Source detection — optional capability on `PluginSpec.detect`. When a\n// user pastes a URL in the onboarding UI, `executor.sources.detect(url)`\n// asks every plugin \"is this yours?\" and returns the best-confidence\n// match so the UI can auto-fill the onboarding form for the right\n// plugin.\n// ---------------------------------------------------------------------------\n\nexport class SourceDetectionResult extends Schema.Class<SourceDetectionResult>(\n \"SourceDetectionResult\",\n)({\n /** Plugin id that recognized the URL (e.g. \"openapi\", \"graphql\"). */\n kind: Schema.String,\n /** Confidence tier — UI uses this to pick a winner when multiple\n * plugins claim a URL. */\n confidence: Schema.Literal(\"high\", \"medium\", \"low\"),\n /** The (possibly normalized) endpoint the plugin will use. */\n endpoint: Schema.String,\n /** Human-readable name suggestion, typically derived from spec title\n * or URL hostname. */\n name: Schema.String,\n /** Namespace suggestion — the plugin's recommendation for the source\n * id. UI may override. */\n namespace: Schema.String,\n}) {}\n\n// ---------------------------------------------------------------------------\n// Filter passed to `executor.tools.list(...)`. Empty filter = all tools.\n// ---------------------------------------------------------------------------\n\nexport interface ToolListFilter {\n /** Only tools under this source id. */\n readonly sourceId?: string;\n /** Case-insensitive substring match against `name` OR `description`. */\n readonly query?: string;\n}\n","// ---------------------------------------------------------------------------\n// Core data model — the SDK owns these tables. Plugins write into them via\n// `ctx.core.sources.register(...)` and `ctx.core.definitions.register(...)`;\n// the executor reads from them directly on every list / invoke / schema\n// call. There is no in-memory registry layered on top.\n//\n// Static (code-declared) sources and tools are NOT in these tables — they\n// live in an in-memory map built at executor startup from each plugin's\n// `staticSources` declaration. See executor.ts. The DB only holds\n// dynamic (runtime-registered) rows.\n// ---------------------------------------------------------------------------\n\nimport type {\n DBSchema,\n InferDBFieldsOutput,\n} from \"@executor/storage-core\";\n\nexport const coreSchema = {\n source: {\n fields: {\n id: { type: \"string\", required: true },\n scope_id: { type: \"string\", required: true, index: true },\n plugin_id: { type: \"string\", required: true, index: true },\n kind: { type: \"string\", required: true },\n name: { type: \"string\", required: true },\n url: { type: \"string\", required: false },\n can_remove: {\n type: \"boolean\",\n required: true,\n defaultValue: true,\n },\n can_refresh: {\n type: \"boolean\",\n required: true,\n defaultValue: false,\n },\n can_edit: {\n type: \"boolean\",\n required: true,\n defaultValue: false,\n },\n created_at: { type: \"date\", required: true },\n updated_at: { type: \"date\", required: true },\n },\n },\n tool: {\n fields: {\n id: { type: \"string\", required: true },\n scope_id: { type: \"string\", required: true, index: true },\n source_id: { type: \"string\", required: true, index: true },\n plugin_id: { type: \"string\", required: true, index: true },\n name: { type: \"string\", required: true },\n description: { type: \"string\", required: true },\n input_schema: { type: \"json\", required: false },\n output_schema: { type: \"json\", required: false },\n // NOTE: tool annotations (requiresApproval, approvalDescription,\n // mayElicit) are NOT stored on this row. They're derived at read\n // time from plugin-owned data via `plugin.resolveAnnotations`,\n // because the source of truth already lives in each plugin's own\n // storage (openapi's OperationBinding, etc.) and duplicating it\n // here would just mean bulk-rewriting rows every time the\n // derivation logic changes.\n created_at: { type: \"date\", required: true },\n updated_at: { type: \"date\", required: true },\n },\n },\n // Shared JSON-schema `$defs` stored once per source. Tool input/output\n // schemas carry `$ref: \"#/$defs/X\"` pointers; the read path attaches\n // matching defs under `$defs` before returning. Keyed by synthetic id\n // `${source_id}.${name}` so cleanup on source removal is a single\n // deleteMany by source_id.\n definition: {\n fields: {\n id: { type: \"string\", required: true },\n scope_id: { type: \"string\", required: true, index: true },\n source_id: { type: \"string\", required: true, index: true },\n plugin_id: { type: \"string\", required: true, index: true },\n name: { type: \"string\", required: true },\n schema: { type: \"json\", required: true },\n created_at: { type: \"date\", required: true },\n },\n },\n // Secrets live in the core surface as metadata (id, display name,\n // provider key). Actual values never touch this table — they live in\n // the secret provider (keychain, 1password, file, etc.) and are\n // resolved on demand via `ctx.secrets.get(id)`.\n secret: {\n fields: {\n id: { type: \"string\", required: true },\n scope_id: { type: \"string\", required: true, index: true },\n name: { type: \"string\", required: true },\n provider: { type: \"string\", required: true, index: true },\n created_at: { type: \"date\", required: true },\n },\n },\n} as const satisfies DBSchema;\n\nexport type CoreSchema = typeof coreSchema;\n\n// ---------------------------------------------------------------------------\n// Row types — derived from the schema. Adding a field to coreSchema.fields\n// adds it to the row type automatically.\n// ---------------------------------------------------------------------------\n\nexport type SourceRow = InferDBFieldsOutput<CoreSchema[\"source\"][\"fields\"]> &\n Record<string, unknown>;\n\nexport type ToolRow = InferDBFieldsOutput<CoreSchema[\"tool\"][\"fields\"]> &\n Record<string, unknown>;\n\nexport type DefinitionRow = InferDBFieldsOutput<\n CoreSchema[\"definition\"][\"fields\"]\n> &\n Record<string, unknown>;\n\nexport type SecretRow = InferDBFieldsOutput<CoreSchema[\"secret\"][\"fields\"]> &\n Record<string, unknown>;\n\n// ---------------------------------------------------------------------------\n// Tool annotations — default-policy metadata the executor consults\n// before invocation. Returned by `plugin.resolveAnnotations` (dynamic\n// tools) or declared inline on `StaticToolDecl` (static tools). Never\n// stored on `tool` rows — every field here is derived at read time\n// from plugin-owned data.\n//\n// OpenAPI derives from HTTP method:\n// - GET / HEAD / OPTIONS → {} (auto-approved)\n// - POST / PUT / PATCH / DELETE → { requiresApproval: true,\n// approvalDescription: \"DELETE /users/:id\" }\n//\n// MCP derives from the server's tool declaration (mcp has its own\n// may-elicit and approval signals).\n// ---------------------------------------------------------------------------\n\nexport interface ToolAnnotations {\n /** If true, the executor will call the invoke-time elicitation handler\n * before running the tool and abort if the user declines. */\n readonly requiresApproval?: boolean;\n /** Free-text message shown in the approval prompt. Falls back to the\n * tool's id / description if unset. */\n readonly approvalDescription?: string;\n /** Hint for UI — tool may suspend to ask the user for input mid-invocation.\n * Not enforced by the executor; purely a UI signal. */\n readonly mayElicit?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// SourceInput — what a plugin passes to `ctx.core.sources.register(...)`.\n// Writes both the source row and all its tool rows in one transaction.\n// Annotations are NOT part of this input — they're computed from\n// plugin-owned data via `plugin.resolveAnnotations` when the executor\n// needs them.\n// ---------------------------------------------------------------------------\n\nexport interface SourceInputTool {\n readonly name: string;\n readonly description: string;\n readonly inputSchema?: unknown;\n readonly outputSchema?: unknown;\n}\n\nexport interface SourceInput {\n readonly id: string;\n readonly kind: string;\n readonly name: string;\n readonly url?: string;\n readonly canRemove?: boolean;\n readonly canRefresh?: boolean;\n readonly canEdit?: boolean;\n readonly tools: readonly SourceInputTool[];\n}\n\n// ---------------------------------------------------------------------------\n// DefinitionsInput — paired with SourceInput when a plugin registers\n// shared JSON-schema `$defs` alongside a source. Usually called inside\n// the same `ctx.transaction` as `sources.register` so a failure rolls\n// back both the source rows and the def rows.\n// ---------------------------------------------------------------------------\n\nexport interface DefinitionsInput {\n readonly sourceId: string;\n readonly definitions: Record<string, unknown>;\n}\n","import { Effect, Schema } from \"effect\";\n\nimport { SecretId, ScopeId } from \"./ids\";\n\n// ---------------------------------------------------------------------------\n// SecretProvider — what a concrete backend (keychain, 1password, file,\n// memory, workos-vault, …) implements. Providers are contributed by\n// plugins via `plugin.secretProviders` and registered in the executor\n// at startup; there's no runtime registration.\n//\n// The `key` field is the provider's identifier in the secret table's\n// `provider` column and in `executor.secrets.set(id, value, provider?)`.\n// Unique per executor.\n// ---------------------------------------------------------------------------\n\nexport interface SecretProvider {\n /** Unique key (e.g. \"keychain\", \"env\", \"1password\", \"memory\"). */\n readonly key: string;\n /** If false, `set` and `delete` are never called. The executor\n * honours this before routing writes — trying to write to a\n * read-only provider is an error, not a silent drop. */\n readonly writable: boolean;\n /** Get a secret value by id. Returns null if not found. Errors are\n * real failures (provider unreachable, decryption failed, etc.). */\n readonly get: (id: string) => Effect.Effect<string | null, Error>;\n /** Set a secret value. Only called on writable providers. */\n readonly set?: (id: string, value: string) => Effect.Effect<void, Error>;\n /** Delete a secret. Only called on writable providers. Returns true\n * if something was deleted. */\n readonly delete?: (id: string) => Effect.Effect<boolean, Error>;\n /** Enumerate known secret entries. Optional — not all backends can\n * enumerate (env-backed providers, for example). */\n readonly list?: () => Effect.Effect<\n readonly { readonly id: string; readonly name: string }[],\n Error\n >;\n}\n\n// ---------------------------------------------------------------------------\n// SecretRef — metadata about a stored secret. Returned from\n// `executor.secrets.list()`. The actual value lives in the provider\n// and is only reachable via `executor.secrets.get(id)`.\n// ---------------------------------------------------------------------------\n\nexport class SecretRef extends Schema.Class<SecretRef>(\"SecretRef\")({\n id: SecretId,\n scopeId: ScopeId,\n /** Human-readable label (e.g. \"Cloudflare API Token\") */\n name: Schema.String,\n /** Which provider holds the value */\n provider: Schema.String,\n createdAt: Schema.DateFromNumber,\n}) {}\n\n// ---------------------------------------------------------------------------\n// SetSecretInput — all the metadata to write a secret in one call.\n// `executor.secrets.set(input)` takes this and writes both the\n// value (to the provider) and the ref (to the `secret` table).\n// ---------------------------------------------------------------------------\n\nexport class SetSecretInput extends Schema.Class<SetSecretInput>(\n \"SetSecretInput\",\n)({\n id: SecretId,\n /** Display name shown in secret-list UI. */\n name: Schema.String,\n /** The secret value itself — never persisted outside the provider. */\n value: Schema.String,\n /** Optional provider routing. If unset the executor picks the first\n * writable provider in registration order. */\n provider: Schema.optional(Schema.String),\n}) {}\n","import { Effect, Schema } from \"effect\";\n\nimport { ToolId } from \"./ids\";\n\n// ---------------------------------------------------------------------------\n// Elicitation request — what a tool sends when it needs user input\n// ---------------------------------------------------------------------------\n\n/** Tool needs structured input from the user (render a form) */\nexport class FormElicitation extends Schema.TaggedClass<FormElicitation>()(\"FormElicitation\", {\n message: Schema.String,\n /** JSON Schema describing the fields to collect */\n requestedSchema: Schema.Record({ key: Schema.String, value: Schema.Unknown }),\n}) {}\n\n/** Tool needs the user to visit a URL (OAuth, approval page, etc.) */\nexport class UrlElicitation extends Schema.TaggedClass<UrlElicitation>()(\"UrlElicitation\", {\n message: Schema.String,\n url: Schema.String,\n /** Unique ID so the host can correlate the callback */\n elicitationId: Schema.String,\n}) {}\n\nexport type ElicitationRequest = FormElicitation | UrlElicitation;\n\n// ---------------------------------------------------------------------------\n// Elicitation response — what the host sends back\n// ---------------------------------------------------------------------------\n\nexport const ElicitationAction = Schema.Literal(\"accept\", \"decline\", \"cancel\");\nexport type ElicitationAction = typeof ElicitationAction.Type;\n\nexport class ElicitationResponse extends Schema.Class<ElicitationResponse>(\"ElicitationResponse\")({\n action: ElicitationAction,\n /** Present when action is \"accept\" — the data the user provided */\n content: Schema.optional(Schema.Record({ key: Schema.String, value: Schema.Unknown })),\n}) {}\n\n// ---------------------------------------------------------------------------\n// Elicitation handler — the host provides this to handle requests\n// ---------------------------------------------------------------------------\n\nexport interface ElicitationContext {\n readonly toolId: ToolId;\n readonly args: unknown;\n readonly request: ElicitationRequest;\n}\n\n/**\n * A function the host provides to handle elicitation.\n * The SDK calls this when a tool suspends to ask for user input.\n * The host renders UI / prompts the user / does OAuth / etc.\n */\nexport type ElicitationHandler = (ctx: ElicitationContext) => Effect.Effect<ElicitationResponse>;\n\n// ---------------------------------------------------------------------------\n// Elicitation error — tool was declined or cancelled\n// ---------------------------------------------------------------------------\n\nexport class ElicitationDeclinedError extends Schema.TaggedError<ElicitationDeclinedError>()(\n \"ElicitationDeclinedError\",\n {\n toolId: ToolId,\n action: Schema.Literal(\"decline\", \"cancel\"),\n },\n) {}\n","// ---------------------------------------------------------------------------\n// BlobStore — the seam for large, opaque, write-once data. Separate from\n// the relational adapter on purpose: blobs want different lifecycle,\n// durability, and placement (think S3/R2 in cloud, flat files locally)\n// than the metadata that indexes them.\n//\n// Plugins see a `ScopedBlobStore` that's already namespaced to the plugin\n// id, so key collisions across plugins are structurally impossible.\n// ---------------------------------------------------------------------------\n\nimport { Effect } from \"effect\";\n\nexport interface BlobStore {\n readonly get: (\n namespace: string,\n key: string,\n ) => Effect.Effect<string | null, Error>;\n readonly put: (\n namespace: string,\n key: string,\n value: string,\n ) => Effect.Effect<void, Error>;\n readonly delete: (\n namespace: string,\n key: string,\n ) => Effect.Effect<void, Error>;\n readonly has: (\n namespace: string,\n key: string,\n ) => Effect.Effect<boolean, Error>;\n}\n\nexport interface ScopedBlobStore {\n readonly get: (key: string) => Effect.Effect<string | null, Error>;\n readonly put: (key: string, value: string) => Effect.Effect<void, Error>;\n readonly delete: (key: string) => Effect.Effect<void, Error>;\n readonly has: (key: string) => Effect.Effect<boolean, Error>;\n}\n\nexport const scopeBlobStore = (\n store: BlobStore,\n namespace: string,\n): ScopedBlobStore => ({\n get: (key) => store.get(namespace, key),\n put: (key, value) => store.put(namespace, key, value),\n delete: (key) => store.delete(namespace, key),\n has: (key) => store.has(namespace, key),\n});\n\n/**\n * Minimal in-memory BlobStore — good for tests and trivial hosts. Real\n * backends (filesystem, S3/R2, SQLite-table-backed) implement the same\n * interface.\n */\nexport const makeInMemoryBlobStore = (): BlobStore => {\n const store = new Map<string, string>();\n const k = (ns: string, key: string) => `${ns}::${key}`;\n return {\n get: (ns, key) => Effect.sync(() => store.get(k(ns, key)) ?? null),\n put: (ns, key, value) =>\n Effect.sync(() => {\n store.set(k(ns, key), value);\n }),\n delete: (ns, key) =>\n Effect.sync(() => {\n store.delete(k(ns, key));\n }),\n has: (ns, key) => Effect.sync(() => store.has(k(ns, key))),\n };\n};\n","import { Effect, FiberRef } from \"effect\";\nimport {\n typedAdapter,\n type DBAdapter,\n type DBSchema,\n type DBTransactionAdapter,\n type TypedAdapter,\n} from \"@executor/storage-core\";\n\nimport {\n scopeBlobStore,\n type BlobStore,\n} from \"./blob\";\nimport {\n coreSchema,\n type CoreSchema,\n type DefinitionsInput,\n type SourceInput,\n type SourceRow,\n type ToolAnnotations,\n type ToolRow,\n} from \"./core-schema\";\nimport {\n ElicitationDeclinedError,\n ElicitationResponse,\n FormElicitation,\n type ElicitationHandler,\n type ElicitationRequest,\n} from \"./elicitation\";\nimport {\n NoHandlerError,\n PluginNotLoadedError,\n SourceRemovalNotAllowedError,\n ToolInvocationError,\n ToolNotFoundError,\n} from \"./errors\";\nimport { SecretId, ToolId } from \"./ids\";\nimport type {\n AnyPlugin,\n Elicit,\n PluginCtx,\n PluginExtensions,\n StaticSourceDecl,\n StaticToolDecl,\n StorageDeps,\n} from \"./plugin\";\nimport type { Scope } from \"./scope\";\nimport {\n SecretRef,\n SetSecretInput,\n type SecretProvider,\n} from \"./secrets\";\nimport {\n ToolSchema,\n type Source,\n type SourceDetectionResult,\n type Tool,\n type ToolListFilter,\n} from \"./types\";\nimport { buildToolTypeScriptPreview } from \"./schema-types\";\nimport { scopeAdapter } from \"./scoped-adapter\";\n\n// ---------------------------------------------------------------------------\n// InvokeOptions — passed to `executor.tools.invoke(id, args, options)`.\n// The `onElicitation` handler is threaded into the `elicit` function\n// exposed on plugin ctx / InvokeToolInput. Tools that never elicit\n// simply don't call it.\n//\n// The \"accept-all\" sentinel is convenient for tests and CLI automation —\n// every elicitation request gets auto-accepted with an empty content\n// payload. For real interactive hosts, pass a real handler.\n// ---------------------------------------------------------------------------\n\nexport interface InvokeOptions {\n readonly onElicitation?: ElicitationHandler | \"accept-all\";\n}\n\nconst acceptAllHandler: ElicitationHandler = () =>\n Effect.succeed(new ElicitationResponse({ action: \"accept\" }));\n\nconst resolveElicitationHandler = (\n options: InvokeOptions | undefined,\n): ElicitationHandler => {\n const handler = options?.onElicitation;\n if (!handler || handler === \"accept-all\") return acceptAllHandler;\n return handler;\n};\n\n// ---------------------------------------------------------------------------\n// Executor — public surface. Every list/invoke/schema call is a direct\n// core-table query (for dynamic rows) unioned with the in-memory static\n// pool. No ToolRegistry, no SourceRegistry, no SecretStore services.\n// ---------------------------------------------------------------------------\n\nexport type Executor<TPlugins extends readonly AnyPlugin[] = []> = {\n readonly scope: Scope;\n\n readonly tools: {\n readonly list: (\n filter?: ToolListFilter,\n ) => Effect.Effect<readonly Tool[], Error>;\n /** Fetch a tool's full schema view: JSON schemas with `$defs`\n * attached from the core `definition` table, plus TypeScript\n * preview strings rendered from them. Returns `null` for unknown\n * tool ids. */\n readonly schema: (\n toolId: string,\n ) => Effect.Effect<ToolSchema | null, Error>;\n /** Every `$defs` entry across every source, grouped by source id.\n * Used for bulk schema export and downstream TypeScript rendering. */\n readonly definitions: () => Effect.Effect<\n Record<string, Record<string, unknown>>,\n Error\n >;\n readonly invoke: (\n toolId: string,\n args: unknown,\n options?: InvokeOptions,\n ) => Effect.Effect<\n unknown,\n | ToolNotFoundError\n | PluginNotLoadedError\n | NoHandlerError\n | ToolInvocationError\n | ElicitationDeclinedError\n | Error\n >;\n };\n\n readonly sources: {\n readonly list: () => Effect.Effect<readonly Source[], Error>;\n readonly remove: (\n sourceId: string,\n ) => Effect.Effect<void, SourceRemovalNotAllowedError | Error>;\n readonly refresh: (sourceId: string) => Effect.Effect<void, Error>;\n /** URL autodetection — fans out to every plugin's `detect` hook\n * (if declared), returns every high/medium/low-confidence match.\n * UI picks a winner from the list. */\n readonly detect: (\n url: string,\n ) => Effect.Effect<readonly SourceDetectionResult[], Error>;\n /** All `$defs` registered for a single source, keyed by def name. */\n readonly definitions: (\n sourceId: string,\n ) => Effect.Effect<Record<string, unknown>, Error>;\n };\n\n readonly secrets: {\n readonly get: (id: string) => Effect.Effect<string | null, Error>;\n /** Fast-path existence check — hits the core `secret` routing table\n * only, never calls the provider. Use this for UI state (\"secret\n * missing, prompt to add\") to avoid keychain permission prompts\n * or 1password IPC roundtrips on a pre-flight check. */\n readonly status: (\n id: string,\n ) => Effect.Effect<\"resolved\" | \"missing\", Error>;\n readonly set: (input: SetSecretInput) => Effect.Effect<SecretRef, Error>;\n readonly remove: (id: string) => Effect.Effect<void, Error>;\n readonly list: () => Effect.Effect<readonly SecretRef[], Error>;\n readonly providers: () => Effect.Effect<readonly string[]>;\n };\n\n readonly close: () => Effect.Effect<void, Error>;\n} & PluginExtensions<TPlugins>;\n\nexport interface ExecutorConfig<\n TPlugins extends readonly AnyPlugin[] = [],\n> {\n readonly scope: Scope;\n readonly adapter: DBAdapter;\n readonly blobs: BlobStore;\n readonly plugins?: TPlugins;\n}\n\n// ---------------------------------------------------------------------------\n// collectSchemas — merge coreSchema with every plugin's declared schema.\n// Hosts call this and pass the result to the migration runner (or to\n// the adapter factory for backends that auto-migrate from a schema\n// manifest) before constructing the executor.\n// ---------------------------------------------------------------------------\n\nexport const collectSchemas = (\n plugins: readonly AnyPlugin[],\n): DBSchema => {\n const merged: Record<string, DBSchema[string]> = { ...coreSchema };\n for (const plugin of plugins) {\n if (!plugin.schema) continue;\n for (const [modelKey, model] of Object.entries(plugin.schema)) {\n if (merged[modelKey]) {\n throw new Error(\n `Duplicate model \"${modelKey}\" contributed by plugin \"${plugin.id}\"` +\n ` (reserved by core or another plugin)`,\n );\n }\n merged[modelKey] = model as DBSchema[string];\n }\n }\n return merged;\n};\n\n// ---------------------------------------------------------------------------\n// Row → public projection conversions\n// ---------------------------------------------------------------------------\n\nconst rowToSource = (row: SourceRow): Source => ({\n id: row.id,\n kind: row.kind,\n name: row.name,\n url: row.url ?? undefined,\n pluginId: row.plugin_id,\n canRemove: Boolean(row.can_remove),\n canRefresh: Boolean(row.can_refresh),\n canEdit: Boolean(row.can_edit),\n runtime: false,\n});\n\nconst staticDeclToSource = (\n decl: StaticSourceDecl,\n pluginId: string,\n): Source => ({\n id: decl.id,\n kind: decl.kind,\n name: decl.name,\n url: decl.url,\n pluginId,\n canRemove: decl.canRemove ?? false,\n canRefresh: decl.canRefresh ?? false,\n canEdit: decl.canEdit ?? false,\n runtime: true,\n});\n\nconst decodeJsonColumn = (value: unknown): unknown => {\n if (value === null || value === undefined) return undefined;\n if (typeof value !== \"string\") return value;\n try {\n return JSON.parse(value);\n } catch {\n return value;\n }\n};\n\nconst rowToTool = (\n row: ToolRow,\n annotations?: ToolAnnotations,\n): Tool => ({\n id: row.id,\n sourceId: row.source_id,\n pluginId: row.plugin_id,\n name: row.name,\n description: row.description,\n inputSchema: decodeJsonColumn(row.input_schema),\n outputSchema: decodeJsonColumn(row.output_schema),\n annotations,\n});\n\nconst staticDeclToTool = (\n source: StaticSourceDecl,\n tool: StaticToolDecl,\n pluginId: string,\n): Tool => ({\n id: `${source.id}.${tool.name}`,\n sourceId: source.id,\n pluginId,\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema,\n outputSchema: tool.outputSchema,\n annotations: tool.annotations,\n});\n\n// ---------------------------------------------------------------------------\n// Dynamic-row writers — used by ctx.core.sources.register. Static sources\n// never touch these functions.\n// ---------------------------------------------------------------------------\n\n// Upsert shape: delete any existing source + tools + definitions for\n// `input.id` before creating fresh rows. Keeps replayable — boot-time\n// sync from executor.jsonc can call register() on rows that already\n// exist without tripping a UNIQUE constraint.\nconst writeSourceInput = (\n core: TypedAdapter<CoreSchema>,\n pluginId: string,\n input: SourceInput,\n): Effect.Effect<void, Error> =>\n Effect.gen(function* () {\n yield* deleteSourceById(core, input.id);\n\n const now = new Date();\n yield* core.create({\n model: \"source\",\n data: {\n id: input.id,\n plugin_id: pluginId,\n kind: input.kind,\n name: input.name,\n url: input.url ?? undefined,\n can_remove: input.canRemove ?? true,\n can_refresh: input.canRefresh ?? false,\n can_edit: input.canEdit ?? false,\n created_at: now,\n updated_at: now,\n },\n forceAllowId: true,\n });\n\n if (input.tools.length > 0) {\n yield* core.createMany({\n model: \"tool\",\n data: input.tools.map((tool) => ({\n id: `${input.id}.${tool.name}`,\n source_id: input.id,\n plugin_id: pluginId,\n name: tool.name,\n description: tool.description,\n input_schema: tool.inputSchema ?? undefined,\n output_schema: tool.outputSchema ?? undefined,\n created_at: now,\n updated_at: now,\n })),\n forceAllowId: true,\n });\n }\n });\n\nconst deleteSourceById = (\n core: TypedAdapter<CoreSchema>,\n sourceId: string,\n): Effect.Effect<void, Error> =>\n Effect.gen(function* () {\n yield* core.deleteMany({\n model: \"tool\",\n where: [{ field: \"source_id\", value: sourceId }],\n });\n yield* core.deleteMany({\n model: \"definition\",\n where: [{ field: \"source_id\", value: sourceId }],\n });\n yield* core.delete({\n model: \"source\",\n where: [{ field: \"id\", value: sourceId }],\n });\n });\n\nconst writeDefinitions = (\n core: TypedAdapter<CoreSchema>,\n pluginId: string,\n input: DefinitionsInput,\n): Effect.Effect<void, Error> =>\n Effect.gen(function* () {\n yield* core.deleteMany({\n model: \"definition\",\n where: [{ field: \"source_id\", value: input.sourceId }],\n });\n const entries = Object.entries(input.definitions);\n if (entries.length === 0) return;\n const now = new Date();\n yield* core.createMany({\n model: \"definition\",\n data: entries.map(([name, schema]) => ({\n id: `${input.sourceId}.${name}`,\n source_id: input.sourceId,\n plugin_id: pluginId,\n name,\n schema: schema as Record<string, unknown>,\n created_at: now,\n })),\n forceAllowId: true,\n });\n });\n\n// ---------------------------------------------------------------------------\n// Filtering — shared between dynamic (DB) and static (in-memory) pools\n// so `tools.list({ query, sourceId })` matches across both.\n// ---------------------------------------------------------------------------\n\nconst toolMatchesFilter = (tool: Tool, filter: ToolListFilter): boolean => {\n if (filter.sourceId && tool.sourceId !== filter.sourceId) return false;\n if (filter.query) {\n const q = filter.query.toLowerCase();\n const hay = `${tool.name} ${tool.description}`.toLowerCase();\n if (!hay.includes(q)) return false;\n }\n return true;\n};\n\n// ---------------------------------------------------------------------------\n// Active-adapter FiberRef. Nested plugin writes read this ref so that\n// `ctx.transaction` can swap in a tx-bound adapter handle without changing\n// the ctx shape — the root adapter returned by `buildAdapterRouter` below\n// resolves its target per call, so any Effect running inside\n// `Effect.locally(_, activeAdapterRef, trx)` automatically routes every\n// query through the same sql.begin connection. This is what makes nested\n// writes atomic on postgres + Hyperdrive without deadlocking a pool of 1.\n// ---------------------------------------------------------------------------\nconst activeAdapterRef = FiberRef.unsafeMake<DBTransactionAdapter | null>(\n null,\n);\n\n// A `DBAdapter` whose methods dispatch to the active adapter (tx handle or\n// root) on every call. Stable identity for consumers (plugin storage,\n// `typedAdapter`, etc.) — they see one adapter object, but the routing is\n// decided at call time via the FiberRef above.\nconst buildAdapterRouter = (root: DBAdapter): DBAdapter => {\n const pick = <A, E>(\n use: (active: DBTransactionAdapter) => Effect.Effect<A, E>,\n ): Effect.Effect<A, E> =>\n Effect.flatMap(FiberRef.get(activeAdapterRef), (active) =>\n use((active ?? (root as DBTransactionAdapter))),\n );\n\n return {\n id: root.id,\n create: (data) => pick((a) => a.create(data)),\n createMany: (data) => pick((a) => a.createMany(data)),\n findOne: (data) => pick((a) => a.findOne(data)),\n findMany: (data) => pick((a) => a.findMany(data)),\n count: (data) => pick((a) => a.count(data)),\n update: (data) => pick((a) => a.update(data)),\n updateMany: (data) => pick((a) => a.updateMany(data)),\n delete: (data) => pick((a) => a.delete(data)),\n deleteMany: (data) => pick((a) => a.deleteMany(data)),\n // transaction() always opens a real boundary on the ROOT adapter so the\n // tx uses one real connection from the pool. If we're already inside a\n // parent tx (FiberRef set), skip opening a nested sql.begin — that's\n // the postgres.js + Hyperdrive deadlock path — and just run the\n // callback with the existing tx handle. In both cases the callback\n // sees a FiberRef-substituted adapter so further nested writes thread\n // through.\n transaction: (callback) =>\n Effect.flatMap(FiberRef.get(activeAdapterRef), (active) => {\n if (active) return callback(active);\n return root.transaction((trx) =>\n Effect.locally(callback(trx), activeAdapterRef, trx),\n );\n }),\n } as DBAdapter;\n};\n\n// ---------------------------------------------------------------------------\n// createExecutor\n// ---------------------------------------------------------------------------\n\ninterface StaticTools {\n readonly source: StaticSourceDecl;\n readonly tool: StaticToolDecl;\n readonly pluginId: string;\n readonly ctx: PluginCtx<unknown>;\n}\n\ninterface StaticSources {\n readonly source: StaticSourceDecl;\n readonly pluginId: string;\n}\n\ninterface PluginRuntime {\n readonly plugin: AnyPlugin;\n readonly storage: unknown;\n readonly ctx: PluginCtx<unknown>;\n}\n\nexport const createExecutor = <\n const TPlugins extends readonly AnyPlugin[] = [],\n>(\n config: ExecutorConfig<TPlugins>,\n): Effect.Effect<Executor<TPlugins>, Error> =>\n Effect.gen(function* () {\n const {\n scope,\n adapter: rootAdapter,\n blobs,\n plugins = [] as unknown as TPlugins,\n } = config;\n\n // Scope-wrap the root adapter so every read on a tenant-scoped table\n // filters by the current scope stack and every write stamps the\n // write target. Today the stack has one element; the adapter's\n // `ScopeContext` shape already accepts an ordered list so layering\n // (org → workspace → user) can land later without changing plugin\n // code. Only tables whose schema declares `scope_id` are scoped.\n const schema = collectSchemas(plugins);\n const scopedRoot = scopeAdapter(\n rootAdapter,\n { read: [scope.id], write: scope.id },\n schema,\n );\n const adapter = buildAdapterRouter(scopedRoot);\n const core = typedAdapter<CoreSchema>(adapter);\n\n // Populated once, never mutated after startup.\n const staticTools = new Map<string, StaticTools>();\n const staticSources = new Map<string, StaticSources>();\n\n // Per-plugin runtime state.\n const runtimes = new Map<string, PluginRuntime>();\n // Secret providers keyed by `provider.key`.\n const secretProviders = new Map<string, SecretProvider>();\n const extensions: Record<string, object> = {};\n\n // ------------------------------------------------------------------\n // Secrets facade — fast path is the core `secret` routing table\n // (explicit set()s, keychain entries, etc). Fallback is a walk\n // across providers that implement `list()`, because those are the\n // providers that own their own inventories (1password, file-secrets,\n // workos-vault, env) and enumerate-without-register. Providers\n // without a list() implementation (keychain) never hit the fallback\n // walk because their secrets must be registered through set() to\n // be known at all.\n // ------------------------------------------------------------------\n const secretsGet = (id: string): Effect.Effect<string | null, Error> =>\n Effect.gen(function* () {\n // Fast path: routing table\n const row = yield* core.findOne({\n model: \"secret\",\n where: [{ field: \"id\", value: id }],\n });\n if (row) {\n const provider = secretProviders.get(row.provider);\n if (!provider) return null;\n return yield* provider.get(id);\n }\n\n // Fallback: ask every enumerating provider in parallel. First\n // non-null in registration order wins. Providers that throw\n // are treated as \"don't have it\" so one flaky provider can't\n // block resolution via others.\n const candidates = [...secretProviders.values()].filter(\n (p) => p.list,\n );\n const values = yield* Effect.all(\n candidates.map((p) =>\n p.get(id).pipe(Effect.catchAll(() => Effect.succeed(null))),\n ),\n { concurrency: \"unbounded\" },\n );\n for (const value of values) if (value !== null) return value;\n return null;\n });\n\n const secretsSet = (\n input: SetSecretInput,\n ): Effect.Effect<SecretRef, Error> =>\n Effect.gen(function* () {\n // Pick provider: explicit or first-writable.\n let target: SecretProvider | undefined;\n if (input.provider) {\n target = secretProviders.get(input.provider);\n if (!target) {\n return yield* Effect.fail(\n new Error(`Unknown secret provider: ${input.provider}`),\n );\n }\n } else {\n for (const provider of secretProviders.values()) {\n if (provider.writable && provider.set) {\n target = provider;\n break;\n }\n }\n if (!target) {\n return yield* Effect.fail(\n new Error(\"No writable secret providers registered\"),\n );\n }\n }\n if (!target.writable || !target.set) {\n return yield* Effect.fail(\n new Error(`Secret provider \"${target.key}\" is read-only`),\n );\n }\n\n yield* target.set(input.id, input.value);\n\n // Upsert metadata row in the core `secret` table.\n const now = new Date();\n yield* core.delete({\n model: \"secret\",\n where: [{ field: \"id\", value: input.id }],\n });\n yield* core.create({\n model: \"secret\",\n data: {\n id: input.id,\n name: input.name,\n provider: target.key,\n created_at: now,\n },\n forceAllowId: true,\n });\n\n return new SecretRef({\n id: input.id,\n scopeId: scope.id,\n name: input.name,\n provider: target.key,\n createdAt: now,\n });\n });\n\n const secretsRemove = (id: string): Effect.Effect<void, Error> =>\n Effect.gen(function* () {\n // Providers don't coordinate on which of them own the id — they\n // each get asked. Most calls are no-ops; fan them out so one\n // slow provider doesn't serialize the rest.\n const deleters = [...secretProviders.values()].filter(\n (p): p is typeof p & { delete: NonNullable<typeof p.delete> } =>\n !!(p.writable && p.delete),\n );\n yield* Effect.all(\n deleters.map((p) => p.delete(id)),\n { concurrency: \"unbounded\" },\n );\n yield* core.delete({\n model: \"secret\",\n where: [{ field: \"id\", value: id }],\n });\n });\n\n // List is a union of two sources of truth:\n //\n // 1. Core `secret` rows — secrets explicitly registered via\n // executor.secrets.set(...). These carry their pinned provider\n // and are authoritative for routing (get() uses them).\n // 2. Each provider's own `list()` — for read-only or\n // already-populated providers (1password, file-secrets,\n // workos-vault, env), the provider enumerates what's actually\n // in its backend. These show up in the list even if the user\n // never called set() through the executor.\n //\n // Dedupe by secret id; core rows win over provider-enumerated ones\n // so that routing information in the core table is authoritative.\n // Providers without a list() method (e.g. keychain) contribute\n // only via the core table path.\n const secretsList = (): Effect.Effect<readonly SecretRef[], Error> =>\n Effect.gen(function* () {\n const byId = new Map<string, SecretRef>();\n\n // Core routing rows first\n const rows = yield* core.findMany({ model: \"secret\" });\n for (const row of rows) {\n byId.set(\n row.id,\n new SecretRef({\n id: SecretId.make(row.id),\n scopeId: scope.id,\n name: row.name,\n provider: row.provider,\n createdAt:\n row.created_at instanceof Date\n ? row.created_at\n : new Date(row.created_at as string),\n }),\n );\n }\n\n // Then every provider that can enumerate itself, in parallel.\n // If a provider fails to list (unlocked vault, network error),\n // swallow the failure so one flaky provider can't block the\n // whole list. Merge in registration order afterwards so the\n // \"first provider wins\" precedence stays deterministic.\n const listers = [...secretProviders.entries()].filter(\n ([, p]) => p.list,\n );\n const lists = yield* Effect.all(\n listers.map(([key, p]) =>\n p\n .list!()\n .pipe(\n Effect.catchAll(() => Effect.succeed([] as const)),\n Effect.map((entries) => ({ key, entries })),\n ),\n ),\n { concurrency: \"unbounded\" },\n );\n for (const { key, entries } of lists) {\n for (const entry of entries) {\n if (byId.has(entry.id)) continue; // core row wins\n byId.set(\n entry.id,\n new SecretRef({\n id: SecretId.make(entry.id),\n scopeId: scope.id,\n name: entry.name,\n provider: key,\n createdAt: new Date(),\n }),\n );\n }\n }\n\n return Array.from(byId.values());\n });\n\n // Same union shape as secretsList but projected to the leaner\n // SecretListEntry shape that plugins get via ctx.secrets.list().\n const secretsListForCtx = () =>\n Effect.gen(function* () {\n const list = yield* secretsList();\n return list.map((ref) => ({\n id: ref.id as unknown as string,\n name: ref.name,\n provider: ref.provider,\n }));\n });\n\n // ------------------------------------------------------------------\n // Plugin wiring — build ctx, run extension, populate static pools,\n // register secret providers. No adapter reads here.\n // ------------------------------------------------------------------\n for (const plugin of plugins) {\n if (runtimes.has(plugin.id)) {\n return yield* Effect.fail(\n new Error(`Duplicate plugin id: ${plugin.id}`),\n );\n }\n\n // `typedAdapter(adapter)` is a zero-cost cast at the type level —\n // the runtime value IS the adapter, but the type is whatever the\n // plugin's schema declares. Plugins never import `typedAdapter` or\n // `DBAdapter` themselves; they just destructure `{ adapter }` in\n // their storage factory and get a store typed to their own schema.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const storageDeps: StorageDeps<any> = {\n scope,\n adapter: typedAdapter(adapter) as never,\n // Blob keys are namespaced by `<scope>/<plugin>` so two tenants\n // sharing a backing BlobStore can't collide or leak on the same\n // `(plugin, key)` pair. Mirrors the adapter's scope-stamping.\n blobs: scopeBlobStore(blobs, `${scope.id}/${plugin.id}`),\n };\n const storage = plugin.storage(storageDeps);\n\n const ctx: PluginCtx<unknown> = {\n scope,\n storage,\n core: {\n sources: {\n register: (input: SourceInput) =>\n Effect.gen(function* () {\n // Guard: reject a dynamic source whose id collides with\n // a static source id, or any of whose would-be tool ids\n // collide with a static tool id. Tool ids are\n // `${source_id}.${tool.name}` — static and dynamic\n // share the same string space.\n if (staticSources.has(input.id)) {\n return yield* Effect.fail(\n new Error(\n `Source id \"${input.id}\" collides with a static source`,\n ),\n );\n }\n for (const tool of input.tools) {\n const fqid = `${input.id}.${tool.name}`;\n if (staticTools.has(fqid)) {\n return yield* Effect.fail(\n new Error(\n `Tool id \"${fqid}\" collides with a static tool`,\n ),\n );\n }\n }\n // Wrap in adapter.transaction so a standalone register()\n // call is atomic (source create + tools createMany group\n // together). When already inside a parent ctx.transaction,\n // the router short-circuits to the active tx handle\n // instead of opening a nested sql.begin — that nested\n // sql.begin is the postgres.js + pool=1 deadlock path.\n yield* adapter.transaction(() =>\n writeSourceInput(core, plugin.id, input),\n );\n }),\n unregister: (sourceId: string) =>\n adapter.transaction(() => deleteSourceById(core, sourceId)),\n },\n definitions: {\n register: (input: DefinitionsInput) =>\n adapter.transaction(() =>\n writeDefinitions(core, plugin.id, input),\n ),\n },\n },\n secrets: {\n get: secretsGet,\n list: secretsListForCtx,\n set: secretsSet,\n remove: secretsRemove,\n },\n // Open one real tx boundary and route every nested write inside\n // `effect` through that same handle via the activeAdapterRef —\n // see buildAdapterRouter above for the dispatch logic.\n transaction: <A, E>(effect: Effect.Effect<A, E>) =>\n adapter.transaction(() => effect) as Effect.Effect<A, E | Error>,\n };\n\n // Build extension FIRST so it's available as `self` when resolving\n // staticSources. Field ordering in the plugin spec matters — TS\n // infers TExtension from `extension`'s return type, then NoInfer\n // locks `self` to that inferred type on `staticSources`.\n const extension: object = plugin.extension\n ? plugin.extension(ctx)\n : {};\n if (plugin.extension) {\n extensions[plugin.id] = extension;\n }\n\n // Resolve static declarations to the in-memory pools. NO DB WRITES.\n const decls = plugin.staticSources\n ? plugin.staticSources(extension)\n : [];\n for (const source of decls) {\n if (staticSources.has(source.id)) {\n return yield* Effect.fail(\n new Error(\n `Duplicate static source id: ${source.id} (plugin ${plugin.id})`,\n ),\n );\n }\n staticSources.set(source.id, { source, pluginId: plugin.id });\n\n for (const tool of source.tools) {\n const fqid = `${source.id}.${tool.name}`;\n if (staticTools.has(fqid)) {\n return yield* Effect.fail(\n new Error(\n `Duplicate static tool id: ${fqid} (plugin ${plugin.id})`,\n ),\n );\n }\n staticTools.set(fqid, {\n source,\n tool,\n pluginId: plugin.id,\n ctx,\n });\n }\n }\n\n runtimes.set(plugin.id, { plugin, storage, ctx });\n\n if (plugin.secretProviders) {\n const providers =\n typeof plugin.secretProviders === \"function\"\n ? plugin.secretProviders(ctx)\n : plugin.secretProviders;\n for (const provider of providers) {\n if (secretProviders.has(provider.key)) {\n return yield* Effect.fail(\n new Error(\n `Duplicate secret provider key: ${provider.key} (from plugin ${plugin.id})`,\n ),\n );\n }\n secretProviders.set(provider.key, provider);\n }\n }\n }\n\n // ------------------------------------------------------------------\n // Executor surface\n // ------------------------------------------------------------------\n const listSources = () =>\n Effect.gen(function* () {\n const dynamic = yield* core.findMany({ model: \"source\" });\n const staticList: Source[] = [];\n for (const { source, pluginId } of staticSources.values()) {\n staticList.push(staticDeclToSource(source, pluginId));\n }\n return [...staticList, ...dynamic.map(rowToSource)];\n });\n\n // Bulk-resolve annotations across a set of dynamic tool rows by\n // grouping them under their owning plugin's resolveAnnotations\n // callback. One plugin call per (plugin_id, source_id) pair, not\n // per row. Plugins without a resolver simply contribute no\n // annotations for their rows.\n const resolveAnnotationsFor = (rows: readonly ToolRow[]) =>\n Effect.gen(function* () {\n const result = new Map<string, ToolAnnotations>();\n if (rows.length === 0) return result;\n\n // Group by (plugin_id, source_id)\n const groups = new Map<string, ToolRow[]>();\n for (const row of rows) {\n const key = `${row.plugin_id}\\u0000${row.source_id}`;\n const bucket = groups.get(key);\n if (bucket) bucket.push(row);\n else groups.set(key, [row]);\n }\n\n for (const [key, groupRows] of groups) {\n const [pluginId, sourceId] = key.split(\"\\u0000\") as [\n string,\n string,\n ];\n const runtime = runtimes.get(pluginId);\n if (!runtime?.plugin.resolveAnnotations) continue;\n const map = yield* runtime.plugin.resolveAnnotations({\n ctx: runtime.ctx,\n sourceId,\n toolRows: groupRows,\n });\n for (const [toolId, annotations] of Object.entries(map)) {\n result.set(toolId, annotations);\n }\n }\n return result;\n });\n\n const listTools = (filter?: ToolListFilter) =>\n Effect.gen(function* () {\n const dynamic = yield* core.findMany({ model: \"tool\" });\n const annotations = yield* resolveAnnotationsFor(dynamic);\n\n const out: Tool[] = [];\n // Static tools — annotations from the declaration, not a resolver.\n for (const entry of staticTools.values()) {\n out.push(staticDeclToTool(entry.source, entry.tool, entry.pluginId));\n }\n for (const row of dynamic) {\n out.push(rowToTool(row, annotations.get(row.id)));\n }\n if (!filter) return out;\n return out.filter((t) => toolMatchesFilter(t, filter));\n });\n\n // Load all definitions for a single source as a plain map.\n const loadDefinitionsForSource = (sourceId: string) =>\n Effect.gen(function* () {\n const defRows = yield* core.findMany({\n model: \"definition\",\n where: [{ field: \"source_id\", value: sourceId }],\n });\n const out: Record<string, unknown> = {};\n for (const row of defRows) out[row.name] = row.schema;\n return out;\n });\n\n // Render the ToolSchema view for a tool — wraps the raw JSON schemas\n // with attached `$defs` and runs them through the TypeScript preview\n // helpers so the UI gets ready-to-display code samples.\n const buildToolSchemaView = (opts: {\n toolId: string;\n name?: string;\n description?: string;\n sourceId: string | undefined;\n rawInput: unknown;\n rawOutput: unknown;\n }) =>\n Effect.gen(function* () {\n const defs: Record<string, unknown> = opts.sourceId\n ? yield* loadDefinitionsForSource(opts.sourceId)\n : {};\n\n const attachDefs = (schema: unknown): unknown => {\n if (schema == null || typeof schema !== \"object\") return schema;\n if (Object.keys(defs).length === 0) return schema;\n return { ...(schema as Record<string, unknown>), $defs: defs };\n };\n\n const inputSchema = attachDefs(opts.rawInput);\n const outputSchema = attachDefs(opts.rawOutput);\n\n const defsMap = new Map<string, unknown>(Object.entries(defs));\n const preview = buildToolTypeScriptPreview({\n inputSchema,\n outputSchema,\n defs: defsMap,\n });\n\n return new ToolSchema({\n id: ToolId.make(opts.toolId),\n name: opts.name,\n description: opts.description,\n inputSchema,\n outputSchema,\n inputTypeScript: preview.inputTypeScript ?? undefined,\n outputTypeScript: preview.outputTypeScript ?? undefined,\n typeScriptDefinitions: preview.typeScriptDefinitions ?? undefined,\n });\n });\n\n const toolSchema = (toolId: string) =>\n Effect.gen(function* () {\n // Static pool first — static tools have no source in the DB so\n // no `$defs` attach; just wrap the declared schemas.\n const staticEntry = staticTools.get(toolId);\n if (staticEntry) {\n return yield* buildToolSchemaView({\n toolId,\n name: staticEntry.tool.name,\n description: staticEntry.tool.description,\n sourceId: undefined,\n rawInput: staticEntry.tool.inputSchema,\n rawOutput: staticEntry.tool.outputSchema,\n });\n }\n const row = yield* core.findOne({\n model: \"tool\",\n where: [{ field: \"id\", value: toolId }],\n });\n if (!row) return null;\n return yield* buildToolSchemaView({\n toolId,\n name: row.name,\n description: row.description,\n sourceId: row.source_id,\n rawInput: decodeJsonColumn(row.input_schema),\n rawOutput: decodeJsonColumn(row.output_schema),\n });\n });\n\n // Bulk definitions accessor — every source's $defs, grouped by\n // source id. One query against the definition table, plus an\n // in-memory group-by.\n const toolsDefinitions = () =>\n Effect.gen(function* () {\n const rows = yield* core.findMany({ model: \"definition\" });\n const out: Record<string, Record<string, unknown>> = {};\n for (const row of rows) {\n let bucket = out[row.source_id];\n if (!bucket) {\n bucket = {};\n out[row.source_id] = bucket;\n }\n bucket[row.name] = row.schema;\n }\n return out;\n });\n\n const buildElicit = (toolId: string, args: unknown, options: InvokeOptions | undefined): Elicit => {\n const handler = resolveElicitationHandler(options);\n return (request: ElicitationRequest) =>\n Effect.gen(function* () {\n const tid = ToolId.make(toolId);\n const response: ElicitationResponse = yield* handler({\n toolId: tid,\n args,\n request,\n });\n if (response.action !== \"accept\") {\n return yield* new ElicitationDeclinedError({\n toolId: tid,\n action: response.action,\n });\n }\n return response;\n });\n };\n\n const enforceApproval = (\n annotations: ToolAnnotations | undefined,\n toolId: string,\n args: unknown,\n options: InvokeOptions | undefined,\n ) =>\n Effect.gen(function* () {\n if (!annotations?.requiresApproval) return;\n const handler = resolveElicitationHandler(options);\n const tid = ToolId.make(toolId);\n const request = new FormElicitation({\n message: annotations.approvalDescription ?? `Approve ${toolId}?`,\n requestedSchema: {},\n });\n const response = yield* handler({ toolId: tid, args, request });\n if (response.action !== \"accept\") {\n return yield* new ElicitationDeclinedError({\n toolId: tid,\n action: response.action,\n });\n }\n });\n\n const invokeTool = (\n toolId: string,\n args: unknown,\n options?: InvokeOptions,\n ) =>\n Effect.gen(function* () {\n const wrapInvocationError = <A, E>(\n effect: Effect.Effect<A, E>,\n ): Effect.Effect<A, ToolInvocationError> =>\n effect.pipe(\n Effect.mapError(\n (cause) =>\n new ToolInvocationError({\n toolId: ToolId.make(toolId),\n message:\n cause instanceof Error ? cause.message : String(cause),\n cause,\n }),\n ),\n );\n\n // Static path — O(1) map lookup, no DB hit.\n const staticEntry = staticTools.get(toolId);\n if (staticEntry) {\n yield* enforceApproval(\n staticEntry.tool.annotations,\n toolId,\n args,\n options,\n );\n return yield* wrapInvocationError(\n staticEntry.tool.handler({\n ctx: staticEntry.ctx,\n args,\n elicit: buildElicit(toolId, args, options),\n }),\n );\n }\n\n // Dynamic path — DB lookup + delegate to owning plugin.\n const row = yield* core.findOne({\n model: \"tool\",\n where: [{ field: \"id\", value: toolId }],\n });\n if (!row) {\n return yield* new ToolNotFoundError({\n toolId: ToolId.make(toolId),\n });\n }\n const runtime = runtimes.get(row.plugin_id);\n if (!runtime) {\n return yield* new PluginNotLoadedError({\n pluginId: row.plugin_id,\n toolId: ToolId.make(toolId),\n });\n }\n if (!runtime.plugin.invokeTool) {\n return yield* new NoHandlerError({\n toolId: ToolId.make(toolId),\n pluginId: row.plugin_id,\n });\n }\n\n // Ask the plugin to derive annotations for this one row, if it\n // has a resolver. Cheap because the plugin typically already\n // needs to load its enrichment data to invoke the tool —\n // implementations should structure their resolver + invokeTool\n // around a single storage read.\n let annotations: ToolAnnotations | undefined;\n if (runtime.plugin.resolveAnnotations) {\n const map = yield* runtime.plugin.resolveAnnotations({\n ctx: runtime.ctx,\n sourceId: row.source_id,\n toolRows: [row],\n });\n annotations = map[toolId];\n }\n yield* enforceApproval(annotations, toolId, args, options);\n\n return yield* wrapInvocationError(\n runtime.plugin.invokeTool({\n ctx: runtime.ctx,\n toolRow: row,\n args,\n elicit: buildElicit(toolId, args, options),\n }),\n );\n });\n\n const removeSource = (sourceId: string) =>\n Effect.gen(function* () {\n // Block removal of static sources structurally.\n if (staticSources.has(sourceId)) {\n return yield* new SourceRemovalNotAllowedError({ sourceId });\n }\n const sourceRow = yield* core.findOne({\n model: \"source\",\n where: [{ field: \"id\", value: sourceId }],\n });\n if (!sourceRow) return;\n if (!sourceRow.can_remove) {\n return yield* new SourceRemovalNotAllowedError({ sourceId });\n }\n const runtime = runtimes.get(sourceRow.plugin_id);\n // Group the plugin's own cleanup + the core row delete into one\n // tx so a removeSource never leaves orphan rows on failure. The\n // router short-circuits on nested calls when the caller is\n // already inside a parent ctx.transaction.\n yield* adapter.transaction(() =>\n Effect.gen(function* () {\n if (runtime?.plugin.removeSource) {\n yield* runtime.plugin.removeSource({\n ctx: runtime.ctx,\n sourceId,\n });\n }\n yield* deleteSourceById(core, sourceId);\n }),\n );\n });\n\n const refreshSource = (sourceId: string) =>\n Effect.gen(function* () {\n if (staticSources.has(sourceId)) return;\n const sourceRow = yield* core.findOne({\n model: \"source\",\n where: [{ field: \"id\", value: sourceId }],\n });\n if (!sourceRow) return;\n const runtime = runtimes.get(sourceRow.plugin_id);\n if (runtime?.plugin.refreshSource) {\n yield* runtime.plugin.refreshSource({\n ctx: runtime.ctx,\n sourceId,\n });\n }\n });\n\n // URL autodetection — fan out across every plugin that declared a\n // `detect` hook. Collect all non-null results. Plugin-level detect\n // implementations should swallow fetch errors and return null, so\n // one flaky plugin doesn't block the whole dispatch.\n const detectSource = (url: string) =>\n Effect.gen(function* () {\n const results: SourceDetectionResult[] = [];\n for (const runtime of runtimes.values()) {\n if (!runtime.plugin.detect) continue;\n const result = yield* runtime.plugin\n .detect({ ctx: runtime.ctx, url })\n .pipe(Effect.catchAll(() => Effect.succeed(null)));\n if (result) results.push(result);\n }\n return results;\n });\n\n // Per-source definitions accessor — one query, one mapping pass.\n const sourceDefinitions = (sourceId: string) =>\n loadDefinitionsForSource(sourceId);\n\n // Fast-path existence check. Hits the core `secret` routing row\n // first (no provider call). If no routing row, walks enumerating\n // providers and checks their lists for a matching id — same\n // fallback strategy as secretsGet. Still avoids provider.get()\n // so no keychain permission prompts / 1password value IPC fires\n // just to ask \"does this exist?\"\n const secretsStatus = (\n id: string,\n ): Effect.Effect<\"resolved\" | \"missing\", Error> =>\n Effect.gen(function* () {\n const row = yield* core.findOne({\n model: \"secret\",\n where: [{ field: \"id\", value: id }],\n });\n if (row) return \"resolved\";\n\n for (const provider of secretProviders.values()) {\n if (!provider.list) continue;\n const entries = yield* provider\n .list()\n .pipe(Effect.catchAll(() => Effect.succeed([] as const)));\n if (entries.some((e) => e.id === id)) return \"resolved\";\n }\n return \"missing\";\n });\n\n const close = () =>\n Effect.gen(function* () {\n for (const runtime of runtimes.values()) {\n if (runtime.plugin.close) {\n yield* runtime.plugin.close();\n }\n }\n });\n\n const base = {\n scope,\n tools: {\n list: listTools,\n schema: toolSchema,\n definitions: toolsDefinitions,\n invoke: invokeTool,\n },\n sources: {\n list: listSources,\n remove: removeSource,\n refresh: refreshSource,\n detect: detectSource,\n definitions: sourceDefinitions,\n },\n secrets: {\n get: secretsGet,\n status: secretsStatus,\n set: secretsSet,\n remove: secretsRemove,\n list: secretsList,\n providers: () =>\n Effect.sync(\n () => Array.from(secretProviders.keys()) as readonly string[],\n ),\n },\n close,\n };\n\n return Object.assign(base, extensions) as Executor<TPlugins>;\n });\n","// ---------------------------------------------------------------------------\n// In-memory CustomAdapter, piped through createAdapter to produce a full\n// DBAdapter. Used by the conformance suite and for unit tests that don't\n// need real persistence.\n//\n// Vendored from better-auth (packages/memory-adapter/src/memory-adapter.ts)\n// under MIT. Adapted for executor:\n// - Promise/async → Effect.Effect<T, Error>\n// - Stripped the BetterAuthOptions layering (our config is simpler)\n// - Reuses the filter logic from better-auth's memoryAdapter, minus\n// join support (join resolution is not needed for the storage-core\n// testing path — plugins in-process do their own joining)\n// - Transaction uses structuredClone snapshot/rollback, same as\n// better-auth's memory-adapter\n// ---------------------------------------------------------------------------\n\nimport { Effect } from \"effect\";\n\nimport type {\n CleanedWhere,\n CustomAdapter,\n DBAdapter,\n DBAdapterFactoryConfig,\n JoinConfig,\n} from \"../adapter\";\nimport type { DBSchema } from \"../schema\";\nimport { createAdapter } from \"../factory\";\n\ntype Row = Record<string, unknown>;\ntype Store = Record<string, Row[]>;\n\nconst evalClause = (record: Row, clause: CleanedWhere): boolean => {\n const { field, value, operator, mode } = clause;\n const isInsensitive =\n mode === \"insensitive\" &&\n (typeof value === \"string\" ||\n (Array.isArray(value) &&\n (value as unknown[]).every((v) => typeof v === \"string\")));\n\n const lhs = record[field];\n const lowerStr = (v: unknown) =>\n typeof v === \"string\" ? v.toLowerCase() : v;\n\n const cmp = (a: unknown, b: unknown): boolean =>\n isInsensitive ? lowerStr(a) === lowerStr(b) : a === b;\n\n switch (operator) {\n case \"in\":\n if (!Array.isArray(value)) throw new Error(\"Value must be an array\");\n return (value as unknown[]).some((v) => cmp(lhs, v));\n case \"not_in\":\n if (!Array.isArray(value)) throw new Error(\"Value must be an array\");\n return !(value as unknown[]).some((v) => cmp(lhs, v));\n case \"contains\": {\n if (typeof lhs !== \"string\" || typeof value !== \"string\") return false;\n return isInsensitive\n ? lhs.toLowerCase().includes(value.toLowerCase())\n : lhs.includes(value);\n }\n case \"starts_with\": {\n if (typeof lhs !== \"string\" || typeof value !== \"string\") return false;\n return isInsensitive\n ? lhs.toLowerCase().startsWith(value.toLowerCase())\n : lhs.startsWith(value);\n }\n case \"ends_with\": {\n if (typeof lhs !== \"string\" || typeof value !== \"string\") return false;\n return isInsensitive\n ? lhs.toLowerCase().endsWith(value.toLowerCase())\n : lhs.endsWith(value);\n }\n case \"ne\":\n return !cmp(lhs, value);\n case \"gt\":\n return value != null && (lhs as never) > (value as never);\n case \"gte\":\n return value != null && (lhs as never) >= (value as never);\n case \"lt\":\n return value != null && (lhs as never) < (value as never);\n case \"lte\":\n return value != null && (lhs as never) <= (value as never);\n case \"eq\":\n default:\n return cmp(lhs, value);\n }\n};\n\n// Split-group AND/OR grouping: clauses with `connector: \"AND\"` (or no\n// connector) are conjoined, clauses with `connector: \"OR\"` are disjoined,\n// and the two groups are ANDed together. Mirrors the upstream better-auth\n// drizzle adapter's `convertWhereClause` so every backend (memory, sqlite,\n// postgres) observes the same mixed-connector semantics under the shared\n// conformance suite. This diverges from upstream's *memory* adapter, which\n// still uses a left-to-right fold; we prefer drizzle parity so that a\n// plugin that works against memory always works against SQL.\nconst matchAll = (record: Row, where: readonly CleanedWhere[]): boolean => {\n if (where.length === 0) return true;\n if (where.length === 1) return evalClause(record, where[0]!);\n const andGroup = where.filter(\n (w) => w.connector === \"AND\" || !w.connector,\n );\n const orGroup = where.filter((w) => w.connector === \"OR\");\n const andResult =\n andGroup.length === 0 ? true : andGroup.every((w) => evalClause(record, w));\n const orResult =\n orGroup.length === 0 ? true : orGroup.some((w) => evalClause(record, w));\n return andResult && orResult;\n};\n\nconst filterWhere = (rows: Row[], where: readonly CleanedWhere[]): Row[] =>\n rows.filter((r) => matchAll(r, where));\n\nconst cloneStore = (s: Store): Store => {\n const out: Store = {};\n for (const [k, v] of Object.entries(s)) {\n out[k] = v.map((r) => ({ ...r }));\n }\n return out;\n};\n\n// ---------------------------------------------------------------------------\n// makeMemoryAdapter — builds a DBAdapter wired up through createAdapter.\n// ---------------------------------------------------------------------------\n\nexport interface MakeMemoryAdapterOptions {\n readonly schema: DBSchema;\n readonly adapterId?: string;\n readonly generateId?: () => string;\n}\n\nexport const makeMemoryAdapter = (\n options: MakeMemoryAdapterOptions,\n): DBAdapter => {\n let store: Store = {};\n\n const tableFor = (model: string): Row[] => {\n if (!store[model]) store[model] = [];\n return store[model]!;\n };\n\n // Join resolver — mirrors the upstream memory adapter's path. Given a\n // base row and a resolved JoinConfig, look up matching rows in the\n // target model's table and attach them under the target's logical name.\n // For one-to-one we attach a single row (or null), otherwise we attach\n // an array capped at `limit`.\n const attachJoins = (base: Row, join: JoinConfig): Row => {\n const out: Row = { ...base };\n for (const [target, cfg] of Object.entries(join)) {\n const targetRows = tableFor(target);\n const matches = targetRows.filter(\n (r) => r[cfg.on.to] === base[cfg.on.from],\n );\n if (cfg.relation === \"one-to-one\") {\n out[target] = matches[0] ?? null;\n } else {\n const limit = cfg.limit ?? 100;\n out[target] = matches.slice(0, limit);\n }\n }\n return out;\n };\n\n const custom: CustomAdapter = {\n create: ({ model, data }) =>\n Effect.sync(() => {\n const table = tableFor(model);\n table.push(data as Row);\n return data;\n }),\n\n createMany: ({ model, data }) =>\n Effect.sync(() => {\n const table = tableFor(model);\n for (const row of data) table.push(row as Row);\n return data.slice() as never;\n }),\n\n findOne: ({ model, where, join }) =>\n Effect.sync(() => {\n const rows = filterWhere(tableFor(model), where);\n const first = rows[0];\n if (!first) return null as never;\n return (join ? attachJoins(first, join) : first) as never;\n }),\n\n findMany: ({ model, where, limit, sortBy, offset, join }) =>\n Effect.sync(() => {\n let rows = filterWhere(tableFor(model), where ?? []);\n if (sortBy) {\n const { field, direction } = sortBy;\n const sign = direction === \"asc\" ? 1 : -1;\n rows = rows.slice().sort((a, b) => {\n const av = a[field];\n const bv = b[field];\n if (av === bv) return 0;\n return (av as never) < (bv as never) ? -sign : sign;\n });\n }\n if (offset !== undefined) rows = rows.slice(offset);\n if (limit !== undefined && limit > 0) rows = rows.slice(0, limit);\n if (join) {\n return rows.map((r) => attachJoins(r, join)) as never[];\n }\n return rows as never[];\n }),\n\n count: ({ model, where }) =>\n Effect.sync(() => filterWhere(tableFor(model), where ?? []).length),\n\n update: ({ model, where, update }) =>\n Effect.sync(() => {\n const rows = filterWhere(tableFor(model), where);\n const first = rows[0];\n if (!first) return null;\n Object.assign(first, update as Row);\n return first as never;\n }),\n\n updateMany: ({ model, where, update }) =>\n Effect.sync(() => {\n const rows = filterWhere(tableFor(model), where);\n for (const r of rows) Object.assign(r, update);\n return rows.length;\n }),\n\n delete: ({ model, where }) =>\n Effect.sync(() => {\n const table = tableFor(model);\n const matches = filterWhere(table, where);\n const first = matches[0];\n if (!first) return;\n const idx = table.indexOf(first);\n if (idx >= 0) table.splice(idx, 1);\n }),\n\n deleteMany: ({ model, where }) =>\n Effect.sync(() => {\n const table = tableFor(model);\n const matches = new Set(filterWhere(table, where));\n let count = 0;\n store[model] = table.filter((r) => {\n if (matches.has(r)) {\n count++;\n return false;\n }\n return true;\n });\n return count;\n }),\n };\n\n // Snapshot-based transaction: clone on entry, restore on failure.\n const txFn: DBAdapterFactoryConfig[\"transaction\"] = <R, E>(\n cb: (trx: Parameters<DBAdapter[\"transaction\"]>[0] extends (\n t: infer T,\n ) => unknown\n ? T\n : never) => Effect.Effect<R, E>,\n ) =>\n Effect.gen(function* () {\n const snapshot = cloneStore(store);\n const result = yield* cb(adapter).pipe(\n Effect.catchAll((e) => {\n store = snapshot;\n return Effect.fail(e);\n }),\n );\n return result;\n }) as Effect.Effect<R, E | Error>;\n\n const adapter: DBAdapter = createAdapter({\n schema: options.schema,\n config: {\n adapterId: options.adapterId ?? \"memory\",\n supportsJSON: true,\n supportsDates: true,\n supportsBooleans: true,\n supportsArrays: true,\n customIdGenerator: options.generateId\n ? () => options.generateId!()\n : undefined,\n transaction: txFn,\n },\n adapter: custom,\n });\n\n return adapter;\n};\n","import { existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { createJiti } from \"jiti\";\nimport type { ExecutorCliConfig } from \"@executor/sdk/core\";\n\nconst defaultPaths = [\n \"executor.config.ts\",\n \"executor.config.js\",\n \"src/executor.config.ts\",\n \"src/executor.config.js\",\n];\n\nexport const getConfig = async (opts: {\n cwd: string;\n configPath?: string;\n}): Promise<ExecutorCliConfig | null> => {\n const { cwd, configPath } = opts;\n\n let resolvedPath: string | undefined;\n\n if (configPath) {\n resolvedPath = path.resolve(cwd, configPath);\n if (!existsSync(resolvedPath)) {\n console.error(`Config file not found: ${resolvedPath}`);\n return null;\n }\n } else {\n for (const p of defaultPaths) {\n const candidate = path.resolve(cwd, p);\n if (existsSync(candidate)) {\n resolvedPath = candidate;\n break;\n }\n }\n }\n\n if (!resolvedPath) return null;\n\n const jiti = createJiti(cwd, {\n interopDefault: true,\n moduleCache: false,\n });\n\n const mod = await jiti.import(resolvedPath);\n const config = (mod as { default?: ExecutorCliConfig }).default ?? mod;\n return config as ExecutorCliConfig;\n};\n","// ---------------------------------------------------------------------------\n// Drizzle schema generator — DBSchema → drizzle-orm TS source.\n//\n// Ported from better-auth (packages/cli/src/generators/drizzle.ts) under\n// MIT. Adapted for executor:\n// - Reads our DBSchema shape (modelName optional, key = default)\n// - No auth-specific logic (uuid/serial id modes, usePlural, camelCase)\n// - Always emits text primary keys\n// - Dialect from ExecutorCliConfig, not from adapter.options.provider\n// ---------------------------------------------------------------------------\n\nimport { existsSync } from \"node:fs\";\nimport type { DBSchema, DBFieldAttribute } from \"@executor/storage-core\";\nimport type { SchemaGenerator } from \"./types.js\";\n\ntype Dialect = \"pg\" | \"sqlite\" | \"mysql\";\n\nconst getModelName = (key: string, def: DBSchema[string]): string =>\n def.modelName ?? key;\n\nconst getType = (\n name: string,\n field: DBFieldAttribute,\n dialect: Dialect,\n): string => {\n if (field.references?.field === \"id\") {\n return `text('${name}')`;\n }\n\n const type = field.type;\n\n if (typeof type !== \"string\") {\n // Enum array — e.g. [\"active\", \"inactive\"]\n if (Array.isArray(type) && type.every((x) => typeof x === \"string\")) {\n return {\n sqlite: `text({ enum: [${type.map((x) => `'${x}'`).join(\", \")}] })`,\n pg: `text('${name}', { enum: [${type.map((x) => `'${x}'`).join(\", \")}] })`,\n mysql: `mysqlEnum([${type.map((x) => `'${x}'`).join(\", \")}])`,\n }[dialect];\n }\n throw new TypeError(\n `Invalid field type for field ${name}`,\n );\n }\n\n const typeMap: Record<string, Record<Dialect, string>> = {\n string: {\n sqlite: `text('${name}')`,\n pg: `text('${name}')`,\n mysql: field.unique\n ? `varchar('${name}', { length: 255 })`\n : field.references\n ? `varchar('${name}', { length: 36 })`\n : field.sortable\n ? `varchar('${name}', { length: 255 })`\n : field.index\n ? `varchar('${name}', { length: 255 })`\n : `text('${name}')`,\n },\n boolean: {\n sqlite: `integer('${name}', { mode: 'boolean' })`,\n pg: `boolean('${name}')`,\n mysql: `boolean('${name}')`,\n },\n number: {\n sqlite: `integer('${name}')`,\n pg: field.bigint\n ? `bigint('${name}', { mode: 'number' })`\n : `integer('${name}')`,\n mysql: field.bigint\n ? `bigint('${name}', { mode: 'number' })`\n : `int('${name}')`,\n },\n date: {\n sqlite: `integer('${name}', { mode: 'timestamp_ms' })`,\n pg: `timestamp('${name}')`,\n mysql: `timestamp('${name}', { fsp: 3 })`,\n },\n \"number[]\": {\n sqlite: `text('${name}', { mode: \"json\" })`,\n pg: field.bigint\n ? `bigint('${name}', { mode: 'number' }).array()`\n : `integer('${name}').array()`,\n mysql: `text('${name}', { mode: 'json' })`,\n },\n \"string[]\": {\n sqlite: `text('${name}', { mode: \"json\" })`,\n pg: `text('${name}').array()`,\n mysql: `text('${name}', { mode: \"json\" })`,\n },\n json: {\n sqlite: `text('${name}', { mode: \"json\" })`,\n pg: `jsonb('${name}')`,\n mysql: `json('${name}', { mode: \"json\" })`,\n },\n };\n\n const dbTypeMap = typeMap[type as string];\n if (!dbTypeMap) {\n throw new Error(\n `Unsupported field type '${field.type}' for field '${name}'.`,\n );\n }\n return dbTypeMap[dialect];\n};\n\n// ---------------------------------------------------------------------------\n// Generator\n// ---------------------------------------------------------------------------\n\nexport const generateDrizzleSchema: SchemaGenerator = async ({\n schema,\n dialect,\n file,\n}) => {\n const filePath = file || \"./executor-schema.ts\";\n const fileExist = existsSync(filePath);\n\n let code = generateImport({ dialect, schema });\n\n for (const [tableKey, tableDef] of Object.entries(schema)) {\n const modelName = getModelName(tableKey, tableDef);\n const fields = tableDef.fields;\n\n // Scoped tables get a composite `(scope_id, id)` primary key so two\n // tenants can register rows with the same user-facing id without\n // colliding on a globally-unique PK. Single-column PK stays for\n // unscoped tables (conformance fixtures, the blob store, etc.).\n const hasScopeId = Object.prototype.hasOwnProperty.call(fields, \"scope_id\");\n const id = hasScopeId ? `text('id').notNull()` : `text('id').primaryKey()`;\n\n type TableExtra =\n | { kind: \"uniqueIndex\" | \"index\"; name: string; on: string }\n | { kind: \"primaryKey\"; columns: readonly string[] };\n const extras: TableExtra[] = [];\n\n const assignExtras = (items: TableExtra[]): string => {\n if (!items.length) return \"\";\n const lines: string[] = [`, (table) => [`];\n for (const item of items) {\n if (item.kind === \"primaryKey\") {\n const cols = item.columns.map((c) => `table.${c}`).join(\", \");\n lines.push(` primaryKey({ columns: [${cols}] }),`);\n } else {\n lines.push(` ${item.kind}(\"${item.name}\").on(table.${item.on}),`);\n }\n }\n lines.push(`]`);\n return lines.join(\"\\n\");\n };\n\n if (hasScopeId) {\n extras.push({ kind: \"primaryKey\", columns: [\"scope_id\", \"id\"] });\n }\n\n const tableSchema = `export const ${tableKey} = ${dialect}Table(\"${modelName}\", {\n id: ${id},\n ${Object.entries(fields)\n .filter(([fieldName]) => fieldName !== \"id\")\n .map(([fieldName, attr]) => {\n const physical = attr.fieldName ?? fieldName;\n\n if (attr.index && !attr.unique) {\n extras.push({\n kind: \"index\",\n name: `${tableKey}_${physical}_idx`,\n on: physical,\n });\n } else if (attr.index && attr.unique) {\n extras.push({\n kind: \"uniqueIndex\",\n name: `${tableKey}_${physical}_uidx`,\n on: physical,\n });\n }\n\n let col = getType(physical, attr, dialect);\n\n if (\n attr.defaultValue !== null &&\n typeof attr.defaultValue !== \"undefined\"\n ) {\n if (typeof attr.defaultValue === \"function\") {\n if (\n attr.type === \"date\" &&\n attr.defaultValue.toString().includes(\"new Date()\")\n ) {\n if (dialect === \"sqlite\") {\n col += `.default(sql\\`(cast(unixepoch('subsecond') * 1000 as integer))\\`)`;\n } else {\n col += `.defaultNow()`;\n }\n }\n } else if (typeof attr.defaultValue === \"string\") {\n col += `.default(\"${attr.defaultValue}\")`;\n } else {\n col += `.default(${attr.defaultValue})`;\n }\n }\n\n if (attr.onUpdate && attr.type === \"date\") {\n if (typeof attr.onUpdate === \"function\") {\n col += `.$onUpdate(${attr.onUpdate})`;\n }\n }\n\n return `${physical}: ${col}${attr.required !== false ? \".notNull()\" : \"\"}${\n attr.unique ? \".unique()\" : \"\"\n }${\n attr.references\n ? `.references(()=> ${attr.references.model}.${attr.references.field ?? \"id\"}, { onDelete: '${\n attr.references.onDelete || \"cascade\"\n }' })`\n : \"\"\n }`;\n })\n .join(\",\\n \")}\n}${assignExtras(extras)});`;\n\n code += `\\n${tableSchema}\\n`;\n }\n\n // ---------------------------------------------------------------------------\n // Relations — scan FKs in both directions\n // ---------------------------------------------------------------------------\n\n let relationsString = \"\";\n for (const [tableKey, tableDef] of Object.entries(schema)) {\n const modelName = tableKey;\n\n type Relation = {\n key: string;\n model: string;\n type: \"one\" | \"many\";\n reference?: {\n field: string;\n references: string;\n fieldName: string;\n };\n };\n\n const oneRelations: Relation[] = [];\n const manyRelations: Relation[] = [];\n const manyRelationsSet = new Set<string>();\n\n // Find all FKs in THIS table → \"one\" relations\n for (const [fieldName, field] of Object.entries(tableDef.fields)) {\n if (!field.references) continue;\n const referencedModel = field.references.model;\n const physical = field.fieldName ?? fieldName;\n const fieldRef = `${tableKey}.${physical}`;\n const referenceRef = `${referencedModel}.${field.references.field || \"id\"}`;\n\n oneRelations.push({\n key: referencedModel,\n model: referencedModel,\n type: \"one\",\n reference: {\n field: fieldRef,\n references: referenceRef,\n fieldName,\n },\n });\n }\n\n // Find all OTHER tables that reference THIS table → \"many\" relations\n for (const [otherKey, otherDef] of Object.entries(schema)) {\n if (otherKey === tableKey) continue;\n const hasFK = Object.values(otherDef.fields).some(\n (field) => field.references?.model === tableKey,\n );\n if (!hasFK) continue;\n\n const relationKey = `${otherKey}s`;\n if (!manyRelationsSet.has(relationKey)) {\n manyRelationsSet.add(relationKey);\n manyRelations.push({\n key: relationKey,\n model: otherKey,\n type: \"many\",\n });\n }\n }\n\n // Detect duplicates\n const relationsByModel = new Map<string, Relation[]>();\n for (const rel of oneRelations) {\n if (!rel.reference) continue;\n const arr = relationsByModel.get(rel.key) ?? [];\n arr.push(rel);\n relationsByModel.set(rel.key, arr);\n }\n\n const duplicateRelations: Relation[] = [];\n const singleRelations: Relation[] = [];\n\n for (const [, rels] of relationsByModel.entries()) {\n if (rels.length > 1) {\n duplicateRelations.push(...rels);\n } else {\n singleRelations.push(rels[0]!);\n }\n }\n\n // Duplicate relations get field-specific exports\n for (const rel of duplicateRelations) {\n if (!rel.reference) continue;\n const relExportName = `${modelName}${rel.reference.fieldName.charAt(0).toUpperCase() + rel.reference.fieldName.slice(1)}Relations`;\n const block = `export const ${relExportName} = relations(${modelName}, ({ one }) => ({\n ${rel.key}: one(${rel.model}, {\n fields: [${rel.reference.field}],\n references: [${rel.reference.references}],\n })\n}))`;\n relationsString += `\\n${block}\\n`;\n }\n\n // Combined single relations\n const hasOne = singleRelations.length > 0;\n const hasMany = manyRelations.length > 0;\n\n if (hasOne || hasMany) {\n const destructured = [\n hasOne ? \"one\" : \"\",\n hasMany ? \"many\" : \"\",\n ]\n .filter(Boolean)\n .join(\", \");\n\n const body = [\n ...singleRelations\n .filter((r) => r.reference)\n .map(\n (r) =>\n ` ${r.key}: one(${r.model}, {\\n fields: [${r.reference!.field}],\\n references: [${r.reference!.references}],\\n })`,\n ),\n ...manyRelations.map(\n ({ key, model }) => ` ${key}: many(${model})`,\n ),\n ].join(\",\\n\");\n\n const block = `export const ${modelName}Relations = relations(${modelName}, ({ ${destructured} }) => ({\n${body}\n}))`;\n relationsString += `\\n${block}\\n`;\n }\n }\n\n code += `\\n${relationsString}`;\n\n return {\n code,\n fileName: filePath,\n overwrite: fileExist,\n };\n};\n\n// ---------------------------------------------------------------------------\n// Import generation — only emit what's actually used\n// ---------------------------------------------------------------------------\n\nfunction generateImport({\n dialect,\n schema,\n}: {\n dialect: Dialect;\n schema: DBSchema;\n}) {\n const rootImports: string[] = [];\n const coreImports: string[] = [];\n\n let hasBigint = false;\n let hasJson = false;\n let hasBoolean = false;\n let hasNumber = false;\n let hasDate = false;\n let hasIndex = false;\n let hasUniqueIndex = false;\n let hasReferences = false;\n let hasCompositePrimaryKey = false;\n\n for (const [tableKey, table] of Object.entries(schema)) {\n for (const field of Object.values(table.fields)) {\n if (field.bigint) hasBigint = true;\n if (field.type === \"json\") hasJson = true;\n if (field.type === \"boolean\") hasBoolean = true;\n if (field.type === \"number\" || field.type === \"number[]\") hasNumber = true;\n if (field.type === \"date\") hasDate = true;\n if (field.index && !field.unique) hasIndex = true;\n if (field.index && field.unique) hasUniqueIndex = true;\n if (field.references) hasReferences = true;\n }\n // Scoped tables get a composite (scope_id, id) PK — see generator\n // body where `primaryKey({ columns: [...] })` is emitted.\n if (Object.prototype.hasOwnProperty.call(table.fields, \"scope_id\")) {\n hasCompositePrimaryKey = true;\n }\n // Keep the generator silent about tableKey in this pass — we only\n // need the existence check above. Referenced here to satisfy lint.\n void tableKey;\n }\n\n coreImports.push(`${dialect}Table`);\n coreImports.push(\"text\");\n\n if (hasBoolean && dialect !== \"sqlite\") coreImports.push(\"boolean\");\n if (hasDate) {\n if (dialect === \"pg\") coreImports.push(\"timestamp\");\n // sqlite uses integer for timestamps, pg uses timestamp\n }\n if (hasNumber || dialect === \"sqlite\") {\n if (dialect === \"pg\") coreImports.push(\"integer\");\n else if (dialect === \"mysql\") coreImports.push(\"int\");\n else coreImports.push(\"integer\");\n }\n if (hasBigint && dialect !== \"sqlite\") coreImports.push(\"bigint\");\n if (hasJson) {\n if (dialect === \"pg\") coreImports.push(\"jsonb\");\n else if (dialect === \"mysql\") coreImports.push(\"json\");\n // sqlite uses text for JSON\n }\n if (hasIndex) coreImports.push(\"index\");\n if (hasUniqueIndex) coreImports.push(\"uniqueIndex\");\n if (hasCompositePrimaryKey) coreImports.push(\"primaryKey\");\n\n // sqlite needs integer for boolean + date\n if (dialect === \"sqlite\" && (hasBoolean || hasDate)) {\n if (!coreImports.includes(\"integer\")) coreImports.push(\"integer\");\n }\n // sqlite needs real for number\n if (dialect === \"sqlite\" && hasNumber) {\n // better-auth uses integer for numbers on sqlite; we use real()\n // for floating-point fidelity.\n }\n\n // Has any timestamp with defaultNow function?\n const hasSqliteTimestamp =\n dialect === \"sqlite\" &&\n Object.values(schema).some((table) =>\n Object.values(table.fields).some(\n (field) =>\n field.type === \"date\" &&\n field.defaultValue &&\n typeof field.defaultValue === \"function\" &&\n field.defaultValue.toString().includes(\"new Date()\"),\n ),\n );\n\n if (hasSqliteTimestamp) {\n rootImports.push(\"sql\");\n }\n\n if (hasReferences || dialect === \"mysql\") {\n // mysql might need varchar for FK fields\n }\n\n // `relations` is only imported when the schema has any references that\n // produce relation blocks (see relationsString generation).\n if (hasReferences) rootImports.push(\"relations\");\n\n const filteredCore = coreImports\n .map((x) => x.trim())\n .filter((x) => x !== \"\");\n\n // Deduplicate\n const uniqueCore = [...new Set(filteredCore)];\n const uniqueRoot = [...new Set(rootImports)];\n\n return `${uniqueRoot.length > 0 ? `import { ${uniqueRoot.join(\", \")} } from \"drizzle-orm\";\\n` : \"\"}import { ${uniqueCore.join(\", \")} } from \"drizzle-orm/${dialect}-core\";\\n`;\n}\n"],"mappings":"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","names":["Command","existsSync","path","Schema","Schema","Schema","Schema","Schema","Schema","Schema","Schema","Schema","Schema","Effect","Effect","Effect","existsSync","path","existsSync","Command"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@executor-js/cli",
|
|
3
|
-
"version": "0.0.1-beta.
|
|
3
|
+
"version": "0.0.1-beta.3",
|
|
4
4
|
"description": "CLI for the executor SDK — schema generation, migrations",
|
|
5
5
|
"homepage": "https://github.com/RhysSullivan/executor/tree/main/packages/core/cli",
|
|
6
6
|
"bugs": {
|
|
@@ -37,12 +37,13 @@
|
|
|
37
37
|
"typecheck": "tsc --noEmit"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@executor-js/sdk": "0.0.1",
|
|
41
|
-
"@executor-js/storage-core": "0.0.1-beta.0",
|
|
42
40
|
"commander": "^12.1.0",
|
|
41
|
+
"effect": "^3.21.0",
|
|
43
42
|
"jiti": "^2.6.1"
|
|
44
43
|
},
|
|
45
44
|
"devDependencies": {
|
|
45
|
+
"@executor-js/sdk": "0.0.1",
|
|
46
|
+
"@executor-js/storage-core": "0.0.1-beta.0",
|
|
46
47
|
"@types/node": "^24.3.1",
|
|
47
48
|
"tsup": "^8.5.0",
|
|
48
49
|
"typescript": "^5.9.3",
|