@geekmidas/constructs 10.0.0-alpha.20 → 10.0.0-alpha.22

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 (68) hide show
  1. package/README.md +48 -0
  2. package/dist/{Endpoint-BxkRWQ8G.d.mts → Endpoint-Bmi2-PBN.d.mts} +2 -2
  3. package/dist/{Endpoint-BxkRWQ8G.d.mts.map → Endpoint-Bmi2-PBN.d.mts.map} +1 -1
  4. package/dist/{Endpoint-DbYQJgb-.d.cts → Endpoint-CjvraSfp.d.cts} +2 -2
  5. package/dist/{Endpoint-DbYQJgb-.d.cts.map → Endpoint-CjvraSfp.d.cts.map} +1 -1
  6. package/dist/{EndpointFactory-T06kTFHg.d.mts → EndpointFactory-BFfvgko6.d.mts} +2 -2
  7. package/dist/{EndpointFactory-DE6nKtFZ.d.cts.map → EndpointFactory-BFfvgko6.d.mts.map} +1 -1
  8. package/dist/{EndpointFactory-DE6nKtFZ.d.cts → EndpointFactory-vfC3NiJi.d.cts} +2 -2
  9. package/dist/{EndpointFactory-T06kTFHg.d.mts.map → EndpointFactory-vfC3NiJi.d.cts.map} +1 -1
  10. package/dist/Queue-BB2-DgqJ.cjs +123 -0
  11. package/dist/Queue-BB2-DgqJ.cjs.map +1 -0
  12. package/dist/Queue-Dfumm1WR.mjs +118 -0
  13. package/dist/Queue-Dfumm1WR.mjs.map +1 -0
  14. package/dist/Topic-BaNCgP61.cjs +107 -0
  15. package/dist/Topic-BaNCgP61.cjs.map +1 -0
  16. package/dist/Topic-QqnO_6Dl.mjs +102 -0
  17. package/dist/Topic-QqnO_6Dl.mjs.map +1 -0
  18. package/dist/adaptors/aws.d.cts +1 -1
  19. package/dist/adaptors/aws.d.mts +1 -1
  20. package/dist/adaptors/hono.d.cts +1 -1
  21. package/dist/adaptors/hono.d.mts +1 -1
  22. package/dist/adaptors/testing.cjs +315 -9
  23. package/dist/adaptors/testing.cjs.map +1 -1
  24. package/dist/adaptors/testing.d.cts +129 -8
  25. package/dist/adaptors/testing.d.cts.map +1 -1
  26. package/dist/adaptors/testing.d.mts +129 -8
  27. package/dist/adaptors/testing.d.mts.map +1 -1
  28. package/dist/adaptors/testing.mjs +307 -8
  29. package/dist/adaptors/testing.mjs.map +1 -1
  30. package/dist/{auth-BlO8MUkR.d.cts → auth-C_fHD1We.d.cts} +9 -1
  31. package/dist/{auth-BlO8MUkR.d.cts.map → auth-C_fHD1We.d.cts.map} +1 -1
  32. package/dist/{auth-CHWIdcQO.d.mts → auth-D_Ntp1Nj.d.mts} +9 -1
  33. package/dist/{auth-CHWIdcQO.d.mts.map → auth-D_Ntp1Nj.d.mts.map} +1 -1
  34. package/dist/auth.cjs +15 -2
  35. package/dist/auth.cjs.map +1 -1
  36. package/dist/auth.d.cts +1 -1
  37. package/dist/auth.d.mts +1 -1
  38. package/dist/auth.mjs +15 -2
  39. package/dist/auth.mjs.map +1 -1
  40. package/dist/cache-AgQUMma6.d.mts +44 -0
  41. package/dist/cache-AgQUMma6.d.mts.map +1 -0
  42. package/dist/cache-obq0fOIM.d.cts +44 -0
  43. package/dist/cache-obq0fOIM.d.cts.map +1 -0
  44. package/dist/cache.d.cts +2 -43
  45. package/dist/cache.d.mts +2 -43
  46. package/dist/database/kysely.d.cts +2 -149
  47. package/dist/database/kysely.d.mts +2 -149
  48. package/dist/endpoints/index.d.cts +2 -2
  49. package/dist/endpoints/index.d.mts +2 -2
  50. package/dist/kysely-CXi49zc2.d.cts +150 -0
  51. package/dist/kysely-CXi49zc2.d.cts.map +1 -0
  52. package/dist/kysely-D3-G5b0j.d.mts +150 -0
  53. package/dist/kysely-D3-G5b0j.d.mts.map +1 -0
  54. package/dist/queue/index.cjs +3 -116
  55. package/dist/queue/index.cjs.map +1 -1
  56. package/dist/queue/index.mjs +1 -114
  57. package/dist/queue/index.mjs.map +1 -1
  58. package/dist/rest-api.d.cts +2 -2
  59. package/dist/rest-api.d.mts +2 -2
  60. package/dist/topic/index.cjs +3 -100
  61. package/dist/topic/index.cjs.map +1 -1
  62. package/dist/topic/index.mjs +1 -98
  63. package/dist/topic/index.mjs.map +1 -1
  64. package/package.json +44 -40
  65. package/dist/cache.d.cts.map +0 -1
  66. package/dist/cache.d.mts.map +0 -1
  67. package/dist/database/kysely.d.cts.map +0 -1
  68. package/dist/database/kysely.d.mts.map +0 -1
@@ -1,121 +1,8 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_Construct = require("../Construct-yG9Sw-5p.cjs");
3
2
  const require_construct_interface = require("../construct-interface.cjs");
4
3
  const require_clone = require("../clone-UGJpvQnL.cjs");
5
- let _geekmidas_manifest = require("@geekmidas/manifest");
4
+ const require_Queue = require("../Queue-BB2-DgqJ.cjs");
6
5
  let _geekmidas_logger_console = require("@geekmidas/logger/console");
7
- let _geekmidas_events = require("@geekmidas/events");
8
- //#region src/queue/Queue.ts
9
- /**
10
- * A queue worker — a point-to-point SQS-style queue and its single consumer.
11
- * Unlike a `Subscriber` (topic fan-out, filtered by `subscribedEvents`), a queue
12
- * drains every message of its one `message` type. Build one with `q` (see
13
- * `QueueBuilder`); `gkm build` discovers it into the manifest's `queues` field.
14
- *
15
- * The producer side is {@link Queue.publisher} — a ready-to-inject `Service`
16
- * that any endpoint/function drops into `.services([...])` to send messages.
17
- */
18
- var Queue = class extends require_Construct.Construct {
19
- name;
20
- handler;
21
- messageSchema;
22
- timeout;
23
- services;
24
- logger;
25
- batchSize;
26
- fifo;
27
- __IS_QUEUE__ = true;
28
- static isQueue(obj) {
29
- return Boolean(obj && obj.__IS_QUEUE__ === true && obj.type === "dev.geekmidas.function.queue");
30
- }
31
- /**
32
- * The canonical id this queue is declared under.
33
- *
34
- * Derived from the name rather than given separately, so `emails` and
35
- * `Emails` are one queue. The name stays what it was: it is the wire `type`
36
- * a producer sends and the worker subscribes to, and changing that would
37
- * silently orphan in-flight messages.
38
- */
39
- id;
40
- /**
41
- * The producer's env key. Read by both {@link declare} and
42
- * {@link publisher}, so what the target publishes and what the producer
43
- * looks up cannot drift.
44
- */
45
- connectionKey;
46
- constructor(name, handler, messageSchema, timeout = 3e4, services = [], logger = _geekmidas_logger_console.DEFAULT_LOGGER, batchSize, fifo, constructs = []) {
47
- super("dev.geekmidas.function.queue", logger, services, [], void 0, void 0, timeout, void 0, void 0, constructs);
48
- this.name = name;
49
- this.handler = handler;
50
- this.messageSchema = messageSchema;
51
- this.timeout = timeout;
52
- this.services = services;
53
- this.logger = logger;
54
- this.batchSize = batchSize;
55
- this.fifo = fifo;
56
- this.id = (0, _geekmidas_manifest.canonicalId)(name);
57
- this.connectionKey = (0, _geekmidas_manifest.provideKey)(this.id, "publisherConnectionString");
58
- }
59
- /**
60
- * What this queue is in the manifest.
61
- *
62
- * A queue is a resource, not a handler: the worker is reached *through* it,
63
- * so the declaration carries the producer's key and nothing about the code
64
- * that drains it. The local target reads this to know which broker has to be
65
- * running and what connection string to inject.
66
- */
67
- declare() {
68
- return [{
69
- kind: "queue",
70
- id: this.id,
71
- provides: [this.connectionKey],
72
- ...this.fifo ? { fifo: true } : {},
73
- worker: {
74
- id: `${this.id}Worker`,
75
- handler: `${this.id}.handler`,
76
- dependencies: []
77
- }
78
- }];
79
- }
80
- /**
81
- * The producer side — a `Service` exposing an `EventPublisher` typed to this
82
- * queue's message. Inject it via `.services([queue.publisher])`; the handler
83
- * then calls `services.<name>Publisher.publish([{ type, payload }])`.
84
- *
85
- * It reads `<NAME>_PUBLISHER_CONNECTION_STRING` and builds the transport from
86
- * the URL protocol — `pgboss://` locally, `sqs://` deployed — so the same
87
- * code publishes to Postgres in dev and SQS in prod. Because it's a `Service`,
88
- * `Construct.getEnvironment()` sniffs it, so the env requirement flows into
89
- * the manifest and infra links exactly this queue with least privilege.
90
- */
91
- /**
92
- * The queue as a dependency — what `.dependsOn([emailsQueue])` dissolves
93
- * into, reachable as `services.emails`.
94
- *
95
- * It is the producer: a consumer is the worker written right here, so the
96
- * only thing another construct can want from a queue is the ability to send
97
- * to it. {@link publisher} is the same service under its older
98
- * `<name>Publisher` key, kept for `.services([queue.publisher])`.
99
- */
100
- get service() {
101
- const { register } = this.publisher;
102
- return {
103
- serviceName: (0, _geekmidas_manifest.serviceKey)(this.id),
104
- register
105
- };
106
- }
107
- get publisher() {
108
- const envVar = this.connectionKey;
109
- return {
110
- serviceName: `${this.name}Publisher`,
111
- async register({ envParser }) {
112
- const { connectionString } = envParser.create((get) => ({ connectionString: get(envVar).string() })).parse();
113
- return _geekmidas_events.Publisher.fromConnectionString(connectionString);
114
- }
115
- };
116
- }
117
- };
118
- //#endregion
119
6
  //#region src/queue/QueueBuilder.ts
