@pikku/core 0.12.4 → 0.12.5

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 (229) hide show
  1. package/CHANGELOG.md +11 -1
  2. package/dist/dev/hot-reload.d.ts +10 -0
  3. package/dist/dev/hot-reload.js +158 -0
  4. package/dist/middleware-runner.d.ts +1 -0
  5. package/dist/middleware-runner.js +5 -0
  6. package/dist/permissions.d.ts +1 -0
  7. package/dist/permissions.js +5 -0
  8. package/dist/services/in-memory-workflow-service.js +7 -11
  9. package/dist/wirings/ai-agent/ai-agent-prepare.js +16 -1
  10. package/dist/wirings/channel/channel-middleware-runner.d.ts +1 -0
  11. package/dist/wirings/channel/channel-middleware-runner.js +5 -0
  12. package/dist/wirings/workflow/pikku-workflow-service.js +7 -8
  13. package/package.json +3 -2
  14. package/src/crypto-utils.test.ts +214 -0
  15. package/src/crypto-utils.ts +213 -0
  16. package/src/dev/hot-reload.test.ts +484 -0
  17. package/src/dev/hot-reload.ts +212 -0
  18. package/src/errors/error-handler.ts +62 -0
  19. package/src/errors/error.test.ts +195 -0
  20. package/src/errors/errors.ts +374 -0
  21. package/src/errors/index.ts +2 -0
  22. package/src/factory-functions.test.ts +109 -0
  23. package/src/function/function-runner.test.ts +536 -0
  24. package/src/function/function-runner.ts +365 -0
  25. package/src/function/functions.types.ts +304 -0
  26. package/src/function/index.ts +5 -0
  27. package/src/handle-error.test.ts +424 -0
  28. package/src/handle-error.ts +69 -0
  29. package/src/index.ts +118 -0
  30. package/src/internal.ts +6 -0
  31. package/src/middleware/auth-apikey.test.ts +363 -0
  32. package/src/middleware/auth-apikey.ts +47 -0
  33. package/src/middleware/auth-bearer.test.ts +450 -0
  34. package/src/middleware/auth-bearer.ts +72 -0
  35. package/src/middleware/auth-cookie.test.ts +528 -0
  36. package/src/middleware/auth-cookie.ts +80 -0
  37. package/src/middleware/cors.test.ts +424 -0
  38. package/src/middleware/cors.ts +104 -0
  39. package/src/middleware/index.ts +5 -0
  40. package/src/middleware/remote-auth.test.ts +488 -0
  41. package/src/middleware/remote-auth.ts +68 -0
  42. package/src/middleware/timeout.ts +15 -0
  43. package/src/middleware-runner.test.ts +418 -0
  44. package/src/middleware-runner.ts +240 -0
  45. package/src/permissions.test.ts +434 -0
  46. package/src/permissions.ts +327 -0
  47. package/src/pikku-request.ts +23 -0
  48. package/src/pikku-response.ts +5 -0
  49. package/src/pikku-state.test.ts +224 -0
  50. package/src/pikku-state.ts +216 -0
  51. package/src/run-tests-script.test.ts +49 -0
  52. package/src/schema.test.ts +249 -0
  53. package/src/schema.ts +151 -0
  54. package/src/services/ai-agent-runner-service.ts +41 -0
  55. package/src/services/ai-run-state-service.ts +20 -0
  56. package/src/services/ai-storage-service.ts +39 -0
  57. package/src/services/content-service.ts +76 -0
  58. package/src/services/deployment-service.ts +22 -0
  59. package/src/services/gateway-service.ts +20 -0
  60. package/src/services/gopass-secrets.ts +98 -0
  61. package/src/services/in-memory-ai-run-state-service.ts +73 -0
  62. package/src/services/in-memory-trigger-service.ts +64 -0
  63. package/src/services/in-memory-workflow-service.test.ts +351 -0
  64. package/src/services/in-memory-workflow-service.ts +502 -0
  65. package/src/services/index.ts +56 -0
  66. package/src/services/jwt-service.ts +30 -0
  67. package/src/services/local-content.ts +120 -0
  68. package/src/services/local-gateway-service.ts +62 -0
  69. package/src/services/local-secrets.test.ts +80 -0
  70. package/src/services/local-secrets.ts +94 -0
  71. package/src/services/local-variables.test.ts +61 -0
  72. package/src/services/local-variables.ts +39 -0
  73. package/src/services/logger-console.test.ts +118 -0
  74. package/src/services/logger-console.ts +98 -0
  75. package/src/services/logger.ts +57 -0
  76. package/src/services/scheduler-service.ts +86 -0
  77. package/src/services/schema-service.ts +33 -0
  78. package/src/services/scoped-secret-service.test.ts +74 -0
  79. package/src/services/scoped-secret-service.ts +41 -0
  80. package/src/services/secret-service.ts +38 -0
  81. package/src/services/trigger-service.ts +17 -0
  82. package/src/services/typed-secret-service.test.ts +93 -0
  83. package/src/services/typed-secret-service.ts +70 -0
  84. package/src/services/typed-variables-service.test.ts +73 -0
  85. package/src/services/typed-variables-service.ts +81 -0
  86. package/src/services/user-session-service.test.ts +113 -0
  87. package/src/services/user-session-service.ts +86 -0
  88. package/src/services/variables-service.ts +11 -0
  89. package/src/services/workflow-service.ts +95 -0
  90. package/src/testing/index.ts +2 -0
  91. package/src/testing/service-tests.ts +873 -0
  92. package/src/time-utils.test.ts +56 -0
  93. package/src/time-utils.ts +107 -0
  94. package/src/types/core.types.ts +478 -0
  95. package/src/types/state.types.ts +188 -0
  96. package/src/utils/hash.test.ts +68 -0
  97. package/src/utils/hash.ts +26 -0
  98. package/src/utils.test.ts +350 -0
  99. package/src/utils.ts +129 -0
  100. package/src/version.test.ts +80 -0
  101. package/src/version.ts +25 -0
  102. package/src/wirings/ai-agent/agent-dynamic-workflow.ts +469 -0
  103. package/src/wirings/ai-agent/ai-agent-helpers.test.ts +152 -0
  104. package/src/wirings/ai-agent/ai-agent-helpers.ts +76 -0
  105. package/src/wirings/ai-agent/ai-agent-memory.ts +310 -0
  106. package/src/wirings/ai-agent/ai-agent-model-config.test.ts +115 -0
  107. package/src/wirings/ai-agent/ai-agent-model-config.ts +43 -0
  108. package/src/wirings/ai-agent/ai-agent-prepare.ts +659 -0
  109. package/src/wirings/ai-agent/ai-agent-registry.test.ts +37 -0
  110. package/src/wirings/ai-agent/ai-agent-registry.ts +82 -0
  111. package/src/wirings/ai-agent/ai-agent-runner.test.ts +319 -0
  112. package/src/wirings/ai-agent/ai-agent-runner.ts +773 -0
  113. package/src/wirings/ai-agent/ai-agent-stream.test.ts +859 -0
  114. package/src/wirings/ai-agent/ai-agent-stream.ts +1153 -0
  115. package/src/wirings/ai-agent/ai-agent.types.ts +382 -0
  116. package/src/wirings/ai-agent/index.ts +37 -0
  117. package/src/wirings/channel/channel-common.ts +90 -0
  118. package/src/wirings/channel/channel-handler.ts +250 -0
  119. package/src/wirings/channel/channel-middleware-runner.ts +126 -0
  120. package/src/wirings/channel/channel-runner.ts +166 -0
  121. package/src/wirings/channel/channel-store.ts +29 -0
  122. package/src/wirings/channel/channel.types.ts +172 -0
  123. package/src/wirings/channel/define-channel-routes.ts +25 -0
  124. package/src/wirings/channel/eventhub-service.ts +34 -0
  125. package/src/wirings/channel/eventhub-store.ts +13 -0
  126. package/src/wirings/channel/index.ts +24 -0
  127. package/src/wirings/channel/local/index.ts +3 -0
  128. package/src/wirings/channel/local/local-channel-handler.ts +81 -0
  129. package/src/wirings/channel/local/local-channel-runner.test.ts +215 -0
  130. package/src/wirings/channel/local/local-channel-runner.ts +210 -0
  131. package/src/wirings/channel/local/local-eventhub-service.test.ts +95 -0
  132. package/src/wirings/channel/local/local-eventhub-service.ts +101 -0
  133. package/src/wirings/channel/log-channels.ts +20 -0
  134. package/src/wirings/channel/pikku-abstract-channel-handler.test.ts +54 -0
  135. package/src/wirings/channel/pikku-abstract-channel-handler.ts +45 -0
  136. package/src/wirings/channel/serverless/index.ts +5 -0
  137. package/src/wirings/channel/serverless/serverless-channel-runner.ts +289 -0
  138. package/src/wirings/cli/channel/cli-channel-runner.ts +136 -0
  139. package/src/wirings/cli/channel/index.ts +1 -0
  140. package/src/wirings/cli/cli-runner.test.ts +403 -0
  141. package/src/wirings/cli/cli-runner.ts +529 -0
  142. package/src/wirings/cli/cli.types.ts +358 -0
  143. package/src/wirings/cli/command-parser.test.ts +445 -0
  144. package/src/wirings/cli/command-parser.ts +504 -0
  145. package/src/wirings/cli/define-cli-commands.ts +24 -0
  146. package/src/wirings/cli/index.ts +19 -0
  147. package/src/wirings/gateway/gateway-runner.test.ts +474 -0
  148. package/src/wirings/gateway/gateway-runner.ts +411 -0
  149. package/src/wirings/gateway/gateway.types.ts +149 -0
  150. package/src/wirings/gateway/index.ts +13 -0
  151. package/src/wirings/http/http-routes.test.ts +322 -0
  152. package/src/wirings/http/http-routes.ts +197 -0
  153. package/src/wirings/http/http-runner.test.ts +144 -0
  154. package/src/wirings/http/http-runner.ts +588 -0
  155. package/src/wirings/http/http.types.ts +369 -0
  156. package/src/wirings/http/index.ts +28 -0
  157. package/src/wirings/http/log-http-routes.ts +22 -0
  158. package/src/wirings/http/pikku-fetch-http-request.test.ts +237 -0
  159. package/src/wirings/http/pikku-fetch-http-request.ts +170 -0
  160. package/src/wirings/http/pikku-fetch-http-response.test.ts +82 -0
  161. package/src/wirings/http/pikku-fetch-http-response.ts +147 -0
  162. package/src/wirings/http/routers/http-router.ts +13 -0
  163. package/src/wirings/http/routers/path-to-regex.test.ts +319 -0
  164. package/src/wirings/http/routers/path-to-regex.ts +126 -0
  165. package/src/wirings/http/web-request.test.ts +236 -0
  166. package/src/wirings/http/web-request.ts +104 -0
  167. package/src/wirings/mcp/index.ts +27 -0
  168. package/src/wirings/mcp/mcp-endpoint-registry.test.ts +389 -0
  169. package/src/wirings/mcp/mcp-endpoint-registry.ts +159 -0
  170. package/src/wirings/mcp/mcp-runner.ts +323 -0
  171. package/src/wirings/mcp/mcp.types.ts +216 -0
  172. package/src/wirings/node/index.ts +2 -0
  173. package/src/wirings/node/node.types.ts +23 -0
  174. package/src/wirings/oauth2/index.ts +3 -0
  175. package/src/wirings/oauth2/oauth2-client.test.ts +929 -0
  176. package/src/wirings/oauth2/oauth2-client.ts +335 -0
  177. package/src/wirings/oauth2/oauth2.types.ts +69 -0
  178. package/src/wirings/oauth2/wire-oauth2-credential.ts +23 -0
  179. package/src/wirings/queue/index.ts +31 -0
  180. package/src/wirings/queue/queue-runner.test.ts +710 -0
  181. package/src/wirings/queue/queue-runner.ts +192 -0
  182. package/src/wirings/queue/queue.types.ts +189 -0
  183. package/src/wirings/queue/register-queue-helper.ts +60 -0
  184. package/src/wirings/queue/validate-worker-config.test.ts +108 -0
  185. package/src/wirings/queue/validate-worker-config.ts +116 -0
  186. package/src/wirings/rpc/index.ts +4 -0
  187. package/src/wirings/rpc/rpc-runner.ts +460 -0
  188. package/src/wirings/rpc/rpc-types.ts +56 -0
  189. package/src/wirings/rpc/wire-addon.ts +21 -0
  190. package/src/wirings/scheduler/index.ts +11 -0
  191. package/src/wirings/scheduler/log-schedulers.ts +20 -0
  192. package/src/wirings/scheduler/scheduler-runner.test.ts +660 -0
  193. package/src/wirings/scheduler/scheduler-runner.ts +133 -0
  194. package/src/wirings/scheduler/scheduler.types.ts +53 -0
  195. package/src/wirings/secret/index.ts +9 -0
  196. package/src/wirings/secret/secret.types.ts +32 -0
  197. package/src/wirings/secret/validate-secret-definitions.test.ts +140 -0
  198. package/src/wirings/secret/validate-secret-definitions.ts +82 -0
  199. package/src/wirings/trigger/index.ts +10 -0
  200. package/src/wirings/trigger/pikku-trigger-service.ts +112 -0
  201. package/src/wirings/trigger/trigger-runner.test.ts +79 -0
  202. package/src/wirings/trigger/trigger-runner.ts +135 -0
  203. package/src/wirings/trigger/trigger.types.ts +178 -0
  204. package/src/wirings/variable/index.ts +8 -0
  205. package/src/wirings/variable/validate-variable-definitions.test.ts +91 -0
  206. package/src/wirings/variable/validate-variable-definitions.ts +69 -0
  207. package/src/wirings/variable/variable.types.ts +22 -0
  208. package/src/wirings/workflow/dsl/index.ts +31 -0
  209. package/src/wirings/workflow/dsl/workflow-dsl.types.ts +320 -0
  210. package/src/wirings/workflow/dsl/workflow-runner.ts +28 -0
  211. package/src/wirings/workflow/graph/graph-node.ts +222 -0
  212. package/src/wirings/workflow/graph/graph-runner.test.ts +487 -0
  213. package/src/wirings/workflow/graph/graph-runner.ts +816 -0
  214. package/src/wirings/workflow/graph/graph-validation.test.ts +170 -0
  215. package/src/wirings/workflow/graph/graph-validation.ts +237 -0
  216. package/src/wirings/workflow/graph/index.ts +19 -0
  217. package/src/wirings/workflow/graph/template.test.ts +49 -0
  218. package/src/wirings/workflow/graph/template.ts +42 -0
  219. package/src/wirings/workflow/graph/wire-workflow-graph.ts +29 -0
  220. package/src/wirings/workflow/graph/workflow-graph.types.ts +104 -0
  221. package/src/wirings/workflow/index.ts +82 -0
  222. package/src/wirings/workflow/pikku-workflow-service.test.ts +200 -0
  223. package/src/wirings/workflow/pikku-workflow-service.ts +1179 -0
  224. package/src/wirings/workflow/workflow-helpers.test.ts +129 -0
  225. package/src/wirings/workflow/workflow-helpers.ts +79 -0
  226. package/src/wirings/workflow/workflow.types.ts +274 -0
  227. package/tsconfig.json +14 -0
  228. package/tsconfig.tsbuildinfo +1 -0
  229. package/lcov.info +0 -18891
