every-plugin 0.0.5 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +320 -6
- package/dist/errors.d.ts +4 -4
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +10 -10
- package/dist/index.d.ts +4 -4
- package/dist/index.js +3 -3
- package/dist/plugin.d.ts +99 -54
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +18 -16
- package/dist/runtime/errors.d.ts +62 -0
- package/dist/runtime/errors.d.ts.map +1 -0
- package/dist/runtime/errors.js +9 -0
- package/dist/runtime/index.d.ts +73 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +133 -0
- package/dist/runtime/mf.test.d.ts +2 -0
- package/dist/runtime/mf.test.d.ts.map +1 -0
- package/dist/runtime/mf.test.js +135 -0
- package/dist/runtime/runtime.test.d.ts +2 -0
- package/dist/runtime/runtime.test.d.ts.map +1 -0
- package/dist/runtime/runtime.test.js +378 -0
- package/dist/runtime/services/module-federation.service.d.ts +44 -0
- package/dist/runtime/services/module-federation.service.d.ts.map +1 -0
- package/dist/runtime/services/module-federation.service.js +131 -0
- package/dist/runtime/services/plugin.service.d.ts +75 -0
- package/dist/runtime/services/plugin.service.d.ts.map +1 -0
- package/dist/runtime/services/plugin.service.js +338 -0
- package/dist/runtime/services/secrets.service.d.ts +37 -0
- package/dist/runtime/services/secrets.service.d.ts.map +1 -0
- package/dist/runtime/services/secrets.service.js +36 -0
- package/dist/runtime/services.d.ts +4 -0
- package/dist/runtime/services.d.ts.map +1 -0
- package/dist/runtime/services.js +3 -0
- package/dist/runtime/streaming.d.ts +29 -0
- package/dist/runtime/streaming.d.ts.map +1 -0
- package/dist/runtime/streaming.js +59 -0
- package/dist/runtime/streaming.test.d.ts +2 -0
- package/dist/runtime/streaming.test.d.ts.map +1 -0
- package/dist/runtime/streaming.test.js +271 -0
- package/dist/runtime/test-utils.d.ts +3 -0
- package/dist/runtime/test-utils.d.ts.map +1 -0
- package/dist/runtime/test-utils.js +13 -0
- package/dist/runtime/types.d.ts +40 -0
- package/dist/runtime/types.d.ts.map +1 -0
- package/dist/runtime/types.js +1 -0
- package/dist/runtime/validation.d.ts +10 -0
- package/dist/runtime/validation.d.ts.map +1 -0
- package/dist/runtime/validation.js +16 -0
- package/dist/source.d.ts +63 -191
- package/dist/source.d.ts.map +1 -1
- package/dist/source.js +33 -70
- package/package.json +44 -22
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { Cache, Duration, Effect, Hash, Layer, Schedule } from "effect";
|
|
2
|
+
import { PluginLoggerTag } from "../../plugin";
|
|
3
|
+
import { PluginRuntimeError } from "../errors";
|
|
4
|
+
import { validate } from "../validation";
|
|
5
|
+
import { ModuleFederationService } from "./module-federation.service";
|
|
6
|
+
import { SecretsService } from "./secrets.service";
|
|
7
|
+
export class PluginService extends Effect.Tag("PluginService")() {
|
|
8
|
+
static Live = (registry, secrets) =>
|
|
9
|
+
Layer.effect(
|
|
10
|
+
PluginService,
|
|
11
|
+
Effect.gen(function* () {
|
|
12
|
+
const moduleFederationService = yield* ModuleFederationService;
|
|
13
|
+
const secretsService = yield* SecretsService;
|
|
14
|
+
const logger = yield* PluginLoggerTag;
|
|
15
|
+
const activePlugins = new Set();
|
|
16
|
+
const retrySchedule = Schedule.exponential(Duration.millis(100)).pipe(
|
|
17
|
+
Schedule.compose(Schedule.recurs(2)),
|
|
18
|
+
);
|
|
19
|
+
const resolveUrl = (baseUrl, version) =>
|
|
20
|
+
version && version !== "latest"
|
|
21
|
+
? baseUrl.replace("@latest", `@${version}`)
|
|
22
|
+
: baseUrl;
|
|
23
|
+
// Implementation functions
|
|
24
|
+
const loadPluginImpl = (pluginId) =>
|
|
25
|
+
Effect.gen(function* () {
|
|
26
|
+
const metadata = registry[pluginId];
|
|
27
|
+
if (!metadata) {
|
|
28
|
+
return yield* Effect.fail(
|
|
29
|
+
new PluginRuntimeError({
|
|
30
|
+
pluginId,
|
|
31
|
+
operation: "load-plugin",
|
|
32
|
+
cause: new Error(`Plugin ${pluginId} not found in registry`),
|
|
33
|
+
retryable: false,
|
|
34
|
+
}),
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
const url = resolveUrl(metadata.remoteUrl);
|
|
38
|
+
// Register and load the remote constructor
|
|
39
|
+
yield* moduleFederationService.registerRemote(pluginId, url).pipe(
|
|
40
|
+
Effect.mapError(
|
|
41
|
+
(error) =>
|
|
42
|
+
new PluginRuntimeError({
|
|
43
|
+
pluginId,
|
|
44
|
+
operation: "register-remote",
|
|
45
|
+
cause: error.cause,
|
|
46
|
+
retryable: true,
|
|
47
|
+
}),
|
|
48
|
+
),
|
|
49
|
+
);
|
|
50
|
+
logger.logInfo(`Trying to load ${pluginId}:${url}`);
|
|
51
|
+
const ctor = yield* moduleFederationService
|
|
52
|
+
.loadRemoteConstructor(pluginId, url)
|
|
53
|
+
.pipe(
|
|
54
|
+
Effect.mapError(
|
|
55
|
+
(error) =>
|
|
56
|
+
new PluginRuntimeError({
|
|
57
|
+
pluginId,
|
|
58
|
+
operation: "load-remote",
|
|
59
|
+
cause: error.cause,
|
|
60
|
+
retryable: false,
|
|
61
|
+
}),
|
|
62
|
+
),
|
|
63
|
+
);
|
|
64
|
+
return {
|
|
65
|
+
ctor,
|
|
66
|
+
metadata: {
|
|
67
|
+
pluginId,
|
|
68
|
+
version: metadata.version,
|
|
69
|
+
description: metadata.description,
|
|
70
|
+
type: metadata.type,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
const instantiatePluginImpl = (pluginConstructor) =>
|
|
75
|
+
Effect.gen(function* () {
|
|
76
|
+
const instance = yield* Effect.try({
|
|
77
|
+
try: () => new pluginConstructor.ctor(),
|
|
78
|
+
catch: (error) =>
|
|
79
|
+
new PluginRuntimeError({
|
|
80
|
+
pluginId: pluginConstructor.metadata.pluginId,
|
|
81
|
+
operation: "instantiate-plugin",
|
|
82
|
+
cause:
|
|
83
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
84
|
+
retryable: false,
|
|
85
|
+
}),
|
|
86
|
+
});
|
|
87
|
+
// Validate plugin ID matches
|
|
88
|
+
if (instance.id !== pluginConstructor.metadata.pluginId) {
|
|
89
|
+
return yield* Effect.fail(
|
|
90
|
+
new PluginRuntimeError({
|
|
91
|
+
pluginId: pluginConstructor.metadata.pluginId,
|
|
92
|
+
operation: "validate-plugin-id",
|
|
93
|
+
cause: new Error(
|
|
94
|
+
`Plugin ID mismatch: expected ${pluginConstructor.metadata.pluginId}, got ${instance.id}`,
|
|
95
|
+
),
|
|
96
|
+
retryable: false,
|
|
97
|
+
}),
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
plugin: instance,
|
|
102
|
+
metadata: pluginConstructor.metadata,
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
const initializePluginImpl = (pluginInstance, config) =>
|
|
106
|
+
Effect.gen(function* () {
|
|
107
|
+
const { plugin } = pluginInstance;
|
|
108
|
+
// Validate raw config using plugin's Zod schema
|
|
109
|
+
const validatedConfig = yield* validate(
|
|
110
|
+
plugin.configSchema,
|
|
111
|
+
config,
|
|
112
|
+
plugin.id,
|
|
113
|
+
"config",
|
|
114
|
+
).pipe(
|
|
115
|
+
Effect.mapError(
|
|
116
|
+
(validationError) =>
|
|
117
|
+
new PluginRuntimeError({
|
|
118
|
+
pluginId: plugin.id,
|
|
119
|
+
operation: "validate-config",
|
|
120
|
+
cause: validationError.zodError,
|
|
121
|
+
retryable: false,
|
|
122
|
+
}),
|
|
123
|
+
),
|
|
124
|
+
);
|
|
125
|
+
// Hydrate secrets
|
|
126
|
+
const hydratedConfig = yield* secretsService.hydrateSecrets(
|
|
127
|
+
validatedConfig,
|
|
128
|
+
secrets,
|
|
129
|
+
);
|
|
130
|
+
// Validate hydrated config
|
|
131
|
+
const finalConfig = yield* validate(
|
|
132
|
+
plugin.configSchema,
|
|
133
|
+
hydratedConfig,
|
|
134
|
+
plugin.id,
|
|
135
|
+
"config",
|
|
136
|
+
).pipe(
|
|
137
|
+
Effect.mapError(
|
|
138
|
+
(validationError) =>
|
|
139
|
+
new PluginRuntimeError({
|
|
140
|
+
pluginId: plugin.id,
|
|
141
|
+
operation: "validate-hydrated-config",
|
|
142
|
+
cause: validationError.zodError,
|
|
143
|
+
retryable: false,
|
|
144
|
+
}),
|
|
145
|
+
),
|
|
146
|
+
);
|
|
147
|
+
// Initialize plugin with retry logic
|
|
148
|
+
const pluginLayer = Layer.succeed(PluginLoggerTag, logger);
|
|
149
|
+
yield* plugin.initialize(finalConfig).pipe(
|
|
150
|
+
Effect.mapError(
|
|
151
|
+
(error) =>
|
|
152
|
+
new PluginRuntimeError({
|
|
153
|
+
pluginId: plugin.id,
|
|
154
|
+
operation: "initialize-plugin",
|
|
155
|
+
cause: error,
|
|
156
|
+
retryable: error.retryable ?? false,
|
|
157
|
+
}),
|
|
158
|
+
),
|
|
159
|
+
Effect.retry(
|
|
160
|
+
retrySchedule.pipe(
|
|
161
|
+
Schedule.whileInput((error) => error.retryable),
|
|
162
|
+
),
|
|
163
|
+
),
|
|
164
|
+
Effect.provide(pluginLayer),
|
|
165
|
+
);
|
|
166
|
+
const initializedPlugin = {
|
|
167
|
+
plugin,
|
|
168
|
+
metadata: pluginInstance.metadata,
|
|
169
|
+
config: finalConfig, // TODO: config type
|
|
170
|
+
};
|
|
171
|
+
activePlugins.add(initializedPlugin);
|
|
172
|
+
return initializedPlugin;
|
|
173
|
+
});
|
|
174
|
+
// Cache functionality
|
|
175
|
+
const generateConfigHash = (config) => {
|
|
176
|
+
return Hash.structure(config).toString();
|
|
177
|
+
};
|
|
178
|
+
// Store original config by hash for cache lookup
|
|
179
|
+
const configByHash = new Map();
|
|
180
|
+
const cache = yield* Cache.make({
|
|
181
|
+
capacity: 1024,
|
|
182
|
+
timeToLive: Duration.minutes(60),
|
|
183
|
+
lookup: (cacheKey) => {
|
|
184
|
+
const [pluginId, configHash] = cacheKey.split(":");
|
|
185
|
+
if (!pluginId || !configHash) {
|
|
186
|
+
return Effect.fail(
|
|
187
|
+
new PluginRuntimeError({
|
|
188
|
+
operation: "cache-lookup",
|
|
189
|
+
cause: new Error(`Invalid cache key format: ${cacheKey}`),
|
|
190
|
+
retryable: false,
|
|
191
|
+
}),
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
const config = configByHash.get(configHash);
|
|
195
|
+
if (!config) {
|
|
196
|
+
return Effect.fail(
|
|
197
|
+
new PluginRuntimeError({
|
|
198
|
+
operation: "cache-lookup",
|
|
199
|
+
cause: new Error(`No config found for hash: ${configHash}`),
|
|
200
|
+
retryable: false,
|
|
201
|
+
}),
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
return Effect.gen(function* () {
|
|
205
|
+
const ctor = yield* loadPluginImpl(pluginId);
|
|
206
|
+
const instance = yield* instantiatePluginImpl(ctor);
|
|
207
|
+
return yield* initializePluginImpl(instance, config);
|
|
208
|
+
});
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
return {
|
|
212
|
+
loadPlugin: loadPluginImpl,
|
|
213
|
+
instantiatePlugin: instantiatePluginImpl,
|
|
214
|
+
initializePlugin: initializePluginImpl,
|
|
215
|
+
executePlugin: (initializedPlugin, input) =>
|
|
216
|
+
Effect.gen(function* () {
|
|
217
|
+
const { plugin } = initializedPlugin;
|
|
218
|
+
// Validate input
|
|
219
|
+
const validatedInput = yield* validate(
|
|
220
|
+
plugin.inputSchema,
|
|
221
|
+
input,
|
|
222
|
+
plugin.id,
|
|
223
|
+
"input",
|
|
224
|
+
).pipe(
|
|
225
|
+
Effect.mapError((validationError) => {
|
|
226
|
+
console.log("got input error", validationError.zodError);
|
|
227
|
+
return new PluginRuntimeError({
|
|
228
|
+
pluginId: plugin.id,
|
|
229
|
+
operation: "validate-input",
|
|
230
|
+
cause: validationError.zodError,
|
|
231
|
+
retryable: false,
|
|
232
|
+
});
|
|
233
|
+
}),
|
|
234
|
+
);
|
|
235
|
+
// Execute plugin
|
|
236
|
+
const pluginLayer = Layer.succeed(PluginLoggerTag, logger);
|
|
237
|
+
const output = yield* plugin.execute(validatedInput).pipe(
|
|
238
|
+
Effect.mapError(
|
|
239
|
+
(error) =>
|
|
240
|
+
new PluginRuntimeError({
|
|
241
|
+
pluginId: plugin.id,
|
|
242
|
+
operation: "execute-plugin",
|
|
243
|
+
cause: error,
|
|
244
|
+
retryable: error.retryable ?? false,
|
|
245
|
+
}),
|
|
246
|
+
),
|
|
247
|
+
Effect.retry(
|
|
248
|
+
retrySchedule.pipe(
|
|
249
|
+
Schedule.whileInput((error) => error.retryable),
|
|
250
|
+
),
|
|
251
|
+
),
|
|
252
|
+
Effect.provide(pluginLayer),
|
|
253
|
+
);
|
|
254
|
+
// Validate output
|
|
255
|
+
const validatedOutput = yield* validate(
|
|
256
|
+
plugin.outputSchema,
|
|
257
|
+
output,
|
|
258
|
+
plugin.id,
|
|
259
|
+
"output",
|
|
260
|
+
).pipe(
|
|
261
|
+
Effect.mapError((validationError) => {
|
|
262
|
+
console.log("got output error", validationError.zodError);
|
|
263
|
+
return new PluginRuntimeError({
|
|
264
|
+
pluginId: plugin.id,
|
|
265
|
+
operation: "validate-output",
|
|
266
|
+
cause: validationError.zodError,
|
|
267
|
+
retryable: false,
|
|
268
|
+
});
|
|
269
|
+
}),
|
|
270
|
+
);
|
|
271
|
+
return validatedOutput;
|
|
272
|
+
}),
|
|
273
|
+
usePlugin: (pluginId, config) => {
|
|
274
|
+
const configHash = generateConfigHash(config);
|
|
275
|
+
const cacheKey = `${pluginId}:${configHash}`;
|
|
276
|
+
// Store config for cache lookup
|
|
277
|
+
configByHash.set(configHash, config);
|
|
278
|
+
return cache.get(cacheKey).pipe(Effect.map((plugin) => plugin));
|
|
279
|
+
},
|
|
280
|
+
shutdownPlugin: (plugin) =>
|
|
281
|
+
Effect.gen(function* () {
|
|
282
|
+
const pluginLayer = Layer.succeed(PluginLoggerTag, logger);
|
|
283
|
+
// Remove from active plugins
|
|
284
|
+
activePlugins.delete(plugin);
|
|
285
|
+
// Try to invalidate from cache if it exists there
|
|
286
|
+
const configHash = generateConfigHash(plugin.config);
|
|
287
|
+
const cacheKey = `${plugin.plugin.id}:${configHash}`;
|
|
288
|
+
yield* cache.invalidate(cacheKey).pipe(
|
|
289
|
+
Effect.catchAll(() => Effect.void), // Ignore if not in cache
|
|
290
|
+
);
|
|
291
|
+
// Clean up config hash
|
|
292
|
+
configByHash.delete(configHash);
|
|
293
|
+
// Shutdown the plugin
|
|
294
|
+
yield* plugin.plugin.shutdown().pipe(
|
|
295
|
+
Effect.mapError(
|
|
296
|
+
(error) =>
|
|
297
|
+
new PluginRuntimeError({
|
|
298
|
+
pluginId: plugin.plugin.id,
|
|
299
|
+
operation: "shutdown-plugin",
|
|
300
|
+
cause: error,
|
|
301
|
+
retryable: false,
|
|
302
|
+
}),
|
|
303
|
+
),
|
|
304
|
+
Effect.provide(pluginLayer),
|
|
305
|
+
);
|
|
306
|
+
}),
|
|
307
|
+
clearCache: () =>
|
|
308
|
+
Effect.sync(() => {
|
|
309
|
+
configByHash.clear();
|
|
310
|
+
}),
|
|
311
|
+
cleanup: () =>
|
|
312
|
+
Effect.gen(function* () {
|
|
313
|
+
const pluginLayer = Layer.succeed(PluginLoggerTag, logger);
|
|
314
|
+
// Call shutdown on all active plugins
|
|
315
|
+
for (const initializedPlugin of activePlugins) {
|
|
316
|
+
yield* initializedPlugin.plugin.shutdown().pipe(
|
|
317
|
+
Effect.catchAll((error) => {
|
|
318
|
+
// Log shutdown errors but don't fail the cleanup
|
|
319
|
+
return logger.logError(
|
|
320
|
+
`Failed to shutdown plugin ${initializedPlugin.plugin.id}`,
|
|
321
|
+
error,
|
|
322
|
+
{ pluginId: initializedPlugin.plugin.id },
|
|
323
|
+
);
|
|
324
|
+
}),
|
|
325
|
+
Effect.provide(pluginLayer),
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
// Clear the active plugins set
|
|
329
|
+
activePlugins.clear();
|
|
330
|
+
// Clear cache
|
|
331
|
+
configByHash.clear();
|
|
332
|
+
}),
|
|
333
|
+
};
|
|
334
|
+
}),
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
export const PluginServiceTag = PluginService;
|
|
338
|
+
export const PluginServiceLive = PluginService.Live;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Effect, Layer } from "effect";
|
|
2
|
+
import { PluginRuntimeError } from "../errors";
|
|
3
|
+
import type { SecretsConfig } from "../types";
|
|
4
|
+
export interface ISecretsService {
|
|
5
|
+
readonly hydrateSecrets: (
|
|
6
|
+
config: any, // TODO:
|
|
7
|
+
secretsConfig: SecretsConfig,
|
|
8
|
+
) => Effect.Effect<any, PluginRuntimeError>;
|
|
9
|
+
}
|
|
10
|
+
declare const SecretsService_base: import("effect/Context").TagClass<
|
|
11
|
+
SecretsService,
|
|
12
|
+
"SecretsService",
|
|
13
|
+
ISecretsService
|
|
14
|
+
> &
|
|
15
|
+
Effect.Tag.Proxy<SecretsService, ISecretsService> & {
|
|
16
|
+
use: <X>(
|
|
17
|
+
body: (_: ISecretsService) => X,
|
|
18
|
+
) => [X] extends [Effect.Effect<infer A, infer E, infer R>]
|
|
19
|
+
? Effect.Effect<A, E, R | SecretsService>
|
|
20
|
+
: [X] extends [PromiseLike<infer A_1>]
|
|
21
|
+
? Effect.Effect<
|
|
22
|
+
A_1,
|
|
23
|
+
import("effect/Cause").UnknownException,
|
|
24
|
+
SecretsService
|
|
25
|
+
>
|
|
26
|
+
: Effect.Effect<X, never, SecretsService>;
|
|
27
|
+
};
|
|
28
|
+
export declare class SecretsService extends SecretsService_base {
|
|
29
|
+
static Live: (
|
|
30
|
+
secrets: SecretsConfig,
|
|
31
|
+
) => Layer.Layer<SecretsService, never, never>;
|
|
32
|
+
}
|
|
33
|
+
export declare const SecretsServiceTag: typeof SecretsService;
|
|
34
|
+
export declare const SecretsServiceLive: (
|
|
35
|
+
secrets: SecretsConfig,
|
|
36
|
+
) => Layer.Layer<SecretsService, never, never>;
|
|
37
|
+
//# sourceMappingURL=secrets.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secrets.service.d.ts","sourceRoot":"","sources":["../../../src/runtime/services/secrets.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,cAAc,EAAE,CACxB,MAAM,EAAE,GAAG,EAAE,QAAQ;IACrB,aAAa,EAAE,aAAa,KACxB,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC;CAC5C;;;;AAED,qBAAa,cAAe,SAAQ,mBAGjC;IACF,MAAM,CAAC,IAAI,GAAI,SAAS,aAAa,+CAiCjC;CACJ;AAED,eAAO,MAAM,iBAAiB,uBAAiB,CAAC;AAChD,eAAO,MAAM,kBAAkB,YArCN,aAAa,8CAqCe,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Effect, Layer } from "effect";
|
|
2
|
+
import { PluginRuntimeError } from "../errors";
|
|
3
|
+
export class SecretsService extends Effect.Tag("SecretsService")() {
|
|
4
|
+
static Live = (secrets) =>
|
|
5
|
+
Layer.succeed(SecretsService, {
|
|
6
|
+
hydrateSecrets: (config, secretsConfig) =>
|
|
7
|
+
Effect.gen(function* () {
|
|
8
|
+
if (!config || typeof config !== "object" || !config.secrets) {
|
|
9
|
+
return config;
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
const configString = JSON.stringify(config);
|
|
13
|
+
let hydratedString = configString;
|
|
14
|
+
// Use provided secrets or fallback to secretsConfig
|
|
15
|
+
const effectiveSecrets = { ...secretsConfig, ...secrets };
|
|
16
|
+
// Simple template replacement for {{SECRET_NAME}} patterns
|
|
17
|
+
for (const [key, value] of Object.entries(effectiveSecrets)) {
|
|
18
|
+
const pattern = new RegExp(`{{${key}}}`, "g");
|
|
19
|
+
hydratedString = hydratedString.replace(pattern, value);
|
|
20
|
+
}
|
|
21
|
+
return JSON.parse(hydratedString);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
return yield* Effect.fail(
|
|
24
|
+
new PluginRuntimeError({
|
|
25
|
+
operation: "hydrate-secrets",
|
|
26
|
+
cause:
|
|
27
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
28
|
+
retryable: false,
|
|
29
|
+
}),
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
export const SecretsServiceTag = SecretsService;
|
|
36
|
+
export const SecretsServiceLive = SecretsService.Live;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"services.d.ts","sourceRoot":"","sources":["../../src/runtime/services.ts"],"names":[],"mappings":"AAAA,cAAc,sCAAsC,CAAC;AACrD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Effect, Stream } from "effect";
|
|
2
|
+
import type { SourcePlugin } from "../source";
|
|
3
|
+
import { PluginRuntimeError } from "./errors";
|
|
4
|
+
import type { InitializedPlugin } from "./types";
|
|
5
|
+
import z from "zod";
|
|
6
|
+
export interface SourceStreamOptions<TItem = unknown> {
|
|
7
|
+
maxItems?: number;
|
|
8
|
+
maxInvocations?: number;
|
|
9
|
+
stopWhenEmpty?: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Create a stream from a source plugin
|
|
13
|
+
* Note: Validation is handled at the runtime level before this function is called
|
|
14
|
+
*/
|
|
15
|
+
export declare const createSourceStream: <
|
|
16
|
+
T extends SourcePlugin,
|
|
17
|
+
TInput extends z.infer<T["inputSchema"]>,
|
|
18
|
+
TItem,
|
|
19
|
+
>(
|
|
20
|
+
initializedPlugin: InitializedPlugin<T>,
|
|
21
|
+
executePlugin: (
|
|
22
|
+
plugin: InitializedPlugin<T>,
|
|
23
|
+
input: TInput,
|
|
24
|
+
) => Effect.Effect<any, PluginRuntimeError>,
|
|
25
|
+
input: TInput,
|
|
26
|
+
options: SourceStreamOptions<TItem> | undefined,
|
|
27
|
+
isStreamable: boolean,
|
|
28
|
+
) => Stream.Stream<TItem, PluginRuntimeError>;
|
|
29
|
+
//# sourceMappingURL=streaming.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"streaming.d.ts","sourceRoot":"","sources":["../../src/runtime/streaming.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,MAAM,EAAU,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEjD,OAAO,CAAC,MAAM,KAAK,CAAC;AAGpB,MAAM,WAAW,mBAAmB,CAAC,KAAK,GAAG,OAAO;IACnD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,OAAO,CAAC;CACxB;AAMD;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAC9B,CAAC,SAAS,YAAY,EACtB,MAAM,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,EACxC,KAAK,EAEL,mBAAmB,iBAAiB,CAAC,CAAC,CAAC,EACvC,eAAe,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,kBAAkB,CAAC,EACtG,OAAO,MAAM,EACb,SAAS,mBAAmB,CAAC,KAAK,CAAC,YAAK,EACxC,cAAc,OAAO,KACnB,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,kBAAkB,CAqEzC,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Duration, Effect, Option, Stream } from "effect";
|
|
2
|
+
const extractDelayFromState = (state) => {
|
|
3
|
+
return state?.nextPollMs ? Duration.millis(state.nextPollMs) : null;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Create a stream from a source plugin
|
|
7
|
+
* Note: Validation is handled at the runtime level before this function is called
|
|
8
|
+
*/
|
|
9
|
+
export const createSourceStream = (initializedPlugin, executePlugin, input, options = {}, isStreamable) => {
|
|
10
|
+
// For non-streamable procedures, execute once and return items
|
|
11
|
+
if (!isStreamable) {
|
|
12
|
+
return Stream.fromEffect(executePlugin(initializedPlugin, input).pipe(Effect.map((result) => {
|
|
13
|
+
const resultObj = result;
|
|
14
|
+
const items = Array.isArray(resultObj.items)
|
|
15
|
+
? resultObj.items
|
|
16
|
+
: resultObj.item
|
|
17
|
+
? [resultObj.item]
|
|
18
|
+
: [];
|
|
19
|
+
return items;
|
|
20
|
+
}))).pipe(Stream.flatMap(items => Stream.fromIterable(items)));
|
|
21
|
+
}
|
|
22
|
+
const initialState = input.state;
|
|
23
|
+
const stream = Stream.unfoldEffect({ state: initialState, invocations: 0 }, ({ state: currentState, invocations }) => Effect.gen(function* () {
|
|
24
|
+
console.log(`[STREAM] Invocation ${invocations}:`);
|
|
25
|
+
console.log(`[STREAM] - Current state:`, JSON.stringify(currentState, null, 2));
|
|
26
|
+
console.log(`[STREAM] - Input:`, JSON.stringify(input, null, 2));
|
|
27
|
+
// Check invocation limit if specified
|
|
28
|
+
if (options.maxInvocations && invocations >= options.maxInvocations) {
|
|
29
|
+
console.log(`[STREAM] - Stopping: maxInvocations (${options.maxInvocations}) reached`);
|
|
30
|
+
return Option.none();
|
|
31
|
+
}
|
|
32
|
+
// Extract timing from plugin state (nextPollMs)
|
|
33
|
+
const nextDelay = extractDelayFromState(currentState) || Duration.minutes(1); // 1min default
|
|
34
|
+
yield* Effect.sleep(nextDelay);
|
|
35
|
+
// Execute plugin
|
|
36
|
+
const pluginInput = { ...input, state: currentState };
|
|
37
|
+
console.log(`[STREAM] - Plugin input:`, JSON.stringify(pluginInput, null, 2));
|
|
38
|
+
const result = yield* executePlugin(initializedPlugin, pluginInput);
|
|
39
|
+
const resultObj = result;
|
|
40
|
+
const items = Array.isArray(resultObj.items)
|
|
41
|
+
? resultObj.items
|
|
42
|
+
: [];
|
|
43
|
+
const nextState = resultObj.nextState;
|
|
44
|
+
console.log(`[STREAM] - Plugin output:`, JSON.stringify(resultObj, null, 2));
|
|
45
|
+
console.log(`[STREAM] - Items count: ${items.length}`);
|
|
46
|
+
console.log(`[STREAM] - Next state:`, JSON.stringify(nextState, null, 2));
|
|
47
|
+
// Check stop conditions ONLY
|
|
48
|
+
if (options.stopWhenEmpty && items.length === 0) {
|
|
49
|
+
console.log(`[STREAM] - Stopping: stopWhenEmpty=true and items.length=0`);
|
|
50
|
+
return Option.none();
|
|
51
|
+
}
|
|
52
|
+
console.log(`[STREAM] - Continuing to next iteration`);
|
|
53
|
+
return Option.some([items, { state: nextState, invocations: invocations + 1 }]);
|
|
54
|
+
})).pipe(
|
|
55
|
+
// Flatten items from each iteration
|
|
56
|
+
Stream.flatMap(items => Stream.fromIterable(items)));
|
|
57
|
+
// Apply maxItems limit if specified
|
|
58
|
+
return options.maxItems ? stream.pipe(Stream.take(options.maxItems)) : stream;
|
|
59
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"streaming.test.d.ts","sourceRoot":"","sources":["../../src/runtime/streaming.test.ts"],"names":[],"mappings":""}
|