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.
Files changed (62) hide show
  1. package/README.md +1 -265
  2. package/dist/errors.d.ts +4 -4
  3. package/dist/index.d.ts +2 -4
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +1 -3
  6. package/dist/plugin.d.ts +74 -34
  7. package/dist/plugin.d.ts.map +1 -1
  8. package/dist/plugin.js +100 -17
  9. package/dist/runtime/errors.d.ts +38 -34
  10. package/dist/runtime/errors.d.ts.map +1 -1
  11. package/dist/runtime/errors.js +2 -0
  12. package/dist/runtime/index.d.ts +7 -4
  13. package/dist/runtime/index.d.ts.map +1 -1
  14. package/dist/runtime/index.js +5 -10
  15. package/dist/runtime/services/module-federation.service.d.ts +5 -6
  16. package/dist/runtime/services/module-federation.service.d.ts.map +1 -1
  17. package/dist/runtime/services/module-federation.service.js +53 -15
  18. package/dist/runtime/services/plugin.service.d.ts +13 -6
  19. package/dist/runtime/services/plugin.service.d.ts.map +1 -1
  20. package/dist/runtime/services/plugin.service.js +237 -94
  21. package/dist/runtime/services/secrets.service.d.ts.map +1 -1
  22. package/dist/runtime/services.d.ts +0 -1
  23. package/dist/runtime/services.d.ts.map +1 -1
  24. package/dist/runtime/services.js +0 -1
  25. package/dist/runtime/streaming/index.d.ts +4 -0
  26. package/dist/runtime/streaming/index.d.ts.map +1 -0
  27. package/dist/runtime/streaming/index.js +2 -0
  28. package/dist/runtime/streaming/state-effects.d.ts +28 -0
  29. package/dist/runtime/streaming/state-effects.d.ts.map +1 -0
  30. package/dist/runtime/streaming/state-effects.js +30 -0
  31. package/dist/runtime/streaming/streaming.d.ts +8 -0
  32. package/dist/runtime/streaming/streaming.d.ts.map +1 -0
  33. package/dist/runtime/streaming/streaming.js +107 -0
  34. package/dist/runtime/streaming/types.d.ts +19 -0
  35. package/dist/runtime/streaming/types.d.ts.map +1 -0
  36. package/dist/runtime/streaming/types.js +1 -0
  37. package/dist/runtime/test-utils.d.ts +3 -0
  38. package/dist/runtime/test-utils.d.ts.map +1 -0
  39. package/dist/runtime/test-utils.js +7 -0
  40. package/dist/runtime/types.d.ts +1 -1
  41. package/dist/runtime/types.d.ts.map +1 -1
  42. package/dist/runtime/validation.d.ts +2 -7
  43. package/dist/runtime/validation.d.ts.map +1 -1
  44. package/dist/testing/index.d.ts +25 -0
  45. package/dist/testing/index.d.ts.map +1 -0
  46. package/dist/testing/index.js +40 -0
  47. package/dist/testing/mocks/module-federation.service.d.ts +8 -0
  48. package/dist/testing/mocks/module-federation.service.d.ts.map +1 -0
  49. package/dist/testing/mocks/module-federation.service.js +35 -0
  50. package/package.json +27 -6
  51. package/dist/runtime/runtime.d.ts +0 -58
  52. package/dist/runtime/runtime.d.ts.map +0 -1
  53. package/dist/runtime/runtime.js +0 -75
  54. package/dist/runtime/runtime.test.d.ts +0 -2
  55. package/dist/runtime/runtime.test.d.ts.map +0 -1
  56. package/dist/runtime/runtime.test.js +0 -308
  57. package/dist/runtime/services/plugin-cache.service.d.ts +0 -18
  58. package/dist/runtime/services/plugin-cache.service.d.ts.map +0 -1
  59. package/dist/runtime/services/plugin-cache.service.js +0 -55
  60. package/dist/source.d.ts +0 -198
  61. package/dist/source.d.ts.map +0 -1
  62. package/dist/source.js +0 -90