120
7
  /**
121
8
  * Builds a {@link Queue} worker — `q.queue('orders').services([db]).message(schema).handle(fn)`.
@@ -179,14 +66,14 @@ var QueueBuilder = class {
179
66
  handle(fn) {
180
67
  if (!this._name) throw new Error("Queue requires a name — call .queue(name) before .handle().");
181
68
  if (!this._messageSchema) throw new Error("Queue requires a message schema — call .message(schema) before .handle().");
182
- return new Queue(this._name, fn, this._messageSchema, this._timeout, this._services, this._logger, this._batchSize, this._fifo, this._constructs);
69
+ return new require_Queue.Queue(this._name, fn, this._messageSchema, this._timeout, this._services, this._logger, this._batchSize, this._fifo, this._constructs);
183
70
  }
184
71
  };
185
72
  //#endregion
186
73
  //#region src/queue/index.ts
187
74
  const q = new QueueBuilder();
188
75
  //#endregion
189
- exports.Queue = Queue;
76
+ exports.Queue = require_Queue.Queue;
190
77
  exports.QueueBuilder = QueueBuilder;
191
78
  exports.q = q;
192
79
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["Construct","DEFAULT_LOGGER","canonicalId","provideKey","serviceKey","Publisher","DEFAULT_LOGGER","cloneWith","servicesOf","idsOf"],"sources":["../../src/queue/Queue.ts","../../src/queue/QueueBuilder.ts","../../src/queue/index.ts"],"sourcesContent":["import {\n\ttype EventPublisher,\n\ttype EventPublisherConnectionString,\n\ttype PublishableMessage,\n\tPublisher,\n} from '@geekmidas/events';\nimport type { Logger } from '@geekmidas/logger';\nimport { DEFAULT_LOGGER } from '@geekmidas/logger/console';\nimport {\n\tcanonicalId,\n\ttype Declaration,\n\tprovideKey,\n\tserviceKey,\n} from '@geekmidas/manifest';\nimport type { InferStandardSchema } from '@geekmidas/schema';\nimport type { Service, ServiceRecord } from '@geekmidas/services';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\nimport { Construct, ConstructType } from '../Construct';\n\n/**\n * The wire message a queue carries: `{ type: <queue name>, payload: <message> }`.\n * The producer publishes this shape and the worker receives the `payload`s as\n * its `messages`. Typing `type` to the queue name keeps the publisher fully\n * typed and lets pg-boss (local) / SQS (deployed) route by name.\n */\nexport type QueueMessage<\n\tTName extends string,\n\tTMessage extends StandardSchemaV1,\n> = PublishableMessage<TName, InferStandardSchema<TMessage>>;\n\n/**\n * A queue worker — a point-to-point SQS-style queue and its single consumer.\n * Unlike a `Subscriber` (topic fan-out, filtered by `subscribedEvents`), a queue\n * drains every message of its one `message` type. Build one with `q` (see\n * `QueueBuilder`); `gkm build` discovers it into the manifest's `queues` field.\n *\n * The producer side is {@link Queue.publisher} — a ready-to-inject `Service`\n * that any endpoint/function drops into `.services([...])` to send messages.\n */\nexport class Queue<\n\tTName extends string = string,\n\tTMessage extends StandardSchemaV1 = StandardSchemaV1,\n\tTServices extends Service[] = [],\n\tTLogger extends Logger = Logger,\n> extends Construct<TLogger, string, undefined, undefined, TServices> {\n\t__IS_QUEUE__ = true;\n\n\tstatic isQueue(\n\t\tobj: unknown,\n\t): obj is Queue<string, StandardSchemaV1, Service[], Logger> {\n\t\treturn Boolean(\n\t\t\tobj &&\n\t\t\t\t(obj as { __IS_QUEUE__?: boolean }).__IS_QUEUE__ === true &&\n\t\t\t\t(obj as Construct).type === ConstructType.Queue,\n\t\t);\n\t}\n\n\t/**\n\t * The canonical id this queue is declared under.\n\t *\n\t * Derived from the name rather than given separately, so `emails` and\n\t * `Emails` are one queue. The name stays what it was: it is the wire `type`\n\t * a producer sends and the worker subscribes to, and changing that would\n\t * silently orphan in-flight messages.\n\t */\n\treadonly id: string;\n\n\t/**\n\t * The producer's env key. Read by both {@link declare} and\n\t * {@link publisher}, so what the target publishes and what the producer\n\t * looks up cannot drift.\n\t */\n\tprivate readonly connectionKey: string;\n\n\tconstructor(\n\t\tpublic readonly name: TName,\n\t\tpublic readonly handler: QueueHandler<TMessage, TServices, TLogger>,\n\t\tpublic readonly messageSchema: TMessage,\n\t\tpublic override readonly timeout: number = 30000,\n\t\tpublic override readonly services: TServices = [] as unknown as TServices,\n\t\tpublic override readonly logger: TLogger = DEFAULT_LOGGER as TLogger,\n\t\t/** SQS event-source batch size (deployed). */\n\t\tpublic readonly batchSize?: number,\n\t\t/** Whether the queue is FIFO (deployed). */\n\t\tpublic readonly fifo?: boolean,\n\t\t/**\n\t\t * The construct ids the *worker* `.dependsOn()` named — last, so no\n\t\t * existing positional argument moves.\n\t\t */\n\t\tconstructs: string[] = [],\n\t) {\n\t\tsuper(\n\t\t\tConstructType.Queue,\n\t\t\tlogger,\n\t\t\tservices,\n\t\t\t[],\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\ttimeout,\n\t\t\tundefined, // memorySize\n\t\t\tundefined, // auditorStorageService\n\t\t\tconstructs,\n\t\t);\n\n\t\tthis.id = canonicalId(name);\n\t\tthis.connectionKey = provideKey(this.id, 'publisherConnectionString');\n\t}\n\n\t/**\n\t * What this queue is in the manifest.\n\t *\n\t * A queue is a resource, not a handler: the worker is reached *through* it,\n\t * so the declaration carries the producer's key and nothing about the code\n\t * that drains it. The local target reads this to know which broker has to be\n\t * running and what connection string to inject.\n\t */\n\tdeclare(): Declaration[] {\n\t\treturn [\n\t\t\t{\n\t\t\t\tkind: 'queue',\n\t\t\t\tid: this.id,\n\t\t\t\tprovides: [this.connectionKey],\n\t\t\t\t...(this.fifo ? { fifo: true } : {}),\n\t\t\t\t// Nested rather than a sibling node, because position carries the\n\t\t\t\t// trigger: this handler is reached by messages on this queue and\n\t\t\t\t// by nothing else, so there is no `trigger` field to keep in step.\n\t\t\t\t//\n\t\t\t\t// `handler` is the export name; which directory the build wrote it\n\t\t\t\t// to is the target's business, and a path with a cloud in it does\n\t\t\t\t// not belong in a neutral declaration.\n\t\t\t\tworker: {\n\t\t\t\t\tid: `${this.id}Worker`,\n\t\t\t\t\thandler: `${this.id}.handler`,\n\t\t\t\t\t// Filled by the build from what the worker declared. Empty is a\n\t\t\t\t\t// stated gap, not a claim that it reaches nothing.\n\t\t\t\t\tdependencies: [],\n\t\t\t\t},\n\t\t\t},\n\t\t];\n\t}\n\n\t/**\n\t * The producer side — a `Service` exposing an `EventPublisher` typed to this\n\t * queue's message. Inject it via `.services([queue.publisher])`; the handler\n\t * then calls `services.<name>Publisher.publish([{ type, payload }])`.\n\t *\n\t * It reads `<NAME>_PUBLISHER_CONNECTION_STRING` and builds the transport from\n\t * the URL protocol — `pgboss://` locally, `sqs://` deployed — so the same\n\t * code publishes to Postgres in dev and SQS in prod. Because it's a `Service`,\n\t * `Construct.getEnvironment()` sniffs it, so the env requirement flows into\n\t * the manifest and infra links exactly this queue with least privilege.\n\t */\n\t/**\n\t * The queue as a dependency — what `.dependsOn([emailsQueue])` dissolves\n\t * into, reachable as `services.emails`.\n\t *\n\t * It is the producer: a consumer is the worker written right here, so the\n\t * only thing another construct can want from a queue is the ability to send\n\t * to it. {@link publisher} is the same service under its older\n\t * `<name>Publisher` key, kept for `.services([queue.publisher])`.\n\t */\n\tget service(): Service<\n\t\tUncapitalize<TName>,\n\t\tEventPublisher<QueueMessage<TName, TMessage>>\n\t> {\n\t\tconst { register } = this.publisher;\n\n\t\treturn {\n\t\t\tserviceName: serviceKey(this.id) as Uncapitalize<TName>,\n\t\t\tregister,\n\t\t};\n\t}\n\n\tget publisher(): Service<\n\t\t`${TName}Publisher`,\n\t\tEventPublisher<QueueMessage<TName, TMessage>>\n\t> {\n\t\tconst envVar = this.connectionKey;\n\t\treturn {\n\t\t\tserviceName: `${this.name}Publisher`,\n\t\t\tasync register({ envParser }) {\n\t\t\t\tconst { connectionString } = envParser\n\t\t\t\t\t.create((get) => ({\n\t\t\t\t\t\tconnectionString: get(envVar).string(),\n\t\t\t\t\t}))\n\t\t\t\t\t.parse();\n\n\t\t\t\treturn Publisher.fromConnectionString<QueueMessage<TName, TMessage>>(\n\t\t\t\t\tconnectionString as EventPublisherConnectionString,\n\t\t\t\t);\n\t\t\t},\n\t\t};\n\t}\n}\n\n/** The context a queue handler receives — a batch of typed messages. */\nexport type QueueContext<\n\tTMessage extends StandardSchemaV1,\n\tTServices extends Service[] = [],\n\tTLogger extends Logger = Logger,\n> = {\n\tmessages: InferStandardSchema<TMessage>[];\n\tservices: ServiceRecord<TServices>;\n\tlogger: TLogger;\n};\n\nexport type QueueHandler<\n\tTMessage extends StandardSchemaV1,\n\tTServices extends Service[] = [],\n\tTLogger extends Logger = Logger,\n> = (\n\tctx: QueueContext<TMessage, TServices, TLogger>,\n) => unknown | Promise<unknown>;\n","import type { Logger } from '@geekmidas/logger';\nimport { DEFAULT_LOGGER } from '@geekmidas/logger/console';\nimport type { Service } from '@geekmidas/services';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\nimport { cloneWith } from '../clone';\nimport {\n\ttype Consumable,\n\tidsOf,\n\ttype ServicesOf,\n\tservicesOf,\n} from '../construct-interface';\nimport { Queue, type QueueHandler } from './Queue';\n\n/**\n * Builds a {@link Queue} worker — `q.queue('orders').services([db]).message(schema).handle(fn)`.\n * Services are an array (sniffed for required env vars); `message` is the typed\n * job payload; `handle` is the single consumer. The queue name is captured as a\n * literal so `queue.publisher` is typed to `{ type: '<name>', payload }`.\n */\nexport class QueueBuilder<\n\tTName extends string = string,\n\tTMessage extends StandardSchemaV1 | undefined = undefined,\n\tTServices extends Service[] = [],\n\tTLogger extends Logger = Logger,\n> {\n\tprivate _name?: string;\n\tprivate _messageSchema?: TMessage;\n\tprivate _timeout = 30000;\n\tprivate _batchSize?: number;\n\tprivate _fifo?: boolean;\n\tprivate _services: TServices = [] as Service[] as TServices;\n\t/** The construct ids `.dependsOn()` named — what the manifest records. */\n\tpublic _constructs: string[] = [];\n\tprivate _logger: TLogger = DEFAULT_LOGGER as TLogger;\n\n\t/** The queue name — drives the infra queue and its `<NAME>_*` env vars. */\n\tqueue<T extends string>(\n\t\tname: T,\n\t): QueueBuilder<T, TMessage, TServices, TLogger> {\n\t\treturn cloneWith(this, { _name: name }) as unknown as QueueBuilder<\n\t\t\tT,\n\t\t\tTMessage,\n\t\t\tTServices,\n\t\t\tTLogger\n\t\t>;\n\t}\n\n\ttimeout(timeout: number): this {\n\t\treturn cloneWith(this, { _timeout: timeout });\n\t}\n\n\t/** SQS event-source batch size (deployed). */\n\tbatchSize(batchSize: number): this {\n\t\treturn cloneWith(this, { _batchSize: batchSize });\n\t}\n\n\t/** Mark the queue as FIFO (deployed). */\n\tfifo(fifo = true): this {\n\t\treturn cloneWith(this, { _fifo: fifo });\n\t}\n\n\t/**\n\t * Depend on constructs — a database, a bucket, a mail sender, a queue, a\n\t * topic.\n\t *\n\t * It records the edge and dissolves each construct's client into the\n\t * handler's service record under the construct's own id, so\n\t * `.dependsOn([uploads])` is what makes `services.uploads` exist and type.\n\t *\n\t * Constructs only. A `Service` does not match the shape, which is what keeps\n\t * env sniffing confined to `.services()` and the explicit lift.\n\t */\n\tdependsOn<const T extends readonly Consumable[]>(\n\t\tconstructs: T,\n\t): QueueBuilder<TName, TMessage, [...TServices, ...ServicesOf<T>], TLogger> {\n\t\t// Both halves of the edge, from one call and one clone: the services the\n\t\t// handler runs with, and the ids the manifest records. Recording them\n\t\t// separately is what let them drift apart.\n\t\t//\n\t\t// `servicesOf` is the half that validates, so it runs first — recording\n\t\t// ids ahead of it left a caught `NotAConstruct` with `undefined` already\n\t\t// on a `string[]`.\n\t\tconst services = servicesOf(constructs) as unknown as Service[];\n\n\t\treturn cloneWith(this, {\n\t\t\t_services: [...this._services, ...services],\n\t\t\t_constructs: idsOf(constructs, this._constructs),\n\t\t}) as unknown as QueueBuilder<\n\t\t\tTName,\n\t\t\tTMessage,\n\t\t\t[...TServices, ...ServicesOf<T>],\n\t\t\tTLogger\n\t\t>;\n\t}\n\n\tservices<T extends Service[]>(\n\t\tservices: T,\n\t): QueueBuilder<TName, TMessage, [...TServices, ...T], TLogger> {\n\t\treturn cloneWith(this, {\n\t\t\t_services: [...this._services, ...services] as unknown as TServices,\n\t\t}) as unknown as QueueBuilder<\n\t\t\tTName,\n\t\t\tTMessage,\n\t\t\t[...TServices, ...T],\n\t\t\tTLogger\n\t\t>;\n\t}\n\n\tlogger<T extends Logger>(\n\t\tlogger: T,\n\t): QueueBuilder<TName, TMessage, TServices, T> {\n\t\treturn cloneWith(this, {\n\t\t\t_logger: logger as unknown as TLogger,\n\t\t}) as unknown as QueueBuilder<TName, TMessage, TServices, T>;\n\t}\n\n\t/** The typed message (job) payload the queue carries. */\n\tmessage<T extends StandardSchemaV1>(\n\t\tschema: T,\n\t): QueueBuilder<TName, T, TServices, TLogger> {\n\t\treturn cloneWith(this, {\n\t\t\t_messageSchema: schema as unknown as TMessage,\n\t\t}) as unknown as QueueBuilder<TName, T, TServices, TLogger>;\n\t}\n\n\thandle(\n\t\tfn: QueueHandler<NonNullable<TMessage>, TServices, TLogger>,\n\t): Queue<TName, NonNullable<TMessage>, TServices, TLogger> {\n\t\tif (!this._name) {\n\t\t\tthrow new Error(\n\t\t\t\t'Queue requires a name — call .queue(name) before .handle().',\n\t\t\t);\n\t\t}\n\t\tif (!this._messageSchema) {\n\t\t\tthrow new Error(\n\t\t\t\t'Queue requires a message schema — call .message(schema) before .handle().',\n\t\t\t);\n\t\t}\n\n\t\tconst queue = new Queue<TName, NonNullable<TMessage>, TServices, TLogger>(\n\t\t\tthis._name as TName,\n\t\t\tfn,\n\t\t\tthis._messageSchema as NonNullable<TMessage>,\n\t\t\tthis._timeout,\n\t\t\tthis._services,\n\t\t\tthis._logger,\n\t\t\tthis._batchSize,\n\t\t\tthis._fifo,\n\t\t\tthis._constructs,\n\t\t);\n\n\t\t// No reset. `.handle()` reads this builder and leaves it alone, so a\n\t\t// configured base — `const fn = f.logger(log).timeout(60_000)` — keeps\n\t\t// its configuration for every construct built from it. The reset that\n\t\t// used to be here wiped the base on first use, which made a base usable\n\t\t// exactly once, and quietly reverted the logger to the default.\n\n\t\treturn queue;\n\t}\n}\n","export {\n\tQueue,\n\ttype QueueContext,\n\ttype QueueHandler,\n\ttype QueueMessage,\n} from './Queue';\n\nimport { QueueBuilder } from './QueueBuilder';\n\nexport { QueueBuilder } from './QueueBuilder';\n\nexport const q = new QueueBuilder();\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["DEFAULT_LOGGER","cloneWith","servicesOf","idsOf","Queue"],"sources":["../../src/queue/QueueBuilder.ts","../../src/queue/index.ts"],"sourcesContent":["import type { Logger } from '@geekmidas/logger';\nimport { DEFAULT_LOGGER } from '@geekmidas/logger/console';\nimport type { Service } from '@geekmidas/services';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\nimport { cloneWith } from '../clone';\nimport {\n\ttype Consumable,\n\tidsOf,\n\ttype ServicesOf,\n\tservicesOf,\n} from '../construct-interface';\nimport { Queue, type QueueHandler } from './Queue';\n\n/**\n * Builds a {@link Queue} worker — `q.queue('orders').services([db]).message(schema).handle(fn)`.\n * Services are an array (sniffed for required env vars); `message` is the typed\n * job payload; `handle` is the single consumer. The queue name is captured as a\n * literal so `queue.publisher` is typed to `{ type: '<name>', payload }`.\n */\nexport class QueueBuilder<\n\tTName extends string = string,\n\tTMessage extends StandardSchemaV1 | undefined = undefined,\n\tTServices extends Service[] = [],\n\tTLogger extends Logger = Logger,\n> {\n\tprivate _name?: string;\n\tprivate _messageSchema?: TMessage;\n\tprivate _timeout = 30000;\n\tprivate _batchSize?: number;\n\tprivate _fifo?: boolean;\n\tprivate _services: TServices = [] as Service[] as TServices;\n\t/** The construct ids `.dependsOn()` named — what the manifest records. */\n\tpublic _constructs: string[] = [];\n\tprivate _logger: TLogger = DEFAULT_LOGGER as TLogger;\n\n\t/** The queue name — drives the infra queue and its `<NAME>_*` env vars. */\n\tqueue<T extends string>(\n\t\tname: T,\n\t): QueueBuilder<T, TMessage, TServices, TLogger> {\n\t\treturn cloneWith(this, { _name: name }) as unknown as QueueBuilder<\n\t\t\tT,\n\t\t\tTMessage,\n\t\t\tTServices,\n\t\t\tTLogger\n\t\t>;\n\t}\n\n\ttimeout(timeout: number): this {\n\t\treturn cloneWith(this, { _timeout: timeout });\n\t}\n\n\t/** SQS event-source batch size (deployed). */\n\tbatchSize(batchSize: number): this {\n\t\treturn cloneWith(this, { _batchSize: batchSize });\n\t}\n\n\t/** Mark the queue as FIFO (deployed). */\n\tfifo(fifo = true): this {\n\t\treturn cloneWith(this, { _fifo: fifo });\n\t}\n\n\t/**\n\t * Depend on constructs — a database, a bucket, a mail sender, a queue, a\n\t * topic.\n\t *\n\t * It records the edge and dissolves each construct's client into the\n\t * handler's service record under the construct's own id, so\n\t * `.dependsOn([uploads])` is what makes `services.uploads` exist and type.\n\t *\n\t * Constructs only. A `Service` does not match the shape, which is what keeps\n\t * env sniffing confined to `.services()` and the explicit lift.\n\t */\n\tdependsOn<const T extends readonly Consumable[]>(\n\t\tconstructs: T,\n\t): QueueBuilder<TName, TMessage, [...TServices, ...ServicesOf<T>], TLogger> {\n\t\t// Both halves of the edge, from one call and one clone: the services the\n\t\t// handler runs with, and the ids the manifest records. Recording them\n\t\t// separately is what let them drift apart.\n\t\t//\n\t\t// `servicesOf` is the half that validates, so it runs first — recording\n\t\t// ids ahead of it left a caught `NotAConstruct` with `undefined` already\n\t\t// on a `string[]`.\n\t\tconst services = servicesOf(constructs) as unknown as Service[];\n\n\t\treturn cloneWith(this, {\n\t\t\t_services: [...this._services, ...services],\n\t\t\t_constructs: idsOf(constructs, this._constructs),\n\t\t}) as unknown as QueueBuilder<\n\t\t\tTName,\n\t\t\tTMessage,\n\t\t\t[...TServices, ...ServicesOf<T>],\n\t\t\tTLogger\n\t\t>;\n\t}\n\n\tservices<T extends Service[]>(\n\t\tservices: T,\n\t): QueueBuilder<TName, TMessage, [...TServices, ...T], TLogger> {\n\t\treturn cloneWith(this, {\n\t\t\t_services: [...this._services, ...services] as unknown as TServices,\n\t\t}) as unknown as QueueBuilder<\n\t\t\tTName,\n\t\t\tTMessage,\n\t\t\t[...TServices, ...T],\n\t\t\tTLogger\n\t\t>;\n\t}\n\n\tlogger<T extends Logger>(\n\t\tlogger: T,\n\t): QueueBuilder<TName, TMessage, TServices, T> {\n\t\treturn cloneWith(this, {\n\t\t\t_logger: logger as unknown as TLogger,\n\t\t}) as unknown as QueueBuilder<TName, TMessage, TServices, T>;\n\t}\n\n\t/** The typed message (job) payload the queue carries. */\n\tmessage<T extends StandardSchemaV1>(\n\t\tschema: T,\n\t): QueueBuilder<TName, T, TServices, TLogger> {\n\t\treturn cloneWith(this, {\n\t\t\t_messageSchema: schema as unknown as TMessage,\n\t\t}) as unknown as QueueBuilder<TName, T, TServices, TLogger>;\n\t}\n\n\thandle(\n\t\tfn: QueueHandler<NonNullable<TMessage>, TServices, TLogger>,\n\t): Queue<TName, NonNullable<TMessage>, TServices, TLogger> {\n\t\tif (!this._name) {\n\t\t\tthrow new Error(\n\t\t\t\t'Queue requires a name — call .queue(name) before .handle().',\n\t\t\t);\n\t\t}\n\t\tif (!this._messageSchema) {\n\t\t\tthrow new Error(\n\t\t\t\t'Queue requires a message schema — call .message(schema) before .handle().',\n\t\t\t);\n\t\t}\n\n\t\tconst queue = new Queue<TName, NonNullable<TMessage>, TServices, TLogger>(\n\t\t\tthis._name as TName,\n\t\t\tfn,\n\t\t\tthis._messageSchema as NonNullable<TMessage>,\n\t\t\tthis._timeout,\n\t\t\tthis._services,\n\t\t\tthis._logger,\n\t\t\tthis._batchSize,\n\t\t\tthis._fifo,\n\t\t\tthis._constructs,\n\t\t);\n\n\t\t// No reset. `.handle()` reads this builder and leaves it alone, so a\n\t\t// configured base — `const fn = f.logger(log).timeout(60_000)` — keeps\n\t\t// its configuration for every construct built from it. The reset that\n\t\t// used to be here wiped the base on first use, which made a base usable\n\t\t// exactly once, and quietly reverted the logger to the default.\n\n\t\treturn queue;\n\t}\n}\n","export {\n\tQueue,\n\ttype QueueContext,\n\ttype QueueHandler,\n\ttype QueueMessage,\n} from './Queue';\n\nimport { QueueBuilder } from './QueueBuilder';\n\nexport { QueueBuilder } from './QueueBuilder';\n\nexport const q = new QueueBuilder();\n"],"mappings":";;;;;;;;;;;;AAmBA,IAAa,eAAb,MAKE;CACD;CACA;CACA,WAAmB;CACnB;CACA;CACA,YAA+B,CAAC;;CAEhC,cAA+B,CAAC;CAChC,UAA2BA,0BAAAA;;CAG3B,MACC,MACgD;EAChD,OAAOC,cAAAA,UAAU,MAAM,EAAE,OAAO,KAAK,CAAC;CAMvC;CAEA,QAAQ,SAAuB;EAC9B,OAAOA,cAAAA,UAAU,MAAM,EAAE,UAAU,QAAQ,CAAC;CAC7C;;CAGA,UAAU,WAAyB;EAClC,OAAOA,cAAAA,UAAU,MAAM,EAAE,YAAY,UAAU,CAAC;CACjD;;CAGA,KAAK,OAAO,MAAY;EACvB,OAAOA,cAAAA,UAAU,MAAM,EAAE,OAAO,KAAK,CAAC;CACvC;;;;;;;;;;;;CAaA,UACC,YAC2E;EAQ3E,MAAM,WAAWC,4BAAAA,WAAW,UAAU;EAEtC,OAAOD,cAAAA,UAAU,MAAM;GACtB,WAAW,CAAC,GAAG,KAAK,WAAW,GAAG,QAAQ;GAC1C,aAAaE,4BAAAA,MAAM,YAAY,KAAK,WAAW;EAChD,CAAC;CAMF;CAEA,SACC,UAC+D;EAC/D,OAAOF,cAAAA,UAAU,MAAM,EACtB,WAAW,CAAC,GAAG,KAAK,WAAW,GAAG,QAAQ,EAC3C,CAAC;CAMF;CAEA,OACC,QAC8C;EAC9C,OAAOA,cAAAA,UAAU,MAAM,EACtB,SAAS,OACV,CAAC;CACF;;CAGA,QACC,QAC6C;EAC7C,OAAOA,cAAAA,UAAU,MAAM,EACtB,gBAAgB,OACjB,CAAC;CACF;CAEA,OACC,IAC0D;EAC1D,IAAI,CAAC,KAAK,OACT,MAAM,IAAI,MACT,6DACD;EAED,IAAI,CAAC,KAAK,gBACT,MAAM,IAAI,MACT,2EACD;EAqBD,OAAO,IAlBWG,cAAAA,MACjB,KAAK,OACL,IACA,KAAK,gBACL,KAAK,UACL,KAAK,WACL,KAAK,SACL,KAAK,YACL,KAAK,OACL,KAAK,WASK;CACZ;AACD;;;ACpJA,MAAa,IAAI,IAAI,aAAa"}
@@ -1,120 +1,7 @@
1
- import { t as Construct } from "../Construct-Detz65RA.mjs";
2
1
  import { idsOf, servicesOf } from "../construct-interface.mjs";
3
2
  import { t as cloneWith } from "../clone-EKNovYHu.mjs";
4
- import { canonicalId, provideKey, serviceKey } from "@geekmidas/manifest";
3
+ import { t as Queue } from "../Queue-Dfumm1WR.mjs";
5
4
  import { DEFAULT_LOGGER } from "@geekmidas/logger/console";
6
- import { Publisher } from "@geekmidas/events";
7
- //#region src/queue/Queue.ts
8
- /**
9
- * A queue worker — a point-to-point SQS-style queue and its single consumer.
10
- * Unlike a `Subscriber` (topic fan-out, filtered by `subscribedEvents`), a queue
11
- * drains every message of its one `message` type. Build one with `q` (see
12
- * `QueueBuilder`); `gkm build` discovers it into the manifest's `queues` field.
13
- *
14
- * The producer side is {@link Queue.publisher} — a ready-to-inject `Service`
15
- * that any endpoint/function drops into `.services([...])` to send messages.
16
- */
17
- var Queue = class extends Construct {
18
- name;
19
- handler;
20
- messageSchema;
21
- timeout;
22
- services;
23
- logger;
24
- batchSize;
25
- fifo;
26
- __IS_QUEUE__ = true;
27
- static isQueue(obj) {
28
- return Boolean(obj && obj.__IS_QUEUE__ === true && obj.type === "dev.geekmidas.function.queue");
29
- }
30
- /**
31
- * The canonical id this queue is declared under.
32
- *
33
- * Derived from the name rather than given separately, so `emails` and
34
- * `Emails` are one queue. The name stays what it was: it is the wire `type`
35
- * a producer sends and the worker subscribes to, and changing that would
36
- * silently orphan in-flight messages.
37
- */
38
- id;
39
- /**
40
- * The producer's env key. Read by both {@link declare} and
41
- * {@link publisher}, so what the target publishes and what the producer
42
- * looks up cannot drift.
43
- */
44
- connectionKey;
45
- constructor(name, handler, messageSchema, timeout = 3e4, services = [], logger = DEFAULT_LOGGER, batchSize, fifo, constructs = []) {
46
- super("dev.geekmidas.function.queue", logger, services, [], void 0, void 0, timeout, void 0, void 0, constructs);
47
- this.name = name;
48
- this.handler = handler;
49
- this.messageSchema = messageSchema;
50
- this.timeout = timeout;
51
- this.services = services;
52
- this.logger = logger;
53
- this.batchSize = batchSize;
54
- this.fifo = fifo;
55
- this.id = canonicalId(name);
56
- this.connectionKey = provideKey(this.id, "publisherConnectionString");
57
- }
58
- /**
59
- * What this queue is in the manifest.
60
- *
61
- * A queue is a resource, not a handler: the worker is reached *through* it,
62
- * so the declaration carries the producer's key and nothing about the code
63
- * that drains it. The local target reads this to know which broker has to be
64
- * running and what connection string to inject.
65
- */
66
- declare() {
67
- return [{
68
- kind: "queue",
69
- id: this.id,
70
- provides: [this.connectionKey],
71
- ...this.fifo ? { fifo: true } : {},
72
- worker: {
73
- id: `${this.id}Worker`,
74
- handler: `${this.id}.handler`,
75
- dependencies: []
76
- }
77
- }];
78
- }
79
- /**
80
- * The producer side — a `Service` exposing an `EventPublisher` typed to this
81
- * queue's message. Inject it via `.services([queue.publisher])`; the handler
82
- * then calls `services.<name>Publisher.publish([{ type, payload }])`.
83
- *
84
- * It reads `<NAME>_PUBLISHER_CONNECTION_STRING` and builds the transport from
85
- * the URL protocol — `pgboss://` locally, `sqs://` deployed — so the same
86
- * code publishes to Postgres in dev and SQS in prod. Because it's a `Service`,
87
- * `Construct.getEnvironment()` sniffs it, so the env requirement flows into
88
- * the manifest and infra links exactly this queue with least privilege.
89
- */
90
- /**
91
- * The queue as a dependency — what `.dependsOn([emailsQueue])` dissolves
92
- * into, reachable as `services.emails`.
93
- *
94
- * It is the producer: a consumer is the worker written right here, so the
95
- * only thing another construct can want from a queue is the ability to send
96
- * to it. {@link publisher} is the same service under its older
97
- * `<name>Publisher` key, kept for `.services([queue.publisher])`.
98
- */
99
- get service() {
100
- const { register } = this.publisher;
101
- return {
102
- serviceName: serviceKey(this.id),
103
- register
104
- };
105
- }
106
- get publisher() {
107
- const envVar = this.connectionKey;
108
- return {
109
- serviceName: `${this.name}Publisher`,
110
- async register({ envParser }) {
111
- const { connectionString } = envParser.create((get) => ({ connectionString: get(envVar).string() })).parse();
112
- return Publisher.fromConnectionString(connectionString);
113
- }
114
- };
115
- }
116
- };
117
- //#endregion
118
5
  //#region src/queue/QueueBuilder.ts
119
6
  /**
120
7
  * Builds a {@link Queue} worker — `q.queue('orders').services([db]).message(schema).handle(fn)`.
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/queue/Queue.ts","../../src/queue/QueueBuilder.ts","../../src/queue/index.ts"],"sourcesContent":["import {\n\ttype EventPublisher,\n\ttype EventPublisherConnectionString,\n\ttype PublishableMessage,\n\tPublisher,\n} from '@geekmidas/events';\nimport type { Logger } from '@geekmidas/logger';\nimport { DEFAULT_LOGGER } from '@geekmidas/logger/console';\nimport {\n\tcanonicalId,\n\ttype Declaration,\n\tprovideKey,\n\tserviceKey,\n} from '@geekmidas/manifest';\nimport type { InferStandardSchema } from '@geekmidas/schema';\nimport type { Service, ServiceRecord } from '@geekmidas/services';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\nimport { Construct, ConstructType } from '../Construct';\n\n/**\n * The wire message a queue carries: `{ type: <queue name>, payload: <message> }`.\n * The producer publishes this shape and the worker receives the `payload`s as\n * its `messages`. Typing `type` to the queue name keeps the publisher fully\n * typed and lets pg-boss (local) / SQS (deployed) route by name.\n */\nexport type QueueMessage<\n\tTName extends string,\n\tTMessage extends StandardSchemaV1,\n> = PublishableMessage<TName, InferStandardSchema<TMessage>>;\n\n/**\n * A queue worker — a point-to-point SQS-style queue and its single consumer.\n * Unlike a `Subscriber` (topic fan-out, filtered by `subscribedEvents`), a queue\n * drains every message of its one `message` type. Build one with `q` (see\n * `QueueBuilder`); `gkm build` discovers it into the manifest's `queues` field.\n *\n * The producer side is {@link Queue.publisher} — a ready-to-inject `Service`\n * that any endpoint/function drops into `.services([...])` to send messages.\n */\nexport class Queue<\n\tTName extends string = string,\n\tTMessage extends StandardSchemaV1 = StandardSchemaV1,\n\tTServices extends Service[] = [],\n\tTLogger extends Logger = Logger,\n> extends Construct<TLogger, string, undefined, undefined, TServices> {\n\t__IS_QUEUE__ = true;\n\n\tstatic isQueue(\n\t\tobj: unknown,\n\t): obj is Queue<string, StandardSchemaV1, Service[], Logger> {\n\t\treturn Boolean(\n\t\t\tobj &&\n\t\t\t\t(obj as { __IS_QUEUE__?: boolean }).__IS_QUEUE__ === true &&\n\t\t\t\t(obj as Construct).type === ConstructType.Queue,\n\t\t);\n\t}\n\n\t/**\n\t * The canonical id this queue is declared under.\n\t *\n\t * Derived from the name rather than given separately, so `emails` and\n\t * `Emails` are one queue. The name stays what it was: it is the wire `type`\n\t * a producer sends and the worker subscribes to, and changing that would\n\t * silently orphan in-flight messages.\n\t */\n\treadonly id: string;\n\n\t/**\n\t * The producer's env key. Read by both {@link declare} and\n\t * {@link publisher}, so what the target publishes and what the producer\n\t * looks up cannot drift.\n\t */\n\tprivate readonly connectionKey: string;\n\n\tconstructor(\n\t\tpublic readonly name: TName,\n\t\tpublic readonly handler: QueueHandler<TMessage, TServices, TLogger>,\n\t\tpublic readonly messageSchema: TMessage,\n\t\tpublic override readonly timeout: number = 30000,\n\t\tpublic override readonly services: TServices = [] as unknown as TServices,\n\t\tpublic override readonly logger: TLogger = DEFAULT_LOGGER as TLogger,\n\t\t/** SQS event-source batch size (deployed). */\n\t\tpublic readonly batchSize?: number,\n\t\t/** Whether the queue is FIFO (deployed). */\n\t\tpublic readonly fifo?: boolean,\n\t\t/**\n\t\t * The construct ids the *worker* `.dependsOn()` named — last, so no\n\t\t * existing positional argument moves.\n\t\t */\n\t\tconstructs: string[] = [],\n\t) {\n\t\tsuper(\n\t\t\tConstructType.Queue,\n\t\t\tlogger,\n\t\t\tservices,\n\t\t\t[],\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\ttimeout,\n\t\t\tundefined, // memorySize\n\t\t\tundefined, // auditorStorageService\n\t\t\tconstructs,\n\t\t);\n\n\t\tthis.id = canonicalId(name);\n\t\tthis.connectionKey = provideKey(this.id, 'publisherConnectionString');\n\t}\n\n\t/**\n\t * What this queue is in the manifest.\n\t *\n\t * A queue is a resource, not a handler: the worker is reached *through* it,\n\t * so the declaration carries the producer's key and nothing about the code\n\t * that drains it. The local target reads this to know which broker has to be\n\t * running and what connection string to inject.\n\t */\n\tdeclare(): Declaration[] {\n\t\treturn [\n\t\t\t{\n\t\t\t\tkind: 'queue',\n\t\t\t\tid: this.id,\n\t\t\t\tprovides: [this.connectionKey],\n\t\t\t\t...(this.fifo ? { fifo: true } : {}),\n\t\t\t\t// Nested rather than a sibling node, because position carries the\n\t\t\t\t// trigger: this handler is reached by messages on this queue and\n\t\t\t\t// by nothing else, so there is no `trigger` field to keep in step.\n\t\t\t\t//\n\t\t\t\t// `handler` is the export name; which directory the build wrote it\n\t\t\t\t// to is the target's business, and a path with a cloud in it does\n\t\t\t\t// not belong in a neutral declaration.\n\t\t\t\tworker: {\n\t\t\t\t\tid: `${this.id}Worker`,\n\t\t\t\t\thandler: `${this.id}.handler`,\n\t\t\t\t\t// Filled by the build from what the worker declared. Empty is a\n\t\t\t\t\t// stated gap, not a claim that it reaches nothing.\n\t\t\t\t\tdependencies: [],\n\t\t\t\t},\n\t\t\t},\n\t\t];\n\t}\n\n\t/**\n\t * The producer side — a `Service` exposing an `EventPublisher` typed to this\n\t * queue's message. Inject it via `.services([queue.publisher])`; the handler\n\t * then calls `services.<name>Publisher.publish([{ type, payload }])`.\n\t *\n\t * It reads `<NAME>_PUBLISHER_CONNECTION_STRING` and builds the transport from\n\t * the URL protocol — `pgboss://` locally, `sqs://` deployed — so the same\n\t * code publishes to Postgres in dev and SQS in prod. Because it's a `Service`,\n\t * `Construct.getEnvironment()` sniffs it, so the env requirement flows into\n\t * the manifest and infra links exactly this queue with least privilege.\n\t */\n\t/**\n\t * The queue as a dependency — what `.dependsOn([emailsQueue])` dissolves\n\t * into, reachable as `services.emails`.\n\t *\n\t * It is the producer: a consumer is the worker written right here, so the\n\t * only thing another construct can want from a queue is the ability to send\n\t * to it. {@link publisher} is the same service under its older\n\t * `<name>Publisher` key, kept for `.services([queue.publisher])`.\n\t */\n\tget service(): Service<\n\t\tUncapitalize<TName>,\n\t\tEventPublisher<QueueMessage<TName, TMessage>>\n\t> {\n\t\tconst { register } = this.publisher;\n\n\t\treturn {\n\t\t\tserviceName: serviceKey(this.id) as Uncapitalize<TName>,\n\t\t\tregister,\n\t\t};\n\t}\n\n\tget publisher(): Service<\n\t\t`${TName}Publisher`,\n\t\tEventPublisher<QueueMessage<TName, TMessage>>\n\t> {\n\t\tconst envVar = this.connectionKey;\n\t\treturn {\n\t\t\tserviceName: `${this.name}Publisher`,\n\t\t\tasync register({ envParser }) {\n\t\t\t\tconst { connectionString } = envParser\n\t\t\t\t\t.create((get) => ({\n\t\t\t\t\t\tconnectionString: get(envVar).string(),\n\t\t\t\t\t}))\n\t\t\t\t\t.parse();\n\n\t\t\t\treturn Publisher.fromConnectionString<QueueMessage<TName, TMessage>>(\n\t\t\t\t\tconnectionString as EventPublisherConnectionString,\n\t\t\t\t);\n\t\t\t},\n\t\t};\n\t}\n}\n\n/** The context a queue handler receives — a batch of typed messages. */\nexport type QueueContext<\n\tTMessage extends StandardSchemaV1,\n\tTServices extends Service[] = [],\n\tTLogger extends Logger = Logger,\n> = {\n\tmessages: InferStandardSchema<TMessage>[];\n\tservices: ServiceRecord<TServices>;\n\tlogger: TLogger;\n};\n\nexport type QueueHandler<\n\tTMessage extends StandardSchemaV1,\n\tTServices extends Service[] = [],\n\tTLogger extends Logger = Logger,\n> = (\n\tctx: QueueContext<TMessage, TServices, TLogger>,\n) => unknown | Promise<unknown>;\n","import type { Logger } from '@geekmidas/logger';\nimport { DEFAULT_LOGGER } from '@geekmidas/logger/console';\nimport type { Service } from '@geekmidas/services';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\nimport { cloneWith } from '../clone';\nimport {\n\ttype Consumable,\n\tidsOf,\n\ttype ServicesOf,\n\tservicesOf,\n} from '../construct-interface';\nimport { Queue, type QueueHandler } from './Queue';\n\n/**\n * Builds a {@link Queue} worker — `q.queue('orders').services([db]).message(schema).handle(fn)`.\n * Services are an array (sniffed for required env vars); `message` is the typed\n * job payload; `handle` is the single consumer. The queue name is captured as a\n * literal so `queue.publisher` is typed to `{ type: '<name>', payload }`.\n */\nexport class QueueBuilder<\n\tTName extends string = string,\n\tTMessage extends StandardSchemaV1 | undefined = undefined,\n\tTServices extends Service[] = [],\n\tTLogger extends Logger = Logger,\n> {\n\tprivate _name?: string;\n\tprivate _messageSchema?: TMessage;\n\tprivate _timeout = 30000;\n\tprivate _batchSize?: number;\n\tprivate _fifo?: boolean;\n\tprivate _services: TServices = [] as Service[] as TServices;\n\t/** The construct ids `.dependsOn()` named — what the manifest records. */\n\tpublic _constructs: string[] = [];\n\tprivate _logger: TLogger = DEFAULT_LOGGER as TLogger;\n\n\t/** The queue name — drives the infra queue and its `<NAME>_*` env vars. */\n\tqueue<T extends string>(\n\t\tname: T,\n\t): QueueBuilder<T, TMessage, TServices, TLogger> {\n\t\treturn cloneWith(this, { _name: name }) as unknown as QueueBuilder<\n\t\t\tT,\n\t\t\tTMessage,\n\t\t\tTServices,\n\t\t\tTLogger\n\t\t>;\n\t}\n\n\ttimeout(timeout: number): this {\n\t\treturn cloneWith(this, { _timeout: timeout });\n\t}\n\n\t/** SQS event-source batch size (deployed). */\n\tbatchSize(batchSize: number): this {\n\t\treturn cloneWith(this, { _batchSize: batchSize });\n\t}\n\n\t/** Mark the queue as FIFO (deployed). */\n\tfifo(fifo = true): this {\n\t\treturn cloneWith(this, { _fifo: fifo });\n\t}\n\n\t/**\n\t * Depend on constructs — a database, a bucket, a mail sender, a queue, a\n\t * topic.\n\t *\n\t * It records the edge and dissolves each construct's client into the\n\t * handler's service record under the construct's own id, so\n\t * `.dependsOn([uploads])` is what makes `services.uploads` exist and type.\n\t *\n\t * Constructs only. A `Service` does not match the shape, which is what keeps\n\t * env sniffing confined to `.services()` and the explicit lift.\n\t */\n\tdependsOn<const T extends readonly Consumable[]>(\n\t\tconstructs: T,\n\t): QueueBuilder<TName, TMessage, [...TServices, ...ServicesOf<T>], TLogger> {\n\t\t// Both halves of the edge, from one call and one clone: the services the\n\t\t// handler runs with, and the ids the manifest records. Recording them\n\t\t// separately is what let them drift apart.\n\t\t//\n\t\t// `servicesOf` is the half that validates, so it runs first — recording\n\t\t// ids ahead of it left a caught `NotAConstruct` with `undefined` already\n\t\t// on a `string[]`.\n\t\tconst services = servicesOf(constructs) as unknown as Service[];\n\n\t\treturn cloneWith(this, {\n\t\t\t_services: [...this._services, ...services],\n\t\t\t_constructs: idsOf(constructs, this._constructs),\n\t\t}) as unknown as QueueBuilder<\n\t\t\tTName,\n\t\t\tTMessage,\n\t\t\t[...TServices, ...ServicesOf<T>],\n\t\t\tTLogger\n\t\t>;\n\t}\n\n\tservices<T extends Service[]>(\n\t\tservices: T,\n\t): QueueBuilder<TName, TMessage, [...TServices, ...T], TLogger> {\n\t\treturn cloneWith(this, {\n\t\t\t_services: [...this._services, ...services] as unknown as TServices,\n\t\t}) as unknown as QueueBuilder<\n\t\t\tTName,\n\t\t\tTMessage,\n\t\t\t[...TServices, ...T],\n\t\t\tTLogger\n\t\t>;\n\t}\n\n\tlogger<T extends Logger>(\n\t\tlogger: T,\n\t): QueueBuilder<TName, TMessage, TServices, T> {\n\t\treturn cloneWith(this, {\n\t\t\t_logger: logger as unknown as TLogger,\n\t\t}) as unknown as QueueBuilder<TName, TMessage, TServices, T>;\n\t}\n\n\t/** The typed message (job) payload the queue carries. */\n\tmessage<T extends StandardSchemaV1>(\n\t\tschema: T,\n\t): QueueBuilder<TName, T, TServices, TLogger> {\n\t\treturn cloneWith(this, {\n\t\t\t_messageSchema: schema as unknown as TMessage,\n\t\t}) as unknown as QueueBuilder<TName, T, TServices, TLogger>;\n\t}\n\n\thandle(\n\t\tfn: QueueHandler<NonNullable<TMessage>, TServices, TLogger>,\n\t): Queue<TName, NonNullable<TMessage>, TServices, TLogger> {\n\t\tif (!this._name) {\n\t\t\tthrow new Error(\n\t\t\t\t'Queue requires a name — call .queue(name) before .handle().',\n\t\t\t);\n\t\t}\n\t\tif (!this._messageSchema) {\n\t\t\tthrow new Error(\n\t\t\t\t'Queue requires a message schema — call .message(schema) before .handle().',\n\t\t\t);\n\t\t}\n\n\t\tconst queue = new Queue<TName, NonNullable<TMessage>, TServices, TLogger>(\n\t\t\tthis._name as TName,\n\t\t\tfn,\n\t\t\tthis._messageSchema as NonNullable<TMessage>,\n\t\t\tthis._timeout,\n\t\t\tthis._services,\n\t\t\tthis._logger,\n\t\t\tthis._batchSize,\n\t\t\tthis._fifo,\n\t\t\tthis._constructs,\n\t\t);\n\n\t\t// No reset. `.handle()` reads this builder and leaves it alone, so a\n\t\t// configured base — `const fn = f.logger(log).timeout(60_000)` — keeps\n\t\t// its configuration for every construct built from it. The reset that\n\t\t// used to be here wiped the base on first use, which made a base usable\n\t\t// exactly once, and quietly reverted the logger to the default.\n\n\t\treturn queue;\n\t}\n}\n","export {\n\tQueue,\n\ttype QueueContext,\n\ttype QueueHandler,\n\ttype QueueMessage,\n} from './Queue';\n\nimport { QueueBuilder } from './QueueBuilder';\n\nexport { QueueBuilder } from './QueueBuilder';\n\nexport const q = new QueueBuilder();\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/queue/QueueBuilder.ts","../../src/queue/index.ts"],"sourcesContent":["import type { Logger } from '@geekmidas/logger';\nimport { DEFAULT_LOGGER } from '@geekmidas/logger/console';\nimport type { Service } from '@geekmidas/services';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\nimport { cloneWith } from '../clone';\nimport {\n\ttype Consumable,\n\tidsOf,\n\ttype ServicesOf,\n\tservicesOf,\n} from '../construct-interface';\nimport { Queue, type QueueHandler } from './Queue';\n\n/**\n * Builds a {@link Queue} worker — `q.queue('orders').services([db]).message(schema).handle(fn)`.\n * Services are an array (sniffed for required env vars); `message` is the typed\n * job payload; `handle` is the single consumer. The queue name is captured as a\n * literal so `queue.publisher` is typed to `{ type: '<name>', payload }`.\n */\nexport class QueueBuilder<\n\tTName extends string = string,\n\tTMessage extends StandardSchemaV1 | undefined = undefined,\n\tTServices extends Service[] = [],\n\tTLogger extends Logger = Logger,\n> {\n\tprivate _name?: string;\n\tprivate _messageSchema?: TMessage;\n\tprivate _timeout = 30000;\n\tprivate _batchSize?: number;\n\tprivate _fifo?: boolean;\n\tprivate _services: TServices = [] as Service[] as TServices;\n\t/** The construct ids `.dependsOn()` named — what the manifest records. */\n\tpublic _constructs: string[] = [];\n\tprivate _logger: TLogger = DEFAULT_LOGGER as TLogger;\n\n\t/** The queue name — drives the infra queue and its `<NAME>_*` env vars. */\n\tqueue<T extends string>(\n\t\tname: T,\n\t): QueueBuilder<T, TMessage, TServices, TLogger> {\n\t\treturn cloneWith(this, { _name: name }) as unknown as QueueBuilder<\n\t\t\tT,\n\t\t\tTMessage,\n\t\t\tTServices,\n\t\t\tTLogger\n\t\t>;\n\t}\n\n\ttimeout(timeout: number): this {\n\t\treturn cloneWith(this, { _timeout: timeout });\n\t}\n\n\t/** SQS event-source batch size (deployed). */\n\tbatchSize(batchSize: number): this {\n\t\treturn cloneWith(this, { _batchSize: batchSize });\n\t}\n\n\t/** Mark the queue as FIFO (deployed). */\n\tfifo(fifo = true): this {\n\t\treturn cloneWith(this, { _fifo: fifo });\n\t}\n\n\t/**\n\t * Depend on constructs — a database, a bucket, a mail sender, a queue, a\n\t * topic.\n\t *\n\t * It records the edge and dissolves each construct's client into the\n\t * handler's service record under the construct's own id, so\n\t * `.dependsOn([uploads])` is what makes `services.uploads` exist and type.\n\t *\n\t * Constructs only. A `Service` does not match the shape, which is what keeps\n\t * env sniffing confined to `.services()` and the explicit lift.\n\t */\n\tdependsOn<const T extends readonly Consumable[]>(\n\t\tconstructs: T,\n\t): QueueBuilder<TName, TMessage, [...TServices, ...ServicesOf<T>], TLogger> {\n\t\t// Both halves of the edge, from one call and one clone: the services the\n\t\t// handler runs with, and the ids the manifest records. Recording them\n\t\t// separately is what let them drift apart.\n\t\t//\n\t\t// `servicesOf` is the half that validates, so it runs first — recording\n\t\t// ids ahead of it left a caught `NotAConstruct` with `undefined` already\n\t\t// on a `string[]`.\n\t\tconst services = servicesOf(constructs) as unknown as Service[];\n\n\t\treturn cloneWith(this, {\n\t\t\t_services: [...this._services, ...services],\n\t\t\t_constructs: idsOf(constructs, this._constructs),\n\t\t}) as unknown as QueueBuilder<\n\t\t\tTName,\n\t\t\tTMessage,\n\t\t\t[...TServices, ...ServicesOf<T>],\n\t\t\tTLogger\n\t\t>;\n\t}\n\n\tservices<T extends Service[]>(\n\t\tservices: T,\n\t): QueueBuilder<TName, TMessage, [...TServices, ...T], TLogger> {\n\t\treturn cloneWith(this, {\n\t\t\t_services: [...this._services, ...services] as unknown as TServices,\n\t\t}) as unknown as QueueBuilder<\n\t\t\tTName,\n\t\t\tTMessage,\n\t\t\t[...TServices, ...T],\n\t\t\tTLogger\n\t\t>;\n\t}\n\n\tlogger<T extends Logger>(\n\t\tlogger: T,\n\t): QueueBuilder<TName, TMessage, TServices, T> {\n\t\treturn cloneWith(this, {\n\t\t\t_logger: logger as unknown as TLogger,\n\t\t}) as unknown as QueueBuilder<TName, TMessage, TServices, T>;\n\t}\n\n\t/** The typed message (job) payload the queue carries. */\n\tmessage<T extends StandardSchemaV1>(\n\t\tschema: T,\n\t): QueueBuilder<TName, T, TServices, TLogger> {\n\t\treturn cloneWith(this, {\n\t\t\t_messageSchema: schema as unknown as TMessage,\n\t\t}) as unknown as QueueBuilder<TName, T, TServices, TLogger>;\n\t}\n\n\thandle(\n\t\tfn: QueueHandler<NonNullable<TMessage>, TServices, TLogger>,\n\t): Queue<TName, NonNullable<TMessage>, TServices, TLogger> {\n\t\tif (!this._name) {\n\t\t\tthrow new Error(\n\t\t\t\t'Queue requires a name — call .queue(name) before .handle().',\n\t\t\t);\n\t\t}\n\t\tif (!this._messageSchema) {\n\t\t\tthrow new Error(\n\t\t\t\t'Queue requires a message schema — call .message(schema) before .handle().',\n\t\t\t);\n\t\t}\n\n\t\tconst queue = new Queue<TName, NonNullable<TMessage>, TServices, TLogger>(\n\t\t\tthis._name as TName,\n\t\t\tfn,\n\t\t\tthis._messageSchema as NonNullable<TMessage>,\n\t\t\tthis._timeout,\n\t\t\tthis._services,\n\t\t\tthis._logger,\n\t\t\tthis._batchSize,\n\t\t\tthis._fifo,\n\t\t\tthis._constructs,\n\t\t);\n\n\t\t// No reset. `.handle()` reads this builder and leaves it alone, so a\n\t\t// configured base — `const fn = f.logger(log).timeout(60_000)` — keeps\n\t\t// its configuration for every construct built from it. The reset that\n\t\t// used to be here wiped the base on first use, which made a base usable\n\t\t// exactly once, and quietly reverted the logger to the default.\n\n\t\treturn queue;\n\t}\n}\n","export {\n\tQueue,\n\ttype QueueContext,\n\ttype QueueHandler,\n\ttype QueueMessage,\n} from './Queue';\n\nimport { QueueBuilder } from './QueueBuilder';\n\nexport { QueueBuilder } from './QueueBuilder';\n\nexport const q = new QueueBuilder();\n"],"mappings":";;;;;;;;;;;AAmBA,IAAa,eAAb,MAKE;CACD;CACA;CACA,WAAmB;CACnB;CACA;CACA,YAA+B,CAAC;;CAEhC,cAA+B,CAAC;CAChC,UAA2B;;CAG3B,MACC,MACgD;EAChD,OAAO,UAAU,MAAM,EAAE,OAAO,KAAK,CAAC;CAMvC;CAEA,QAAQ,SAAuB;EAC9B,OAAO,UAAU,MAAM,EAAE,UAAU,QAAQ,CAAC;CAC7C;;CAGA,UAAU,WAAyB;EAClC,OAAO,UAAU,MAAM,EAAE,YAAY,UAAU,CAAC;CACjD;;CAGA,KAAK,OAAO,MAAY;EACvB,OAAO,UAAU,MAAM,EAAE,OAAO,KAAK,CAAC;CACvC;;;;;;;;;;;;CAaA,UACC,YAC2E;EAQ3E,MAAM,WAAW,WAAW,UAAU;EAEtC,OAAO,UAAU,MAAM;GACtB,WAAW,CAAC,GAAG,KAAK,WAAW,GAAG,QAAQ;GAC1C,aAAa,MAAM,YAAY,KAAK,WAAW;EAChD,CAAC;CAMF;CAEA,SACC,UAC+D;EAC/D,OAAO,UAAU,MAAM,EACtB,WAAW,CAAC,GAAG,KAAK,WAAW,GAAG,QAAQ,EAC3C,CAAC;CAMF;CAEA,OACC,QAC8C;EAC9C,OAAO,UAAU,MAAM,EACtB,SAAS,OACV,CAAC;CACF;;CAGA,QACC,QAC6C;EAC7C,OAAO,UAAU,MAAM,EACtB,gBAAgB,OACjB,CAAC;CACF;CAEA,OACC,IAC0D;EAC1D,IAAI,CAAC,KAAK,OACT,MAAM,IAAI,MACT,6DACD;EAED,IAAI,CAAC,KAAK,gBACT,MAAM,IAAI,MACT,2EACD;EAqBD,OAAO,IAlBW,MACjB,KAAK,OACL,IACA,KAAK,gBACL,KAAK,UACL,KAAK,WACL,KAAK,SACL,KAAK,YACL,KAAK,OACL,KAAK,WASK;CACZ;AACD;;;ACpJA,MAAa,IAAI,IAAI,aAAa"}
@@ -1,6 +1,6 @@
1
1
  import { i as Declarable, t as Authenticator } from "./construct-interface-DIaTNKax.cjs";
2
- import { T as BuiltInSecuritySchemeId } from "./Endpoint-DbYQJgb-.cjs";
3
- import { t as EndpointFactory } from "./EndpointFactory-DE6nKtFZ.cjs";
2
+ import { T as BuiltInSecuritySchemeId } from "./Endpoint-CjvraSfp.cjs";
3
+ import { t as EndpointFactory } from "./EndpointFactory-vfC3NiJi.cjs";
4
4
  import { EnvironmentParser } from "@geekmidas/envkit";
5
5
  import { Logger } from "@geekmidas/logger";
6
6
  import { ConstructName, Declaration, Dependency } from "@geekmidas/manifest";
@@ -1,6 +1,6 @@
1
1
  import { i as Declarable, t as Authenticator } from "./construct-interface-Clz7_BwB.mjs";
2
- import { T as BuiltInSecuritySchemeId } from "./Endpoint-BxkRWQ8G.mjs";
3
- import { t as EndpointFactory } from "./EndpointFactory-T06kTFHg.mjs";
2
+ import { T as BuiltInSecuritySchemeId } from "./Endpoint-Bmi2-PBN.mjs";
3
+ import { t as EndpointFactory } from "./EndpointFactory-BFfvgko6.mjs";
4
4
  import { ConstructName, Declaration, Dependency } from "@geekmidas/manifest";
5
5
  import { EnvironmentParser } from "@geekmidas/envkit";
6
6
  import { Logger } from "@geekmidas/logger";
@@ -1,104 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_Construct = require("../Construct-yG9Sw-5p.cjs");
3
2
  const require_clone = require("../clone-UGJpvQnL.cjs");
4
- let _geekmidas_manifest = require("@geekmidas/manifest");
3
+ const require_Topic = require("../Topic-BaNCgP61.cjs");
5
4
  let _geekmidas_logger_console = require("@geekmidas/logger/console");
6
- let _geekmidas_events = require("@geekmidas/events");
7
- //#region src/topic/Topic.ts
8
- /**
9
- * A topic — pub/sub fan-out. Unlike a `Queue` (point-to-point, one consumer), a
10
- * topic is a *resource* owned by no single handler: it declares the event
11
- * contract, derives a typed producer (`topic.publisher`), and any number of
12
- * subscribers (`s.topic(topic)`) bind to it. `gkm build` discovers it into the
13
- * manifest's `topics` field; infra provisions an SNS topic.
14
- *
15
- * This replaces hand-writing a publisher `Service`: the publisher is *derived*
16
- * from the declared event contract, the same way `Queue` derives its publisher.
17
- */
18
- var Topic = class extends require_Construct.Construct {
19
- name;
20
- eventSchemas;
21
- __IS_TOPIC__ = true;
22
- static isTopic(obj) {
23
- return Boolean(obj && obj.__IS_TOPIC__ === true && obj.type === "dev.geekmidas.resource.topic");
24
- }
25
- /**
26
- * The canonical id this topic is declared under.
27
- *
28
- * Derived from the name, so `users` and `Users` are one topic. The name is
29
- * left alone: subscribers bind to it and it is what the broker routes on.
30
- */
31
- id;
32
- /**
33
- * The producer's env key, read by both {@link declare} and
34
- * {@link publisher} so the two cannot drift.
35
- */
36
- connectionKey;
37
- constructor(name, eventSchemas, logger = _geekmidas_logger_console.DEFAULT_LOGGER) {
38
- super("dev.geekmidas.resource.topic", logger, [], []);
39
- this.name = name;
40
- this.eventSchemas = eventSchemas;
41
- this.id = (0, _geekmidas_manifest.canonicalId)(name);
42
- this.connectionKey = (0, _geekmidas_manifest.provideKey)(this.id, "publisherConnectionString");
43
- }
44
- /**
45
- * What this topic is in the manifest.
46
- *
47
- * Only the producer's key: a subscriber is *bound* to a topic rather than
48
- * depending on it, so the binding is an edge the deploy target reads and not
49
- * an env key anyone can hold. Locally both sides meet on the same broker.
50
- */
51
- declare() {
52
- return [{
53
- kind: "topic",
54
- id: this.id,
55
- provides: [this.connectionKey],
56
- events: this.eventTypes,
57
- subscribers: []
58
- }];
59
- }
60
- /** The event type names this topic carries. */
61
- get eventTypes() {
62
- return Object.keys(this.eventSchemas);
63
- }
64
- /**
65
- * The producer side — a `Service` exposing an `EventPublisher` typed to the
66
- * union of this topic's events. Inject via `.publisher(topic.publisher)` (for
67
- * declarative `.event(...)`) or `.services([topic.publisher])` (to publish
68
- * imperatively). Reads `<NAME>_PUBLISHER_CONNECTION_STRING` and selects the
69
- * transport from the URL protocol — `pgboss://` locally, `sns://` deployed.
70
- *
71
- * Because it's a `Service`, the connection-string requirement is sniffed into
72
- * the manifest of whatever construct injects it (least-privilege linking).
73
- */
74
- /**
75
- * The topic as a dependency — what `.dependsOn([users])` dissolves into,
76
- * reachable as `services.users`.
77
- *
78
- * It is the producer, because publishing is the only thing depending on a
79
- * topic can mean: a subscriber *binds* with `s.topic(…)` instead, and is
80
- * deliberately never handed this. {@link publisher} is the same service
81
- * under its older `<name>Publisher` key.
82
- */
83
- get service() {
84
- const { register } = this.publisher;
85
- return {
86
- serviceName: (0, _geekmidas_manifest.serviceKey)(this.id),
87
- register
88
- };
89
- }
90
- get publisher() {
91
- const envVar = this.connectionKey;
92
- return {
93
- serviceName: `${this.name}Publisher`,
94
- async register({ envParser }) {
95
- const { connectionString } = envParser.create((get) => ({ connectionString: get(envVar).string() })).parse();
96
- return _geekmidas_events.Publisher.fromConnectionString(connectionString);
97
- }
98
- };
99
- }
100
- };
101
- //#endregion
102
5
  //#region src/topic/TopicBuilder.ts
103
6
  /**
104
7
  * Builds a {@link Topic} — `t.topic('users').events({ 'user.created': schema, … })`.
@@ -121,14 +24,14 @@ var TopicBuilder = class {
121
24
  */
122
25
  events(events) {
123
26
  if (!this._name) throw new Error("Topic requires a name — call .topic(name) before .events().");
124
- return new Topic(this._name, events, this._logger);
27
+ return new require_Topic.Topic(this._name, events, this._logger);
125
28
  }
126
29
  };
127
30
  //#endregion
128
31
  //#region src/topic/index.ts
129
32
  const t = new TopicBuilder();
130
33
  //#endregion
131
- exports.Topic = Topic;
34
+ exports.Topic = require_Topic.Topic;
132
35
  exports.TopicBuilder = TopicBuilder;
133
36
  exports.t = t;
134
37
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["Construct","DEFAULT_LOGGER","canonicalId","provideKey","serviceKey","Publisher","DEFAULT_LOGGER","cloneWith"],"sources":["../../src/topic/Topic.ts","../../src/topic/TopicBuilder.ts","../../src/topic/index.ts"],"sourcesContent":["import {\n\ttype EventPublisher,\n\ttype EventPublisherConnectionString,\n\ttype PublishableMessage,\n\tPublisher,\n} from '@geekmidas/events';\nimport type { Logger } from '@geekmidas/logger';\nimport { DEFAULT_LOGGER } from '@geekmidas/logger/console';\nimport {\n\tcanonicalId,\n\ttype Declaration,\n\tprovideKey,\n\tserviceKey,\n} from '@geekmidas/manifest';\nimport type { InferStandardSchema } from '@geekmidas/schema';\nimport type { Service } from '@geekmidas/services';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\nimport { Construct, ConstructType } from '../Construct';\n\n/** A topic's event contract — a map of event type → payload schema. */\nexport type TopicEvents = Record<string, StandardSchemaV1>;\n\n/**\n * The union of wire messages a topic carries, derived from its event map:\n * `{ type: 'user.created'; payload: … } | { type: 'user.updated'; payload: … }`.\n */\nexport type TopicMessage<TEvents extends TopicEvents> = {\n\t[K in keyof TEvents & string]: PublishableMessage<\n\t\tK,\n\t\tInferStandardSchema<TEvents[K]>\n\t>;\n}[keyof TEvents & string];\n\n/**\n * A topic — pub/sub fan-out. Unlike a `Queue` (point-to-point, one consumer), a\n * topic is a *resource* owned by no single handler: it declares the event\n * contract, derives a typed producer (`topic.publisher`), and any number of\n * subscribers (`s.topic(topic)`) bind to it. `gkm build` discovers it into the\n * manifest's `topics` field; infra provisions an SNS topic.\n *\n * This replaces hand-writing a publisher `Service`: the publisher is *derived*\n * from the declared event contract, the same way `Queue` derives its publisher.\n */\nexport class Topic<\n\tTName extends string = string,\n\tTEvents extends TopicEvents = TopicEvents,\n> extends Construct {\n\t__IS_TOPIC__ = true;\n\n\tstatic isTopic(obj: unknown): obj is Topic<string, TopicEvents> {\n\t\treturn Boolean(\n\t\t\tobj &&\n\t\t\t\t(obj as { __IS_TOPIC__?: boolean }).__IS_TOPIC__ === true &&\n\t\t\t\t(obj as Construct).type === ConstructType.Topic,\n\t\t);\n\t}\n\n\t/**\n\t * The canonical id this topic is declared under.\n\t *\n\t * Derived from the name, so `users` and `Users` are one topic. The name is\n\t * left alone: subscribers bind to it and it is what the broker routes on.\n\t */\n\treadonly id: string;\n\n\t/**\n\t * The producer's env key, read by both {@link declare} and\n\t * {@link publisher} so the two cannot drift.\n\t */\n\tprivate readonly connectionKey: string;\n\n\tconstructor(\n\t\tpublic readonly name: TName,\n\t\t/**\n\t\t * The event contract — a map of event type → payload schema. Named\n\t\t * `eventSchemas` (not `events`) to avoid clashing with `Construct.events`,\n\t\t * which is the array of `MappedEvent`s a construct *publishes*.\n\t\t */\n\t\tpublic readonly eventSchemas: TEvents,\n\t\tlogger: Logger = DEFAULT_LOGGER,\n\t) {\n\t\tsuper(ConstructType.Topic, logger, [], []);\n\n\t\tthis.id = canonicalId(name);\n\t\tthis.connectionKey = provideKey(this.id, 'publisherConnectionString');\n\t}\n\n\t/**\n\t * What this topic is in the manifest.\n\t *\n\t * Only the producer's key: a subscriber is *bound* to a topic rather than\n\t * depending on it, so the binding is an edge the deploy target reads and not\n\t * an env key anyone can hold. Locally both sides meet on the same broker.\n\t */\n\tdeclare(): Declaration[] {\n\t\treturn [\n\t\t\t{\n\t\t\t\tkind: 'topic',\n\t\t\t\tid: this.id,\n\t\t\t\tprovides: [this.connectionKey],\n\t\t\t\t// The contract this topic carries. Structural: which events exist\n\t\t\t\t// is a fact about the application, and a subscriber binding to one\n\t\t\t\t// that does not is a mistake worth catching at build.\n\t\t\t\tevents: this.eventTypes,\n\t\t\t\t// Bound rather than declared here — a subscriber names the topic,\n\t\t\t\t// not the other way round — so the build fills these from what it\n\t\t\t\t// found. Nested when it does, because position is the trigger.\n\t\t\t\tsubscribers: [],\n\t\t\t},\n\t\t];\n\t}\n\n\t/** The event type names this topic carries. */\n\tget eventTypes(): (keyof TEvents & string)[] {\n\t\treturn Object.keys(this.eventSchemas) as (keyof TEvents & string)[];\n\t}\n\n\t/**\n\t * The producer side — a `Service` exposing an `EventPublisher` typed to the\n\t * union of this topic's events. Inject via `.publisher(topic.publisher)` (for\n\t * declarative `.event(...)`) or `.services([topic.publisher])` (to publish\n\t * imperatively). Reads `<NAME>_PUBLISHER_CONNECTION_STRING` and selects the\n\t * transport from the URL protocol — `pgboss://` locally, `sns://` deployed.\n\t *\n\t * Because it's a `Service`, the connection-string requirement is sniffed into\n\t * the manifest of whatever construct injects it (least-privilege linking).\n\t */\n\t/**\n\t * The topic as a dependency — what `.dependsOn([users])` dissolves into,\n\t * reachable as `services.users`.\n\t *\n\t * It is the producer, because publishing is the only thing depending on a\n\t * topic can mean: a subscriber *binds* with `s.topic(…)` instead, and is\n\t * deliberately never handed this. {@link publisher} is the same service\n\t * under its older `<name>Publisher` key.\n\t */\n\tget service(): Service<\n\t\tUncapitalize<TName>,\n\t\tEventPublisher<TopicMessage<TEvents>>\n\t> {\n\t\tconst { register } = this.publisher;\n\n\t\treturn {\n\t\t\tserviceName: serviceKey(this.id) as Uncapitalize<TName>,\n\t\t\tregister,\n\t\t};\n\t}\n\n\tget publisher(): Service<\n\t\t`${TName}Publisher`,\n\t\tEventPublisher<TopicMessage<TEvents>>\n\t> {\n\t\tconst envVar = this.connectionKey;\n\t\treturn {\n\t\t\tserviceName: `${this.name}Publisher`,\n\t\t\tasync register({ envParser }) {\n\t\t\t\tconst { connectionString } = envParser\n\t\t\t\t\t.create((get) => ({\n\t\t\t\t\t\tconnectionString: get(envVar).string(),\n\t\t\t\t\t}))\n\t\t\t\t\t.parse();\n\n\t\t\t\treturn Publisher.fromConnectionString<TopicMessage<TEvents>>(\n\t\t\t\t\tconnectionString as EventPublisherConnectionString,\n\t\t\t\t);\n\t\t\t},\n\t\t};\n\t}\n}\n","import type { Logger } from '@geekmidas/logger';\nimport { DEFAULT_LOGGER } from '@geekmidas/logger/console';\nimport { cloneWith } from '../clone';\nimport { Topic, type TopicEvents } from './Topic';\n\n/**\n * Builds a {@link Topic} — `t.topic('users').events({ 'user.created': schema, … })`.\n * The event map is the topic's contract: it types the derived `topic.publisher`\n * and the subscribers that bind via `s.topic(topic)`.\n */\nexport class TopicBuilder<\n\tTName extends string = string,\n\tTEvents extends TopicEvents = TopicEvents,\n> {\n\tprivate _name?: string;\n\tprivate _logger: Logger = DEFAULT_LOGGER;\n\n\t/** The topic name — drives the infra topic and its `<NAME>_*` env vars. */\n\ttopic<T extends string>(name: T): TopicBuilder<T, TEvents> {\n\t\treturn cloneWith(this, { _name: name }) as unknown as TopicBuilder<\n\t\t\tT,\n\t\t\tTEvents\n\t\t>;\n\t}\n\n\tlogger(logger: Logger): this {\n\t\treturn cloneWith(this, { _logger: logger });\n\t}\n\n\t/**\n\t * The event contract — a map of event type → payload schema. Building the\n\t * topic requires this; it's what types the publisher and subscribers.\n\t */\n\tevents<T extends TopicEvents>(events: T): Topic<TName, T> {\n\t\tif (!this._name) {\n\t\t\tthrow new Error(\n\t\t\t\t'Topic requires a name — call .topic(name) before .events().',\n\t\t\t);\n\t\t}\n\n\t\tconst topic = new Topic<TName, T>(\n\t\t\tthis._name as TName,\n\t\t\tevents,\n\t\t\tthis._logger,\n\t\t);\n\n\t\t// No reset. `.events()` reads this builder and leaves it alone, so a\n\t\t// configured base stays usable for the next topic — and two chains off one\n\t\t// base cannot feed each other.\n\t\treturn topic;\n\t}\n}\n","export {\n\tTopic,\n\ttype TopicEvents,\n\ttype TopicMessage,\n} from './Topic';\n\nimport { TopicBuilder } from './TopicBuilder';\n\nexport { TopicBuilder } from './TopicBuilder';\n\nexport const t = new TopicBuilder();\n"],"mappings":";;;;;;;;;;;;;;;;;AA2CA,IAAa,QAAb,cAGUA,kBAAAA,UAAU;CA0BF;CAMA;CA/BjB,eAAe;CAEf,OAAO,QAAQ,KAAiD;EAC/D,OAAO,QACN,OACE,IAAmC,iBAAiB,QACpD,IAAkB,SAAA,8BACrB;CACD;;;;;;;CAQA;;;;;CAMA;CAEA,YACC,MAMA,cACA,SAAiBC,0BAAAA,gBAChB;EACD,MAAA,gCAA2B,QAAQ,CAAC,GAAG,CAAC,CAAC;EATzB,KAAA,OAAA;EAMA,KAAA,eAAA;EAKhB,KAAK,MAAA,GAAKC,oBAAAA,YAAAA,CAAY,IAAI;EAC1B,KAAK,iBAAA,GAAgBC,oBAAAA,WAAAA,CAAW,KAAK,IAAI,2BAA2B;CACrE;;;;;;;;CASA,UAAyB;EACxB,OAAO,CACN;GACC,MAAM;GACN,IAAI,KAAK;GACT,UAAU,CAAC,KAAK,aAAa;GAI7B,QAAQ,KAAK;GAIb,aAAa,CAAC;EACf,CACD;CACD;;CAGA,IAAI,aAAyC;EAC5C,OAAO,OAAO,KAAK,KAAK,YAAY;CACrC;;;;;;;;;;;;;;;;;;;;CAqBA,IAAI,UAGF;EACD,MAAM,EAAE,aAAa,KAAK;EAE1B,OAAO;GACN,cAAA,GAAaC,oBAAAA,WAAAA,CAAW,KAAK,EAAE;GAC/B;EACD;CACD;CAEA,IAAI,YAGF;EACD,MAAM,SAAS,KAAK;EACpB,OAAO;GACN,aAAa,GAAG,KAAK,KAAK;GAC1B,MAAM,SAAS,EAAE,aAAa;IAC7B,MAAM,EAAE,qBAAqB,UAC3B,QAAQ,SAAS,EACjB,kBAAkB,IAAI,MAAM,CAAC,CAAC,OAAO,EACtC,EAAE,CAAC,CACF,MAAM;IAER,OAAOC,kBAAAA,UAAU,qBAChB,gBACD;GACD;EACD;CACD;AACD;;;;;;;;AC9JA,IAAa,eAAb,MAGE;CACD;CACA,UAA0BC,0BAAAA;;CAG1B,MAAwB,MAAmC;EAC1D,OAAOC,cAAAA,UAAU,MAAM,EAAE,OAAO,KAAK,CAAC;CAIvC;CAEA,OAAO,QAAsB;EAC5B,OAAOA,cAAAA,UAAU,MAAM,EAAE,SAAS,OAAO,CAAC;CAC3C;;;;;CAMA,OAA8B,QAA4B;EACzD,IAAI,CAAC,KAAK,OACT,MAAM,IAAI,MACT,6DACD;EAYD,OAAO,IATW,MACjB,KAAK,OACL,QACA,KAAK,OAMK;CACZ;AACD;;;ACzCA,MAAa,IAAI,IAAI,aAAa"}
1
+ {"version":3,"file":"index.cjs","names":["DEFAULT_LOGGER","cloneWith","Topic"],"sources":["../../src/topic/TopicBuilder.ts","../../src/topic/index.ts"],"sourcesContent":["import type { Logger } from '@geekmidas/logger';\nimport { DEFAULT_LOGGER } from '@geekmidas/logger/console';\nimport { cloneWith } from '../clone';\nimport { Topic, type TopicEvents } from './Topic';\n\n/**\n * Builds a {@link Topic} — `t.topic('users').events({ 'user.created': schema, … })`.\n * The event map is the topic's contract: it types the derived `topic.publisher`\n * and the subscribers that bind via `s.topic(topic)`.\n */\nexport class TopicBuilder<\n\tTName extends string = string,\n\tTEvents extends TopicEvents = TopicEvents,\n> {\n\tprivate _name?: string;\n\tprivate _logger: Logger = DEFAULT_LOGGER;\n\n\t/** The topic name — drives the infra topic and its `<NAME>_*` env vars. */\n\ttopic<T extends string>(name: T): TopicBuilder<T, TEvents> {\n\t\treturn cloneWith(this, { _name: name }) as unknown as TopicBuilder<\n\t\t\tT,\n\t\t\tTEvents\n\t\t>;\n\t}\n\n\tlogger(logger: Logger): this {\n\t\treturn cloneWith(this, { _logger: logger });\n\t}\n\n\t/**\n\t * The event contract — a map of event type → payload schema. Building the\n\t * topic requires this; it's what types the publisher and subscribers.\n\t */\n\tevents<T extends TopicEvents>(events: T): Topic<TName, T> {\n\t\tif (!this._name) {\n\t\t\tthrow new Error(\n\t\t\t\t'Topic requires a name — call .topic(name) before .events().',\n\t\t\t);\n\t\t}\n\n\t\tconst topic = new Topic<TName, T>(\n\t\t\tthis._name as TName,\n\t\t\tevents,\n\t\t\tthis._logger,\n\t\t);\n\n\t\t// No reset. `.events()` reads this builder and leaves it alone, so a\n\t\t// configured base stays usable for the next topic — and two chains off one\n\t\t// base cannot feed each other.\n\t\treturn topic;\n\t}\n}\n","export {\n\tTopic,\n\ttype TopicEvents,\n\ttype TopicMessage,\n} from './Topic';\n\nimport { TopicBuilder } from './TopicBuilder';\n\nexport { TopicBuilder } from './TopicBuilder';\n\nexport const t = new TopicBuilder();\n"],"mappings":";;;;;;;;;;AAUA,IAAa,eAAb,MAGE;CACD;CACA,UAA0BA,0BAAAA;;CAG1B,MAAwB,MAAmC;EAC1D,OAAOC,cAAAA,UAAU,MAAM,EAAE,OAAO,KAAK,CAAC;CAIvC;CAEA,OAAO,QAAsB;EAC5B,OAAOA,cAAAA,UAAU,MAAM,EAAE,SAAS,OAAO,CAAC;CAC3C;;;;;CAMA,OAA8B,QAA4B;EACzD,IAAI,CAAC,KAAK,OACT,MAAM,IAAI,MACT,6DACD;EAYD,OAAO,IATWC,cAAAA,MACjB,KAAK,OACL,QACA,KAAK,OAMK;CACZ;AACD;;;ACzCA,MAAa,IAAI,IAAI,aAAa"}