@geekmidas/constructs 10.0.0-alpha.37 → 10.0.0-alpha.39
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 +156 -155
- package/dist/{Construct-Detz65RA.mjs → Construct-BO1X-kt9.mjs} +3 -6
- package/dist/Construct-BO1X-kt9.mjs.map +1 -0
- package/dist/Construct-BQALFCh2.d.cts +230 -0
- package/dist/Construct-BQALFCh2.d.cts.map +1 -0
- package/dist/{Construct-yG9Sw-5p.cjs → Construct-DmvXceVY.cjs} +3 -6
- package/dist/Construct-DmvXceVY.cjs.map +1 -0
- package/dist/Construct-QXnl3VqT.d.mts +230 -0
- package/dist/Construct-QXnl3VqT.d.mts.map +1 -0
- package/dist/{Cron-CA2oa2nC.d.cts → Cron-CMEGfRs8.d.cts} +7 -5
- package/dist/Cron-CMEGfRs8.d.cts.map +1 -0
- package/dist/{Cron-B_iuyNxr.d.mts → Cron-D6X4-xjB.d.mts} +7 -5
- package/dist/Cron-D6X4-xjB.d.mts.map +1 -0
- package/dist/{CronBuilder-B_KqaodP.d.cts → CronBuilder-CEK7aOqD.d.cts} +21 -16
- package/dist/CronBuilder-CEK7aOqD.d.cts.map +1 -0
- package/dist/{CronBuilder-BbnT7tqo.cjs → CronBuilder-DB35T5dL.cjs} +13 -11
- package/dist/CronBuilder-DB35T5dL.cjs.map +1 -0
- package/dist/{CronBuilder-BRkzUOAs.mjs → CronBuilder-Dv0IlZs0.mjs} +14 -12
- package/dist/CronBuilder-Dv0IlZs0.mjs.map +1 -0
- package/dist/{CronBuilder-BIKCLWu7.d.mts → CronBuilder-O_gvButp.d.mts} +21 -16
- package/dist/CronBuilder-O_gvButp.d.mts.map +1 -0
- package/dist/{Endpoint-DO332Dnm.mjs → Endpoint-CQAeh1Rh.mjs} +5 -5
- package/dist/Endpoint-CQAeh1Rh.mjs.map +1 -0
- package/dist/{Endpoint-QKuFg5gs.cjs → Endpoint-D1L9qu5C.cjs} +5 -5
- package/dist/Endpoint-D1L9qu5C.cjs.map +1 -0
- package/dist/{Endpoint-1-XYQSx0.d.cts → Endpoint-Dp5AKIXl.d.cts} +10 -13
- package/dist/Endpoint-Dp5AKIXl.d.cts.map +1 -0
- package/dist/{Endpoint-GFIxYb9L.d.mts → Endpoint-DtI7heFm.d.mts} +10 -13
- package/dist/Endpoint-DtI7heFm.d.mts.map +1 -0
- package/dist/{EndpointFactory-B9HAjDAP.mjs → EndpointFactory-BBdNeaGJ.mjs} +23 -52
- package/dist/EndpointFactory-BBdNeaGJ.mjs.map +1 -0
- package/dist/{EndpointFactory-BmO0WnXE.cjs → EndpointFactory-BZOCOaK6.cjs} +22 -51
- package/dist/EndpointFactory-BZOCOaK6.cjs.map +1 -0
- package/dist/{EndpointFactory-BCZdh_u3.d.mts → EndpointFactory-CgMDvERK.d.mts} +58 -48
- package/dist/EndpointFactory-CgMDvERK.d.mts.map +1 -0
- package/dist/{EndpointFactory-BnK_YkW1.d.cts → EndpointFactory-FeFRmlDZ.d.cts} +58 -48
- package/dist/EndpointFactory-FeFRmlDZ.d.cts.map +1 -0
- package/dist/{HonoEndpointAdaptor-CiqAZ5Mt.mjs → HonoEndpointAdaptor-Aui-x9Lg.mjs} +4 -3
- package/dist/HonoEndpointAdaptor-Aui-x9Lg.mjs.map +1 -0
- package/dist/{HonoEndpointAdaptor-DoEQdC3r.cjs → HonoEndpointAdaptor-Cq99r9H7.cjs} +5 -4
- package/dist/HonoEndpointAdaptor-Cq99r9H7.cjs.map +1 -0
- package/dist/{Queue-DpKTLl6E.mjs → Queue-C1uDgjxt.mjs} +29 -48
- package/dist/Queue-C1uDgjxt.mjs.map +1 -0
- package/dist/{Queue-B7yRXQmL.cjs → Queue-Ckmudcm8.cjs} +29 -48
- package/dist/Queue-Ckmudcm8.cjs.map +1 -0
- package/dist/{Queue-Bhzi2WQC.d.cts → Queue-CpQIGB_2.d.cts} +22 -33
- package/dist/Queue-CpQIGB_2.d.cts.map +1 -0
- package/dist/{Queue-BoUA-e83.d.mts → Queue-YmvN2vEP.d.mts} +21 -32
- package/dist/Queue-YmvN2vEP.d.mts.map +1 -0
- package/dist/QueueBuilder-CJNcv4dy.cjs +112 -0
- package/dist/QueueBuilder-CJNcv4dy.cjs.map +1 -0
- package/dist/QueueBuilder-CM0WUxfw.d.mts +62 -0
- package/dist/QueueBuilder-CM0WUxfw.d.mts.map +1 -0
- package/dist/QueueBuilder-XEnnOWGe.d.cts +62 -0
- package/dist/QueueBuilder-XEnnOWGe.d.cts.map +1 -0
- package/dist/QueueBuilder-XuPKZwKH.mjs +95 -0
- package/dist/QueueBuilder-XuPKZwKH.mjs.map +1 -0
- package/dist/{Subscriber-DQCaiOXF.d.cts → Subscriber-B91YSkZt.d.cts} +12 -13
- package/dist/Subscriber-B91YSkZt.d.cts.map +1 -0
- package/dist/{Subscriber-CIO3pwji.d.mts → Subscriber-BjHYe5hK.d.mts} +11 -12
- package/dist/Subscriber-BjHYe5hK.d.mts.map +1 -0
- package/dist/{SubscriberBuilder-CjDGSTZc.d.cts → SubscriberBuilder-BSpQX1JT.d.cts} +19 -23
- package/dist/SubscriberBuilder-BSpQX1JT.d.cts.map +1 -0
- package/dist/{SubscriberBuilder-C2FRfd26.d.mts → SubscriberBuilder-BhMphk4z.d.mts} +18 -22
- package/dist/SubscriberBuilder-BhMphk4z.d.mts.map +1 -0
- package/dist/{SubscriberBuilder-C2bCOuPT.cjs → SubscriberBuilder-Duk028pD.cjs} +11 -19
- package/dist/SubscriberBuilder-Duk028pD.cjs.map +1 -0
- package/dist/{SubscriberBuilder-B4YRTBng.mjs → SubscriberBuilder-DzQUWE08.mjs} +11 -19
- package/dist/SubscriberBuilder-DzQUWE08.mjs.map +1 -0
- package/dist/{Topic-D5rlM9wS.cjs → Topic-BBVXC3H4.cjs} +46 -51
- package/dist/Topic-BBVXC3H4.cjs.map +1 -0
- package/dist/Topic-DOw051ch.mjs +98 -0
- package/dist/Topic-DOw051ch.mjs.map +1 -0
- package/dist/adaptors/aws.cjs +5 -5
- package/dist/adaptors/aws.cjs.map +1 -1
- package/dist/adaptors/aws.d.cts +26 -26
- package/dist/adaptors/aws.d.cts.map +1 -1
- package/dist/adaptors/aws.d.mts +25 -25
- package/dist/adaptors/aws.d.mts.map +1 -1
- package/dist/adaptors/aws.mjs +5 -5
- package/dist/adaptors/aws.mjs.map +1 -1
- package/dist/adaptors/hono.cjs +1 -1
- package/dist/adaptors/hono.d.cts +4 -5
- package/dist/adaptors/hono.d.cts.map +1 -1
- package/dist/adaptors/hono.d.mts +4 -5
- package/dist/adaptors/hono.d.mts.map +1 -1
- package/dist/adaptors/hono.mjs +1 -1
- package/dist/adaptors/testing.cjs +59 -23
- package/dist/adaptors/testing.cjs.map +1 -1
- package/dist/adaptors/testing.d.cts +40 -28
- package/dist/adaptors/testing.d.cts.map +1 -1
- package/dist/adaptors/testing.d.mts +40 -28
- package/dist/adaptors/testing.d.mts.map +1 -1
- package/dist/adaptors/testing.mjs +59 -23
- package/dist/adaptors/testing.mjs.map +1 -1
- package/dist/{auth-c6nWnvD8.d.cts → auth-CMwml9QN.d.mts} +4 -4
- package/dist/{auth-c6nWnvD8.d.cts.map → auth-CMwml9QN.d.mts.map} +1 -1
- package/dist/{auth-pS_U1H_X.d.mts → auth-rVRDjIw_.d.cts} +4 -4
- package/dist/{auth-pS_U1H_X.d.mts.map → auth-rVRDjIw_.d.cts.map} +1 -1
- package/dist/auth.d.cts +1 -1
- package/dist/auth.d.mts +1 -1
- package/dist/{cache-B2s0b-ss.d.mts → cache-C9a_hVCc.d.cts} +3 -3
- package/dist/{cache-B2s0b-ss.d.mts.map → cache-C9a_hVCc.d.cts.map} +1 -1
- package/dist/{cache-CYe2Kzjc.d.cts → cache-CeTMI8LR.d.mts} +3 -3
- package/dist/{cache-CYe2Kzjc.d.cts.map → cache-CeTMI8LR.d.mts.map} +1 -1
- package/dist/cache.d.cts +1 -1
- package/dist/cache.d.mts +1 -1
- package/dist/{construct-interface-D2gesxos.d.mts → construct-interface-_3zgxzXl.d.mts} +11 -20
- package/dist/{construct-interface-D2gesxos.d.mts.map → construct-interface-_3zgxzXl.d.mts.map} +1 -1
- package/dist/{construct-interface-DPusCUSQ.d.cts → construct-interface-_xi4BPN8.d.cts} +11 -20
- package/dist/{construct-interface-DPusCUSQ.d.cts.map → construct-interface-_xi4BPN8.d.cts.map} +1 -1
- package/dist/construct-interface.cjs +4 -33
- package/dist/construct-interface.cjs.map +1 -1
- package/dist/construct-interface.d.cts +2 -2
- package/dist/construct-interface.d.mts +2 -2
- package/dist/construct-interface.mjs +5 -32
- package/dist/construct-interface.mjs.map +1 -1
- package/dist/credential.cjs +22 -9
- package/dist/credential.cjs.map +1 -1
- package/dist/credential.d.cts +12 -2
- package/dist/credential.d.cts.map +1 -1
- package/dist/credential.d.mts +12 -2
- package/dist/credential.d.mts.map +1 -1
- package/dist/credential.mjs +22 -10
- package/dist/credential.mjs.map +1 -1
- package/dist/crons/index.cjs +1 -1
- package/dist/crons/index.d.cts +2 -2
- package/dist/crons/index.d.mts +2 -2
- package/dist/crons/index.mjs +1 -1
- package/dist/database/kysely.d.cts +1 -1
- package/dist/database/kysely.d.mts +1 -1
- package/dist/email.d.cts +1 -1
- package/dist/email.d.mts +1 -1
- package/dist/endpoints/index.cjs +5 -4
- package/dist/endpoints/index.d.cts +4 -13
- package/dist/endpoints/index.d.cts.map +1 -1
- package/dist/endpoints/index.d.mts +4 -13
- package/dist/endpoints/index.d.mts.map +1 -1
- package/dist/endpoints/index.mjs +4 -3
- package/dist/external-api-DS3mujT5.d.mts +86 -0
- package/dist/external-api-DS3mujT5.d.mts.map +1 -0
- package/dist/external-api-LBfE-q3g.d.cts +86 -0
- package/dist/external-api-LBfE-q3g.d.cts.map +1 -0
- package/dist/external-api.cjs +124 -0
- package/dist/external-api.cjs.map +1 -0
- package/dist/external-api.d.cts +2 -0
- package/dist/external-api.d.mts +2 -0
- package/dist/external-api.mjs +121 -0
- package/dist/external-api.mjs.map +1 -0
- package/dist/file-server.d.cts +1 -1
- package/dist/file-server.d.mts +1 -1
- package/dist/functions/index.cjs +1 -1
- package/dist/functions/index.d.cts +1 -1
- package/dist/functions/index.d.mts +1 -1
- package/dist/functions/index.mjs +1 -1
- package/dist/{functions-DlFm8HmH.mjs → functions-DCIfCRR-.mjs} +94 -23
- package/dist/functions-DCIfCRR-.mjs.map +1 -0
- package/dist/{functions-DKN6aQWJ.cjs → functions-ZH60FE7H.cjs} +99 -19
- package/dist/functions-ZH60FE7H.cjs.map +1 -0
- package/dist/{index-BI23K3wy.d.cts → index-B0kxstwf.d.cts} +36 -24
- package/dist/index-B0kxstwf.d.cts.map +1 -0
- package/dist/{index-yb3fXNyH.d.mts → index-CjVEjOw0.d.mts} +36 -24
- package/dist/index-CjVEjOw0.d.mts.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{kysely-CEVXacPN.d.cts → kysely-BJ8IQ_1P.d.mts} +4 -4
- package/dist/{kysely-CEVXacPN.d.cts.map → kysely-BJ8IQ_1P.d.mts.map} +1 -1
- package/dist/{kysely-mcTdPNhl.d.mts → kysely-DVF8wOT4.d.cts} +4 -4
- package/dist/{kysely-mcTdPNhl.d.mts.map → kysely-DVF8wOT4.d.cts.map} +1 -1
- package/dist/{lazyAccessors-BHtcqgz2.mjs → lazyAccessors-CFanua0O.mjs} +2 -39
- package/dist/lazyAccessors-CFanua0O.mjs.map +1 -0
- package/dist/{lazyAccessors-Dx9NLwJZ.cjs → lazyAccessors-hJKGv8Ay.cjs} +1 -50
- package/dist/lazyAccessors-hJKGv8Ay.cjs.map +1 -0
- package/dist/mobile-app.d.cts +1 -1
- package/dist/mobile-app.d.mts +1 -1
- package/dist/object-storage.d.cts +1 -1
- package/dist/object-storage.d.mts +1 -1
- package/dist/processAudits-BpRlPQMW.mjs.map +1 -1
- package/dist/processAudits-D_2W5x8k.cjs.map +1 -1
- package/dist/queue/index.cjs +4 -78
- package/dist/queue/index.d.cts +3 -53
- package/dist/queue/index.d.mts +3 -53
- package/dist/queue/index.mjs +3 -77
- package/dist/rest-api.cjs +2 -4
- package/dist/rest-api.cjs.map +1 -1
- package/dist/rest-api.d.cts +4 -5
- package/dist/rest-api.d.cts.map +1 -1
- package/dist/rest-api.d.mts +4 -5
- package/dist/rest-api.d.mts.map +1 -1
- package/dist/rest-api.mjs +2 -4
- package/dist/rest-api.mjs.map +1 -1
- package/dist/site.d.cts +1 -1
- package/dist/site.d.mts +1 -1
- package/dist/subscribers/index.cjs +1 -1
- package/dist/subscribers/index.d.cts +2 -2
- package/dist/subscribers/index.d.mts +2 -2
- package/dist/subscribers/index.mjs +1 -1
- package/dist/topic/index.cjs +1 -36
- package/dist/topic/index.d.cts +2 -3
- package/dist/topic/index.d.mts +2 -3
- package/dist/topic/index.mjs +2 -35
- package/dist/worker.cjs +26 -17
- package/dist/worker.cjs.map +1 -1
- package/dist/worker.d.cts +37 -29
- package/dist/worker.d.cts.map +1 -1
- package/dist/worker.d.mts +37 -29
- package/dist/worker.d.mts.map +1 -1
- package/dist/worker.mjs +26 -17
- package/dist/worker.mjs.map +1 -1
- package/package.json +41 -31
- package/dist/Construct-DTqYvkPG.d.cts +0 -115
- package/dist/Construct-DTqYvkPG.d.cts.map +0 -1
- package/dist/Construct-Detz65RA.mjs.map +0 -1
- package/dist/Construct-pVadmURD.d.mts +0 -115
- package/dist/Construct-pVadmURD.d.mts.map +0 -1
- package/dist/Construct-yG9Sw-5p.cjs.map +0 -1
- package/dist/Cron-B_iuyNxr.d.mts.map +0 -1
- package/dist/Cron-CA2oa2nC.d.cts.map +0 -1
- package/dist/CronBuilder-BIKCLWu7.d.mts.map +0 -1
- package/dist/CronBuilder-BRkzUOAs.mjs.map +0 -1
- package/dist/CronBuilder-B_KqaodP.d.cts.map +0 -1
- package/dist/CronBuilder-BbnT7tqo.cjs.map +0 -1
- package/dist/Endpoint-1-XYQSx0.d.cts.map +0 -1
- package/dist/Endpoint-DO332Dnm.mjs.map +0 -1
- package/dist/Endpoint-GFIxYb9L.d.mts.map +0 -1
- package/dist/Endpoint-QKuFg5gs.cjs.map +0 -1
- package/dist/EndpointFactory-B9HAjDAP.mjs.map +0 -1
- package/dist/EndpointFactory-BCZdh_u3.d.mts.map +0 -1
- package/dist/EndpointFactory-BmO0WnXE.cjs.map +0 -1
- package/dist/EndpointFactory-BnK_YkW1.d.cts.map +0 -1
- package/dist/HonoEndpointAdaptor-CiqAZ5Mt.mjs.map +0 -1
- package/dist/HonoEndpointAdaptor-DoEQdC3r.cjs.map +0 -1
- package/dist/Queue-B7yRXQmL.cjs.map +0 -1
- package/dist/Queue-Bhzi2WQC.d.cts.map +0 -1
- package/dist/Queue-BoUA-e83.d.mts.map +0 -1
- package/dist/Queue-DpKTLl6E.mjs.map +0 -1
- package/dist/Subscriber-CIO3pwji.d.mts.map +0 -1
- package/dist/Subscriber-DQCaiOXF.d.cts.map +0 -1
- package/dist/SubscriberBuilder-B4YRTBng.mjs.map +0 -1
- package/dist/SubscriberBuilder-C2FRfd26.d.mts.map +0 -1
- package/dist/SubscriberBuilder-C2bCOuPT.cjs.map +0 -1
- package/dist/SubscriberBuilder-CjDGSTZc.d.cts.map +0 -1
- package/dist/Topic-Ct3Le8Wt.mjs +0 -103
- package/dist/Topic-Ct3Le8Wt.mjs.map +0 -1
- package/dist/Topic-D5rlM9wS.cjs.map +0 -1
- package/dist/Topic-D6blllEn.d.cts +0 -89
- package/dist/Topic-D6blllEn.d.cts.map +0 -1
- package/dist/Topic-DVvheJ_-.d.mts +0 -89
- package/dist/Topic-DVvheJ_-.d.mts.map +0 -1
- package/dist/functions-DKN6aQWJ.cjs.map +0 -1
- package/dist/functions-DlFm8HmH.mjs.map +0 -1
- package/dist/index-BI23K3wy.d.cts.map +0 -1
- package/dist/index-fnpjXiA-.d.mts +0 -26
- package/dist/index-fnpjXiA-.d.mts.map +0 -1
- package/dist/index-tE_119SX.d.cts +0 -26
- package/dist/index-tE_119SX.d.cts.map +0 -1
- package/dist/index-yb3fXNyH.d.mts.map +0 -1
- package/dist/lazyAccessors-BHtcqgz2.mjs.map +0 -1
- package/dist/lazyAccessors-Dx9NLwJZ.cjs.map +0 -1
- package/dist/queue/index.cjs.map +0 -1
- package/dist/queue/index.d.cts.map +0 -1
- package/dist/queue/index.d.mts.map +0 -1
- package/dist/queue/index.mjs.map +0 -1
- package/dist/topic/index.cjs.map +0 -1
- package/dist/topic/index.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -33,14 +33,13 @@ import { RestApi } from '@geekmidas/constructs/rest-api';
|
|
|
33
33
|
// returns. There is no free-standing `e` — an endpoint belongs to a surface.
|
|
34
34
|
import { EndpointBuilder, EndpointFactory } from '@geekmidas/constructs/endpoints';
|
|
35
35
|
|
|
36
|
-
//
|
|
37
|
-
|
|
36
|
+
// The process with no port. Crons, topic subscribers, queues and functions
|
|
37
|
+
// are built from it — `worker.cron(…)`, `worker.topic(…)`, `worker.queue(…)`,
|
|
38
|
+
// `worker.input(…)` — and carry its logger.
|
|
39
|
+
import { Worker } from '@geekmidas/constructs/worker';
|
|
38
40
|
|
|
39
|
-
//
|
|
40
|
-
import {
|
|
41
|
-
|
|
42
|
-
// Subscribers
|
|
43
|
-
import { s, SubscriberBuilder } from '@geekmidas/constructs/subscribers';
|
|
41
|
+
// A declared event contract. Its publisher is its `service`.
|
|
42
|
+
import { Topic } from '@geekmidas/constructs/topic';
|
|
44
43
|
```
|
|
45
44
|
|
|
46
45
|
## Quick Start
|
|
@@ -126,15 +125,31 @@ export const searchUsers = api
|
|
|
126
125
|
});
|
|
127
126
|
```
|
|
128
127
|
|
|
128
|
+
### The Worker
|
|
129
|
+
|
|
130
|
+
Everything that is not an HTTP route is built from a `Worker`: the process
|
|
131
|
+
that runs a schedule, drains a queue or consumes a topic. It takes no
|
|
132
|
+
authorizer, because nothing reaches it from outside.
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
// constructs/worker.ts
|
|
136
|
+
import { Worker } from '@geekmidas/constructs/worker';
|
|
137
|
+
import { database } from './database';
|
|
138
|
+
import { logger } from './logger';
|
|
139
|
+
|
|
140
|
+
// `.database(…)` is where a server target keeps cron schedules.
|
|
141
|
+
export const worker = new Worker('Jobs', { logger }).database(database);
|
|
142
|
+
```
|
|
143
|
+
|
|
129
144
|
### Cloud Functions
|
|
130
145
|
|
|
131
|
-
Create
|
|
146
|
+
Create functions with input/output validation:
|
|
132
147
|
|
|
133
148
|
```typescript
|
|
134
|
-
import {
|
|
149
|
+
import { worker } from '../constructs/worker';
|
|
135
150
|
import { z } from 'zod';
|
|
136
151
|
|
|
137
|
-
export const processOrder =
|
|
152
|
+
export const processOrder = worker
|
|
138
153
|
.input(z.object({
|
|
139
154
|
orderId: z.string(),
|
|
140
155
|
items: z.array(z.object({
|
|
@@ -145,13 +160,12 @@ export const processOrder = f
|
|
|
145
160
|
.output(z.object({
|
|
146
161
|
orderId: z.string(),
|
|
147
162
|
status: z.enum(['processing', 'completed', 'failed']),
|
|
148
|
-
processedAt: z.
|
|
163
|
+
processedAt: z.iso.datetime()
|
|
149
164
|
}))
|
|
150
165
|
.timeout(300000) // 5 minutes
|
|
151
166
|
.handle(async ({ input, logger }) => {
|
|
152
167
|
logger.info(`Processing order ${input.orderId}`);
|
|
153
168
|
|
|
154
|
-
// Process order logic
|
|
155
169
|
for (const item of input.items) {
|
|
156
170
|
logger.info(`Processing item ${item.id}, quantity: ${item.quantity}`);
|
|
157
171
|
}
|
|
@@ -166,87 +180,99 @@ export const processOrder = f
|
|
|
166
180
|
|
|
167
181
|
### Scheduled Tasks (Crons)
|
|
168
182
|
|
|
169
|
-
Define cron jobs with schedules:
|
|
170
|
-
|
|
171
183
|
```typescript
|
|
172
|
-
import {
|
|
184
|
+
import { worker } from '../constructs/worker';
|
|
173
185
|
|
|
174
186
|
// Daily report at 9 AM UTC
|
|
175
|
-
export const dailyReport =
|
|
176
|
-
.
|
|
187
|
+
export const dailyReport = worker
|
|
188
|
+
.cron('cron(0 9 * * ? *)')
|
|
177
189
|
.timeout(600000) // 10 minutes
|
|
178
190
|
.handle(async ({ logger }) => {
|
|
179
|
-
logger.info('Generating daily report');
|
|
180
|
-
|
|
181
|
-
const reportDate = new Date().toISOString().split('T')[0];
|
|
182
191
|
const reportData = {
|
|
183
|
-
date:
|
|
192
|
+
date: new Date().toISOString().split('T')[0],
|
|
184
193
|
totalOrders: 150,
|
|
185
|
-
totalRevenue: 12500.00
|
|
186
194
|
};
|
|
187
195
|
|
|
188
|
-
logger.info('Daily report generated'
|
|
196
|
+
logger.info(reportData, 'Daily report generated');
|
|
189
197
|
return reportData;
|
|
190
198
|
});
|
|
191
199
|
|
|
192
200
|
// Hourly cleanup
|
|
193
|
-
export const hourlyCleanup =
|
|
194
|
-
.
|
|
195
|
-
.
|
|
196
|
-
.handle(async ({ logger }) => {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
+
export const hourlyCleanup = worker
|
|
202
|
+
.cron('rate(1 hour)')
|
|
203
|
+
.dependsOn([database])
|
|
204
|
+
.handle(async ({ services, logger }) => {
|
|
205
|
+
const result = await services.database
|
|
206
|
+
.deleteFrom('sessions')
|
|
207
|
+
.where('expires_at', '<', new Date())
|
|
208
|
+
.executeTakeFirst();
|
|
201
209
|
|
|
202
|
-
|
|
210
|
+
logger.info({ deleted: Number(result.numDeletedRows) }, 'Cleaned up');
|
|
203
211
|
});
|
|
204
212
|
```
|
|
205
213
|
|
|
206
|
-
|
|
214
|
+
On AWS a cron is an EventBridge rule; on a server the worker schedules it in
|
|
215
|
+
the database it was given.
|
|
216
|
+
|
|
217
|
+
### Topics and Subscribers
|
|
207
218
|
|
|
208
|
-
|
|
219
|
+
A topic declares the event contract once. A subscriber binds to it from the
|
|
220
|
+
worker and is typed by it:
|
|
209
221
|
|
|
210
222
|
```typescript
|
|
211
|
-
|
|
223
|
+
// constructs/topics.ts
|
|
224
|
+
import { Topic } from '@geekmidas/constructs/topic';
|
|
212
225
|
import { z } from 'zod';
|
|
213
226
|
|
|
214
|
-
export const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
}))
|
|
221
|
-
.handle(async ({ events, logger }) => {
|
|
222
|
-
logger.info(
|
|
223
|
-
{ eventCount: events.length },
|
|
224
|
-
'Processing user events'
|
|
225
|
-
);
|
|
227
|
+
export const users = new Topic('Users', {
|
|
228
|
+
events: {
|
|
229
|
+
'user.created': z.object({ userId: z.string(), email: z.email() }),
|
|
230
|
+
'user.updated': z.object({ userId: z.string(), changes: z.array(z.string()) }),
|
|
231
|
+
},
|
|
232
|
+
});
|
|
226
233
|
|
|
234
|
+
// subscribers/userEvents.ts
|
|
235
|
+
export const userEventsSubscriber = worker
|
|
236
|
+
.topic(users)
|
|
237
|
+
.subscribe(['user.created', 'user.updated'])
|
|
238
|
+
.handle(async ({ events, logger }) => {
|
|
227
239
|
for (const event of events) {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
case 'user.created':
|
|
231
|
-
logger.info({ userId: event.data.userId }, 'User created');
|
|
232
|
-
break;
|
|
233
|
-
case 'user.updated':
|
|
234
|
-
logger.info({ userId: event.data.userId }, 'User updated');
|
|
235
|
-
break;
|
|
236
|
-
case 'user.deleted':
|
|
237
|
-
logger.info({ userId: event.data.userId }, 'User deleted');
|
|
238
|
-
break;
|
|
239
|
-
}
|
|
240
|
-
} catch (error) {
|
|
241
|
-
logger.error({ error, event }, 'Failed to process event');
|
|
242
|
-
throw error;
|
|
243
|
-
}
|
|
240
|
+
// event.type narrows event.payload to that event's schema
|
|
241
|
+
logger.info({ type: event.type, userId: event.payload.userId }, 'User event');
|
|
244
242
|
}
|
|
243
|
+
});
|
|
244
|
+
```
|
|
245
245
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
246
|
+
A subscriber is bound, not handed a publisher. One that emits follow-ups
|
|
247
|
+
depends on the topic it emits to: `.dependsOn([notifications])`.
|
|
248
|
+
|
|
249
|
+
### Queues
|
|
250
|
+
|
|
251
|
+
A queue and its one consumer are one construct. Producers send by depending on
|
|
252
|
+
it:
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
// queues/emails.ts
|
|
256
|
+
export const emails = worker
|
|
257
|
+
.queue('Emails')
|
|
258
|
+
.message(z.object({ to: z.email(), template: z.enum(['welcome']) }))
|
|
259
|
+
.dependsOn([mail])
|
|
260
|
+
.handle(async ({ messages, services }) => {
|
|
261
|
+
for (const { to, template } of messages) {
|
|
262
|
+
await services.mail.sendTemplate(template, { to, subject: 'Welcome', props: {} });
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// endpoints/invites.ts
|
|
267
|
+
export const invite = api
|
|
268
|
+
.post('/invites')
|
|
269
|
+
.body(z.object({ email: z.email() }))
|
|
270
|
+
.dependsOn([emails])
|
|
271
|
+
.handle(async ({ body, services }) => {
|
|
272
|
+
// `type` is the queue's name exactly as written
|
|
273
|
+
await services.emails.publish([
|
|
274
|
+
{ type: 'Emails', payload: { to: body.email, template: 'welcome' } },
|
|
275
|
+
]);
|
|
250
276
|
});
|
|
251
277
|
```
|
|
252
278
|
|
|
@@ -344,99 +370,69 @@ export const getHealth = router
|
|
|
344
370
|
.handle(async () => ({ status: 'ok' }));
|
|
345
371
|
```
|
|
346
372
|
|
|
347
|
-
###
|
|
373
|
+
### Dependencies
|
|
348
374
|
|
|
349
|
-
|
|
375
|
+
`.dependsOn([...])` takes constructs. Each one's client lands in the handler's
|
|
376
|
+
`services` under the construct's own id, and the edge is what the manifest
|
|
377
|
+
records — so the environment, the runtime client and the cloud access all come
|
|
378
|
+
from the one line:
|
|
350
379
|
|
|
351
380
|
```typescript
|
|
352
381
|
import { api } from '../constructs/api';
|
|
353
|
-
import
|
|
354
|
-
import type { EnvironmentParser } from '@geekmidas/envkit';
|
|
355
|
-
import { Kysely } from 'kysely';
|
|
382
|
+
import { database } from '../constructs/database';
|
|
356
383
|
import { z } from 'zod';
|
|
357
384
|
|
|
358
|
-
// Define a database service
|
|
359
|
-
const databaseService = {
|
|
360
|
-
serviceName: 'database' as const,
|
|
361
|
-
async register(envParser: EnvironmentParser<{}>) {
|
|
362
|
-
const config = envParser.create((get) => ({
|
|
363
|
-
url: get('DATABASE_URL').string()
|
|
364
|
-
})).parse();
|
|
365
|
-
|
|
366
|
-
const db = new Kysely({ /* config */ });
|
|
367
|
-
return db;
|
|
368
|
-
}
|
|
369
|
-
} satisfies Service<'database', Kysely<Database>>;
|
|
370
|
-
|
|
371
|
-
// Use service in endpoint
|
|
372
385
|
export const getUserFromDb = api
|
|
373
386
|
.get('/users/:id')
|
|
374
387
|
.params(z.object({ id: z.string() }))
|
|
375
|
-
.
|
|
388
|
+
.dependsOn([database])
|
|
376
389
|
.handle(async ({ params, services }) => {
|
|
377
|
-
// services.database is
|
|
378
|
-
|
|
390
|
+
// services.database is a Kysely client typed by the construct's schema
|
|
391
|
+
return services.database
|
|
379
392
|
.selectFrom('users')
|
|
380
393
|
.where('id', '=', params.id)
|
|
381
394
|
.selectAll()
|
|
382
395
|
.executeTakeFirst();
|
|
383
|
-
|
|
384
|
-
return user;
|
|
385
396
|
});
|
|
386
397
|
```
|
|
387
398
|
|
|
399
|
+
A hand-written `Service` still works through `.services()` on every builder,
|
|
400
|
+
for code that has not moved to a construct yet. It records no edge, so a deploy
|
|
401
|
+
target cannot grant it anything.
|
|
402
|
+
|
|
388
403
|
### Event Publishing
|
|
389
404
|
|
|
390
|
-
|
|
405
|
+
An endpoint, function or cron publishes to a topic with `.event(topic, …)`,
|
|
406
|
+
once its handler has succeeded:
|
|
391
407
|
|
|
392
408
|
```typescript
|
|
393
|
-
import {
|
|
394
|
-
import type { Service } from '@geekmidas/services';
|
|
395
|
-
import type { EventPublisher, PublishableMessage } from '@geekmidas/events';
|
|
396
|
-
import { z } from 'zod';
|
|
409
|
+
import { users } from '../constructs/topics';
|
|
397
410
|
|
|
398
|
-
|
|
399
|
-
type UserEvents =
|
|
400
|
-
| PublishableMessage<'user.created', { userId: string; email: string }>
|
|
401
|
-
| PublishableMessage<'user.updated', { userId: string }>;
|
|
402
|
-
|
|
403
|
-
// Create event publisher service
|
|
404
|
-
const userEventPublisher = {
|
|
405
|
-
serviceName: 'userEventPublisher' as const,
|
|
406
|
-
async register(envParser: EnvironmentParser<{}>) {
|
|
407
|
-
const config = envParser.create((get) => ({
|
|
408
|
-
publisherUrl: get('EVENT_PUBLISHER_URL').string()
|
|
409
|
-
})).parse();
|
|
410
|
-
|
|
411
|
-
const { Publisher } = await import('@geekmidas/events');
|
|
412
|
-
return Publisher.fromConnectionString<UserEvents>(config.publisherUrl);
|
|
413
|
-
}
|
|
414
|
-
} satisfies Service<'userEventPublisher', EventPublisher<UserEvents>>;
|
|
415
|
-
|
|
416
|
-
// Use in endpoint with event publishing
|
|
417
|
-
export const createUser = api
|
|
411
|
+
export const createUser = router
|
|
418
412
|
.post('/users')
|
|
419
|
-
.body(z.object({
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
}))
|
|
428
|
-
.handle(async ({ body, publish }) => {
|
|
429
|
-
const user = {
|
|
430
|
-
id: crypto.randomUUID(),
|
|
431
|
-
name: body.name,
|
|
432
|
-
email: body.email
|
|
433
|
-
};
|
|
434
|
-
|
|
435
|
-
// Events are automatically published after successful execution
|
|
436
|
-
return user;
|
|
437
|
-
});
|
|
413
|
+
.body(z.object({ name: z.string(), email: z.email() }))
|
|
414
|
+
.output(z.object({ id: z.string(), name: z.string(), email: z.email() }))
|
|
415
|
+
.event(users, {
|
|
416
|
+
type: 'user.created',
|
|
417
|
+
payload: (user) => ({ userId: user.id, email: user.email }),
|
|
418
|
+
when: (user) => !user.email.endsWith('@example.com'), // optional
|
|
419
|
+
})
|
|
420
|
+
.handle(async ({ body }) => ({ id: crypto.randomUUID(), ...body }));
|
|
438
421
|
```
|
|
439
422
|
|
|
423
|
+
- `type` is checked against the topic's events, and `payload` against that
|
|
424
|
+
event's schema.
|
|
425
|
+
- `.event()` is repeatable across topics. Two calls to two topics publish to
|
|
426
|
+
each, every event through its own topic's publisher.
|
|
427
|
+
- It also puts the topic's publisher in the handler as `services.users`, as
|
|
428
|
+
`.dependsOn([users])` would, for events the handler decides on:
|
|
429
|
+
`await services.users.publish([{ type: 'user.updated', payload }])`.
|
|
430
|
+
- A failed publish is logged, never thrown — the handler already succeeded.
|
|
431
|
+
|
|
432
|
+
The publisher reads `USERS_PUBLISHER_CONNECTION_STRING`, which only constructs
|
|
433
|
+
that publish to the topic are given, and picks its transport from the protocol:
|
|
434
|
+
`pgboss://` locally, `sns://` deployed.
|
|
435
|
+
|
|
440
436
|
### Database Context
|
|
441
437
|
|
|
442
438
|
Inject a database instance directly into the handler context using `.database()`:
|
|
@@ -633,10 +629,11 @@ For complex scenarios, use `ctx.auditor` to record audits manually within your h
|
|
|
633
629
|
|
|
634
630
|
```typescript
|
|
635
631
|
export const processOrder = api
|
|
636
|
-
.
|
|
637
|
-
.database(databaseService)
|
|
638
|
-
.services([paymentService])
|
|
632
|
+
.database(database)
|
|
639
633
|
.auditor(auditStorageService)
|
|
634
|
+
.post('/orders')
|
|
635
|
+
// `payments` is an ExternalApi whose client has `charge()`
|
|
636
|
+
.dependsOn([payments])
|
|
640
637
|
.actor(({ session }) => ({ id: session.userId, type: 'user' }))
|
|
641
638
|
.handle(async ({ body, db, services, auditor }) => {
|
|
642
639
|
// db is automatically the transaction when auditor uses KyselyAuditStorage
|
|
@@ -647,7 +644,7 @@ export const processOrder = api
|
|
|
647
644
|
.executeTakeFirstOrThrow();
|
|
648
645
|
|
|
649
646
|
// Manual audit for payment (external service call)
|
|
650
|
-
const payment = await services.
|
|
647
|
+
const payment = await services.payments.charge(order.total);
|
|
651
648
|
auditor.audit('payment.processed', {
|
|
652
649
|
orderId: order.id,
|
|
653
650
|
amount: order.total,
|
|
@@ -1097,19 +1094,19 @@ Every construct has an async `getEnvironment()` method that returns the environm
|
|
|
1097
1094
|
|
|
1098
1095
|
```typescript
|
|
1099
1096
|
import { api } from '../constructs/api';
|
|
1100
|
-
import {
|
|
1101
|
-
import {
|
|
1097
|
+
import { cache } from '../constructs/cache';
|
|
1098
|
+
import { database } from '../constructs/database';
|
|
1102
1099
|
|
|
1103
1100
|
const endpoint = api
|
|
1104
1101
|
.get('/users')
|
|
1105
|
-
.
|
|
1102
|
+
.dependsOn([database, cache])
|
|
1106
1103
|
.handle(async ({ services }) => {
|
|
1107
1104
|
// Implementation
|
|
1108
1105
|
});
|
|
1109
1106
|
|
|
1110
1107
|
// Detect required environment variables
|
|
1111
1108
|
const envVars = await endpoint.getEnvironment();
|
|
1112
|
-
// Returns: ['
|
|
1109
|
+
// Returns: ['DATABASE_URL', 'SESSIONS_URL'] (sorted alphabetically)
|
|
1113
1110
|
```
|
|
1114
1111
|
|
|
1115
1112
|
### How It Works
|
|
@@ -1197,33 +1194,37 @@ This manifest can then be used by infrastructure-as-code tools (Terraform, CDK,
|
|
|
1197
1194
|
- **Deduplication**: Each variable listed once even if used by multiple services
|
|
1198
1195
|
- **Sorted Output**: Variables always returned in alphabetical order
|
|
1199
1196
|
- **Error Resilient**: Parse failures don't affect detection
|
|
1200
|
-
- **
|
|
1197
|
+
- **Topic Support**: A topic named by `.event(topic, …)` contributes its `<ID>_PUBLISHER_CONNECTION_STRING`
|
|
1201
1198
|
|
|
1202
1199
|
### Example with Multiple Services
|
|
1203
1200
|
|
|
1204
1201
|
```typescript
|
|
1205
1202
|
import { api } from '../constructs/api';
|
|
1206
|
-
import {
|
|
1207
|
-
import {
|
|
1208
|
-
import {
|
|
1203
|
+
import { cache } from '../constructs/cache';
|
|
1204
|
+
import { database } from '../constructs/database';
|
|
1205
|
+
import { mail } from '../constructs/email';
|
|
1206
|
+
import { users } from '../constructs/topics';
|
|
1209
1207
|
|
|
1210
1208
|
const endpoint = api
|
|
1211
1209
|
.post('/users')
|
|
1212
|
-
.
|
|
1210
|
+
.dependsOn([database, cache, mail])
|
|
1211
|
+
.output(userSchema)
|
|
1212
|
+
.event(users, {
|
|
1213
|
+
type: 'user.created',
|
|
1214
|
+
payload: (user) => ({ userId: user.id, email: user.email }),
|
|
1215
|
+
})
|
|
1213
1216
|
.handle(async ({ services }) => {
|
|
1214
1217
|
// Create user
|
|
1215
1218
|
});
|
|
1216
1219
|
|
|
1217
|
-
//
|
|
1220
|
+
// Detects the keys of every construct it names
|
|
1218
1221
|
const envVars = await endpoint.getEnvironment();
|
|
1219
1222
|
// Returns: [
|
|
1220
|
-
// 'CACHE_URL',
|
|
1221
1223
|
// 'DATABASE_URL',
|
|
1222
|
-
// '
|
|
1223
|
-
// '
|
|
1224
|
-
// '
|
|
1225
|
-
// '
|
|
1226
|
-
// 'SMTP_PASS'
|
|
1224
|
+
// 'MAIL_FROM',
|
|
1225
|
+
// 'MAIL_URL',
|
|
1226
|
+
// 'SESSIONS_URL',
|
|
1227
|
+
// 'USERS_PUBLISHER_CONNECTION_STRING'
|
|
1227
1228
|
// ]
|
|
1228
1229
|
```
|
|
1229
1230
|
|
|
@@ -88,7 +88,6 @@ var Construct = class {
|
|
|
88
88
|
logger;
|
|
89
89
|
services;
|
|
90
90
|
events;
|
|
91
|
-
publisherService;
|
|
92
91
|
outputSchema;
|
|
93
92
|
timeout;
|
|
94
93
|
memorySize;
|
|
@@ -109,12 +108,11 @@ var Construct = class {
|
|
|
109
108
|
* still how `c`, `s` and `f` work.
|
|
110
109
|
*/
|
|
111
110
|
owner;
|
|
112
|
-
constructor(type, logger, services, events = [],
|
|
111
|
+
constructor(type, logger, services, events = [], outputSchema, timeout, memorySize, auditorStorageService, constructs = []) {
|
|
113
112
|
this.type = type;
|
|
114
113
|
this.logger = logger;
|
|
115
114
|
this.services = services;
|
|
116
115
|
this.events = events;
|
|
117
|
-
this.publisherService = publisherService;
|
|
118
116
|
this.outputSchema = outputSchema;
|
|
119
117
|
this.timeout = timeout;
|
|
120
118
|
this.memorySize = memorySize;
|
|
@@ -150,8 +148,7 @@ var Construct = class {
|
|
|
150
148
|
const optionalVars = /* @__PURE__ */ new Set();
|
|
151
149
|
const services = (0, import_lodash_compact.default)([
|
|
152
150
|
...this.services,
|
|
153
|
-
this.
|
|
154
|
-
this.publisherService,
|
|
151
|
+
...this.events.map(({ topic }) => topic),
|
|
155
152
|
this.auditorStorageService,
|
|
156
153
|
this.databaseService
|
|
157
154
|
]);
|
|
@@ -221,4 +218,4 @@ async function sniffService(service) {
|
|
|
221
218
|
//#endregion
|
|
222
219
|
export { __commonJSMin as a, snifferContext as i, ConstructType as n, __toESM as o, sniffService as r, Construct as t };
|
|
223
220
|
|
|
224
|
-
//# sourceMappingURL=Construct-
|
|
221
|
+
//# sourceMappingURL=Construct-BO1X-kt9.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Construct-BO1X-kt9.mjs","names":["compact"],"sources":["../../../node_modules/.pnpm/lodash.compact@3.0.1/node_modules/lodash.compact/index.js","../src/Construct.ts"],"sourcesContent":["/**\n * lodash 3.0.1 (Custom Build) <https://lodash.com/>\n * Build: `lodash modularize exports=\"npm\" -o ./`\n * Copyright 2012-2016 The Dojo Foundation <http://dojofoundation.org/>\n * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>\n * Copyright 2009-2016 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors\n * Available under MIT license <https://lodash.com/license>\n */\n\n/**\n * Creates an array with all falsey values removed. The values `false`, `null`,\n * `0`, `\"\"`, `undefined`, and `NaN` are falsey.\n *\n * @static\n * @memberOf _\n * @category Array\n * @param {Array} array The array to compact.\n * @returns {Array} Returns the new array of filtered values.\n * @example\n *\n * _.compact([0, 1, false, 2, '', 3]);\n * // => [1, 2, 3]\n */\nfunction compact(array) {\n var index = -1,\n length = array ? array.length : 0,\n resIndex = 0,\n result = [];\n\n while (++index < length) {\n var value = array[index];\n if (value) {\n result[resIndex++] = value;\n }\n }\n return result;\n}\n\nmodule.exports = compact;\n","import type { AuditStorage } from '@geekmidas/audit';\nimport {\n\tSnifferEnvironmentParser,\n\ttype SniffResult,\n\tsniffWithFireAndForget,\n} from '@geekmidas/envkit/sniffer';\nimport type { Logger } from '@geekmidas/logger';\nimport type { Service, ServiceContext } from '@geekmidas/services';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\nimport compact from 'lodash.compact';\nimport type { TopicEvent } from './publisher';\n\n// Cache for service environment variables to handle singleton services\n// Stores both the full list and the optional subset so the same sniff result\n// can satisfy both getEnvironment() and getEnvironment({ markOptional: true }).\nconst serviceEnvCache = new Map<\n\tService,\n\t{ envVars: string[]; optionalEnvVars: string[] }\n>();\n\n/**\n * Noop context for environment sniffing.\n * Used when calling service.register() to detect env vars without a real request.\n * Returns dummy values since services should only use context in instance methods,\n * not during registration.\n */\nconst snifferContext: ServiceContext = {\n\tgetLogger() {\n\t\t// Return a noop logger for sniffing - services shouldn't log during registration\n\t\treturn {\n\t\t\tdebug: () => {},\n\t\t\tinfo: () => {},\n\t\t\twarn: () => {},\n\t\t\terror: () => {},\n\t\t\tchild: () => snifferContext.getLogger(),\n\t\t} as unknown as Logger;\n\t},\n\tgetRequestId() {\n\t\treturn 'sniffer-context';\n\t},\n\tgetRequestStartTime() {\n\t\treturn Date.now();\n\t},\n\thasContext() {\n\t\treturn false;\n\t},\n};\n\nexport abstract class Construct<\n\tTLogger extends Logger = Logger,\n\tOutSchema extends StandardSchemaV1 | undefined = undefined,\n\tTServices extends Service[] = [],\n\tTAuditStorageServiceName extends string = string,\n\tTAuditStorage extends AuditStorage | undefined = undefined,\n\tTDatabaseServiceName extends string = string,\n\tTDatabase = unknown,\n> {\n\t/** Database service for this construct (used by Endpoint for RLS/audit context) */\n\tpublic databaseService?: Service<TDatabaseServiceName, TDatabase>;\n\n\t/**\n\t * The deploy unit that runs this, by construct id.\n\t *\n\t * What an endpoint's `surface` is to it, and for the same reason: something\n\t * has to say which process a runnable belongs to, and until this existed the\n\t * only thing that said so was the directory its file sat in. Set by the\n\t * factory it was built from — `worker.crons`, `api.get(…)` — so it is\n\t * carried rather than configured.\n\t *\n\t * Undefined for a construct built from a free-standing builder, which is\n\t * still how `c`, `s` and `f` work.\n\t */\n\tpublic owner?: string;\n\n\tconstructor(\n\t\tpublic readonly type: ConstructType,\n\t\tpublic readonly logger: TLogger,\n\t\tpublic readonly services: TServices,\n\t\t/**\n\t\t * What this construct publishes once its handler succeeds — each event\n\t\t * bound to its own topic, so a construct publishes to as many as it names.\n\t\t */\n\t\tpublic readonly events: TopicEvent[] = [],\n\t\tpublic outputSchema?: OutSchema,\n\t\tpublic readonly timeout?: number,\n\t\tpublic readonly memorySize?: number,\n\t\tpublic readonly auditorStorageService?: Service<\n\t\t\tTAuditStorageServiceName,\n\t\t\tTAuditStorage\n\t\t>,\n\t\t/**\n\t\t * The constructs this one declared an edge to, by id.\n\t\t *\n\t\t * What the manifest records. `services` is the same information in the\n\t\t * form a handler runs with — clients, keyed by name — and an id cannot be\n\t\t * recovered from one of those reliably, so both are kept.\n\t\t *\n\t\t * Last in the list because every other parameter is positional and already\n\t\t * spoken for; a new one anywhere else silently rebinds existing callers.\n\t\t */\n\t\tpublic readonly constructs: string[] = [],\n\t) {}\n\n\t/**\n\t * Returns an array of environment variable names required by this construct's services.\n\t * This is determined by running a \"sniffer\" EnvironmentParser through each service's\n\t * register method to track which environment variables are accessed.\n\t *\n\t * Results are cached per service class to handle singleton patterns where\n\t * subsequent register() calls may short-circuit and not access env vars.\n\t *\n\t * @param markOptional - When true, optional vars (those accessed via `.optional()` or\n\t * `.default()`) are suffixed with `?` in the returned array (e.g. `'PORT?'`).\n\t * @returns Promise that resolves to array of environment variable names, sorted alphabetically\n\t *\n\t * @example\n\t * ```typescript\n\t * const endpoint = e\n\t * .services([databaseService, authService])\n\t * .get('/users')\n\t * .handle(async () => []);\n\t *\n\t * await endpoint.getEnvironment(); // ['AUTH_SECRET', 'DATABASE_URL']\n\t * await endpoint.getEnvironment({ markOptional: true }); // ['AUTH_SECRET', 'DATABASE_URL', 'PORT?']\n\t * ```\n\t */\n\tasync getEnvironment(\n\t\topts: { markOptional?: boolean } = {},\n\t): Promise<string[]> {\n\t\tconst { markOptional = false } = opts;\n\t\tconst envVars = new Set<string>();\n\t\tconst optionalVars = new Set<string>();\n\t\tconst services: Service[] = compact([\n\t\t\t...this.services,\n\t\t\t// Each topic an event names is a dependency like any other.\n\t\t\t...this.events.map(({ topic }) => topic),\n\t\t\tthis.auditorStorageService,\n\t\t\tthis.databaseService,\n\t\t]);\n\n\t\ttry {\n\t\t\tfor (const service of services) {\n\t\t\t\t// Check cache first - handles singleton services that short-circuit\n\t\t\t\tif (serviceEnvCache.has(service)) {\n\t\t\t\t\tconst cached = serviceEnvCache.get(service)!;\n\t\t\t\t\tcached.envVars.forEach((v) => envVars.add(v));\n\t\t\t\t\tcached.optionalEnvVars.forEach((v) => optionalVars.add(v));\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Use sniffService to properly handle all error scenarios\n\t\t\t\tconst result = await sniffService(service);\n\n\t\t\t\t// Log any issues for debugging\n\t\t\t\tif (result.error) {\n\t\t\t\t\tthis.logger.warn(\n\t\t\t\t\t\t{ error: result.error.message, service: service.serviceName },\n\t\t\t\t\t\t'Service threw error during env sniffing (env vars still captured)',\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (result.unhandledRejections.length > 0) {\n\t\t\t\t\tthis.logger.warn(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\terrors: result.unhandledRejections.map((e) => e.message),\n\t\t\t\t\t\t\tservice: service.serviceName,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t'Fire-and-forget rejections during env sniffing (suppressed)',\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// Cache and collect env vars (both required and optional)\n\t\t\t\tserviceEnvCache.set(service, {\n\t\t\t\t\tenvVars: result.envVars,\n\t\t\t\t\toptionalEnvVars: result.optionalEnvVars,\n\t\t\t\t});\n\t\t\t\tresult.envVars.forEach((v) => envVars.add(v));\n\t\t\t\tresult.optionalEnvVars.forEach((v) => optionalVars.add(v));\n\t\t\t}\n\n\t\t\tconst sorted = Array.from(envVars).sort();\n\t\t\tif (!markOptional || optionalVars.size === 0) return sorted;\n\t\t\treturn sorted.map((v) => (optionalVars.has(v) ? `${v}?` : v));\n\t\t} catch (error) {\n\t\t\tthis.logger.error(\n\t\t\t\t{ error },\n\t\t\t\t'Error determining environment variables for construct',\n\t\t\t);\n\n\t\t\treturn [];\n\t\t}\n\t}\n}\n\nexport enum ConstructType {\n\tCron = 'dev.geekmidas.function.cron',\n\tEndpoint = 'dev.geekmidas.function.endpoint',\n\tFunction = 'dev.geekmidas.function.function',\n\tSubscriber = 'dev.geekmidas.function.subscriber',\n\tQueue = 'dev.geekmidas.function.queue',\n\tTopic = 'dev.geekmidas.resource.topic',\n}\n\n/**\n * Utility to test sniffing a service for environment variables.\n * Useful for debugging services that throw errors during sniffing.\n *\n * @example\n * ```typescript\n * import { sniffService } from '@geekmidas/constructs';\n * import { authService } from './services/AuthService';\n *\n * const result = await sniffService(authService);\n * console.log('Env vars:', result.envVars);\n * console.log('Error:', result.error);\n * ```\n */\nexport async function sniffService(service: Service): Promise<SniffResult> {\n\tconst sniffer = new SnifferEnvironmentParser();\n\n\treturn sniffWithFireAndForget(sniffer, () =>\n\t\tservice.register({\n\t\t\tenvParser: sniffer as any,\n\t\t\tcontext: snifferContext,\n\t\t}),\n\t);\n}\n\n// Export for testing\nexport { snifferContext };\n"],"x_google_ignoreList":[0],"mappings":"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"}
|