@@ -0,0 +1,62 @@
1
+ import type { GatewayService } from './gateway-service.js'
2
+ import type { GatewayAdapter } from '../wirings/gateway/gateway.types.js'
3
+ import { pikkuState, getSingletonServices } from '../pikku-state.js'
4
+ import { createListenerMessageHandler } from '../wirings/gateway/gateway-runner.js'
5
+
6
+ /**
7
+ * Local GatewayService implementation.
8
+ *
9
+ * Starts all registered listener gateways unconditionally (single-process).
10
+ * For distributed deployments, implement `GatewayService` with leader
11
+ * election or similar coordination.
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * const gatewayService = new LocalGatewayService()
16
+ * await gatewayService.start()
17
+ *
18
+ * // On shutdown
19
+ * await gatewayService.stop()
20
+ * ```
21
+ */
22
+ export class LocalGatewayService implements GatewayService {
23
+ private activeAdapters = new Map<string, GatewayAdapter>()
24
+
25
+ async start(): Promise<void> {
26
+ const singletonServices = getSingletonServices()
27
+ const gateways = pikkuState(null, 'gateway', 'gateways')
28
+
29
+ for (const [name, config] of gateways) {
30
+ if (config.type !== 'listener') continue
31
+ if (this.activeAdapters.has(name)) continue
32
+
33
+ const handleMessage = createListenerMessageHandler(
34
+ name,
35
+ config,
36
+ singletonServices
37
+ )
38
+
39
+ await config.adapter.init(handleMessage)
40
+ this.activeAdapters.set(name, config.adapter)
41
+ singletonServices.logger.info(`Started listener gateway: ${name}`)
42
+ }
43
+ }
44
+
45
+ async stop(): Promise<void> {
46
+ for (const [name, adapter] of this.activeAdapters) {
47
+ try {
48
+ await adapter.close()
49
+ } catch (e: any) {
50
+ try {
51
+ getSingletonServices().logger.error(
52
+ `Error closing listener gateway '${name}':`,
53
+ e
54
+ )
55
+ } catch {
56
+ // logger may not be available during shutdown
57
+ }
58
+ }
59
+ }
60
+ this.activeAdapters.clear()
61
+ }
62
+ }
@@ -0,0 +1,80 @@
1
+ import { describe, test } from 'node:test'
2
+ import assert from 'node:assert'
3
+ import { LocalSecretService } from './local-secrets.js'
4
+ import { LocalVariablesService } from './local-variables.js'
5
+
6
+ describe('LocalSecretService', () => {
7
+ test('should get secret from local storage', async () => {
8
+ const service = new LocalSecretService()
9
+ await service.setSecretJSON('MY_KEY', { token: 'abc' })
10
+ const result = await service.getSecret('MY_KEY')
11
+ assert.strictEqual(result, '{"token":"abc"}')
12
+ })
13
+
14
+ test('should get secret from environment variables', async () => {
15
+ const vars = new LocalVariablesService({ MY_SECRET: 'secret-value' })
16
+ const service = new LocalSecretService(vars)
17
+ const result = await service.getSecret('MY_SECRET')
18
+ assert.strictEqual(result, 'secret-value')
19
+ })
20
+
21
+ test('should throw when secret not found', async () => {
22
+ const vars = new LocalVariablesService({})
23
+ const service = new LocalSecretService(vars)
24
+ await assert.rejects(() => service.getSecret('MISSING'), {
25
+ message: 'Secret Not Found: MISSING',
26
+ })
27
+ })
28
+
29
+ test('should get JSON secret from local storage', async () => {
30
+ const service = new LocalSecretService()
31
+ await service.setSecretJSON('JSON_KEY', { data: 42 })
32
+ const result = await service.getSecretJSON('JSON_KEY')
33
+ assert.deepStrictEqual(result, { data: 42 })
34
+ })
35
+
36
+ test('should get JSON secret from environment variables', async () => {
37
+ const vars = new LocalVariablesService({ CONFIG: '{"port":3000}' })
38
+ const service = new LocalSecretService(vars)
39
+ const result = await service.getSecretJSON('CONFIG')
40
+ assert.deepStrictEqual(result, { port: 3000 })
41
+ })
42
+
43
+ test('should throw when JSON secret not found', async () => {
44
+ const vars = new LocalVariablesService({})
45
+ const service = new LocalSecretService(vars)
46
+ await assert.rejects(() => service.getSecretJSON('MISSING'), {
47
+ message: 'Secret Not Found: MISSING',
48
+ })
49
+ })
50
+
51
+ test('should prefer local storage over env variables', async () => {
52
+ const vars = new LocalVariablesService({ KEY: 'env-value' })
53
+ const service = new LocalSecretService(vars)
54
+ await service.setSecretJSON('KEY', 'local-value')
55
+ const result = await service.getSecret('KEY')
56
+ assert.strictEqual(result, '"local-value"')
57
+ })
58
+
59
+ test('should check hasSecret in local storage', async () => {
60
+ const vars = new LocalVariablesService({})
61
+ const service = new LocalSecretService(vars)
62
+ await service.setSecretJSON('EXISTS', 'yes')
63
+ assert.strictEqual(await service.hasSecret('EXISTS'), true)
64
+ assert.strictEqual(await service.hasSecret('NOPE'), false)
65
+ })
66
+
67
+ test('should check hasSecret in environment', async () => {
68
+ const vars = new LocalVariablesService({ ENV_KEY: 'val' })
69
+ const service = new LocalSecretService(vars)
70
+ assert.strictEqual(await service.hasSecret('ENV_KEY'), true)
71
+ })
72
+
73
+ test('should delete secret from local storage', async () => {
74
+ const service = new LocalSecretService()
75
+ await service.setSecretJSON('DEL_KEY', 'value')
76
+ assert.strictEqual(await service.hasSecret('DEL_KEY'), true)
77
+ await service.deleteSecret('DEL_KEY')
78
+ assert.strictEqual(await service.hasSecret('DEL_KEY'), false)
79
+ })
80
+ })
@@ -0,0 +1,94 @@
1
+ import { LocalVariablesService } from './local-variables.js'
2
+ import type { SecretService } from './secret-service.js'
3
+ import type { VariablesService } from './variables-service.js'
4
+
5
+ /**
6
+ * Service for retrieving secrets from environment variables.
7
+ * Supports storing secrets locally in memory for CLI operations.
8
+ */
9
+ export class LocalSecretService implements SecretService {
10
+ private localSecrets: Map<string, string> = new Map()
11
+
12
+ /**
13
+ * Creates an instance of LocalSecretService.
14
+ */
15
+ constructor(
16
+ private variables: VariablesService = new LocalVariablesService()
17
+ ) {}
18
+
19
+ /**
20
+ * Retrieves a secret by key.
21
+ * Checks local storage first, then falls back to environment variables.
22
+ * @param key - The key of the secret to retrieve.
23
+ * @returns A promise that resolves to the secret value.
24
+ * @throws {Error} If the secret is not found.
25
+ */
26
+ public async getSecretJSON<R>(key: string): Promise<R> {
27
+ // Check local storage first
28
+ const localValue = this.localSecrets.get(key)
29
+ if (localValue) {
30
+ return JSON.parse(localValue)
31
+ }
32
+
33
+ // Fall back to environment variables
34
+ const value = await this.variables.get(key)
35
+ if (value) {
36
+ return JSON.parse(value)
37
+ }
38
+ throw new Error(`Secret Not Found: ${key}`)
39
+ }
40
+
41
+ /**
42
+ * Retrieves a secret by key.
43
+ * Checks local storage first, then falls back to environment variables.
44
+ * @param key - The key of the secret to retrieve.
45
+ * @returns A promise that resolves to the secret value.
46
+ * @throws {Error} If the secret is not found.
47
+ */
48
+ public async getSecret(key: string): Promise<string> {
49
+ // Check local storage first
50
+ const localValue = this.localSecrets.get(key)
51
+ if (localValue) {
52
+ return localValue
53
+ }
54
+
55
+ // Fall back to environment variables
56
+ const value = await this.variables.get(key)
57
+ if (value) {
58
+ return value
59
+ }
60
+ throw new Error(`Secret Not Found: ${key}`)
61
+ }
62
+
63
+ /**
64
+ * Stores a JSON value as a secret in local storage.
65
+ * @param key - The key to store the secret under.
66
+ * @param value - The JSON value to store.
67
+ * @returns A promise that resolves when the secret is stored.
68
+ */
69
+ public async setSecretJSON(key: string, value: unknown): Promise<void> {
70
+ this.localSecrets.set(key, JSON.stringify(value))
71
+ }
72
+
73
+ /**
74
+ * Checks if a secret exists without throwing.
75
+ * @param key - The key of the secret to check.
76
+ * @returns A promise that resolves to true if the secret exists.
77
+ */
78
+ public async hasSecret(key: string): Promise<boolean> {
79
+ if (this.localSecrets.has(key)) {
80
+ return true
81
+ }
82
+ const value = await this.variables.get(key)
83
+ return value !== undefined && value !== null && value !== ''
84
+ }
85
+
86
+ /**
87
+ * Deletes a secret from local storage.
88
+ * @param key - The key of the secret to delete.
89
+ * @returns A promise that resolves when the secret is deleted.
90
+ */
91
+ public async deleteSecret(key: string): Promise<void> {
92
+ this.localSecrets.delete(key)
93
+ }
94
+ }
@@ -0,0 +1,61 @@
1
+ import { describe, test } from 'node:test'
2
+ import assert from 'node:assert'
3
+ import { LocalVariablesService } from './local-variables.js'
4
+
5
+ describe('LocalVariablesService', () => {
6
+ test('should get a variable', () => {
7
+ const service = new LocalVariablesService({ MY_VAR: 'hello' })
8
+ assert.strictEqual(service.get('MY_VAR'), 'hello')
9
+ })
10
+
11
+ test('should return undefined for missing variable', () => {
12
+ const service = new LocalVariablesService({})
13
+ assert.strictEqual(service.get('MISSING'), undefined)
14
+ })
15
+
16
+ test('should set a variable', () => {
17
+ const service = new LocalVariablesService({})
18
+ service.set('KEY', 'value')
19
+ assert.strictEqual(service.get('KEY'), 'value')
20
+ })
21
+
22
+ test('should delete a variable', () => {
23
+ const service = new LocalVariablesService({ KEY: 'val' })
24
+ service.delete('KEY')
25
+ assert.strictEqual(service.get('KEY'), undefined)
26
+ })
27
+
28
+ test('should check if variable exists', () => {
29
+ const service = new LocalVariablesService({ KEY: 'val' })
30
+ assert.strictEqual(service.has('KEY'), true)
31
+ assert.strictEqual(service.has('MISSING'), false)
32
+ })
33
+
34
+ test('should get all variables', () => {
35
+ const vars = { A: '1', B: '2' }
36
+ const service = new LocalVariablesService(vars)
37
+ assert.deepStrictEqual(service.getAll(), vars)
38
+ })
39
+
40
+ test('should get JSON variable', () => {
41
+ const service = new LocalVariablesService({ DATA: '{"key":"val"}' })
42
+ const result = service.getJSON('DATA')
43
+ assert.deepStrictEqual(result, { key: 'val' })
44
+ })
45
+
46
+ test('should return undefined for missing JSON variable', () => {
47
+ const service = new LocalVariablesService({})
48
+ assert.strictEqual(service.getJSON('MISSING'), undefined)
49
+ })
50
+
51
+ test('should set JSON variable', () => {
52
+ const service = new LocalVariablesService({})
53
+ service.setJSON('DATA', { key: 'val' })
54
+ assert.strictEqual(service.get('DATA'), '{"key":"val"}')
55
+ })
56
+
57
+ test('should handle has with undefined value', () => {
58
+ const service = new LocalVariablesService({ KEY: undefined })
59
+ assert.strictEqual(service.has('KEY'), false)
60
+ })
61
+ })
@@ -0,0 +1,39 @@
1
+ import type { VariablesService } from './variables-service.js'
2
+
3
+ export class LocalVariablesService implements VariablesService {
4
+ constructor(
5
+ private variables: Record<string, string | undefined> = process.env
6
+ ) {}
7
+
8
+ public getAll():
9
+ | Promise<Record<string, string | undefined>>
10
+ | Record<string, string | undefined> {
11
+ return this.variables || {}
12
+ }
13
+
14
+ public get(name: string): Promise<string | undefined> | string | undefined {
15
+ return this.variables[name]
16
+ }
17
+
18
+ public getJSON<T = unknown>(name: string): T | undefined {
19
+ const value = this.variables[name]
20
+ if (value === undefined) return undefined
21
+ return JSON.parse(value)
22
+ }
23
+
24
+ public set(name: string, value: string): void {
25
+ this.variables[name] = value
26
+ }
27
+
28
+ public setJSON(name: string, value: unknown): void {
29
+ this.variables[name] = JSON.stringify(value)
30
+ }
31
+
32
+ public has(name: string): boolean {
33
+ return name in this.variables && this.variables[name] !== undefined
34
+ }
35
+
36
+ public delete(name: string): void {
37
+ delete this.variables[name]
38
+ }
39
+ }
@@ -0,0 +1,118 @@
1
+ import { describe, test, beforeEach } from 'node:test'
2
+ import assert from 'node:assert'
3
+ import { ConsoleLogger } from './logger-console.js'
4
+ import { LogLevel } from './logger.js'
5
+
6
+ describe('ConsoleLogger', () => {
7
+ let logger: ConsoleLogger
8
+ let captured: { method: string; args: any[] }[]
9
+ let origConsole: Record<string, any>
10
+
11
+ beforeEach(() => {
12
+ logger = new ConsoleLogger()
13
+ captured = []
14
+ origConsole = {
15
+ trace: console.trace,
16
+ debug: console.debug,
17
+ info: console.info,
18
+ warn: console.warn,
19
+ error: console.error,
20
+ log: console.log,
21
+ }
22
+ console.trace = (...args: any[]) => captured.push({ method: 'trace', args })
23
+ console.debug = (...args: any[]) => captured.push({ method: 'debug', args })
24
+ console.info = (...args: any[]) => captured.push({ method: 'info', args })
25
+ console.warn = (...args: any[]) => captured.push({ method: 'warn', args })
26
+ console.error = (...args: any[]) => captured.push({ method: 'error', args })
27
+ console.log = (...args: any[]) => captured.push({ method: 'log', args })
28
+ })
29
+
30
+ // Restore console after each test
31
+ test('should log info at default level', () => {
32
+ logger.info('test message')
33
+ assert.strictEqual(captured.length, 1)
34
+ assert.strictEqual(captured[0].method, 'info')
35
+ assert.ok(captured[0].args.includes('test message'))
36
+ Object.assign(console, origConsole)
37
+ })
38
+
39
+ test('should not log debug at default info level', () => {
40
+ logger.debug('debug message')
41
+ assert.strictEqual(captured.length, 0)
42
+ Object.assign(console, origConsole)
43
+ })
44
+
45
+ test('should log debug when level is set to debug', () => {
46
+ logger.setLevel(LogLevel.debug)
47
+ logger.debug('debug message')
48
+ assert.strictEqual(captured.length, 1)
49
+ assert.strictEqual(captured[0].method, 'debug')
50
+ Object.assign(console, origConsole)
51
+ })
52
+
53
+ test('should log warn at info level', () => {
54
+ logger.warn('warning')
55
+ assert.strictEqual(captured.length, 1)
56
+ assert.strictEqual(captured[0].method, 'warn')
57
+ Object.assign(console, origConsole)
58
+ })
59
+
60
+ test('should log error at info level', () => {
61
+ logger.error('error message')
62
+ assert.strictEqual(captured.length, 1)
63
+ assert.strictEqual(captured[0].method, 'error')
64
+ Object.assign(console, origConsole)
65
+ })
66
+
67
+ test('should log Error instance with stack trace', () => {
68
+ const err = new Error('test error')
69
+ logger.error(err)
70
+ assert.strictEqual(captured.length, 2) // error message + stack
71
+ assert.ok(captured[0].args.includes('test error'))
72
+ assert.strictEqual(captured[1].args[0], 'STACK:')
73
+ Object.assign(console, origConsole)
74
+ })
75
+
76
+ test('should not log info when level is error', () => {
77
+ logger.setLevel(LogLevel.error)
78
+ logger.info('info')
79
+ logger.warn('warn')
80
+ logger.debug('debug')
81
+ assert.strictEqual(captured.length, 0)
82
+ Object.assign(console, origConsole)
83
+ })
84
+
85
+ test('should log error when level is error', () => {
86
+ logger.setLevel(LogLevel.error)
87
+ logger.error('critical')
88
+ assert.strictEqual(captured.length, 1)
89
+ Object.assign(console, origConsole)
90
+ })
91
+
92
+ test('should support trace level when enabled', () => {
93
+ logger.setLevel(LogLevel.trace)
94
+ logger.trace!('trace message')
95
+ assert.strictEqual(captured.length, 1)
96
+ assert.strictEqual(captured[0].method, 'trace')
97
+ Object.assign(console, origConsole)
98
+ })
99
+
100
+ test('should log with additional metadata', () => {
101
+ logger.info('message', { extra: 'data' })
102
+ assert.ok(captured[0].args.includes('message'))
103
+ Object.assign(console, origConsole)
104
+ })
105
+
106
+ test('should handle object messages in info', () => {
107
+ logger.info({ key: 'value' })
108
+ assert.strictEqual(captured.length, 1)
109
+ Object.assign(console, origConsole)
110
+ })
111
+
112
+ test('log method should respect level', () => {
113
+ logger.setLevel(LogLevel.error)
114
+ logger.log('info', 'should not show')
115
+ assert.strictEqual(captured.length, 0)
116
+ Object.assign(console, origConsole)
117
+ })
118
+ })
@@ -0,0 +1,98 @@
1
+ import type { Logger } from './logger.js'
2
+ import { LogLevel } from './logger.js'
3
+
4
+ /**
5
+ * A logger implementation that logs messages to the console.
6
+ */
7
+ export class ConsoleLogger implements Logger {
8
+ /**
9
+ * The current logging level.
10
+ */
11
+ private level: LogLevel = LogLevel.info
12
+
13
+ /**
14
+ * Sets the logging level.
15
+ * @param level - The logging level to set.
16
+ */
17
+ setLevel(level: LogLevel): void {
18
+ this.level = level
19
+ }
20
+
21
+ /**
22
+ * Logs a trace message.
23
+ * @param message - The message to log.
24
+ * @param meta - Additional metadata to log.
25
+ */
26
+ trace?(message: string, ...meta: any[]): void {
27
+ if (this.level <= LogLevel.trace) {
28
+ console.trace('TRACE:', message, ...meta)
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Logs a debug message.
34
+ * @param message - The message to log.
35
+ * @param meta - Additional metadata to log.
36
+ */
37
+ debug(message: string, ...meta: any[]): void {
38
+ if (this.level <= LogLevel.debug) {
39
+ console.debug('DEBUG:', message, ...meta)
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Logs an informational message.
45
+ * @param messageOrObj - The message or object to log.
46
+ * @param meta - Additional metadata to log.
47
+ */
48
+ info(messageOrObj: string | Record<string, any>, ...meta: any[]): void {
49
+ if (this.level <= LogLevel.info) {
50
+ console.info('INFO:', messageOrObj, ...meta)
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Logs a warning message.
56
+ * @param messageOrObj - The message or object to log.
57
+ * @param meta - Additional metadata to log.
58
+ */
59
+ warn(messageOrObj: string | Record<string, any>, ...meta: any[]): void {
60
+ if (this.level <= LogLevel.warn) {
61
+ console.warn('WARN:', messageOrObj, ...meta)
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Logs an error message.
67
+ * @param messageOrObj - The message, object, or error to log.
68
+ * @param meta - Additional metadata to log.
69
+ */
70
+ error(
71
+ messageOrObj: string | Record<string, any> | Error,
72
+ ...meta: any[]
73
+ ): void {
74
+ if (this.level <= LogLevel.error) {
75
+ console.error(
76
+ 'ERROR:',
77
+ messageOrObj instanceof Error ? messageOrObj.message : messageOrObj,
78
+ ...meta
79
+ )
80
+ if (messageOrObj instanceof Error) {
81
+ console.error('STACK:', messageOrObj.stack)
82
+ }
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Logs a message at a specified level.
88
+ * @param level - The logging level.
89
+ * @param message - The message to log.
90
+ * @param meta - Additional metadata to log.
91
+ */
92
+ log(level: string, message: string, ...meta: any[]): void {
93
+ const logLevel = LogLevel[level as keyof typeof LogLevel]
94
+ if (this.level <= logLevel) {
95
+ console.log(`${level.toUpperCase()}:`, message, ...meta)
96
+ }
97
+ }
98
+ }
@@ -0,0 +1,57 @@
1
+ export enum LogLevel {
2
+ 'trace', // Add this level
3
+ 'debug',
4
+ 'info',
5
+ 'warn',
6
+ 'error',
7
+ 'critical',
8
+ }
9
+
10
+ /**
11
+ * Interface for logging messages at various levels.
12
+ */
13
+ export interface Logger {
14
+ /**
15
+ * Logs an informational message.
16
+ * @param messageOrObj - The message or object to log.
17
+ * @param meta - Additional metadata to log.
18
+ */
19
+ info(messageOrObj: string | Record<string, any>, ...meta: any[]): void
20
+
21
+ /**
22
+ * Logs a warning message.
23
+ * @param messageOrObj - The message or object to log.
24
+ * @param meta - Additional metadata to log.
25
+ */
26
+ warn(messageOrObj: string | Record<string, any>, ...meta: any[]): void
27
+
28
+ /**
29
+ * Logs an error message.
30
+ * @param messageOrObj - The message, object, or error to log.
31
+ * @param meta - Additional metadata to log.
32
+ */
33
+ error(
34
+ messageOrObj: string | Record<string, any> | Error,
35
+ ...meta: any[]
36
+ ): void
37
+
38
+ /**
39
+ * Logs a debug message.
40
+ * @param message - The message to log.
41
+ * @param meta - Additional metadata to log.
42
+ */
43
+ debug(message: string, ...meta: any[]): void
44
+
45
+ /**
46
+ * Logs a trace message.
47
+ * @param message - The message to log.
48
+ * @param meta - Additional metadata to log.
49
+ */
50
+ trace?(message: string, ...meta: any[]): void
51
+
52
+ /**
53
+ * Sets the logging level.
54
+ * @param level - The logging level to set.
55
+ */
56
+ setLevel(level: LogLevel): void
57
+ }
@@ -0,0 +1,86 @@
1
+ import type { CoreUserSession } from '../types/core.types.js'
2
+
3
+ /**
4
+ * Minimal metadata for listing scheduled tasks
5
+ */
6
+ export interface ScheduledTaskSummary {
7
+ /** Unique task ID */
8
+ taskId: string
9
+ /** RPC function name to invoke */
10
+ rpcName: string
11
+ /** Scheduled execution time */
12
+ scheduledFor: Date
13
+ }
14
+
15
+ /**
16
+ * Full metadata for a scheduled task retrieved from the queue
17
+ */
18
+ export interface ScheduledTaskInfo extends ScheduledTaskSummary {
19
+ /** Data to pass to the RPC function */
20
+ data?: any
21
+ /** User session */
22
+ session?: CoreUserSession
23
+ /** Task status */
24
+ status?: 'scheduled' | 'active' | 'completed' | 'failed'
25
+ }
26
+
27
+ /**
28
+ * Abstract scheduler service for persistent scheduled task management
29
+ * Implementations use queue backends (pg-boss, BullMQ) for distributed scheduling
30
+ */
31
+ export abstract class SchedulerService {
32
+ /**
33
+ * Initialize the scheduler service
34
+ */
35
+ abstract init(): Promise<void>
36
+
37
+ /**
38
+ * Schedule a one-off delayed RPC call
39
+ * @param delay - Delay before execution (number in milliseconds or duration string like "5h", "30m")
40
+ * @param rpcName - RPC function name to invoke
41
+ * @param data - Data to pass to the RPC function
42
+ * @param session - Optional user session
43
+ * @returns Task ID
44
+ */
45
+ abstract scheduleRPC(
46
+ delay: number | string,
47
+ rpcName: string,
48
+ data?: any,
49
+ session?: CoreUserSession
50
+ ): Promise<string>
51
+
52
+ /**
53
+ * Unschedule a task by ID
54
+ * @param taskId - Task ID
55
+ * @returns Success status
56
+ */
57
+ abstract unschedule(taskId: string): Promise<boolean>
58
+
59
+ /**
60
+ * Get a scheduled task by ID with full details
61
+ * @param taskId - Task ID
62
+ * @returns Task info or null if not found
63
+ */
64
+ abstract getTask(taskId: string): Promise<ScheduledTaskInfo | null>
65
+
66
+ /**
67
+ * Get all scheduled tasks with minimal info
68
+ * @returns Array of task summaries with taskId, rpcName, and schedule
69
+ */
70
+ abstract getAllTasks(): Promise<ScheduledTaskSummary[]>
71
+
72
+ /**
73
+ * Close any open connections
74
+ */
75
+ abstract close(): Promise<void>
76
+
77
+ /**
78
+ * Start recurring scheduled tasks
79
+ */
80
+ async start(): Promise<void> {}
81
+
82
+ /**
83
+ * Stop recurring scheduled tasks
84
+ */
85
+ async stop(): Promise<void> {}
86
+ }