owletto 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +58 -0
- package/src/api-paginated.ts +185 -0
- package/src/base.ts +173 -0
- package/src/browser/launcher.ts +213 -0
- package/src/browser/stealth.ts +297 -0
- package/src/browser-paginated.ts +425 -0
- package/src/cli.ts +438 -0
- package/src/connector-runtime.ts +63 -0
- package/src/connector-types.ts +299 -0
- package/src/http.ts +82 -0
- package/src/index.ts +106 -0
- package/src/logger.ts +10 -0
- package/src/paginated.ts +301 -0
- package/src/retry.ts +168 -0
- package/src/scoring.ts +57 -0
- package/src/types.ts +289 -0
package/src/cli.ts
ADDED
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Owletto SDK CLI
|
|
5
|
+
*
|
|
6
|
+
* Compile connectors and generate manifest.json:
|
|
7
|
+
*
|
|
8
|
+
* owletto-sdk compile connectors/reddit.ts connectors/github.ts
|
|
9
|
+
* owletto-sdk compile connectors/*.ts
|
|
10
|
+
* owletto-sdk compile connectors/ # discover .ts files in dir
|
|
11
|
+
* owletto-sdk compile --outdir dist connectors/*.ts
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
copyFileSync,
|
|
16
|
+
existsSync,
|
|
17
|
+
mkdirSync,
|
|
18
|
+
readdirSync,
|
|
19
|
+
readFileSync,
|
|
20
|
+
statSync,
|
|
21
|
+
unlinkSync,
|
|
22
|
+
writeFileSync,
|
|
23
|
+
} from 'node:fs';
|
|
24
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
25
|
+
import { pathToFileURL } from 'node:url';
|
|
26
|
+
import { build } from 'esbuild';
|
|
27
|
+
|
|
28
|
+
interface ManifestConnectorEntry {
|
|
29
|
+
key: string;
|
|
30
|
+
file: string;
|
|
31
|
+
name: string;
|
|
32
|
+
version: string;
|
|
33
|
+
description?: string;
|
|
34
|
+
authSchema?: Record<string, unknown>;
|
|
35
|
+
feeds?: Record<string, unknown>;
|
|
36
|
+
actions?: Record<string, unknown>;
|
|
37
|
+
optionsSchema?: Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface ManifestInsightEntry {
|
|
41
|
+
slug: string;
|
|
42
|
+
file: string;
|
|
43
|
+
name: string;
|
|
44
|
+
description?: string;
|
|
45
|
+
version: number;
|
|
46
|
+
source_types: {
|
|
47
|
+
required: string[];
|
|
48
|
+
recommended: string[];
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface ReleaseManifest {
|
|
53
|
+
connectors: ManifestConnectorEntry[];
|
|
54
|
+
insights: ManifestInsightEntry[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Back-compat alias
|
|
58
|
+
type ManifestEntry = ManifestConnectorEntry;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Resolve input paths to concrete entry points.
|
|
62
|
+
*
|
|
63
|
+
* Accepts a mix of:
|
|
64
|
+
* - .ts files → compile directly
|
|
65
|
+
* - directories → discover .ts files inside
|
|
66
|
+
*/
|
|
67
|
+
function resolveEntryPoints(paths: string[]): Array<{ name: string; entryPoint: string }> {
|
|
68
|
+
const entries: Array<{ name: string; entryPoint: string }> = [];
|
|
69
|
+
|
|
70
|
+
for (const p of paths) {
|
|
71
|
+
const abs = resolve(p);
|
|
72
|
+
if (!existsSync(abs)) {
|
|
73
|
+
console.error(`Not found: ${abs}`);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (statSync(abs).isFile()) {
|
|
78
|
+
if (!abs.endsWith('.ts')) {
|
|
79
|
+
console.error(`Not a TypeScript file: ${abs}`);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
entries.push({ name: basename(abs, '.ts'), entryPoint: abs });
|
|
83
|
+
} else {
|
|
84
|
+
// Directory: discover .ts files
|
|
85
|
+
for (const child of readdirSync(abs, { withFileTypes: true })) {
|
|
86
|
+
if (!child.isFile() || !child.name.endsWith('.ts')) continue;
|
|
87
|
+
entries.push({
|
|
88
|
+
name: child.name.replace(/\.ts$/, ''),
|
|
89
|
+
entryPoint: join(abs, child.name),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return entries;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Extract definition metadata from a compiled bundle by dynamically importing
|
|
100
|
+
* it and finding the ConnectorRuntime subclass.
|
|
101
|
+
*/
|
|
102
|
+
async function extractDefinitionFromBundle(
|
|
103
|
+
bundlePath: string
|
|
104
|
+
): Promise<Omit<ManifestEntry, 'file'> | null> {
|
|
105
|
+
const moduleHref = `${pathToFileURL(bundlePath).href}?ts=${Date.now()}`;
|
|
106
|
+
const mod = (await import(moduleHref)) as Record<string, unknown>;
|
|
107
|
+
|
|
108
|
+
// Find ConnectorRuntime subclass: class with .sync() + .execute()
|
|
109
|
+
const candidates = Object.values(mod);
|
|
110
|
+
const RuntimeClass = candidates.find(
|
|
111
|
+
(candidate) =>
|
|
112
|
+
typeof candidate === 'function' &&
|
|
113
|
+
!!(candidate as { prototype?: { sync?: unknown; execute?: unknown } }).prototype?.sync &&
|
|
114
|
+
!!(candidate as { prototype?: { sync?: unknown; execute?: unknown } }).prototype?.execute
|
|
115
|
+
) as (new () => { definition: unknown }) | undefined;
|
|
116
|
+
|
|
117
|
+
if (!RuntimeClass) return null;
|
|
118
|
+
|
|
119
|
+
const instance = new RuntimeClass();
|
|
120
|
+
const rawDef = instance.definition as Record<string, unknown> | undefined;
|
|
121
|
+
if (!rawDef) return null;
|
|
122
|
+
|
|
123
|
+
const key = typeof rawDef.key === 'string' ? rawDef.key : null;
|
|
124
|
+
const name = typeof rawDef.name === 'string' ? rawDef.name : null;
|
|
125
|
+
const version = typeof rawDef.version === 'string' ? rawDef.version : null;
|
|
126
|
+
if (!key || !name || !version) {
|
|
127
|
+
throw new Error('Connector definition must include key, name, and version.');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
key,
|
|
132
|
+
name,
|
|
133
|
+
version,
|
|
134
|
+
description: typeof rawDef.description === 'string' ? rawDef.description : undefined,
|
|
135
|
+
authSchema:
|
|
136
|
+
rawDef.authSchema && typeof rawDef.authSchema === 'object'
|
|
137
|
+
? (rawDef.authSchema as Record<string, unknown>)
|
|
138
|
+
: undefined,
|
|
139
|
+
feeds:
|
|
140
|
+
rawDef.feeds && typeof rawDef.feeds === 'object'
|
|
141
|
+
? (rawDef.feeds as Record<string, unknown>)
|
|
142
|
+
: undefined,
|
|
143
|
+
actions:
|
|
144
|
+
rawDef.actions && typeof rawDef.actions === 'object'
|
|
145
|
+
? (rawDef.actions as Record<string, unknown>)
|
|
146
|
+
: undefined,
|
|
147
|
+
optionsSchema:
|
|
148
|
+
rawDef.optionsSchema && typeof rawDef.optionsSchema === 'object'
|
|
149
|
+
? (rawDef.optionsSchema as Record<string, unknown>)
|
|
150
|
+
: undefined,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* esbuild plugin to resolve `npm:package@version` specifiers.
|
|
156
|
+
* Strips the `npm:` prefix and version, leaving just the bare package name
|
|
157
|
+
* so esbuild resolves it from node_modules.
|
|
158
|
+
*/
|
|
159
|
+
function npmSpecifierPlugin(): import('esbuild').Plugin {
|
|
160
|
+
return {
|
|
161
|
+
name: 'npm-specifier',
|
|
162
|
+
setup(build) {
|
|
163
|
+
build.onResolve({ filter: /^npm:/ }, (args) => {
|
|
164
|
+
const specifier = args.path.slice(4); // strip "npm:"
|
|
165
|
+
const isScoped = specifier.startsWith('@');
|
|
166
|
+
const match = isScoped
|
|
167
|
+
? specifier.match(/^(@[^/]+\/[^/@]+)(?:@[^/]+)?(\/.*)?$/)
|
|
168
|
+
: specifier.match(/^([^/@]+)(?:@[^/]+)?(\/.*)?$/);
|
|
169
|
+
if (!match) return { external: true, path: args.path };
|
|
170
|
+
const pkg = match[1];
|
|
171
|
+
const subpath = match[2] ?? '';
|
|
172
|
+
return { path: `${pkg}${subpath}`, external: true };
|
|
173
|
+
});
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* esbuild plugin that stubs all external runtime dependencies (npm: specifiers,
|
|
180
|
+
* playwright) with empty modules. Used to build a lightweight "metadata-only"
|
|
181
|
+
* bundle that can be imported just to read the ConnectorRuntime definition,
|
|
182
|
+
* without needing any npm packages installed.
|
|
183
|
+
*/
|
|
184
|
+
function stubRuntimeDepsPlugin(): import('esbuild').Plugin {
|
|
185
|
+
return {
|
|
186
|
+
name: 'stub-runtime-deps',
|
|
187
|
+
setup(build) {
|
|
188
|
+
// Stub npm: specifiers
|
|
189
|
+
build.onResolve({ filter: /^npm:/ }, (args) => ({
|
|
190
|
+
path: args.path,
|
|
191
|
+
namespace: 'stub',
|
|
192
|
+
}));
|
|
193
|
+
// Stub playwright
|
|
194
|
+
build.onResolve({ filter: /^playwright/ }, (args) => ({
|
|
195
|
+
path: args.path,
|
|
196
|
+
namespace: 'stub',
|
|
197
|
+
}));
|
|
198
|
+
// Return a CJS module whose exports are a callable/constructable proxy.
|
|
199
|
+
// __esModule must be falsy so esbuild's __toESM sets target.default = mod,
|
|
200
|
+
// making default imports resolve to the proxy (which is constructable).
|
|
201
|
+
build.onLoad({ filter: /.*/, namespace: 'stub' }, () => ({
|
|
202
|
+
contents: `const stub = new Proxy(function(){}, { get: (_, k) => k === '__esModule' ? undefined : stub, apply: () => stub, construct: () => stub }); module.exports = stub;`,
|
|
203
|
+
loader: 'js',
|
|
204
|
+
}));
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Build a temporary "metadata-only" bundle with all runtime deps stubbed,
|
|
211
|
+
* extract the definition, then clean up.
|
|
212
|
+
*/
|
|
213
|
+
async function extractDefinitionViaStubBuild(
|
|
214
|
+
entryPoint: string,
|
|
215
|
+
outputDir: string,
|
|
216
|
+
sdkEntry: string,
|
|
217
|
+
name: string
|
|
218
|
+
): Promise<Omit<ManifestEntry, 'file'> | null> {
|
|
219
|
+
const stubFile = join(outputDir, `.${name}.meta.mjs`);
|
|
220
|
+
try {
|
|
221
|
+
await build({
|
|
222
|
+
entryPoints: [entryPoint],
|
|
223
|
+
bundle: true,
|
|
224
|
+
format: 'esm',
|
|
225
|
+
platform: 'node',
|
|
226
|
+
target: 'node20',
|
|
227
|
+
outfile: stubFile,
|
|
228
|
+
alias: { owletto: sdkEntry },
|
|
229
|
+
banner: {
|
|
230
|
+
js: `import { createRequire as __createRequire } from 'module'; const require = __createRequire(import.meta.url);`,
|
|
231
|
+
},
|
|
232
|
+
plugins: [stubRuntimeDepsPlugin()],
|
|
233
|
+
minify: false,
|
|
234
|
+
sourcemap: false,
|
|
235
|
+
logLevel: 'silent',
|
|
236
|
+
});
|
|
237
|
+
return await extractDefinitionFromBundle(stubFile);
|
|
238
|
+
} finally {
|
|
239
|
+
try {
|
|
240
|
+
unlinkSync(stubFile);
|
|
241
|
+
} catch {}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async function compile(inputPaths: string[], outdir: string) {
|
|
246
|
+
const entries = resolveEntryPoints(inputPaths);
|
|
247
|
+
if (entries.length === 0) {
|
|
248
|
+
console.error('No connector source files found.');
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const outputDir = resolve(outdir);
|
|
253
|
+
mkdirSync(outputDir, { recursive: true });
|
|
254
|
+
|
|
255
|
+
// Resolve SDK directory for the owletto alias.
|
|
256
|
+
const sdkEntry = join(import.meta.dirname, 'index.ts');
|
|
257
|
+
|
|
258
|
+
const manifest: ManifestEntry[] = [];
|
|
259
|
+
|
|
260
|
+
for (const entry of entries) {
|
|
261
|
+
const outFile = join(outputDir, `${entry.name}.js`);
|
|
262
|
+
|
|
263
|
+
try {
|
|
264
|
+
await build({
|
|
265
|
+
entryPoints: [entry.entryPoint],
|
|
266
|
+
bundle: true,
|
|
267
|
+
format: 'esm',
|
|
268
|
+
platform: 'node',
|
|
269
|
+
target: 'node20',
|
|
270
|
+
outfile: outFile,
|
|
271
|
+
alias: {
|
|
272
|
+
owletto: sdkEntry,
|
|
273
|
+
},
|
|
274
|
+
banner: {
|
|
275
|
+
js: `import { createRequire as __createRequire } from 'module'; const require = __createRequire(import.meta.url);`,
|
|
276
|
+
},
|
|
277
|
+
external: ['playwright'],
|
|
278
|
+
plugins: [npmSpecifierPlugin()],
|
|
279
|
+
minify: false,
|
|
280
|
+
sourcemap: false,
|
|
281
|
+
});
|
|
282
|
+
console.log(`Built: ${entry.name} -> ${outFile}`);
|
|
283
|
+
} catch (err) {
|
|
284
|
+
console.error(`Failed to build ${entry.name}:`, err);
|
|
285
|
+
process.exit(1);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Extract metadata: try the real bundle first, fall back to a stub build
|
|
289
|
+
// that replaces all runtime deps with empty modules (we only need to
|
|
290
|
+
// instantiate the class and read .definition, not run sync/execute).
|
|
291
|
+
let def: Omit<ManifestEntry, 'file'> | null = null;
|
|
292
|
+
try {
|
|
293
|
+
def = await extractDefinitionFromBundle(outFile);
|
|
294
|
+
} catch {
|
|
295
|
+
// Real bundle failed (missing npm deps) — try stub build
|
|
296
|
+
try {
|
|
297
|
+
def = await extractDefinitionViaStubBuild(
|
|
298
|
+
entry.entryPoint,
|
|
299
|
+
outputDir,
|
|
300
|
+
sdkEntry,
|
|
301
|
+
entry.name
|
|
302
|
+
);
|
|
303
|
+
} catch (stubErr) {
|
|
304
|
+
console.error(` Failed to extract metadata from ${entry.name}:`, stubErr);
|
|
305
|
+
process.exit(1);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (def) {
|
|
310
|
+
manifest.push({ file: `${entry.name}.js`, ...def });
|
|
311
|
+
console.log(` Extracted metadata: ${def.key}@${def.version}`);
|
|
312
|
+
} else {
|
|
313
|
+
console.warn(
|
|
314
|
+
` Warning: no ConnectorRuntime class found in ${entry.name}, skipping metadata`
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Discover insight template JSON files from insights/ directories
|
|
320
|
+
// (sibling to each connector input path)
|
|
321
|
+
const insightEntries: ManifestInsightEntry[] = [];
|
|
322
|
+
const seenInsightsDir = new Set<string>();
|
|
323
|
+
|
|
324
|
+
for (const inputPath of inputPaths) {
|
|
325
|
+
const abs = resolve(inputPath);
|
|
326
|
+
const parentDir = statSync(abs).isDirectory() ? resolve(abs, '..') : dirname(abs);
|
|
327
|
+
const insightsDir = join(parentDir, 'insights');
|
|
328
|
+
|
|
329
|
+
if (seenInsightsDir.has(insightsDir) || !existsSync(insightsDir)) continue;
|
|
330
|
+
seenInsightsDir.add(insightsDir);
|
|
331
|
+
|
|
332
|
+
for (const child of readdirSync(insightsDir, { withFileTypes: true })) {
|
|
333
|
+
if (!child.isFile() || !child.name.endsWith('.json')) continue;
|
|
334
|
+
|
|
335
|
+
const srcFile = join(insightsDir, child.name);
|
|
336
|
+
const destFile = join(outputDir, child.name);
|
|
337
|
+
|
|
338
|
+
try {
|
|
339
|
+
const content = JSON.parse(readFileSync(srcFile, 'utf-8')) as Record<string, unknown>;
|
|
340
|
+
|
|
341
|
+
if (!content.slug || !content.name || !content.version || !content.prompt) {
|
|
342
|
+
console.warn(
|
|
343
|
+
` Skipping ${child.name}: missing required fields (slug, name, version, prompt)`
|
|
344
|
+
);
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const sourceTypes = (content.source_types ?? {}) as {
|
|
349
|
+
required?: string[];
|
|
350
|
+
recommended?: string[];
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
insightEntries.push({
|
|
354
|
+
slug: content.slug as string,
|
|
355
|
+
file: child.name,
|
|
356
|
+
name: content.name as string,
|
|
357
|
+
description: typeof content.description === 'string' ? content.description : undefined,
|
|
358
|
+
version: content.version as number,
|
|
359
|
+
source_types: {
|
|
360
|
+
required: Array.isArray(sourceTypes.required) ? sourceTypes.required : [],
|
|
361
|
+
recommended: Array.isArray(sourceTypes.recommended) ? sourceTypes.recommended : [],
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
copyFileSync(srcFile, destFile);
|
|
366
|
+
console.log(` Insight template: ${content.slug} v${content.version}`);
|
|
367
|
+
} catch (err) {
|
|
368
|
+
console.warn(
|
|
369
|
+
` Skipping ${child.name}: ${err instanceof Error ? err.message : String(err)}`
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const releaseManifest: ReleaseManifest = {
|
|
376
|
+
connectors: manifest,
|
|
377
|
+
insights: insightEntries,
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
const manifestPath = join(outputDir, 'manifest.json');
|
|
381
|
+
writeFileSync(manifestPath, JSON.stringify(releaseManifest, null, 2));
|
|
382
|
+
|
|
383
|
+
console.log(
|
|
384
|
+
`\nCompiled ${manifest.length} connector(s), ${insightEntries.length} insight template(s). Manifest: ${manifestPath}`
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// --- CLI entry ---
|
|
389
|
+
|
|
390
|
+
function parseArgs(argv: string[]) {
|
|
391
|
+
let outdir = 'dist';
|
|
392
|
+
const paths: string[] = [];
|
|
393
|
+
|
|
394
|
+
let i = 0;
|
|
395
|
+
while (i < argv.length) {
|
|
396
|
+
if (argv[i] === '--outdir' || argv[i] === '-o') {
|
|
397
|
+
outdir = argv[++i] || 'dist';
|
|
398
|
+
} else {
|
|
399
|
+
paths.push(argv[i]);
|
|
400
|
+
}
|
|
401
|
+
i++;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return { outdir, paths };
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const allArgs = process.argv.slice(2);
|
|
408
|
+
const command = allArgs[0];
|
|
409
|
+
|
|
410
|
+
if (command === 'compile') {
|
|
411
|
+
const { outdir, paths } = parseArgs(allArgs.slice(1));
|
|
412
|
+
|
|
413
|
+
if (paths.length === 0) {
|
|
414
|
+
paths.push('.');
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
compile(paths, outdir).catch((err) => {
|
|
418
|
+
console.error('Compile failed:', err);
|
|
419
|
+
process.exit(1);
|
|
420
|
+
});
|
|
421
|
+
} else {
|
|
422
|
+
console.log('Usage: owletto-sdk compile [options] <files or dirs...>');
|
|
423
|
+
console.log('');
|
|
424
|
+
console.log('Commands:');
|
|
425
|
+
console.log(' compile Compile connector(s) and generate manifest.json');
|
|
426
|
+
console.log('');
|
|
427
|
+
console.log('Options:');
|
|
428
|
+
console.log(' --outdir, -o <dir> Output directory (default: "dist")');
|
|
429
|
+
console.log('');
|
|
430
|
+
console.log('Examples:');
|
|
431
|
+
console.log(' owletto-sdk compile connectors/reddit.ts');
|
|
432
|
+
console.log(' owletto-sdk compile connectors/*.ts');
|
|
433
|
+
console.log(' owletto-sdk compile connectors/');
|
|
434
|
+
console.log(' owletto-sdk compile -o build connectors/*.ts');
|
|
435
|
+
if (command && command !== '--help' && command !== '-h') {
|
|
436
|
+
process.exit(1);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Connector Runtime
|
|
3
|
+
*
|
|
4
|
+
* Abstract base class that all connectors must implement.
|
|
5
|
+
* Provides the contract for sync (read) and execute (write) operations.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
ActionContext,
|
|
10
|
+
ActionResult,
|
|
11
|
+
ConnectorDefinition,
|
|
12
|
+
SyncContext,
|
|
13
|
+
SyncResult,
|
|
14
|
+
} from './connector-types.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* ConnectorRuntime is the base class for all connectors.
|
|
18
|
+
*
|
|
19
|
+
* Subclasses must:
|
|
20
|
+
* - Set `definition` with connector metadata
|
|
21
|
+
* - Implement `sync()` for feed data ingestion
|
|
22
|
+
* - Implement `execute()` for action execution
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* class GmailConnector extends ConnectorRuntime {
|
|
27
|
+
* definition = { key: 'google.gmail', name: 'Gmail', version: '1.0.0', ... };
|
|
28
|
+
*
|
|
29
|
+
* async sync(ctx: SyncContext): Promise<SyncResult> {
|
|
30
|
+
* // Fetch threads from Gmail API
|
|
31
|
+
* }
|
|
32
|
+
*
|
|
33
|
+
* async execute(ctx: ActionContext): Promise<ActionResult> {
|
|
34
|
+
* // Create draft, send email, etc.
|
|
35
|
+
* }
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export abstract class ConnectorRuntime {
|
|
40
|
+
/** Connector definition with metadata, feed schemas, and action schemas */
|
|
41
|
+
abstract readonly definition: ConnectorDefinition;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Sync data from the connected service.
|
|
45
|
+
*
|
|
46
|
+
* Called by the worker when a sync run is executed.
|
|
47
|
+
* Should return events to ingest and an updated checkpoint.
|
|
48
|
+
*
|
|
49
|
+
* @param ctx - Sync context with feed config, checkpoint, and credentials
|
|
50
|
+
* @returns Events and updated checkpoint
|
|
51
|
+
*/
|
|
52
|
+
abstract sync(ctx: SyncContext): Promise<SyncResult>;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Execute an action on the connected service.
|
|
56
|
+
*
|
|
57
|
+
* Called either inline (low-risk) or by the worker (high-risk with approval).
|
|
58
|
+
*
|
|
59
|
+
* @param ctx - Action context with action key, input, and credentials
|
|
60
|
+
* @returns Action result with output data
|
|
61
|
+
*/
|
|
62
|
+
abstract execute(ctx: ActionContext): Promise<ActionResult>;
|
|
63
|
+
}
|