@@ -1,127 +1,270 @@
1
- import { Duration, Effect, Layer, Schedule } from "effect";
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 { PluginCacheService } from "./plugin-cache.service";
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 cacheService = yield* PluginCacheService;
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
- return {
17
- loadPlugin: (pluginId) => Effect.gen(function* () {
18
- const metadata = registry[pluginId];
19
- if (!metadata) {
20
- return yield* Effect.fail(new PluginRuntimeError({
21
- pluginId,
22
- operation: "load-plugin",
23
- cause: new Error(`Plugin ${pluginId} not found in registry`),
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 url = resolveUrl(metadata.remoteUrl);
28
- const cacheKey = `${pluginId}:${url}`;
29
- const cachedConstructor = yield* cacheService.getCachedConstructor(cacheKey);
30
- return {
31
- ctor: cachedConstructor.ctor,
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
- plugin: instance,
61
- metadata: pluginConstructor.metadata,
62
- };
63
- }),
64
- initializePlugin: (pluginInstance, config) => Effect.gen(function* () {
65
- const { plugin } = pluginInstance;
66
- // Validate raw config using plugin's Zod schema
67
- const validatedConfig = yield* validate(plugin.configSchema, config, plugin.id, "config").pipe(Effect.mapError((validationError) => new PluginRuntimeError({
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-config",
160
+ operation: "validate-input",
70
161
  cause: validationError.zodError,
71
162
  retryable: false,
72
- })));
73
- // Hydrate secrets
74
- const hydratedConfig = yield* secretsService.hydrateSecrets(validatedConfig, secrets);
75
- // Validate hydrated config
76
- const finalConfig = yield* validate(plugin.configSchema, hydratedConfig, plugin.id, "config").pipe(Effect.mapError((validationError) => new PluginRuntimeError({
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: "validate-hydrated-config",
79
- cause: validationError.zodError,
171
+ operation: "execute-plugin",
172
+ cause: new Error(`Unknown procedure: ${procedureName}`),
80
173
  retryable: false,
81
- })));
82
- // Initialize plugin with retry logic
83
- const pluginLayer = Layer.succeed(PluginLoggerTag, logger);
84
- yield* plugin
85
- .initialize(finalConfig)
86
- .pipe(Effect.mapError((error) => new PluginRuntimeError({
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: "initialize-plugin",
89
- cause: error,
90
- retryable: error.retryable ?? false,
91
- })), Effect.retry(retrySchedule.pipe(Schedule.whileInput((error) => error.retryable))), Effect.provide(pluginLayer));
92
- return {
93
- plugin,
94
- metadata: pluginInstance.metadata,
95
- config: finalConfig, // TODO: config type
96
- };
97
- }),
98
- executePlugin: (initializedPlugin, input) => Effect.gen(function* () {
99
- const { plugin } = initializedPlugin;
100
- // Validate input
101
- const validatedInput = yield* validate(plugin.inputSchema, input, plugin.id, "input").pipe(Effect.mapError((validationError) => new PluginRuntimeError({
102
- pluginId: plugin.id,
103
- operation: "validate-input",
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
- // Execute plugin
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
- const output = yield* plugin
110
- .execute(validatedInput)
111
- .pipe(Effect.mapError((error) => new PluginRuntimeError({
112
- pluginId: plugin.id,
113
- operation: "execute-plugin",
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
- return validatedOutput;
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;IAC9B,QAAQ,CAAC,cAAc,EAAE,CACvB,MAAM,EAAE,GAAG,EAAE,QAAQ;IACrB,aAAa,EAAE,aAAa,KACzB,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC;CAC7C;;;;AAED,qBAAa,cAAe,SAAQ,mBAGjC;IACD,MAAM,CAAC,IAAI,GAAI,SAAS,aAAa,+CAgChC;CACN;AAED,eAAO,MAAM,iBAAiB,uBAAiB,CAAC;AAChD,eAAO,MAAM,kBAAkB,YApCL,aAAa,8CAoCc,CAAC"}
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,5 +1,4 @@
1
1
  export * from "./services/module-federation.service";
2
2
  export * from "./services/plugin.service";
3
- export * from "./services/plugin-cache.service";
4
3
  export * from "./services/secrets.service";
5
4
  //# sourceMappingURL=services.d.ts.map
@@ -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,iCAAiC,CAAC;AAChD,cAAc,4BAA4B,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"}
@@ -1,4 +1,3 @@
1
1
  export * from "./services/module-federation.service";
2
2
  export * from "./services/plugin.service";
3
- export * from "./services/plugin-cache.service";
4
3
  export * from "./services/secrets.service";
@@ -0,0 +1,4 @@
1
+ export { pipe, StateTransitions } from './state-effects';
2
+ export { createSourceStream } from './streaming';
3
+ export type { StreamingOptions, StreamState } from './types';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -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,2 @@
1
+ export { pipe, StateTransitions } from './state-effects';
2
+ export { createSourceStream } from './streaming';
@@ -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,3 @@
1
+ import type { PluginLogger } from "../plugin";
2
+ export declare const createTestLogger: () => PluginLogger;
3
+ //# sourceMappingURL=test-utils.d.ts.map
@@ -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
+ });