every-plugin 0.0.6 → 0.1.1
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 +1 -265
- package/dist/errors.d.ts +4 -4
- package/dist/index.d.ts +2 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -3
- package/dist/plugin.d.ts +74 -34
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +100 -17
- package/dist/runtime/errors.d.ts +38 -34
- package/dist/runtime/errors.d.ts.map +1 -1
- package/dist/runtime/errors.js +2 -0
- package/dist/runtime/index.d.ts +7 -4
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +5 -10
- package/dist/runtime/services/module-federation.service.d.ts +5 -6
- package/dist/runtime/services/module-federation.service.d.ts.map +1 -1
- package/dist/runtime/services/module-federation.service.js +53 -15
- package/dist/runtime/services/plugin.service.d.ts +13 -6
- package/dist/runtime/services/plugin.service.d.ts.map +1 -1
- package/dist/runtime/services/plugin.service.js +237 -94
- package/dist/runtime/services/secrets.service.d.ts.map +1 -1
- package/dist/runtime/services.d.ts +0 -1
- package/dist/runtime/services.d.ts.map +1 -1
- package/dist/runtime/services.js +0 -1
- package/dist/runtime/streaming/index.d.ts +4 -0
- package/dist/runtime/streaming/index.d.ts.map +1 -0
- package/dist/runtime/streaming/index.js +2 -0
- package/dist/runtime/streaming/state-effects.d.ts +28 -0
- package/dist/runtime/streaming/state-effects.d.ts.map +1 -0
- package/dist/runtime/streaming/state-effects.js +30 -0
- package/dist/runtime/streaming/streaming.d.ts +8 -0
- package/dist/runtime/streaming/streaming.d.ts.map +1 -0
- package/dist/runtime/streaming/streaming.js +107 -0
- package/dist/runtime/streaming/types.d.ts +19 -0
- package/dist/runtime/streaming/types.d.ts.map +1 -0
- package/dist/runtime/streaming/types.js +1 -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 +7 -0
- package/dist/runtime/types.d.ts +1 -1
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/runtime/validation.d.ts +2 -7
- package/dist/runtime/validation.d.ts.map +1 -1
- package/dist/testing/index.d.ts +25 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +40 -0
- package/dist/testing/mocks/module-federation.service.d.ts +8 -0
- package/dist/testing/mocks/module-federation.service.d.ts.map +1 -0
- package/dist/testing/mocks/module-federation.service.js +35 -0
- package/package.json +27 -6
- package/dist/runtime/runtime.d.ts +0 -58
- package/dist/runtime/runtime.d.ts.map +0 -1
- package/dist/runtime/runtime.js +0 -75
- package/dist/runtime/runtime.test.d.ts +0 -2
- package/dist/runtime/runtime.test.d.ts.map +0 -1
- package/dist/runtime/runtime.test.js +0 -308
- package/dist/runtime/services/plugin-cache.service.d.ts +0 -18
- package/dist/runtime/services/plugin-cache.service.d.ts.map +0 -1
- package/dist/runtime/services/plugin-cache.service.js +0 -55
- package/dist/source.d.ts +0 -198
- package/dist/source.d.ts.map +0 -1
- package/dist/source.js +0 -90
|
@@ -1,127 +1,270 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { call } from "@orpc/server";
|
|
2
|
+
import { Cache, Duration, Effect, Hash, Layer, Schedule } from "effect";
|
|
2
3
|
import { PluginLoggerTag } from "../../plugin";
|
|
3
4
|
import { PluginRuntimeError } from "../errors";
|
|
5
|
+
import { createSourceStream } from "../streaming";
|
|
4
6
|
import { validate } from "../validation";
|
|
5
|
-
import {
|
|
7
|
+
import { ModuleFederationService } from "./module-federation.service";
|
|
6
8
|
import { SecretsService } from "./secrets.service";
|
|
7
9
|
export class PluginService extends Effect.Tag("PluginService")() {
|
|
8
10
|
static Live = (registry, secrets) => Layer.effect(PluginService, Effect.gen(function* () {
|
|
9
|
-
const
|
|
11
|
+
const moduleFederationService = yield* ModuleFederationService;
|
|
10
12
|
const secretsService = yield* SecretsService;
|
|
11
13
|
const logger = yield* PluginLoggerTag;
|
|
14
|
+
const activePlugins = new Set();
|
|
12
15
|
const retrySchedule = Schedule.exponential(Duration.millis(100)).pipe(Schedule.compose(Schedule.recurs(2)));
|
|
13
16
|
const resolveUrl = (baseUrl, version) => version && version !== "latest"
|
|
14
17
|
? baseUrl.replace("@latest", `@${version}`)
|
|
15
18
|
: baseUrl;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
// Implementation functions
|
|
20
|
+
const loadPluginImpl = (pluginId) => Effect.gen(function* () {
|
|
21
|
+
const metadata = registry[pluginId];
|
|
22
|
+
if (!metadata) {
|
|
23
|
+
return yield* Effect.fail(new PluginRuntimeError({
|
|
24
|
+
pluginId,
|
|
25
|
+
operation: "load-plugin",
|
|
26
|
+
cause: new Error(`Plugin ${pluginId} not found in registry`),
|
|
27
|
+
retryable: false,
|
|
28
|
+
}));
|
|
29
|
+
}
|
|
30
|
+
const url = resolveUrl(metadata.remoteUrl);
|
|
31
|
+
// Register and load the remote constructor
|
|
32
|
+
yield* moduleFederationService.registerRemote(pluginId, url).pipe(Effect.mapError((error) => new PluginRuntimeError({
|
|
33
|
+
pluginId,
|
|
34
|
+
operation: "register-remote",
|
|
35
|
+
cause: error.cause,
|
|
36
|
+
retryable: true,
|
|
37
|
+
})));
|
|
38
|
+
logger.logDebug(`Trying to load ${pluginId}:${url}`);
|
|
39
|
+
const ctor = yield* moduleFederationService.loadRemoteConstructor(pluginId, url).pipe(Effect.mapError((error) => new PluginRuntimeError({
|
|
40
|
+
pluginId,
|
|
41
|
+
operation: "load-remote",
|
|
42
|
+
cause: error.cause,
|
|
43
|
+
retryable: false
|
|
44
|
+
})));
|
|
45
|
+
return {
|
|
46
|
+
ctor,
|
|
47
|
+
metadata: {
|
|
48
|
+
pluginId,
|
|
49
|
+
version: metadata.version,
|
|
50
|
+
description: metadata.description,
|
|
51
|
+
type: metadata.type,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
const instantiatePluginImpl = (pluginConstructor) => Effect.gen(function* () {
|
|
56
|
+
const instance = yield* Effect.try({
|
|
57
|
+
try: () => new pluginConstructor.ctor(),
|
|
58
|
+
catch: (error) => new PluginRuntimeError({
|
|
59
|
+
pluginId: pluginConstructor.metadata.pluginId,
|
|
60
|
+
operation: "instantiate-plugin",
|
|
61
|
+
cause: error instanceof Error ? error : new Error(String(error)),
|
|
62
|
+
retryable: false,
|
|
63
|
+
}),
|
|
64
|
+
});
|
|
65
|
+
// Validate plugin ID matches
|
|
66
|
+
if (instance.id !== pluginConstructor.metadata.pluginId) {
|
|
67
|
+
return yield* Effect.fail(new PluginRuntimeError({
|
|
68
|
+
pluginId: pluginConstructor.metadata.pluginId,
|
|
69
|
+
operation: "validate-plugin-id",
|
|
70
|
+
cause: new Error(`Plugin ID mismatch: expected ${pluginConstructor.metadata.pluginId}, got ${instance.id}`),
|
|
71
|
+
retryable: false,
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
plugin: instance,
|
|
76
|
+
metadata: pluginConstructor.metadata,
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
const initializePluginImpl = (pluginInstance, config) => Effect.gen(function* () {
|
|
80
|
+
const { plugin } = pluginInstance;
|
|
81
|
+
// Validate raw config using plugin's Zod schema
|
|
82
|
+
const validatedConfig = yield* validate(plugin.configSchema, config, plugin.id, "config").pipe(Effect.mapError((validationError) => new PluginRuntimeError({
|
|
83
|
+
pluginId: plugin.id,
|
|
84
|
+
operation: "validate-config",
|
|
85
|
+
cause: validationError.zodError,
|
|
86
|
+
retryable: false,
|
|
87
|
+
})));
|
|
88
|
+
// Hydrate secrets
|
|
89
|
+
const hydratedConfig = yield* secretsService.hydrateSecrets(validatedConfig, secrets);
|
|
90
|
+
// Validate hydrated config
|
|
91
|
+
const finalConfig = yield* validate(plugin.configSchema, hydratedConfig, plugin.id, "config").pipe(Effect.mapError((validationError) => new PluginRuntimeError({
|
|
92
|
+
pluginId: plugin.id,
|
|
93
|
+
operation: "validate-hydrated-config",
|
|
94
|
+
cause: validationError.zodError,
|
|
95
|
+
retryable: false,
|
|
96
|
+
})));
|
|
97
|
+
// Initialize plugin with retry logic
|
|
98
|
+
const pluginLayer = Layer.succeed(PluginLoggerTag, logger);
|
|
99
|
+
yield* plugin.initialize(finalConfig).pipe(Effect.mapError((error) => new PluginRuntimeError({
|
|
100
|
+
pluginId: plugin.id,
|
|
101
|
+
operation: "initialize-plugin",
|
|
102
|
+
cause: error,
|
|
103
|
+
retryable: error.retryable ?? false,
|
|
104
|
+
})), Effect.retry(retrySchedule.pipe(Schedule.whileInput((error) => error.retryable))), Effect.provide(pluginLayer));
|
|
105
|
+
const initializedPlugin = {
|
|
106
|
+
plugin,
|
|
107
|
+
metadata: pluginInstance.metadata,
|
|
108
|
+
config: finalConfig, // TODO: config type
|
|
109
|
+
};
|
|
110
|
+
activePlugins.add(initializedPlugin);
|
|
111
|
+
return initializedPlugin;
|
|
112
|
+
});
|
|
113
|
+
// Cache functionality
|
|
114
|
+
const generateConfigHash = (config) => {
|
|
115
|
+
return Hash.structure(config).toString();
|
|
116
|
+
};
|
|
117
|
+
// Store original config by hash for cache lookup
|
|
118
|
+
const configByHash = new Map();
|
|
119
|
+
const cache = yield* Cache.make({
|
|
120
|
+
capacity: 1024,
|
|
121
|
+
timeToLive: Duration.minutes(60),
|
|
122
|
+
lookup: (cacheKey) => {
|
|
123
|
+
const [pluginId, configHash] = cacheKey.split(':');
|
|
124
|
+
if (!pluginId || !configHash) {
|
|
125
|
+
return Effect.fail(new PluginRuntimeError({
|
|
126
|
+
operation: "cache-lookup",
|
|
127
|
+
cause: new Error(`Invalid cache key format: ${cacheKey}`),
|
|
24
128
|
retryable: false,
|
|
25
129
|
}));
|
|
26
130
|
}
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
metadata: {
|
|
33
|
-
pluginId,
|
|
34
|
-
version: metadata.version,
|
|
35
|
-
description: metadata.description,
|
|
36
|
-
type: metadata.type,
|
|
37
|
-
},
|
|
38
|
-
};
|
|
39
|
-
}),
|
|
40
|
-
instantiatePlugin: (pluginConstructor) => Effect.gen(function* () {
|
|
41
|
-
const instance = yield* Effect.try({
|
|
42
|
-
try: () => new pluginConstructor.ctor(),
|
|
43
|
-
catch: (error) => new PluginRuntimeError({
|
|
44
|
-
pluginId: pluginConstructor.metadata.pluginId,
|
|
45
|
-
operation: "instantiate-plugin",
|
|
46
|
-
cause: error instanceof Error ? error : new Error(String(error)),
|
|
47
|
-
retryable: false,
|
|
48
|
-
}),
|
|
49
|
-
});
|
|
50
|
-
// Validate plugin ID matches
|
|
51
|
-
if (instance.id !== pluginConstructor.metadata.pluginId) {
|
|
52
|
-
return yield* Effect.fail(new PluginRuntimeError({
|
|
53
|
-
pluginId: pluginConstructor.metadata.pluginId,
|
|
54
|
-
operation: "validate-plugin-id",
|
|
55
|
-
cause: new Error(`Plugin ID mismatch: expected ${pluginConstructor.metadata.pluginId}, got ${instance.id}`),
|
|
131
|
+
const config = configByHash.get(configHash);
|
|
132
|
+
if (!config) {
|
|
133
|
+
return Effect.fail(new PluginRuntimeError({
|
|
134
|
+
operation: "cache-lookup",
|
|
135
|
+
cause: new Error(`No config found for hash: ${configHash}`),
|
|
56
136
|
retryable: false,
|
|
57
137
|
}));
|
|
58
138
|
}
|
|
59
|
-
return {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
139
|
+
return Effect.gen(function* () {
|
|
140
|
+
const ctor = yield* loadPluginImpl(pluginId);
|
|
141
|
+
const instance = yield* instantiatePluginImpl(ctor);
|
|
142
|
+
return yield* initializePluginImpl(instance, config);
|
|
143
|
+
});
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
const usePluginImpl = (pluginId, config) => {
|
|
147
|
+
const configHash = generateConfigHash(config);
|
|
148
|
+
const cacheKey = `${pluginId}:${configHash}`;
|
|
149
|
+
// Store config for cache lookup
|
|
150
|
+
configByHash.set(configHash, config);
|
|
151
|
+
return cache.get(cacheKey).pipe(Effect.map(plugin => plugin));
|
|
152
|
+
};
|
|
153
|
+
// Extract executePlugin implementation for reuse
|
|
154
|
+
const executePluginImpl = (initializedPlugin, input) => Effect.gen(function* () {
|
|
155
|
+
const { plugin } = initializedPlugin;
|
|
156
|
+
// Validate input
|
|
157
|
+
const validatedInput = yield* validate(plugin.inputSchema, input, plugin.id, "input").pipe(Effect.mapError((validationError) => {
|
|
158
|
+
return new PluginRuntimeError({
|
|
68
159
|
pluginId: plugin.id,
|
|
69
|
-
operation: "validate-
|
|
160
|
+
operation: "validate-input",
|
|
70
161
|
cause: validationError.zodError,
|
|
71
162
|
retryable: false,
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
163
|
+
});
|
|
164
|
+
}));
|
|
165
|
+
// Get oRPC router and call procedure directly
|
|
166
|
+
const router = plugin.createRouter();
|
|
167
|
+
const procedureName = validatedInput.procedure;
|
|
168
|
+
if (!(procedureName in router)) {
|
|
169
|
+
return yield* Effect.fail(new PluginRuntimeError({
|
|
77
170
|
pluginId: plugin.id,
|
|
78
|
-
operation: "
|
|
79
|
-
cause:
|
|
171
|
+
operation: "execute-plugin",
|
|
172
|
+
cause: new Error(`Unknown procedure: ${procedureName}`),
|
|
80
173
|
retryable: false,
|
|
81
|
-
}))
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
174
|
+
}));
|
|
175
|
+
}
|
|
176
|
+
const context = 'state' in validatedInput ? { state: validatedInput.state } : {};
|
|
177
|
+
const output = yield* Effect.tryPromise({
|
|
178
|
+
try: () => {
|
|
179
|
+
const procedure = router[procedureName];
|
|
180
|
+
if (!procedure) {
|
|
181
|
+
throw new Error(`Procedure ${procedureName} not found in router`);
|
|
182
|
+
}
|
|
183
|
+
// Use oRPC call utility as per docs
|
|
184
|
+
return call(procedure, validatedInput.input, { context });
|
|
185
|
+
},
|
|
186
|
+
catch: (error) => new PluginRuntimeError({
|
|
87
187
|
pluginId: plugin.id,
|
|
88
|
-
operation: "
|
|
89
|
-
cause: error,
|
|
90
|
-
retryable:
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
188
|
+
operation: "execute-plugin",
|
|
189
|
+
cause: error instanceof Error ? error : new Error(String(error)),
|
|
190
|
+
retryable: true,
|
|
191
|
+
}),
|
|
192
|
+
}).pipe(Effect.retry(retrySchedule.pipe(Schedule.whileInput((error) => error.retryable))));
|
|
193
|
+
return output;
|
|
194
|
+
});
|
|
195
|
+
return {
|
|
196
|
+
loadPlugin: loadPluginImpl,
|
|
197
|
+
instantiatePlugin: instantiatePluginImpl,
|
|
198
|
+
initializePlugin: initializePluginImpl,
|
|
199
|
+
executePlugin: executePluginImpl,
|
|
200
|
+
usePlugin: usePluginImpl,
|
|
201
|
+
streamPlugin: (pluginId, config, input, options) => Effect.gen(function* () {
|
|
202
|
+
const initializedPlugin = yield* usePluginImpl(pluginId, config);
|
|
203
|
+
// Check if procedure is streamable and validate state
|
|
204
|
+
const procedureName = input.procedure;
|
|
205
|
+
const isStreamable = initializedPlugin.plugin.isStreamable(procedureName);
|
|
206
|
+
if (!isStreamable) {
|
|
207
|
+
return yield* Effect.fail(new PluginRuntimeError({
|
|
208
|
+
pluginId,
|
|
209
|
+
operation: "stream-plugin-validate",
|
|
210
|
+
cause: new Error(`Procedure ${procedureName} is not streamable`),
|
|
211
|
+
retryable: false,
|
|
212
|
+
}));
|
|
213
|
+
}
|
|
214
|
+
// Check stateSchema exists for streamable procedures
|
|
215
|
+
if (!('stateSchema' in initializedPlugin.plugin) || !initializedPlugin.plugin.stateSchema) {
|
|
216
|
+
return yield* Effect.fail(new PluginRuntimeError({
|
|
217
|
+
pluginId: initializedPlugin.plugin.id,
|
|
218
|
+
operation: "validate-state",
|
|
219
|
+
cause: new Error(`Streamable plugin ${initializedPlugin.plugin.id} must have a stateSchema`),
|
|
220
|
+
retryable: false,
|
|
221
|
+
}));
|
|
222
|
+
}
|
|
223
|
+
// Validate initial state
|
|
224
|
+
yield* validate(initializedPlugin.plugin.stateSchema, input.state, initializedPlugin.plugin.id, "state").pipe(Effect.mapError((validationError) => new PluginRuntimeError({
|
|
225
|
+
pluginId: initializedPlugin.plugin.id,
|
|
226
|
+
operation: "validate-state",
|
|
104
227
|
cause: validationError.zodError,
|
|
105
228
|
retryable: false,
|
|
106
229
|
})));
|
|
107
|
-
//
|
|
230
|
+
// Create and return the stream
|
|
231
|
+
return createSourceStream(initializedPlugin, executePluginImpl, input, options);
|
|
232
|
+
}),
|
|
233
|
+
shutdownPlugin: (plugin) => Effect.gen(function* () {
|
|
108
234
|
const pluginLayer = Layer.succeed(PluginLoggerTag, logger);
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
235
|
+
// Remove from active plugins
|
|
236
|
+
activePlugins.delete(plugin);
|
|
237
|
+
// Try to invalidate from cache if it exists there
|
|
238
|
+
const configHash = generateConfigHash(plugin.config);
|
|
239
|
+
const cacheKey = `${plugin.plugin.id}:${configHash}`;
|
|
240
|
+
yield* cache.invalidate(cacheKey).pipe(Effect.catchAll(() => Effect.void) // Ignore if not in cache
|
|
241
|
+
);
|
|
242
|
+
// Clean up config hash
|
|
243
|
+
configByHash.delete(configHash);
|
|
244
|
+
// Shutdown the plugin
|
|
245
|
+
yield* plugin.plugin.shutdown().pipe(Effect.mapError((error) => new PluginRuntimeError({
|
|
246
|
+
pluginId: plugin.plugin.id,
|
|
247
|
+
operation: "shutdown-plugin",
|
|
114
248
|
cause: error,
|
|
115
|
-
retryable: error.retryable ?? false,
|
|
116
|
-
})), Effect.retry(retrySchedule.pipe(Schedule.whileInput((error) => error.retryable))), Effect.provide(pluginLayer));
|
|
117
|
-
// Validate output
|
|
118
|
-
const validatedOutput = yield* validate(plugin.outputSchema, output, plugin.id, "output").pipe(Effect.mapError((validationError) => new PluginRuntimeError({
|
|
119
|
-
pluginId: plugin.id,
|
|
120
|
-
operation: "validate-output",
|
|
121
|
-
cause: validationError.zodError,
|
|
122
249
|
retryable: false,
|
|
123
|
-
})));
|
|
124
|
-
|
|
250
|
+
})), Effect.provide(pluginLayer));
|
|
251
|
+
}),
|
|
252
|
+
clearCache: () => Effect.sync(() => {
|
|
253
|
+
configByHash.clear();
|
|
254
|
+
}),
|
|
255
|
+
cleanup: () => Effect.gen(function* () {
|
|
256
|
+
const pluginLayer = Layer.succeed(PluginLoggerTag, logger);
|
|
257
|
+
// Call shutdown on all active plugins
|
|
258
|
+
for (const initializedPlugin of activePlugins) {
|
|
259
|
+
yield* initializedPlugin.plugin.shutdown().pipe(Effect.catchAll((error) => {
|
|
260
|
+
// Log shutdown errors but don't fail the cleanup
|
|
261
|
+
return logger.logError(`Failed to shutdown plugin ${initializedPlugin.plugin.id}`, error, { pluginId: initializedPlugin.plugin.id });
|
|
262
|
+
}), Effect.provide(pluginLayer));
|
|
263
|
+
}
|
|
264
|
+
// Clear the active plugins set
|
|
265
|
+
activePlugins.clear();
|
|
266
|
+
// Clear cache
|
|
267
|
+
configByHash.clear();
|
|
125
268
|
}),
|
|
126
269
|
};
|
|
127
270
|
}));
|
|
@@ -1 +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;
|
|
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"}
|
|
@@ -1 +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,
|
|
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"}
|
package/dist/runtime/services.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/runtime/streaming/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,YAAY,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State transition helpers for common patterns
|
|
3
|
+
*/
|
|
4
|
+
export declare const StateTransitions: {
|
|
5
|
+
/**
|
|
6
|
+
* Transition to a new phase with optional updates
|
|
7
|
+
*/
|
|
8
|
+
to: <T extends {
|
|
9
|
+
phase?: string;
|
|
10
|
+
}>(phase: string, updates?: Partial<T>) => (state: T) => T;
|
|
11
|
+
/**
|
|
12
|
+
* Add polling delay to state
|
|
13
|
+
*/
|
|
14
|
+
withPolling: <T>(delayMs: number) => (state: T) => T;
|
|
15
|
+
/**
|
|
16
|
+
* Set error state with message and stop polling
|
|
17
|
+
*/
|
|
18
|
+
withError: <T>(errorMessage: string) => (state: T) => T;
|
|
19
|
+
/**
|
|
20
|
+
* Preserve existing state with updates
|
|
21
|
+
*/
|
|
22
|
+
update: <T>(updates: Partial<T>) => (state: T) => T;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Simple pipe utility for composing state transformations
|
|
26
|
+
*/
|
|
27
|
+
export declare const pipe: <T>(value: T, ...fns: Array<(val: T) => T>) => T;
|
|
28
|
+
//# sourceMappingURL=state-effects.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-effects.d.ts","sourceRoot":"","sources":["../../../src/runtime/streaming/state-effects.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,gBAAgB;IAC3B;;OAEG;SACE,CAAC,SAAS;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,SAAS,MAAM,YAAW,OAAO,CAAC,CAAC,CAAC,MAClE,OAAO,CAAC,KAAG,CAAC;IAEf;;OAEG;kBACW,CAAC,WAAW,MAAM,MAC7B,OAAO,CAAC,KAAG,CAAC;IAEf;;OAEG;gBACS,CAAC,gBAAgB,MAAM,MAChC,OAAO,CAAC,KAAG,CAAC;IAOf;;OAEG;aACM,CAAC,WAAW,OAAO,CAAC,CAAC,CAAC,MAC5B,OAAO,CAAC,KAAG,CAAC;CAChB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,IAAI,GAAI,CAAC,EAAE,OAAO,CAAC,EAAE,GAAG,KAAK,KAAK,CAAC,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,KAAG,CACxB,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State transition helpers for common patterns
|
|
3
|
+
*/
|
|
4
|
+
export const StateTransitions = {
|
|
5
|
+
/**
|
|
6
|
+
* Transition to a new phase with optional updates
|
|
7
|
+
*/
|
|
8
|
+
to: (phase, updates = {}) => (state) => ({ ...state, phase, ...updates }),
|
|
9
|
+
/**
|
|
10
|
+
* Add polling delay to state
|
|
11
|
+
*/
|
|
12
|
+
withPolling: (delayMs) => (state) => ({ ...state, nextPollMs: delayMs }),
|
|
13
|
+
/**
|
|
14
|
+
* Set error state with message and stop polling
|
|
15
|
+
*/
|
|
16
|
+
withError: (errorMessage) => (state) => ({
|
|
17
|
+
...state,
|
|
18
|
+
phase: 'error',
|
|
19
|
+
errorMessage,
|
|
20
|
+
nextPollMs: null
|
|
21
|
+
}),
|
|
22
|
+
/**
|
|
23
|
+
* Preserve existing state with updates
|
|
24
|
+
*/
|
|
25
|
+
update: (updates) => (state) => ({ ...state, ...updates }),
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Simple pipe utility for composing state transformations
|
|
29
|
+
*/
|
|
30
|
+
export const pipe = (value, ...fns) => fns.reduce((acc, fn) => fn(acc), value);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Effect, Stream } from "effect";
|
|
2
|
+
import type { z } from "zod";
|
|
3
|
+
import type { Plugin } from "../../plugin";
|
|
4
|
+
import type { PluginRuntimeError } from "../errors";
|
|
5
|
+
import type { InitializedPlugin } from "../types";
|
|
6
|
+
import type { StreamingOptions } from "./types";
|
|
7
|
+
export declare const createSourceStream: <T extends Plugin, TInput extends z.infer<T["inputSchema"]>, TItem, TPluginState extends z.infer<T["stateSchema"]>>(initializedPlugin: InitializedPlugin<T>, executePlugin: (plugin: InitializedPlugin<T>, input: TInput) => Effect.Effect<any, PluginRuntimeError>, input: TInput, options?: StreamingOptions<TItem, TPluginState>) => Stream.Stream<TItem, PluginRuntimeError>;
|
|
8
|
+
//# sourceMappingURL=streaming.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"streaming.d.ts","sourceRoot":"","sources":["../../../src/runtime/streaming/streaming.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,MAAM,EAAyB,MAAM,EAAE,MAAM,QAAQ,CAAC;AACzE,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAClD,OAAO,KAAK,EAAE,gBAAgB,EAAe,MAAM,SAAS,CAAC;AA8B7D,eAAO,MAAM,kBAAkB,GAC7B,CAAC,SAAS,MAAM,EAChB,MAAM,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,EACxC,KAAK,EACL,YAAY,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,EAE9C,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,UAAS,gBAAgB,CAAC,KAAK,EAAE,YAAY,CAAM,KAClD,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,kBAAkB,CAyGzC,CAAC"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Duration, Effect, Stream } from "effect";
|
|
2
|
+
/**
|
|
3
|
+
* Extract delay from plugin state (nextPollMs field)
|
|
4
|
+
* Returns null if nextPollMs is null (signals termination) or undefined (no delay specified)
|
|
5
|
+
*/
|
|
6
|
+
const extractDelayFromState = (state) => {
|
|
7
|
+
const stateWithPolling = state;
|
|
8
|
+
if (stateWithPolling?.nextPollMs === null) {
|
|
9
|
+
return null; // Explicit termination signal
|
|
10
|
+
}
|
|
11
|
+
return stateWithPolling?.nextPollMs ? Duration.millis(stateWithPolling.nextPollMs) : null;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Check if plugin state signals stream termination
|
|
15
|
+
*/
|
|
16
|
+
const isTerminalState = (state) => {
|
|
17
|
+
const stateWithPolling = state;
|
|
18
|
+
return stateWithPolling?.nextPollMs === null;
|
|
19
|
+
};
|
|
20
|
+
export const createSourceStream = (initializedPlugin, executePlugin, input, options = {}) => {
|
|
21
|
+
// Create initial stream state with plugin's initial state
|
|
22
|
+
const initialStreamState = {
|
|
23
|
+
invocations: 0,
|
|
24
|
+
pluginState: input.state,
|
|
25
|
+
lastItemCount: 0,
|
|
26
|
+
};
|
|
27
|
+
// Use Stream.async for full control over emission timing and delays
|
|
28
|
+
return Stream.async((emit) => {
|
|
29
|
+
let currentStreamState = initialStreamState;
|
|
30
|
+
let totalItemsEmitted = 0;
|
|
31
|
+
const loop = async () => {
|
|
32
|
+
try {
|
|
33
|
+
while (true) {
|
|
34
|
+
console.log(`[STREAMING] Starting iteration ${currentStreamState.invocations + 1}`);
|
|
35
|
+
// Check invocation limit BEFORE executing
|
|
36
|
+
if (options.maxInvocations && currentStreamState.invocations >= options.maxInvocations) {
|
|
37
|
+
console.log(`[STREAMING] Reached maxInvocations limit: ${options.maxInvocations}`);
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
// Check maxItems limit BEFORE executing
|
|
41
|
+
if (options.maxItems && totalItemsEmitted >= options.maxItems) {
|
|
42
|
+
console.log(`[STREAMING] Reached maxItems limit: ${options.maxItems}`);
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
// Execute plugin with current plugin state
|
|
46
|
+
const pluginInput = {
|
|
47
|
+
...input,
|
|
48
|
+
state: currentStreamState.pluginState ?? null
|
|
49
|
+
};
|
|
50
|
+
console.log(`[STREAMING] About to execute plugin with input:`, JSON.stringify(pluginInput, null, 2));
|
|
51
|
+
console.log(`[STREAMING] Plugin state:`, JSON.stringify(currentStreamState.pluginState, null, 2));
|
|
52
|
+
const rawResult = await Effect.runPromise(executePlugin(initializedPlugin, pluginInput));
|
|
53
|
+
console.log(`[STREAMING] Plugin execution completed`);
|
|
54
|
+
// Transform raw plugin result to streaming format
|
|
55
|
+
const resultObj = rawResult;
|
|
56
|
+
const items = Array.isArray(resultObj.items) ? resultObj.items : [];
|
|
57
|
+
const nextPluginState = resultObj.nextState;
|
|
58
|
+
console.log(`[STREAMING] Transformed result: ${items.length} items`);
|
|
59
|
+
// Update stream state (increment AFTER execution)
|
|
60
|
+
currentStreamState = {
|
|
61
|
+
invocations: currentStreamState.invocations + 1,
|
|
62
|
+
pluginState: nextPluginState,
|
|
63
|
+
lastItemCount: items.length,
|
|
64
|
+
};
|
|
65
|
+
console.log(`[STREAMING] Invocation ${currentStreamState.invocations}, Items: ${items.length}`);
|
|
66
|
+
// Call state change hook if provided
|
|
67
|
+
if (options.onStateChange) {
|
|
68
|
+
await Effect.runPromise(options.onStateChange(nextPluginState, items));
|
|
69
|
+
}
|
|
70
|
+
// Emit items immediately (before any delays)
|
|
71
|
+
for (const item of items) {
|
|
72
|
+
if (options.maxItems && totalItemsEmitted >= options.maxItems) {
|
|
73
|
+
console.log(`[STREAMING] Stopping emission at maxItems limit: ${options.maxItems}`);
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
emit.single(item);
|
|
77
|
+
totalItemsEmitted++;
|
|
78
|
+
}
|
|
79
|
+
// Check for termination conditions
|
|
80
|
+
if (nextPluginState && isTerminalState(nextPluginState)) {
|
|
81
|
+
console.log(`[STREAMING] Plugin signaled termination`);
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
// Check stopWhenEmpty condition
|
|
85
|
+
if (options.stopWhenEmpty && items.length === 0) {
|
|
86
|
+
console.log(`[STREAMING] Stopping due to empty result and stopWhenEmpty=true`);
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
// Handle delay AFTER emission, BEFORE next iteration
|
|
90
|
+
const nextDelay = extractDelayFromState(nextPluginState);
|
|
91
|
+
if (nextDelay && Duration.toMillis(nextDelay) > 0) {
|
|
92
|
+
console.log(`[STREAMING] Sleeping for ${Duration.toMillis(nextDelay)}ms before next iteration`);
|
|
93
|
+
await Effect.runPromise(Effect.sleep(nextDelay));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// End the stream
|
|
97
|
+
emit.end();
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
console.log(`[STREAMING] Error occurred:`, error);
|
|
101
|
+
emit.fail(error);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
// Start the loop
|
|
105
|
+
loop();
|
|
106
|
+
});
|
|
107
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Effect } from "effect";
|
|
2
|
+
/**
|
|
3
|
+
* Internal stream state managed by the runtime
|
|
4
|
+
*/
|
|
5
|
+
export interface StreamState<TPluginState> {
|
|
6
|
+
readonly invocations: number;
|
|
7
|
+
readonly pluginState: TPluginState;
|
|
8
|
+
readonly lastItemCount: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Enhanced streaming options with Effect-based hooks
|
|
12
|
+
*/
|
|
13
|
+
export interface StreamingOptions<TItem = unknown, TPluginState = unknown> {
|
|
14
|
+
maxItems?: number;
|
|
15
|
+
maxInvocations?: number;
|
|
16
|
+
stopWhenEmpty?: boolean;
|
|
17
|
+
onStateChange?: (newPluginState: TPluginState, items: TItem[]) => Effect.Effect<void>;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/runtime/streaming/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAErC;;GAEG;AACH,MAAM,WAAW,WAAW,CAAC,YAAY;IACvC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,YAAY,CAAC;IACnC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB,CAAC,KAAK,GAAG,OAAO,EAAE,YAAY,GAAG,OAAO;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,OAAO,CAAC;IAGxB,aAAa,CAAC,EAAE,CAAC,cAAc,EAAE,YAAY,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;CACvF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-utils.d.ts","sourceRoot":"","sources":["../../src/runtime/test-utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAE9C,eAAO,MAAM,gBAAgB,QAAO,YAYlC,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
export const createTestLogger = () => ({
|
|
3
|
+
logInfo: (message, context) => Effect.sync(() => console.log(`[INFO] ${message}`, context || '')),
|
|
4
|
+
logWarning: (message, context) => Effect.sync(() => console.warn(`[WARNING] ${message}`, context || '')),
|
|
5
|
+
logError: (message, error, context) => Effect.sync(() => console.error(`[ERROR] ${message}`, error, context || '')),
|
|
6
|
+
logDebug: (message, context) => Effect.sync(() => console.log(`[DEBUG] ${message}`, context || '')),
|
|
7
|
+
});
|