@geekmidas/constructs 10.0.0-alpha.124 → 10.0.0-alpha.125

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 (157) hide show
  1. package/dist/{Construct-CFW0zjdD.d.cts → Construct-127M7yrl.d.cts} +3 -2
  2. package/dist/{Construct-B7AXn0xH.d.mts.map → Construct-127M7yrl.d.cts.map} +1 -1
  3. package/dist/{Construct-CKUA-CTA.mjs → Construct-CGuc_Ijk.mjs} +2 -1
  4. package/dist/{Construct-CKUA-CTA.mjs.map → Construct-CGuc_Ijk.mjs.map} +1 -1
  5. package/dist/{Construct-DEOVauyA.cjs → Construct-DorKS0Ua.cjs} +2 -1
  6. package/dist/{Construct-DEOVauyA.cjs.map → Construct-DorKS0Ua.cjs.map} +1 -1
  7. package/dist/{Construct-B7AXn0xH.d.mts → Construct-DySlwh8o.d.mts} +3 -2
  8. package/dist/{Construct-CFW0zjdD.d.cts.map → Construct-DySlwh8o.d.mts.map} +1 -1
  9. package/dist/{Cron-bdGRSROY.d.cts → Cron-CFkpEys6.d.cts} +4 -4
  10. package/dist/{Cron-bdGRSROY.d.cts.map → Cron-CFkpEys6.d.cts.map} +1 -1
  11. package/dist/{Cron-i4Uetezh.d.mts → Cron-CQoDSu-n.d.mts} +4 -4
  12. package/dist/{Cron-i4Uetezh.d.mts.map → Cron-CQoDSu-n.d.mts.map} +1 -1
  13. package/dist/{CronBuilder-9H4BnD6W.d.mts → CronBuilder-1W-G_i15.d.mts} +5 -5
  14. package/dist/{CronBuilder-9H4BnD6W.d.mts.map → CronBuilder-1W-G_i15.d.mts.map} +1 -1
  15. package/dist/{CronBuilder-D3sF9tS_.mjs → CronBuilder-BJ5LoadF.mjs} +3 -3
  16. package/dist/{CronBuilder-D3sF9tS_.mjs.map → CronBuilder-BJ5LoadF.mjs.map} +1 -1
  17. package/dist/{CronBuilder-BXTG10u5.cjs → CronBuilder-B_NSP-Y5.cjs} +3 -3
  18. package/dist/{CronBuilder-BXTG10u5.cjs.map → CronBuilder-B_NSP-Y5.cjs.map} +1 -1
  19. package/dist/{CronBuilder-B_24LZpn.d.cts → CronBuilder-OaCRRFVk.d.cts} +5 -5
  20. package/dist/{CronBuilder-B_24LZpn.d.cts.map → CronBuilder-OaCRRFVk.d.cts.map} +1 -1
  21. package/dist/{Endpoint-DgrpG15P.d.cts → Endpoint-BEh-jW-f.d.cts} +4 -4
  22. package/dist/{Endpoint-CeFkSwh5.d.mts.map → Endpoint-BEh-jW-f.d.cts.map} +1 -1
  23. package/dist/{Endpoint-yhR2T2Wt.cjs → Endpoint-BuGFNFSB.cjs} +3 -3
  24. package/dist/{Endpoint-yhR2T2Wt.cjs.map → Endpoint-BuGFNFSB.cjs.map} +1 -1
  25. package/dist/{Endpoint-CeFkSwh5.d.mts → Endpoint-DTXDggNl.d.mts} +4 -4
  26. package/dist/{Endpoint-DgrpG15P.d.cts.map → Endpoint-DTXDggNl.d.mts.map} +1 -1
  27. package/dist/{Endpoint-gPXhHKhJ.mjs → Endpoint-VdaYKc2g.mjs} +3 -3
  28. package/dist/{Endpoint-gPXhHKhJ.mjs.map → Endpoint-VdaYKc2g.mjs.map} +1 -1
  29. package/dist/{EndpointFactory-Cc863XJS.d.cts → EndpointFactory-B2dCS9dy.d.cts} +4 -4
  30. package/dist/{EndpointFactory-Dbw9ugkX.d.mts.map → EndpointFactory-B2dCS9dy.d.cts.map} +1 -1
  31. package/dist/{EndpointFactory-CSrAyazj.cjs → EndpointFactory-C6pVda4y.cjs} +4 -4
  32. package/dist/{EndpointFactory-CSrAyazj.cjs.map → EndpointFactory-C6pVda4y.cjs.map} +1 -1
  33. package/dist/{EndpointFactory-c2ZugGig.mjs → EndpointFactory-CUpqgtaH.mjs} +4 -4
  34. package/dist/{EndpointFactory-c2ZugGig.mjs.map → EndpointFactory-CUpqgtaH.mjs.map} +1 -1
  35. package/dist/{EndpointFactory-Dbw9ugkX.d.mts → EndpointFactory-D2HXcUrJ.d.mts} +4 -4
  36. package/dist/{EndpointFactory-Cc863XJS.d.cts.map → EndpointFactory-D2HXcUrJ.d.mts.map} +1 -1
  37. package/dist/{Function-B0uZhZnU.d.cts → Function-BIEsY0Hm.d.cts} +2 -2
  38. package/dist/{Function-B0uZhZnU.d.cts.map → Function-BIEsY0Hm.d.cts.map} +1 -1
  39. package/dist/{Function-B_9R7Zy1.d.mts → Function-DfMzTGS4.d.mts} +2 -2
  40. package/dist/{Function-B_9R7Zy1.d.mts.map → Function-DfMzTGS4.d.mts.map} +1 -1
  41. package/dist/{HonoEndpointAdaptor-CN1jjUBJ.cjs → HonoEndpointAdaptor-BBTzbP38.cjs} +3 -3
  42. package/dist/{HonoEndpointAdaptor-CN1jjUBJ.cjs.map → HonoEndpointAdaptor-BBTzbP38.cjs.map} +1 -1
  43. package/dist/{HonoEndpointAdaptor-euH1oSdY.mjs → HonoEndpointAdaptor-BR_LyaFE.mjs} +3 -3
  44. package/dist/{HonoEndpointAdaptor-euH1oSdY.mjs.map → HonoEndpointAdaptor-BR_LyaFE.mjs.map} +1 -1
  45. package/dist/{ObjectSubscriberBuilder-C0WUU-ov.d.mts → ObjectSubscriberBuilder-HmBwFn2o.d.mts} +4 -4
  46. package/dist/{ObjectSubscriberBuilder-B4j3zgCZ.d.cts.map → ObjectSubscriberBuilder-HmBwFn2o.d.mts.map} +1 -1
  47. package/dist/{ObjectSubscriberBuilder-B4j3zgCZ.d.cts → ObjectSubscriberBuilder-l-64yUiF.d.cts} +4 -4
  48. package/dist/{ObjectSubscriberBuilder-C0WUU-ov.d.mts.map → ObjectSubscriberBuilder-l-64yUiF.d.cts.map} +1 -1
  49. package/dist/ProcessorBuilder-B2_stA8f.d.mts +216 -0
  50. package/dist/ProcessorBuilder-B2_stA8f.d.mts.map +1 -0
  51. package/dist/ProcessorBuilder-C62bIOwj.d.cts +216 -0
  52. package/dist/ProcessorBuilder-C62bIOwj.d.cts.map +1 -0
  53. package/dist/{Queue-B-dDXyjF.d.mts → Queue-BnK3UH8r.d.mts} +3 -3
  54. package/dist/{Queue-B-dDXyjF.d.mts.map → Queue-BnK3UH8r.d.mts.map} +1 -1
  55. package/dist/{Queue-JbIjVtTf.d.cts → Queue-C4rijiM7.d.cts} +3 -3
  56. package/dist/{Queue-JbIjVtTf.d.cts.map → Queue-C4rijiM7.d.cts.map} +1 -1
  57. package/dist/{Queue-BhuofM3k.cjs → Queue-D4Iaidrs.cjs} +3 -3
  58. package/dist/{Queue-BhuofM3k.cjs.map → Queue-D4Iaidrs.cjs.map} +1 -1
  59. package/dist/{Queue-cmhqEBOB.mjs → Queue-u8w8_HpE.mjs} +3 -3
  60. package/dist/{Queue-cmhqEBOB.mjs.map → Queue-u8w8_HpE.mjs.map} +1 -1
  61. package/dist/{QueueBuilder-DXu1qxT1.mjs → QueueBuilder-2wP5lEC8.mjs} +2 -2
  62. package/dist/{QueueBuilder-DXu1qxT1.mjs.map → QueueBuilder-2wP5lEC8.mjs.map} +1 -1
  63. package/dist/{QueueBuilder-48W-q8In.d.mts → QueueBuilder-B3vL1ED2.d.mts} +2 -2
  64. package/dist/{QueueBuilder-48W-q8In.d.mts.map → QueueBuilder-B3vL1ED2.d.mts.map} +1 -1
  65. package/dist/{QueueBuilder-MfpkJ3R9.d.cts → QueueBuilder-BcPBQSSX.d.cts} +2 -2
  66. package/dist/{QueueBuilder-MfpkJ3R9.d.cts.map → QueueBuilder-BcPBQSSX.d.cts.map} +1 -1
  67. package/dist/{QueueBuilder-DdU2klPu.cjs → QueueBuilder-BxsZ9o5z.cjs} +2 -2
  68. package/dist/{QueueBuilder-DdU2klPu.cjs.map → QueueBuilder-BxsZ9o5z.cjs.map} +1 -1
  69. package/dist/{Subscriber-Bfw5cqzz.d.cts → Subscriber-BdCtfFXL.d.cts} +3 -3
  70. package/dist/{Subscriber-Bfw5cqzz.d.cts.map → Subscriber-BdCtfFXL.d.cts.map} +1 -1
  71. package/dist/{Subscriber-Czs77h-H.d.mts → Subscriber-DDo4HFNn.d.mts} +3 -3
  72. package/dist/{Subscriber-Czs77h-H.d.mts.map → Subscriber-DDo4HFNn.d.mts.map} +1 -1
  73. package/dist/{SubscriberBuilder-DUMXzjbR.mjs → SubscriberBuilder-3msj1U2w.mjs} +2 -2
  74. package/dist/{SubscriberBuilder-DUMXzjbR.mjs.map → SubscriberBuilder-3msj1U2w.mjs.map} +1 -1
  75. package/dist/{SubscriberBuilder-CYluzubx.cjs → SubscriberBuilder-CQst6Iz3.cjs} +2 -2
  76. package/dist/{SubscriberBuilder-CYluzubx.cjs.map → SubscriberBuilder-CQst6Iz3.cjs.map} +1 -1
  77. package/dist/{Topic-D2R0r24C.mjs → Topic-DN2gYJ-p.mjs} +3 -3
  78. package/dist/{Topic-D2R0r24C.mjs.map → Topic-DN2gYJ-p.mjs.map} +1 -1
  79. package/dist/{Topic-DwS3QO0m.cjs → Topic-Dp9Q7Lj2.cjs} +3 -3
  80. package/dist/{Topic-DwS3QO0m.cjs.map → Topic-Dp9Q7Lj2.cjs.map} +1 -1
  81. package/dist/adaptors/aws.cjs +2 -2
  82. package/dist/adaptors/aws.d.cts +5 -5
  83. package/dist/adaptors/aws.d.mts +5 -5
  84. package/dist/adaptors/aws.mjs +3 -3
  85. package/dist/adaptors/hono.cjs +1 -1
  86. package/dist/adaptors/hono.d.cts +1 -1
  87. package/dist/adaptors/hono.d.mts +1 -1
  88. package/dist/adaptors/hono.mjs +1 -1
  89. package/dist/adaptors/testing.cjs +5 -5
  90. package/dist/adaptors/testing.d.cts +4 -4
  91. package/dist/adaptors/testing.d.mts +4 -4
  92. package/dist/adaptors/testing.mjs +5 -5
  93. package/dist/crons/index.cjs +2 -2
  94. package/dist/crons/index.d.cts +2 -2
  95. package/dist/crons/index.d.mts +2 -2
  96. package/dist/crons/index.mjs +2 -2
  97. package/dist/endpoints/index.cjs +3 -3
  98. package/dist/endpoints/index.d.cts +3 -3
  99. package/dist/endpoints/index.d.mts +3 -3
  100. package/dist/endpoints/index.mjs +3 -3
  101. package/dist/file-server.cjs +101 -31
  102. package/dist/file-server.cjs.map +1 -1
  103. package/dist/file-server.d.cts +95 -0
  104. package/dist/file-server.d.cts.map +1 -1
  105. package/dist/file-server.d.mts +95 -0
  106. package/dist/file-server.d.mts.map +1 -1
  107. package/dist/file-server.mjs +101 -33
  108. package/dist/file-server.mjs.map +1 -1
  109. package/dist/functions/index.cjs +1 -1
  110. package/dist/functions/index.d.cts +2 -2
  111. package/dist/functions/index.d.mts +2 -2
  112. package/dist/functions/index.mjs +1 -1
  113. package/dist/{functions-CTI-hDem.mjs → functions-BvIDQKAA.mjs} +3 -3
  114. package/dist/{functions-CTI-hDem.mjs.map → functions-BvIDQKAA.mjs.map} +1 -1
  115. package/dist/{functions-D7O5SGVc.cjs → functions-DLF6-IK8.cjs} +3 -3
  116. package/dist/{functions-D7O5SGVc.cjs.map → functions-DLF6-IK8.cjs.map} +1 -1
  117. package/dist/{index-Cd2w-DRL.d.cts → index-BtzMzhMA.d.cts} +3 -3
  118. package/dist/{index-Cd2w-DRL.d.cts.map → index-BtzMzhMA.d.cts.map} +1 -1
  119. package/dist/{index-aBIcAiH5.d.mts → index-DuriFjYD.d.mts} +3 -3
  120. package/dist/{index-aBIcAiH5.d.mts.map → index-DuriFjYD.d.mts.map} +1 -1
  121. package/dist/index.cjs +1 -1
  122. package/dist/index.d.cts +1 -1
  123. package/dist/index.d.mts +1 -1
  124. package/dist/index.mjs +1 -1
  125. package/dist/pattern-C1CEcglD.mjs +241 -0
  126. package/dist/pattern-C1CEcglD.mjs.map +1 -0
  127. package/dist/pattern-QFaUIr77.cjs +288 -0
  128. package/dist/pattern-QFaUIr77.cjs.map +1 -0
  129. package/dist/processors/index.cjs +214 -0
  130. package/dist/processors/index.cjs.map +1 -0
  131. package/dist/processors/index.d.cts +78 -0
  132. package/dist/processors/index.d.cts.map +1 -0
  133. package/dist/processors/index.d.mts +78 -0
  134. package/dist/processors/index.d.mts.map +1 -0
  135. package/dist/processors/index.mjs +193 -0
  136. package/dist/processors/index.mjs.map +1 -0
  137. package/dist/queue/index.cjs +2 -2
  138. package/dist/queue/index.d.cts +2 -2
  139. package/dist/queue/index.d.mts +2 -2
  140. package/dist/queue/index.mjs +2 -2
  141. package/dist/rest-api.cjs +1 -1
  142. package/dist/rest-api.d.cts +2 -2
  143. package/dist/rest-api.d.mts +2 -2
  144. package/dist/rest-api.mjs +1 -1
  145. package/dist/subscribers/index.cjs +1 -1
  146. package/dist/subscribers/index.d.cts +2 -2
  147. package/dist/subscribers/index.d.mts +2 -2
  148. package/dist/subscribers/index.mjs +1 -1
  149. package/dist/topic/index.cjs +1 -1
  150. package/dist/topic/index.d.cts +1 -1
  151. package/dist/topic/index.d.mts +1 -1
  152. package/dist/topic/index.mjs +1 -1
  153. package/dist/worker.cjs +4 -4
  154. package/dist/worker.d.cts +7 -7
  155. package/dist/worker.d.mts +7 -7
  156. package/dist/worker.mjs +4 -4
  157. package/package.json +41 -31
@@ -1 +1 @@
1
- {"version":3,"file":"Topic-DwS3QO0m.cjs","names":["Construct","DEFAULT_LOGGER","canonicalId","provideKey","serviceKey","Publisher"],"sources":["../src/topic/Topic.ts"],"sourcesContent":["import {\n\ttype EventPublisher,\n\ttype EventPublisherConnectionString,\n\ttype PublishableMessage,\n\tPublisher,\n} from '@geekmidas/events';\nimport { DEFAULT_LOGGER } from '@geekmidas/logger/console';\nimport {\n\ttype ConstructName,\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, and any number of subscribers (`worker.topic(topic)`) bind to it.\n * `gkm build` discovers it into the manifest's `topics` field; infra provisions\n * an SNS topic.\n *\n * A construct publishes with `.event(topic, { type, payload })`, repeatable\n * across topics, each event going through its own topic's publisher. That also\n * puts `services.<topic>` in the handler, as `.dependsOn([topic])` does, for an\n * event the handler decides on itself.\n *\n * ```ts\n * export const users = new Topic('Users', {\n * events: { 'user.created': z.object({ userId: z.string() }) },\n * });\n * ```\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/** The name it was declared with — what the broker routes on. */\n\treadonly name: TName;\n\n\t/**\n\t * The event contract — a map of event type → payload schema. Named\n\t * `eventSchemas` (not `events`) to avoid clashing with `Construct.events`,\n\t * which is the array of `MappedEvent`s a construct *publishes*.\n\t */\n\treadonly eventSchemas: TEvents;\n\n\t/**\n\t * The topic as a dependency — what `.dependsOn([users])` and\n\t * `.event(users, …)` dissolve into, reachable as `services.users`.\n\t *\n\t * The producer, because publishing is the only thing depending on a topic\n\t * can mean: a subscriber binds to the topic instead, and is never handed\n\t * this. It reads `<NAME>_PUBLISHER_CONNECTION_STRING` and picks the\n\t * transport from its protocol — `pgboss://` locally, `sns://` deployed.\n\t *\n\t * A field assigned once, not a getter: services are cached by object\n\t * identity, and a fresh literal on every access never hit that cache.\n\t */\n\treadonly service: Service<\n\t\tUncapitalize<TName>,\n\t\tEventPublisher<TopicMessage<TEvents>>\n\t>;\n\n\t/** The producer's env key, read by both {@link declare} and the service. */\n\tprivate readonly connectionKey: string;\n\n\tconstructor(name: ConstructName<TName>, options: TopicOptions<TEvents>) {\n\t\tsuper(ConstructType.Topic, DEFAULT_LOGGER, [], []);\n\n\t\tthis.name = name as TName;\n\t\tthis.id = canonicalId(name as string);\n\t\tthis.eventSchemas = options.events;\n\t\tthis.connectionKey = provideKey(this.id, 'publisherConnectionString');\n\n\t\tconst envVar = this.connectionKey;\n\t\tconst topic = this.name as string;\n\t\tthis.service = {\n\t\t\tserviceName: serviceKey(this.id) as Uncapitalize<TName>,\n\t\t\tasync register({ envParser }) {\n\t\t\t\tconst { connectionString } = envParser\n\t\t\t\t\t.create((get) => ({ connectionString: get(envVar).string() }))\n\t\t\t\t\t.parse();\n\n\t\t\t\t// Named as a topic, so a broker with one address for everything\n\t\t\t\t// (pg-boss) fans each message out to every subscriber.\n\t\t\t\treturn Publisher.fromConnectionString<TopicMessage<TEvents>>(\n\t\t\t\t\tconnectionString as EventPublisherConnectionString,\n\t\t\t\t\t{ topic },\n\t\t\t\t);\n\t\t\t},\n\t\t};\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\nexport interface TopicOptions<TEvents extends TopicEvents> {\n\t/** Each event this topic carries — its type, and its payload's schema. */\n\tevents: TEvents;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAmDA,IAAa,QAAb,cAGUA,kBAAAA,UAAU;CACnB,eAAe;CAEf,OAAO,QAAQ,KAAiD;EAC/D,OAAO,QACN,OACE,IAAmC,iBAAiB,QACpD,IAAkB,SAAA,8BACrB;CACD;;;;;;;CAQA;;CAGA;;;;;;CAOA;;;;;;;;;;;;;CAcA;;CAMA;CAEA,YAAY,MAA4B,SAAgC;EACvE,MAAA,gCAA2BC,0BAAAA,gBAAgB,CAAC,GAAG,CAAC,CAAC;EAEjD,KAAK,OAAO;EACZ,KAAK,MAAA,GAAKC,oBAAAA,YAAAA,CAAY,IAAc;EACpC,KAAK,eAAe,QAAQ;EAC5B,KAAK,iBAAA,GAAgBC,oBAAAA,WAAAA,CAAW,KAAK,IAAI,2BAA2B;EAEpE,MAAM,SAAS,KAAK;EACpB,MAAM,QAAQ,KAAK;EACnB,KAAK,UAAU;GACd,cAAA,GAAaC,oBAAAA,WAAAA,CAAW,KAAK,EAAE;GAC/B,MAAM,SAAS,EAAE,aAAa;IAC7B,MAAM,EAAE,qBAAqB,UAC3B,QAAQ,SAAS,EAAE,kBAAkB,IAAI,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAC7D,MAAM;IAIR,OAAOC,kBAAAA,UAAU,qBAChB,kBACA,EAAE,MAAM,CACT;GACD;EACD;CACD;;;;;;;;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;AACD"}
1
+ {"version":3,"file":"Topic-Dp9Q7Lj2.cjs","names":["Construct","DEFAULT_LOGGER","canonicalId","provideKey","serviceKey","Publisher"],"sources":["../src/topic/Topic.ts"],"sourcesContent":["import {\n\ttype EventPublisher,\n\ttype EventPublisherConnectionString,\n\ttype PublishableMessage,\n\tPublisher,\n} from '@geekmidas/events';\nimport { DEFAULT_LOGGER } from '@geekmidas/logger/console';\nimport {\n\ttype ConstructName,\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, and any number of subscribers (`worker.topic(topic)`) bind to it.\n * `gkm build` discovers it into the manifest's `topics` field; infra provisions\n * an SNS topic.\n *\n * A construct publishes with `.event(topic, { type, payload })`, repeatable\n * across topics, each event going through its own topic's publisher. That also\n * puts `services.<topic>` in the handler, as `.dependsOn([topic])` does, for an\n * event the handler decides on itself.\n *\n * ```ts\n * export const users = new Topic('Users', {\n * events: { 'user.created': z.object({ userId: z.string() }) },\n * });\n * ```\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/** The name it was declared with — what the broker routes on. */\n\treadonly name: TName;\n\n\t/**\n\t * The event contract — a map of event type → payload schema. Named\n\t * `eventSchemas` (not `events`) to avoid clashing with `Construct.events`,\n\t * which is the array of `MappedEvent`s a construct *publishes*.\n\t */\n\treadonly eventSchemas: TEvents;\n\n\t/**\n\t * The topic as a dependency — what `.dependsOn([users])` and\n\t * `.event(users, …)` dissolve into, reachable as `services.users`.\n\t *\n\t * The producer, because publishing is the only thing depending on a topic\n\t * can mean: a subscriber binds to the topic instead, and is never handed\n\t * this. It reads `<NAME>_PUBLISHER_CONNECTION_STRING` and picks the\n\t * transport from its protocol — `pgboss://` locally, `sns://` deployed.\n\t *\n\t * A field assigned once, not a getter: services are cached by object\n\t * identity, and a fresh literal on every access never hit that cache.\n\t */\n\treadonly service: Service<\n\t\tUncapitalize<TName>,\n\t\tEventPublisher<TopicMessage<TEvents>>\n\t>;\n\n\t/** The producer's env key, read by both {@link declare} and the service. */\n\tprivate readonly connectionKey: string;\n\n\tconstructor(name: ConstructName<TName>, options: TopicOptions<TEvents>) {\n\t\tsuper(ConstructType.Topic, DEFAULT_LOGGER, [], []);\n\n\t\tthis.name = name as TName;\n\t\tthis.id = canonicalId(name as string);\n\t\tthis.eventSchemas = options.events;\n\t\tthis.connectionKey = provideKey(this.id, 'publisherConnectionString');\n\n\t\tconst envVar = this.connectionKey;\n\t\tconst topic = this.name as string;\n\t\tthis.service = {\n\t\t\tserviceName: serviceKey(this.id) as Uncapitalize<TName>,\n\t\t\tasync register({ envParser }) {\n\t\t\t\tconst { connectionString } = envParser\n\t\t\t\t\t.create((get) => ({ connectionString: get(envVar).string() }))\n\t\t\t\t\t.parse();\n\n\t\t\t\t// Named as a topic, so a broker with one address for everything\n\t\t\t\t// (pg-boss) fans each message out to every subscriber.\n\t\t\t\treturn Publisher.fromConnectionString<TopicMessage<TEvents>>(\n\t\t\t\t\tconnectionString as EventPublisherConnectionString,\n\t\t\t\t\t{ topic },\n\t\t\t\t);\n\t\t\t},\n\t\t};\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\nexport interface TopicOptions<TEvents extends TopicEvents> {\n\t/** Each event this topic carries — its type, and its payload's schema. */\n\tevents: TEvents;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAmDA,IAAa,QAAb,cAGUA,kBAAAA,UAAU;CACnB,eAAe;CAEf,OAAO,QAAQ,KAAiD;EAC/D,OAAO,QACN,OACE,IAAmC,iBAAiB,QACpD,IAAkB,SAAA,8BACrB;CACD;;;;;;;CAQA;;CAGA;;;;;;CAOA;;;;;;;;;;;;;CAcA;;CAMA;CAEA,YAAY,MAA4B,SAAgC;EACvE,MAAA,gCAA2BC,0BAAAA,gBAAgB,CAAC,GAAG,CAAC,CAAC;EAEjD,KAAK,OAAO;EACZ,KAAK,MAAA,GAAKC,oBAAAA,YAAAA,CAAY,IAAc;EACpC,KAAK,eAAe,QAAQ;EAC5B,KAAK,iBAAA,GAAgBC,oBAAAA,WAAAA,CAAW,KAAK,IAAI,2BAA2B;EAEpE,MAAM,SAAS,KAAK;EACpB,MAAM,QAAQ,KAAK;EACnB,KAAK,UAAU;GACd,cAAA,GAAaC,oBAAAA,WAAAA,CAAW,KAAK,EAAE;GAC/B,MAAM,SAAS,EAAE,aAAa;IAC7B,MAAM,EAAE,qBAAqB,UAC3B,QAAQ,SAAS,EAAE,kBAAkB,IAAI,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAC7D,MAAM;IAIR,OAAOC,kBAAAA,UAAU,qBAChB,kBACA,EAAE,MAAM,CACT;GACD;EACD;CACD;;;;;;;;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;AACD"}
@@ -1,8 +1,8 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_rolldown_runtime = require("../rolldown-runtime-y5ARvAq5.cjs");
3
3
  const require_tracing = require("../tracing-C0ooU0V2.cjs");
4
- const require_functions = require("../functions-D7O5SGVc.cjs");
5
- const require_Endpoint = require("../Endpoint-yhR2T2Wt.cjs");
4
+ const require_functions = require("../functions-DLF6-IK8.cjs");
5
+ const require_Endpoint = require("../Endpoint-BuGFNFSB.cjs");
6
6
  const require_surfaceEnv = require("../surfaceEnv-CNimK76p.cjs");
7
7
  const require_readRequestBody = require("../readRequestBody-Dej_L3c4.cjs");
8
8
  const require_runSubscriber = require("../runSubscriber-B1Yg4dID.cjs");
@@ -1,11 +1,11 @@
1
1
  import { n as StandardSchemaV1 } from "../index-DdRjYhiJ.cjs";
2
- import { i as FunctionHandler, n as Function$1 } from "../Function-B0uZhZnU.cjs";
3
- import { t as Cron } from "../Cron-bdGRSROY.cjs";
2
+ import { i as FunctionHandler, n as Function$1 } from "../Function-BIEsY0Hm.cjs";
3
+ import { t as Cron } from "../Cron-CFkpEys6.cjs";
4
4
  import { t as HttpMethod } from "../types-DMuSQeLL.cjs";
5
- import { c as EndpointSchemas, i as Endpoint, r as CookieFn } from "../Endpoint-DgrpG15P.cjs";
5
+ import { c as EndpointSchemas, i as Endpoint, r as CookieFn } from "../Endpoint-BEh-jW-f.cjs";
6
6
  import { t as Telemetry } from "../lambdaTelemetry-8vnmVZQ5.cjs";
7
- import { t as Queue } from "../Queue-JbIjVtTf.cjs";
8
- import { t as Subscriber } from "../Subscriber-Bfw5cqzz.cjs";
7
+ import { t as Queue } from "../Queue-C4rijiM7.cjs";
8
+ import { t as Subscriber } from "../Subscriber-BdCtfFXL.cjs";
9
9
  import { EnvironmentParser } from "@geekmidas/envkit";
10
10
  import { Logger } from "@geekmidas/logger";
11
11
  import { ComposableStandardSchema, InferComposableStandardSchema, InferStandardSchema } from "@geekmidas/schema";
@@ -1,11 +1,11 @@
1
1
  import { n as StandardSchemaV1 } from "../index-DdRjYhiJ.mjs";
2
- import { i as FunctionHandler, n as Function$1 } from "../Function-B_9R7Zy1.mjs";
3
- import { t as Cron } from "../Cron-i4Uetezh.mjs";
2
+ import { i as FunctionHandler, n as Function$1 } from "../Function-DfMzTGS4.mjs";
3
+ import { t as Cron } from "../Cron-CQoDSu-n.mjs";
4
4
  import { t as HttpMethod } from "../types-DMuSQeLL.mjs";
5
- import { c as EndpointSchemas, i as Endpoint, r as CookieFn } from "../Endpoint-CeFkSwh5.mjs";
5
+ import { c as EndpointSchemas, i as Endpoint, r as CookieFn } from "../Endpoint-DTXDggNl.mjs";
6
6
  import { t as Telemetry } from "../lambdaTelemetry-8vnmVZQ5.mjs";
7
- import { t as Queue } from "../Queue-B-dDXyjF.mjs";
8
- import { t as Subscriber } from "../Subscriber-Czs77h-H.mjs";
7
+ import { t as Queue } from "../Queue-BnK3UH8r.mjs";
8
+ import { t as Subscriber } from "../Subscriber-DDo4HFNn.mjs";
9
9
  import { EventPublisher } from "@geekmidas/events";
10
10
  import { Service, ServiceDiscovery, ServiceRecord } from "@geekmidas/services";
11
11
  import { EnvironmentParser } from "@geekmidas/envkit";
@@ -1,7 +1,7 @@
1
- import { o as __toESM } from "../Construct-CKUA-CTA.mjs";
1
+ import { o as __toESM } from "../Construct-CGuc_Ijk.mjs";
2
2
  import { r as recordCarrier } from "../tracing-Bbj1uoFJ.mjs";
3
- import { a as publishEvents, i as publishConstructEvents, t as FunctionBuilder } from "../functions-CTI-hDem.mjs";
4
- import { o as require_lodash_set, r as ResponseBuilder, t as Endpoint } from "../Endpoint-gPXhHKhJ.mjs";
3
+ import { a as publishEvents, i as publishConstructEvents, t as FunctionBuilder } from "../functions-BvIDQKAA.mjs";
4
+ import { o as require_lodash_set, r as ResponseBuilder, t as Endpoint } from "../Endpoint-VdaYKc2g.mjs";
5
5
  import { n as envParserFor } from "../surfaceEnv-wMT5QzgZ.mjs";
6
6
  import { a as createApiGatewayCookies, i as readRequestBody } from "../readRequestBody-BTIl3k-D.mjs";
7
7
  import { n as subscribedEvents, r as subscriberContext, t as runSubscriber } from "../runSubscriber-JCgkeYbE.mjs";
@@ -1,3 +1,3 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_HonoEndpointAdaptor = require("../HonoEndpointAdaptor-CN1jjUBJ.cjs");
2
+ const require_HonoEndpointAdaptor = require("../HonoEndpointAdaptor-BBTzbP38.cjs");
3
3
  exports.HonoEndpoint = require_HonoEndpointAdaptor.HonoEndpoint;
@@ -1,6 +1,6 @@
1
1
  import { n as StandardSchemaV1 } from "../index-DdRjYhiJ.cjs";
2
2
  import { t as HttpMethod } from "../types-DMuSQeLL.cjs";
3
- import { c as EndpointSchemas, i as Endpoint } from "../Endpoint-DgrpG15P.cjs";
3
+ import { c as EndpointSchemas, i as Endpoint } from "../Endpoint-BEh-jW-f.cjs";
4
4
  import { EnvironmentParser } from "@geekmidas/envkit";
5
5
  import { Logger } from "@geekmidas/logger";
6
6
  import { Service, ServiceDiscovery, ServiceRecord } from "@geekmidas/services";
@@ -1,6 +1,6 @@
1
1
  import { n as StandardSchemaV1 } from "../index-DdRjYhiJ.mjs";
2
2
  import { t as HttpMethod } from "../types-DMuSQeLL.mjs";
3
- import { c as EndpointSchemas, i as Endpoint } from "../Endpoint-CeFkSwh5.mjs";
3
+ import { c as EndpointSchemas, i as Endpoint } from "../Endpoint-DTXDggNl.mjs";
4
4
  import { Service, ServiceDiscovery, ServiceRecord } from "@geekmidas/services";
5
5
  import { EnvironmentParser } from "@geekmidas/envkit";
6
6
  import { Context, Hono } from "hono";
@@ -1,2 +1,2 @@
1
- import { t as HonoEndpoint } from "../HonoEndpointAdaptor-euH1oSdY.mjs";
1
+ import { t as HonoEndpoint } from "../HonoEndpointAdaptor-BR_LyaFE.mjs";
2
2
  export { HonoEndpoint };
@@ -2,14 +2,14 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_rolldown_runtime = require("../rolldown-runtime-y5ARvAq5.cjs");
3
3
  const require_construct_interface = require("../construct-interface.cjs");
4
4
  const require_auth = require("../auth.cjs");
5
- const require_functions = require("../functions-D7O5SGVc.cjs");
6
- const require_Endpoint = require("../Endpoint-yhR2T2Wt.cjs");
7
- const require_Queue = require("../Queue-BhuofM3k.cjs");
5
+ const require_functions = require("../functions-DLF6-IK8.cjs");
6
+ const require_Endpoint = require("../Endpoint-BuGFNFSB.cjs");
7
+ const require_Queue = require("../Queue-D4Iaidrs.cjs");
8
8
  const require_email = require("../email.cjs");
9
9
  const require_kysely = require("../kysely-CukArHJ0.cjs");
10
10
  const require_readRequestBody = require("../readRequestBody-Dej_L3c4.cjs");
11
- const require_Topic = require("../Topic-DwS3QO0m.cjs");
12
- const require_HonoEndpointAdaptor = require("../HonoEndpointAdaptor-CN1jjUBJ.cjs");
11
+ const require_Topic = require("../Topic-Dp9Q7Lj2.cjs");
12
+ const require_HonoEndpointAdaptor = require("../HonoEndpointAdaptor-BBTzbP38.cjs");
13
13
  const require_requestLogger = require("../requestLogger-BZcXwdBs.cjs");
14
14
  let _geekmidas_manifest = require("@geekmidas/manifest");
15
15
  let node_crypto = require("node:crypto");
@@ -1,9 +1,9 @@
1
1
  import { n as StandardSchemaV1 } from "../index-DdRjYhiJ.cjs";
2
- import { n as Function } from "../Function-B0uZhZnU.cjs";
2
+ import { n as Function } from "../Function-BIEsY0Hm.cjs";
3
3
  import { t as HttpMethod } from "../types-DMuSQeLL.cjs";
4
- import { c as EndpointSchemas, i as Endpoint } from "../Endpoint-DgrpG15P.cjs";
5
- import { t as Queue } from "../Queue-JbIjVtTf.cjs";
6
- import { t as Subscriber } from "../Subscriber-Bfw5cqzz.cjs";
4
+ import { c as EndpointSchemas, i as Endpoint } from "../Endpoint-BEh-jW-f.cjs";
5
+ import { t as Queue } from "../Queue-C4rijiM7.cjs";
6
+ import { t as Subscriber } from "../Subscriber-BdCtfFXL.cjs";
7
7
  import { t as KyselyDatabase } from "../kysely-BHYV-WC-.cjs";
8
8
  import { t as Fake } from "../fake-B7rvi4Qp.cjs";
9
9
  import { o as TestStage } from "../stages-BSCF19o2.cjs";
@@ -1,9 +1,9 @@
1
1
  import { n as StandardSchemaV1 } from "../index-DdRjYhiJ.mjs";
2
- import { n as Function } from "../Function-B_9R7Zy1.mjs";
2
+ import { n as Function } from "../Function-DfMzTGS4.mjs";
3
3
  import { t as HttpMethod } from "../types-DMuSQeLL.mjs";
4
- import { c as EndpointSchemas, i as Endpoint } from "../Endpoint-CeFkSwh5.mjs";
5
- import { t as Queue } from "../Queue-B-dDXyjF.mjs";
6
- import { t as Subscriber } from "../Subscriber-Czs77h-H.mjs";
4
+ import { c as EndpointSchemas, i as Endpoint } from "../Endpoint-DTXDggNl.mjs";
5
+ import { t as Queue } from "../Queue-BnK3UH8r.mjs";
6
+ import { t as Subscriber } from "../Subscriber-DDo4HFNn.mjs";
7
7
  import { t as KyselyDatabase } from "../kysely-CAW3QSqp.mjs";
8
8
  import { t as Fake } from "../fake-JXbkdao8.mjs";
9
9
  import { o as TestStage } from "../stages-BSCF19o2.mjs";
@@ -1,13 +1,13 @@
1
1
  import { isConsumable } from "../construct-interface.mjs";
2
2
  import { BetterAuth } from "../auth.mjs";
3
- import { a as publishEvents, i as publishConstructEvents, t as FunctionBuilder } from "../functions-CTI-hDem.mjs";
4
- import { r as ResponseBuilder, t as Endpoint } from "../Endpoint-gPXhHKhJ.mjs";
5
- import { t as Queue } from "../Queue-cmhqEBOB.mjs";
3
+ import { a as publishEvents, i as publishConstructEvents, t as FunctionBuilder } from "../functions-BvIDQKAA.mjs";
4
+ import { r as ResponseBuilder, t as Endpoint } from "../Endpoint-VdaYKc2g.mjs";
5
+ import { t as Queue } from "../Queue-u8w8_HpE.mjs";
6
6
  import { Email } from "../email.mjs";
7
7
  import { t as KyselyDatabase } from "../kysely-Cs4uPO8k.mjs";
8
8
  import { f as createObjectHeaders, s as createCookieHeaderAccessor } from "../readRequestBody-BTIl3k-D.mjs";
9
- import { t as Topic } from "../Topic-D2R0r24C.mjs";
10
- import { t as HonoEndpoint } from "../HonoEndpointAdaptor-euH1oSdY.mjs";
9
+ import { t as Topic } from "../Topic-DN2gYJ-p.mjs";
10
+ import { t as HonoEndpoint } from "../HonoEndpointAdaptor-BR_LyaFE.mjs";
11
11
  import { i as executeWithAuditTransaction, n as enforceRateLimit, t as requestLogger } from "../requestLogger-pA4gi3E1.mjs";
12
12
  import { provideKey } from "@geekmidas/manifest";
13
13
  import { randomUUID } from "node:crypto";
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_functions = require("../functions-D7O5SGVc.cjs");
3
- const require_CronBuilder = require("../CronBuilder-BXTG10u5.cjs");
2
+ const require_functions = require("../functions-DLF6-IK8.cjs");
3
+ const require_CronBuilder = require("../CronBuilder-B_NSP-Y5.cjs");
4
4
  let _geekmidas_services = require("@geekmidas/services");
5
5
  let _opentelemetry_api = require("@opentelemetry/api");
6
6
  let cron_parser = require("cron-parser");
@@ -1,5 +1,5 @@
1
- import { i as ScheduleExpression, n as CronExpression, r as RateExpression, t as Cron } from "../Cron-bdGRSROY.cjs";
2
- import { t as CronBuilder } from "../CronBuilder-B_24LZpn.cjs";
1
+ import { i as ScheduleExpression, n as CronExpression, r as RateExpression, t as Cron } from "../Cron-CFkpEys6.cjs";
2
+ import { t as CronBuilder } from "../CronBuilder-OaCRRFVk.cjs";
3
3
  import { ServiceDiscovery } from "@geekmidas/services";
4
4
  //#region src/crons/inProcess.d.ts
5
5
  /** A schedule that is running, and the way to stop it. */
@@ -1,5 +1,5 @@
1
- import { i as ScheduleExpression, n as CronExpression, r as RateExpression, t as Cron } from "../Cron-i4Uetezh.mjs";
2
- import { t as CronBuilder } from "../CronBuilder-9H4BnD6W.mjs";
1
+ import { i as ScheduleExpression, n as CronExpression, r as RateExpression, t as Cron } from "../Cron-CQoDSu-n.mjs";
2
+ import { t as CronBuilder } from "../CronBuilder-1W-G_i15.mjs";
3
3
  import { ServiceDiscovery } from "@geekmidas/services";
4
4
  //#region src/crons/inProcess.d.ts
5
5
  /** A schedule that is running, and the way to stop it. */
@@ -1,5 +1,5 @@
1
- import { a as publishEvents } from "../functions-CTI-hDem.mjs";
2
- import { n as Cron, t as CronBuilder } from "../CronBuilder-D3sF9tS_.mjs";
1
+ import { a as publishEvents } from "../functions-BvIDQKAA.mjs";
2
+ import { n as Cron, t as CronBuilder } from "../CronBuilder-BJ5LoadF.mjs";
3
3
  import { runWithRequestContext } from "@geekmidas/services";
4
4
  import { ROOT_CONTEXT, SpanKind, SpanStatusCode, context, trace } from "@opentelemetry/api";
5
5
  import { CronExpressionParser } from "cron-parser";
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_EndpointFactory = require("../EndpointFactory-CSrAyazj.cjs");
3
- const require_functions = require("../functions-D7O5SGVc.cjs");
4
- const require_Endpoint = require("../Endpoint-yhR2T2Wt.cjs");
2
+ const require_EndpointFactory = require("../EndpointFactory-C6pVda4y.cjs");
3
+ const require_functions = require("../functions-DLF6-IK8.cjs");
4
+ const require_Endpoint = require("../Endpoint-BuGFNFSB.cjs");
5
5
  const require_surfaceEnv = require("../surfaceEnv-CNimK76p.cjs");
6
6
  const require_readRequestBody = require("../readRequestBody-Dej_L3c4.cjs");
7
7
  exports.BUILT_IN_SECURITY_SCHEMES = require_EndpointFactory.BUILT_IN_SECURITY_SCHEMES;
@@ -1,6 +1,6 @@
1
- import { s as publishConstructEvents } from "../Construct-CFW0zjdD.cjs";
2
- import { A as SecurityScheme, C as ActorExtractor, D as BuiltInSecuritySchemeId, E as BUILT_IN_SECURITY_SCHEMES, M as getSecurityScheme, N as isBuiltInSecurityScheme, O as OAuthFlow, S as RlsContextExtractor, T as Authorizer, _ as SessionAuth, a as EndpointContext, b as RlsConfig, c as EndpointSchemas, d as HeaderFn, f as RedirectStatus, g as NoAuthenticator, h as SuccessStatus, i as Endpoint, j as createAuthorizer, k as OAuthFlows, l as EndpointStatus, n as ConvertRouteParams, o as EndpointHandler, p as ResponseBuilder, r as CookieFn, s as EndpointOutput, u as EndpointSurface, v as RLS_BYPASS, w as MappedAudit, x as RlsContext, y as RlsBypass } from "../Endpoint-DgrpG15P.cjs";
3
- import { a as UPLOAD_POST_SCHEMA, c as UploadContentTypeUnresolved, d as UploadOptions, f as UploadPost, i as UploadHandler, l as UploadExpiryInvalid, n as EndpointBuilder, o as UploadAcceptInvalid, r as UploadEndpointBuilder, s as UploadContentTypeRefused, t as EndpointFactory, u as UploadMaxSizeInvalid } from "../EndpointFactory-Cc863XJS.cjs";
1
+ import { s as publishConstructEvents } from "../Construct-127M7yrl.cjs";
2
+ import { A as SecurityScheme, C as ActorExtractor, D as BuiltInSecuritySchemeId, E as BUILT_IN_SECURITY_SCHEMES, M as getSecurityScheme, N as isBuiltInSecurityScheme, O as OAuthFlow, S as RlsContextExtractor, T as Authorizer, _ as SessionAuth, a as EndpointContext, b as RlsConfig, c as EndpointSchemas, d as HeaderFn, f as RedirectStatus, g as NoAuthenticator, h as SuccessStatus, i as Endpoint, j as createAuthorizer, k as OAuthFlows, l as EndpointStatus, n as ConvertRouteParams, o as EndpointHandler, p as ResponseBuilder, r as CookieFn, s as EndpointOutput, u as EndpointSurface, v as RLS_BYPASS, w as MappedAudit, x as RlsContext, y as RlsBypass } from "../Endpoint-BEh-jW-f.cjs";
3
+ import { a as UPLOAD_POST_SCHEMA, c as UploadContentTypeUnresolved, d as UploadOptions, f as UploadPost, i as UploadHandler, l as UploadExpiryInvalid, n as EndpointBuilder, o as UploadAcceptInvalid, r as UploadEndpointBuilder, s as UploadContentTypeRefused, t as EndpointFactory, u as UploadMaxSizeInvalid } from "../EndpointFactory-B2dCS9dy.cjs";
4
4
  import { EnvironmentParser } from "@geekmidas/envkit";
5
5
  import { Context } from "hono";
6
6
  import { HttpError } from "@geekmidas/errors";
@@ -1,6 +1,6 @@
1
- import { s as publishConstructEvents } from "../Construct-B7AXn0xH.mjs";
2
- import { A as SecurityScheme, C as ActorExtractor, D as BuiltInSecuritySchemeId, E as BUILT_IN_SECURITY_SCHEMES, M as getSecurityScheme, N as isBuiltInSecurityScheme, O as OAuthFlow, S as RlsContextExtractor, T as Authorizer, _ as SessionAuth, a as EndpointContext, b as RlsConfig, c as EndpointSchemas, d as HeaderFn, f as RedirectStatus, g as NoAuthenticator, h as SuccessStatus, i as Endpoint, j as createAuthorizer, k as OAuthFlows, l as EndpointStatus, n as ConvertRouteParams, o as EndpointHandler, p as ResponseBuilder, r as CookieFn, s as EndpointOutput, u as EndpointSurface, v as RLS_BYPASS, w as MappedAudit, x as RlsContext, y as RlsBypass } from "../Endpoint-CeFkSwh5.mjs";
3
- import { a as UPLOAD_POST_SCHEMA, c as UploadContentTypeUnresolved, d as UploadOptions, f as UploadPost, i as UploadHandler, l as UploadExpiryInvalid, n as EndpointBuilder, o as UploadAcceptInvalid, r as UploadEndpointBuilder, s as UploadContentTypeRefused, t as EndpointFactory, u as UploadMaxSizeInvalid } from "../EndpointFactory-Dbw9ugkX.mjs";
1
+ import { s as publishConstructEvents } from "../Construct-DySlwh8o.mjs";
2
+ import { A as SecurityScheme, C as ActorExtractor, D as BuiltInSecuritySchemeId, E as BUILT_IN_SECURITY_SCHEMES, M as getSecurityScheme, N as isBuiltInSecurityScheme, O as OAuthFlow, S as RlsContextExtractor, T as Authorizer, _ as SessionAuth, a as EndpointContext, b as RlsConfig, c as EndpointSchemas, d as HeaderFn, f as RedirectStatus, g as NoAuthenticator, h as SuccessStatus, i as Endpoint, j as createAuthorizer, k as OAuthFlows, l as EndpointStatus, n as ConvertRouteParams, o as EndpointHandler, p as ResponseBuilder, r as CookieFn, s as EndpointOutput, u as EndpointSurface, v as RLS_BYPASS, w as MappedAudit, x as RlsContext, y as RlsBypass } from "../Endpoint-DTXDggNl.mjs";
3
+ import { a as UPLOAD_POST_SCHEMA, c as UploadContentTypeUnresolved, d as UploadOptions, f as UploadPost, i as UploadHandler, l as UploadExpiryInvalid, n as EndpointBuilder, o as UploadAcceptInvalid, r as UploadEndpointBuilder, s as UploadContentTypeRefused, t as EndpointFactory, u as UploadMaxSizeInvalid } from "../EndpointFactory-D2HXcUrJ.mjs";
4
4
  import { HttpError } from "@geekmidas/errors";
5
5
  import { EnvironmentParser } from "@geekmidas/envkit";
6
6
  import { Context } from "hono";
@@ -1,6 +1,6 @@
1
- import { a as UploadAcceptInvalid, c as UploadExpiryInvalid, d as BUILT_IN_SECURITY_SCHEMES, f as createAuthorizer, i as UPLOAD_POST_SCHEMA, l as UploadMaxSizeInvalid, m as isBuiltInSecurityScheme, n as EndpointBuilder, o as UploadContentTypeRefused, p as getSecurityScheme, r as UploadEndpointBuilder, s as UploadContentTypeUnresolved, t as EndpointFactory, u as RLS_BYPASS } from "../EndpointFactory-c2ZugGig.mjs";
2
- import { i as publishConstructEvents } from "../functions-CTI-hDem.mjs";
3
- import { a as NoAuthenticator, i as SuccessStatus, n as RedirectStatus, r as ResponseBuilder, t as Endpoint } from "../Endpoint-gPXhHKhJ.mjs";
1
+ import { a as UploadAcceptInvalid, c as UploadExpiryInvalid, d as BUILT_IN_SECURITY_SCHEMES, f as createAuthorizer, i as UPLOAD_POST_SCHEMA, l as UploadMaxSizeInvalid, m as isBuiltInSecurityScheme, n as EndpointBuilder, o as UploadContentTypeRefused, p as getSecurityScheme, r as UploadEndpointBuilder, s as UploadContentTypeUnresolved, t as EndpointFactory, u as RLS_BYPASS } from "../EndpointFactory-CUpqgtaH.mjs";
2
+ import { i as publishConstructEvents } from "../functions-BvIDQKAA.mjs";
3
+ import { a as NoAuthenticator, i as SuccessStatus, n as RedirectStatus, r as ResponseBuilder, t as Endpoint } from "../Endpoint-VdaYKc2g.mjs";
4
4
  import { n as envParserFor, t as defaultEnvParser } from "../surfaceEnv-wMT5QzgZ.mjs";
5
5
  import { a as createApiGatewayCookies, c as createHonoCookies, d as createNoopHeaders, i as readRequestBody, l as createHonoHeaders, n as SUPPORTED_BODY_TYPES, o as createApiGatewayHeaders, r as UnsupportedRequestContentType, t as MalformedRequestBody, u as createNoopCookies } from "../readRequestBody-BTIl3k-D.mjs";
6
6
  export { BUILT_IN_SECURITY_SCHEMES, Endpoint, EndpointBuilder, EndpointFactory, MalformedRequestBody, NoAuthenticator, RLS_BYPASS, RedirectStatus, ResponseBuilder, SUPPORTED_BODY_TYPES, SuccessStatus, UPLOAD_POST_SCHEMA, UnsupportedRequestContentType, UploadAcceptInvalid, UploadContentTypeRefused, UploadContentTypeUnresolved, UploadEndpointBuilder, UploadExpiryInvalid, UploadMaxSizeInvalid, createApiGatewayCookies, createApiGatewayHeaders, createAuthorizer, createHonoCookies, createHonoHeaders, createNoopCookies, createNoopHeaders, defaultEnvParser, envParserFor, getSecurityScheme, isBuiltInSecurityScheme, publishConstructEvents, readRequestBody };
@@ -1,38 +1,36 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_expire = require("./expire-CieftS01.cjs");
3
+ const require_pattern = require("./pattern-QFaUIr77.cjs");
3
4
  let _geekmidas_manifest = require("@geekmidas/manifest");
4
5
  let _geekmidas_storage = require("@geekmidas/storage");
6
+ let _geekmidas_logger_console = require("@geekmidas/logger/console");
5
7
  //#region src/file-server.ts
6
- /**
7
- * `FileServer` — a domain that serves a bucket's objects.
8
- *
9
- * `ObjectStorage` is the bucket: it writes, it presigns, and its URL is never
10
- * public. Serving those objects on a domain is a different thing, and it is a
11
- * construct of its own rather than a flag on the bucket — see
12
- * `docs/design/constructs-paradigm.md`, "Why a separate construct, and what it
13
- * costs", for the comparison that rules the other two shapes out.
14
- *
15
- * ```ts
16
- * export const uploads = new FileServer('Uploads', { open: ['brand/**'] });
17
- *
18
- * uploads.getUploadURL({ path, contentType }) // an S3 presign at the bucket
19
- * uploads.url('brand/logo.png') // open path — no signature
20
- * uploads.signedUrl('invoices/7.pdf') // one object, one recipient
21
- * ```
22
- *
23
- * **One construct, one client, both halves.** How a file lands in the bucket is
24
- * the same whether or not it is served, so splitting the upload half from the
25
- * serving half would be ceremony rather than safety. The client is a *superset*
26
- * of `StorageClient`, made literal in the type, so anything taking
27
- * `services.uploads` keeps compiling when the construct behind it grows a
28
- * serving half.
29
- *
30
- * **The id names the bucket, not the surface.** This is the detail that has to
31
- * be right before anything ships, because getting it backwards is destructive:
32
- * the migration people actually perform is serving a bucket they already have,
33
- * and naming the bucket after the construct makes that edit *add* a node rather
34
- * than rename the one holding the data.
35
- */
8
+ /** A processed URL was asked of a file server the processor is not on. */
9
+ var ProcessorNotOnThisServer = class extends Error {
10
+ pattern;
11
+ server;
12
+ expected;
13
+ constructor(pattern, server, expected) {
14
+ super(`The processor at '${pattern}' belongs to '${server}', not '${expected}'. Ask its own file server for the URL: services.<its id>.processedUrl(…).`);
15
+ this.pattern = pattern;
16
+ this.server = server;
17
+ this.expected = expected;
18
+ this.name = "ProcessorNotOnThisServer";
19
+ }
20
+ };
21
+ /** An immutable pattern that no open pattern admits. */
22
+ var ImmutableNotOpen = class extends Error {
23
+ id;
24
+ pattern;
25
+ open;
26
+ constructor(id, pattern, open) {
27
+ super(`'${id}' says '${pattern}' is immutable, and no open path admits it` + (open.length ? ` (open: ${open.join(", ")})` : " — it opens none") + ". Only an open path is served by the edge, so only one can be cached there: add it to `open`, or narrow it to one that is.");
28
+ this.id = id;
29
+ this.pattern = pattern;
30
+ this.open = open;
31
+ this.name = "ImmutableNotOpen";
32
+ }
33
+ };
36
34
  /**
37
35
  * A bucket setting given to a file server that fronts a bucket declared
38
36
  * elsewhere — where the bucket's own construct is the one that says it.
@@ -67,21 +65,33 @@ var FileServer = class {
67
65
  /** The id of the surface node, as opposed to the bucket's. */
68
66
  surfaceId;
69
67
  open;
68
+ immutable;
70
69
  /** The vended bucket's expiry, checked once: prefix → days. */
71
70
  expire;
71
+ /** Days its processors' derivatives are kept, when it says. */
72
+ derivedDays;
72
73
  /**
73
74
  * Declared once and read by both `declare()` and `connect()`, so the keys the
74
75
  * target publishes and the keys the client reads cannot drift.
75
76
  */
76
77
  keys;
78
+ /**
79
+ * How a processor reaches the bucket: the bucket's own URL and nothing of
80
+ * the serving half, which a processor never needs. A field, not a getter:
81
+ * services are cached by object identity.
82
+ */
83
+ origin;
77
84
  constructor(id, options = {}) {
78
85
  this.options = options;
79
86
  const canonical = (0, _geekmidas_manifest.canonicalId)(id);
80
87
  this.id = canonical;
81
88
  this.open = options.open ?? [];
89
+ this.immutable = options.immutable ?? [];
90
+ for (const pattern of this.immutable) if (!this.covers(pattern)) throw new ImmutableNotOpen(canonical, pattern, this.open);
82
91
  const originId = options.origin?.id ?? canonical;
83
92
  if (options.origin && options.expire) throw new BucketOptionOnOrigin(canonical, originId, "expire");
84
93
  this.expire = require_expire.expiryDays(canonical, options.expire);
94
+ this.derivedDays = options.derived ? require_expire.expiryDays(canonical, { [_geekmidas_manifest.DERIVED_PREFIX]: options.derived })?.[_geekmidas_manifest.DERIVED_PREFIX] : void 0;
85
95
  this.surfaceId = `${canonical}Server`;
86
96
  this.keys = {
87
97
  origin: (0, _geekmidas_manifest.provideKey)(originId, "url"),
@@ -91,6 +101,48 @@ var FileServer = class {
91
101
  serviceName: (0, _geekmidas_manifest.serviceKey)(canonical),
92
102
  register: (registerOptions) => this.connect(registerOptions)
93
103
  };
104
+ const originKey = this.keys.origin;
105
+ this.origin = {
106
+ serviceName: `${(0, _geekmidas_manifest.serviceKey)(originId)}ProcessorOrigin`,
107
+ register: ({ envParser }) => {
108
+ const { url } = envParser.create((get) => ({ url: get(originKey).string() })).parse();
109
+ return (0, _geekmidas_storage.createStorageClient)(url);
110
+ }
111
+ };
112
+ }
113
+ /**
114
+ * A processor behind one path pattern of this server: a handler that makes
115
+ * a derivative of an open object on read.
116
+ *
117
+ * ```ts
118
+ * export const thumbnails = assets
119
+ * .process('/i/:width/*')
120
+ * .params(z.object({ width: z.enum(['64', '256', '1024']) }))
121
+ * .native(['sharp'])
122
+ * .handle(async ({ params, object }) => ({
123
+ * body: await sharp(await object.bytes()).resize(Number(params.width)).webp().toBuffer(),
124
+ * contentType: 'image/webp',
125
+ * cache: 'immutable',
126
+ * }));
127
+ * ```
128
+ *
129
+ * Open paths only: a key no `open` pattern admits is answered 404, as an
130
+ * unsigned read of it would be.
131
+ *
132
+ * @throws {ProcessorPatternInvalid} for a pattern the edge cannot route.
133
+ */
134
+ process(pattern, options = {}) {
135
+ return new require_pattern.ProcessorBuilder({
136
+ parsed: require_pattern.parsePattern(pattern),
137
+ surfaceId: this.surfaceId,
138
+ bucketId: this.options.origin?.id ?? this.id,
139
+ origin: this.origin,
140
+ serves: (key) => this.serves(key),
141
+ logger: options.logger ?? _geekmidas_logger_console.DEFAULT_LOGGER,
142
+ native: [],
143
+ services: [],
144
+ constructs: []
145
+ });
94
146
  }
95
147
  /**
96
148
  * The served surface, as something an edge can point at.
@@ -129,6 +181,9 @@ var FileServer = class {
129
181
  id: this.surfaceId,
130
182
  of: origin?.id ?? this.id,
131
183
  ...this.open.length ? { open: this.open } : {},
184
+ ...this.immutable.length ? { immutable: this.immutable } : {},
185
+ ...this.derivedDays !== void 0 ? { derivedDays: this.derivedDays } : {},
186
+ ...this.options.telemetry ? { telemetry: this.options.telemetry.id } : {},
132
187
  provides: [this.keys.server]
133
188
  };
134
189
  if (origin) return [surface];
@@ -162,7 +217,12 @@ var FileServer = class {
162
217
  return Object.assign(Object.create(storage), {
163
218
  url: serve,
164
219
  openUrl: serve,
165
- signedUrl: (key, expiresIn) => storage.getDownloadURL({ path: key }, expiresIn)
220
+ signedUrl: (key, expiresIn) => storage.getDownloadURL({ path: key }, expiresIn),
221
+ processedUrl: (processor, params, key) => {
222
+ if (processor.surfaceId !== this.surfaceId) throw new ProcessorNotOnThisServer(processor.pattern, processor.surfaceId, this.surfaceId);
223
+ if (!this.serves(key)) throw new NotOpen(key, this.open);
224
+ return `${base}${require_pattern.fillPattern(processor.parsed, params, key)}`;
225
+ }
166
226
  });
167
227
  }
168
228
  /**
@@ -176,6 +236,14 @@ var FileServer = class {
176
236
  serves(key) {
177
237
  return this.open.some((pattern) => globToRegExp(pattern).test(key));
178
238
  }
239
+ /**
240
+ * Whether every key a pattern admits is open: the pattern itself, each of
241
+ * its wildcards taken as a segment of its own, is a key an open pattern
242
+ * serves — `brand/logos/*` under `brand/**`, but not `**` under `brand/**`.
243
+ */
244
+ covers(pattern) {
245
+ return this.serves(pattern.replace(/\*\*/g, "x/x").replace(/\*/g, "x"));
246
+ }
179
247
  };
180
248
  /**
181
249
  * One open pattern as an anchored expression.
@@ -204,6 +272,8 @@ function globToRegExp(pattern) {
204
272
  //#endregion
205
273
  exports.BucketOptionOnOrigin = BucketOptionOnOrigin;
206
274
  exports.FileServer = FileServer;
275
+ exports.ImmutableNotOpen = ImmutableNotOpen;
207
276
  exports.NotOpen = NotOpen;
277
+ exports.ProcessorNotOnThisServer = ProcessorNotOnThisServer;
208
278
 
209
279
  //# sourceMappingURL=file-server.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"file-server.cjs","names":["canonicalId","expiryDays","provideKey","serviceKey","createStorageClient"],"sources":["../src/file-server.ts"],"sourcesContent":["/**\n * `FileServer` — a domain that serves a bucket's objects.\n *\n * `ObjectStorage` is the bucket: it writes, it presigns, and its URL is never\n * public. Serving those objects on a domain is a different thing, and it is a\n * construct of its own rather than a flag on the bucket — see\n * `docs/design/constructs-paradigm.md`, \"Why a separate construct, and what it\n * costs\", for the comparison that rules the other two shapes out.\n *\n * ```ts\n * export const uploads = new FileServer('Uploads', { open: ['brand/**'] });\n *\n * uploads.getUploadURL({ path, contentType }) // an S3 presign at the bucket\n * uploads.url('brand/logo.png') // open path — no signature\n * uploads.signedUrl('invoices/7.pdf') // one object, one recipient\n * ```\n *\n * **One construct, one client, both halves.** How a file lands in the bucket is\n * the same whether or not it is served, so splitting the upload half from the\n * serving half would be ceremony rather than safety. The client is a *superset*\n * of `StorageClient`, made literal in the type, so anything taking\n * `services.uploads` keeps compiling when the construct behind it grows a\n * serving half.\n *\n * **The id names the bucket, not the surface.** This is the detail that has to\n * be right before anything ships, because getting it backwards is destructive:\n * the migration people actually perform is serving a bucket they already have,\n * and naming the bucket after the construct makes that edit *add* a node rather\n * than rename the one holding the data.\n */\n\nimport {\n\ttype ConstructName,\n\tcanonicalId,\n\ttype Declaration,\n\tprovideKey,\n\tserviceKey,\n} from '@geekmidas/manifest';\nimport type { Service, ServiceRegisterOptions } from '@geekmidas/services';\nimport { createStorageClient, type StorageClient } from '@geekmidas/storage';\nimport type { Construct, Declarable } from './construct-interface';\nimport { expiryDays, type ObjectExpiry } from './expire';\n\n/**\n * A key an open pattern admits.\n *\n * The same template-literal trick `ConstructName` uses, so `open` is checked at\n * the call site rather than only at the edge:\n *\n * ```ts\n * files.url('brand/logo.png') // ok\n * files.url(`avatars/${id}.png`) // ok — a template literal still matches\n * files.url('invoices/7.pdf') // not assignable — never served unsigned\n * ```\n *\n * A single star and a double star resolve to the same type, stated rather than\n * discovered: a template literal cannot exclude `/`, so this is a prefix-and-\n * suffix guard while the *exact* pattern is enforced by the bucket policy, by\n * the cache behaviour, and by this construct's own runtime check. The type says\n * plausibly open; the infrastructure says definitely.\n */\nexport type Served<P extends string> = P extends `${infer Head}**`\n\t? `${Head}${string}`\n\t: P extends `${infer Head}*${infer Tail}`\n\t\t? `${Head}${string}${Tail}`\n\t\t: P;\n\n/** What consuming a file server hands you: a bucket client that also serves. */\nexport interface FileServerClient<TOpen extends string = never>\n\textends StorageClient {\n\t/**\n\t * The unsigned address of an open object.\n\t *\n\t * Compile-checked against the declared patterns, *and* checked again at\n\t * runtime — a JavaScript caller gets no compiler, and minting an unsigned\n\t * URL for a private object is a leak rather than a mistake.\n\t *\n\t * @throws {NotOpen} when the key matches no declared pattern.\n\t */\n\turl(key: Served<TOpen>): string;\n\n\t/**\n\t * The same thing for a key the compiler cannot see.\n\t *\n\t * A fully dynamic key does not type, which is correct and is also the case\n\t * people will hit — so the escape hatch is a named method rather than a\n\t * cast, and it runs the *same* runtime check rather than being a way around\n\t * it.\n\t *\n\t * @throws {NotOpen} when the key matches no declared pattern.\n\t */\n\topenUrl(key: string): string;\n\n\t/**\n\t * A signed address for one object and one recipient.\n\t *\n\t * Today this is an **S3 presign at the bucket**, which is what works\n\t * unchanged against MinIO locally and against S3 deployed. It is deliberately\n\t * not described as a CDN URL: CloudFront signing is a different mechanism,\n\t * with different keys, against a different host, and it needs key material\n\t * this construct does not yet declare.\n\t *\n\t * @param expiresIn - minutes, matching `getUploadURL`.\n\t */\n\tsignedUrl(key: string, expiresIn?: number): Promise<string>;\n}\n\nexport interface FileServerOptions<TOpen extends string = never> {\n\t/**\n\t * Paths served without a signature. Everything else needs one.\n\t *\n\t * Private by default: a bucket where forgetting a flag publishes user\n\t * uploads is the wrong default, so this is an exception list, and listing it\n\t * in code means exactly one set of paths is unauthenticated and it is\n\t * reviewable in a diff.\n\t *\n\t * Uploading is deliberately *not* restricted to these. Writing to a path\n\t * that happens to be open is ordinary; the only asymmetry worth enforcing is\n\t * that you cannot hand out an unsigned URL for something the server would\n\t * refuse to serve unsigned.\n\t */\n\topen?: readonly TOpen[];\n\t/** Keep previous versions of an object. */\n\tversioned?: boolean;\n\t/**\n\t * Delete the objects under each prefix this many days after they were\n\t * written — `{ 'previews/': { days: 7 } }`. A setting of the bucket this\n\t * server vends, so it cannot be given with `origin`.\n\t */\n\texpire?: ObjectExpiry;\n\t/**\n\t * A bucket declared elsewhere, instead of one of this server's own.\n\t *\n\t * `new FileServer('Uploads')` declares both nodes; this fronts a bucket that\n\t * already exists, which is also how the two edges come back when you want\n\t * them separately — a handler depending on the bucket writes, a handler\n\t * depending on the server mints URLs.\n\t */\n\torigin?: Declarable;\n}\n\n/**\n * A bucket setting given to a file server that fronts a bucket declared\n * elsewhere — where the bucket's own construct is the one that says it.\n */\nexport class BucketOptionOnOrigin extends Error {\n\tconstructor(\n\t\treadonly server: string,\n\t\treadonly origin: string,\n\t\treadonly option: string,\n\t) {\n\t\tsuper(\n\t\t\t`'${server}' fronts '${origin}', which declares itself, so '${option}' ` +\n\t\t\t\t`belongs on the construct that declares '${origin}', not on the file server.`,\n\t\t);\n\t\tthis.name = 'BucketOptionOnOrigin';\n\t}\n}\n\n/** A key was asked to be served unsigned, and no open pattern admits it. */\nexport class NotOpen extends Error {\n\tconstructor(\n\t\treadonly key: string,\n\t\treadonly open: readonly string[],\n\t) {\n\t\tsuper(\n\t\t\t`'${key}' is not served without a signature. ` +\n\t\t\t\t(open.length\n\t\t\t\t\t? `Open paths: ${open.join(', ')}. Use signedUrl() for anything else.`\n\t\t\t\t\t: 'This file server declares no open paths, so every object needs signedUrl().'),\n\t\t);\n\t\tthis.name = 'NotOpen';\n\t}\n}\n\nexport class FileServer<\n\tTName extends string = string,\n\tTOpen extends string = never,\n> implements Construct<TName, FileServerClient<TOpen>>\n{\n\treadonly id: TName;\n\treadonly service: Service<Uncapitalize<TName>, FileServerClient<TOpen>>;\n\n\t/** The id of the surface node, as opposed to the bucket's. */\n\treadonly surfaceId: string;\n\n\tprivate readonly open: readonly string[];\n\n\t/** The vended bucket's expiry, checked once: prefix → days. */\n\tprivate readonly expire: Record<string, number> | undefined;\n\n\t/**\n\t * Declared once and read by both `declare()` and `connect()`, so the keys the\n\t * target publishes and the keys the client reads cannot drift.\n\t */\n\tprivate readonly keys: { origin: string; server: string };\n\n\tconstructor(\n\t\tid: ConstructName<TName>,\n\t\tprivate readonly options: FileServerOptions<TOpen> = {},\n\t) {\n\t\tconst canonical = canonicalId(id as string);\n\n\t\tthis.id = canonical as TName;\n\t\tthis.open = options.open ?? [];\n\n\t\t// The surface takes the suffix and the bucket keeps the plain id, which\n\t\t// is what makes \"serve a bucket I already have\" an edit that adds a node\n\t\t// instead of renaming the one holding the data.\n\t\tconst originId = options.origin?.id ?? canonical;\n\t\tif (options.origin && options.expire) {\n\t\t\tthrow new BucketOptionOnOrigin(canonical, originId, 'expire');\n\t\t}\n\t\tthis.expire = expiryDays(canonical, options.expire);\n\t\tthis.surfaceId = `${canonical}Server`;\n\n\t\tthis.keys = {\n\t\t\torigin: provideKey(originId, 'url'),\n\t\t\tserver: provideKey(this.surfaceId, 'url'),\n\t\t};\n\n\t\t// A field, not a getter: consumers cache services by object identity.\n\t\tthis.service = {\n\t\t\tserviceName: serviceKey(canonical) as Uncapitalize<TName>,\n\t\t\tregister: (registerOptions) => this.connect(registerOptions),\n\t\t};\n\t}\n\n\t/**\n\t * The served surface, as something an edge can point at.\n\t *\n\t * Depending on the `FileServer` itself points at the **bucket** — its\n\t * canonical node — which is right for a handler: it presigns and writes. But\n\t * a bucket's URL is never public, so a site depending on it gets nothing\n\t * inlined, which is correct and also useless.\n\t *\n\t * This is the other half: `.dependsOn([uploads.server])` points at the\n\t * surface, whose address *is* public, and is what puts\n\t * `VITE_UPLOADS_SERVER_URL` in a bundle. The same accessor shape\n\t * `database.reader()` uses, and for the same reason — one construct owning\n\t * two addresses needs a way to name the second.\n\t */\n\tget server(): Declarable {\n\t\tconst surface = this.declare().find(\n\t\t\t(declaration) => declaration.id === this.surfaceId,\n\t\t);\n\n\t\treturn {\n\t\t\tid: this.surfaceId,\n\t\t\tdeclare: () => (surface ? [surface] : []),\n\t\t};\n\t}\n\n\t/**\n\t * The bucket and the surface over it — or just the surface, when the bucket\n\t * was declared elsewhere.\n\t *\n\t * The surface names its origin through `of`, which is what makes the\n\t * reference check work: an origin resolving to nothing is a build failure,\n\t * the same guarantee `assertDerivations` already gives a reader and a schema\n\t * tenant.\n\t */\n\tdeclare(): Declaration[] {\n\t\tconst origin = this.options.origin;\n\n\t\tconst surface: Declaration = {\n\t\t\tkind: 'file-server',\n\t\t\tid: this.surfaceId,\n\t\t\tof: origin?.id ?? this.id,\n\t\t\t...(this.open.length ? { open: this.open } : {}),\n\t\t\tprovides: [this.keys.server],\n\t\t};\n\n\t\t// A vended bucket is declared here; a given one already declares itself,\n\t\t// and declaring it twice is the duplication the whole model removes.\n\t\tif (origin) return [surface];\n\n\t\treturn [\n\t\t\t{\n\t\t\t\tkind: 'objects',\n\t\t\t\tid: this.id,\n\t\t\t\tprovides: [this.keys.origin],\n\t\t\t\t...(this.options.versioned ? { versioned: true } : {}),\n\t\t\t\t...(this.expire ? { expire: this.expire } : {}),\n\t\t\t},\n\t\t\tsurface,\n\t\t];\n\t}\n\n\t/**\n\t * The bucket's client, with the serving half added.\n\t *\n\t * Both addresses are read here, which is also how the environment reaches a\n\t * handler: the sniffer records what this reads, so `.dependsOn([uploads])`\n\t * carries the bucket URL and the server URL without either being written\n\t * down beside the handler.\n\t */\n\tprivate async connect(\n\t\toptions: ServiceRegisterOptions,\n\t): Promise<FileServerClient<TOpen>> {\n\t\tconst { origin, server } = options.envParser\n\t\t\t.create((get) => ({\n\t\t\t\torigin: get(this.keys.origin).string(),\n\t\t\t\tserver: get(this.keys.server).string(),\n\t\t\t}))\n\t\t\t.parse();\n\n\t\tconst storage = createStorageClient(origin);\n\t\tconst base = server.replace(/\\/+$/, '');\n\n\t\tconst serve = (key: string): string => {\n\t\t\tif (!this.serves(key)) throw new NotOpen(key, this.open);\n\n\t\t\treturn `${base}/${key.replace(/^\\/+/, '')}`;\n\t\t};\n\n\t\t// Delegating rather than copying: the bucket half is whatever the driver\n\t\t// produced, including anything it grows later, and only the serving half\n\t\t// is added on top.\n\t\treturn Object.assign(Object.create(storage) as StorageClient, {\n\t\t\turl: serve as (key: Served<TOpen>) => string,\n\t\t\topenUrl: serve,\n\t\t\tsignedUrl: (key: string, expiresIn?: number) =>\n\t\t\t\tstorage.getDownloadURL({ path: key } as never, expiresIn),\n\t\t}) as FileServerClient<TOpen>;\n\t}\n\n\t/**\n\t * Whether an open pattern admits a key.\n\t *\n\t * The runtime half of {@link Served}, and stricter than it: here a single\n\t * star stops at a segment boundary and a double star does not, which is the\n\t * distinction a template-literal type cannot draw and the infrastructure\n\t * does.\n\t */\n\tprivate serves(key: string): boolean {\n\t\treturn this.open.some((pattern) => globToRegExp(pattern).test(key));\n\t}\n}\n\n/**\n * One open pattern as an anchored expression.\n *\n * A double star crosses `/`; a single star does not. Both are consumed in one\n * left-to-right pass rather than by substitution, so a pattern cannot be\n * rewritten into a placeholder that a later pass mistakes for literal text.\n */\nfunction globToRegExp(pattern: string): RegExp {\n\tlet source = '';\n\n\tfor (let index = 0; index < pattern.length; index++) {\n\t\tconst character = pattern[index];\n\n\t\tif (character !== '*') {\n\t\t\tsource += character?.replace(/[.+^${}()|[\\]\\\\?]/, '\\\\$&') ?? '';\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (pattern[index + 1] === '*') {\n\t\t\tsource += '.*';\n\t\t\tindex++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tsource += '[^/]*';\n\t}\n\n\treturn new RegExp(`^${source}$`);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"file-server.cjs","names":["canonicalId","expiryDays","DERIVED_PREFIX","provideKey","serviceKey","createStorageClient","ProcessorBuilder","parsePattern","DEFAULT_LOGGER","fillPattern"],"sources":["../src/file-server.ts"],"sourcesContent":["/**\n * `FileServer` — a domain that serves a bucket's objects.\n *\n * `ObjectStorage` is the bucket: it writes, it presigns, and its URL is never\n * public. Serving those objects on a domain is a different thing, and it is a\n * construct of its own rather than a flag on the bucket — see\n * `docs/design/constructs-paradigm.md`, \"Why a separate construct, and what it\n * costs\", for the comparison that rules the other two shapes out.\n *\n * ```ts\n * export const uploads = new FileServer('Uploads', { open: ['brand/**'] });\n *\n * uploads.getUploadURL({ path, contentType }) // an S3 presign at the bucket\n * uploads.url('brand/logo.png') // open path — no signature\n * uploads.signedUrl('invoices/7.pdf') // one object, one recipient\n * ```\n *\n * **One construct, one client, both halves.** How a file lands in the bucket is\n * the same whether or not it is served, so splitting the upload half from the\n * serving half would be ceremony rather than safety. The client is a *superset*\n * of `StorageClient`, made literal in the type, so anything taking\n * `services.uploads` keeps compiling when the construct behind it grows a\n * serving half.\n *\n * **The id names the bucket, not the surface.** This is the detail that has to\n * be right before anything ships, because getting it backwards is destructive:\n * the migration people actually perform is serving a bucket they already have,\n * and naming the bucket after the construct makes that edit *add* a node rather\n * than rename the one holding the data.\n */\n\nimport type { Logger } from '@geekmidas/logger';\nimport { DEFAULT_LOGGER } from '@geekmidas/logger/console';\nimport {\n\ttype ConstructName,\n\tcanonicalId,\n\tDERIVED_PREFIX,\n\ttype Declaration,\n\tprovideKey,\n\tserviceKey,\n} from '@geekmidas/manifest';\nimport type { Service, ServiceRegisterOptions } from '@geekmidas/services';\nimport { createStorageClient, type StorageClient } from '@geekmidas/storage';\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\nimport type { Construct, Declarable } from './construct-interface';\nimport { expiryDays, type ObjectExpiry } from './expire';\nimport type { Processor } from './processors/Processor';\nimport { ProcessorBuilder } from './processors/ProcessorBuilder';\nimport {\n\tfillPattern,\n\ttype PatternParams,\n\tparsePattern,\n} from './processors/pattern';\nimport type { Telemetry } from './telemetry';\n\n/**\n * A key an open pattern admits.\n *\n * The same template-literal trick `ConstructName` uses, so `open` is checked at\n * the call site rather than only at the edge:\n *\n * ```ts\n * files.url('brand/logo.png') // ok\n * files.url(`avatars/${id}.png`) // ok — a template literal still matches\n * files.url('invoices/7.pdf') // not assignable — never served unsigned\n * ```\n *\n * A single star and a double star resolve to the same type, stated rather than\n * discovered: a template literal cannot exclude `/`, so this is a prefix-and-\n * suffix guard while the *exact* pattern is enforced by the bucket policy, by\n * the cache behaviour, and by this construct's own runtime check. The type says\n * plausibly open; the infrastructure says definitely.\n */\nexport type Served<P extends string> = P extends `${infer Head}**`\n\t? `${Head}${string}`\n\t: P extends `${infer Head}*${infer Tail}`\n\t\t? `${Head}${string}${Tail}`\n\t\t: P;\n\n/** What consuming a file server hands you: a bucket client that also serves. */\nexport interface FileServerClient<TOpen extends string = never>\n\textends StorageClient {\n\t/**\n\t * The unsigned address of an open object.\n\t *\n\t * Compile-checked against the declared patterns, *and* checked again at\n\t * runtime — a JavaScript caller gets no compiler, and minting an unsigned\n\t * URL for a private object is a leak rather than a mistake.\n\t *\n\t * @throws {NotOpen} when the key matches no declared pattern.\n\t */\n\turl(key: Served<TOpen>): string;\n\n\t/**\n\t * The same thing for a key the compiler cannot see.\n\t *\n\t * A fully dynamic key does not type, which is correct and is also the case\n\t * people will hit — so the escape hatch is a named method rather than a\n\t * cast, and it runs the *same* runtime check rather than being a way around\n\t * it.\n\t *\n\t * @throws {NotOpen} when the key matches no declared pattern.\n\t */\n\topenUrl(key: string): string;\n\n\t/**\n\t * A signed address for one object and one recipient.\n\t *\n\t * Today this is an **S3 presign at the bucket**, which is what works\n\t * unchanged against MinIO locally and against S3 deployed. It is deliberately\n\t * not described as a CDN URL: CloudFront signing is a different mechanism,\n\t * with different keys, against a different host, and it needs key material\n\t * this construct does not yet declare.\n\t *\n\t * @param expiresIn - minutes, matching `getUploadURL`.\n\t */\n\tsignedUrl(key: string, expiresIn?: number): Promise<string>;\n\n\t/**\n\t * The address of a processor's derivative of an open object — the\n\t * thumbnail, not the original.\n\t *\n\t * @throws {NotOpen} when the key matches no declared pattern.\n\t * @throws {ProcessorNotOnThisServer} for another file server's processor.\n\t */\n\tprocessedUrl<\n\t\tTPattern extends string,\n\t\tTSchema extends StandardSchemaV1 | undefined,\n\t>(\n\t\tprocessor: Processor<TPattern, TSchema, any, any>,\n\t\tparams: ProcessorUrlParams<TPattern, TSchema>,\n\t\tkey: string,\n\t): string;\n}\n\n/**\n * What addresses a derivative: the values the processor's schema accepts —\n * its input, since these go into a path — or, without one, the raw strings.\n */\nexport type ProcessorUrlParams<\n\tTPattern extends string,\n\tTSchema extends StandardSchemaV1 | undefined,\n> = TSchema extends StandardSchemaV1\n\t? StandardSchemaV1.InferInput<TSchema>\n\t: PatternParams<TPattern>;\n\n/** A processed URL was asked of a file server the processor is not on. */\nexport class ProcessorNotOnThisServer extends Error {\n\tconstructor(\n\t\treadonly pattern: string,\n\t\treadonly server: string,\n\t\treadonly expected: string,\n\t) {\n\t\tsuper(\n\t\t\t`The processor at '${pattern}' belongs to '${server}', not '${expected}'. ` +\n\t\t\t\t`Ask its own file server for the URL: services.<its id>.processedUrl(…).`,\n\t\t);\n\t\tthis.name = 'ProcessorNotOnThisServer';\n\t}\n}\n\nexport interface FileServerOptions<TOpen extends string = never> {\n\t/**\n\t * Paths served without a signature. Everything else needs one.\n\t *\n\t * Private by default: a bucket where forgetting a flag publishes user\n\t * uploads is the wrong default, so this is an exception list, and listing it\n\t * in code means exactly one set of paths is unauthenticated and it is\n\t * reviewable in a diff.\n\t *\n\t * Uploading is deliberately *not* restricted to these. Writing to a path\n\t * that happens to be open is ordinary; the only asymmetry worth enforcing is\n\t * that you cannot hand out an unsigned URL for something the server would\n\t * refuse to serve unsigned.\n\t */\n\topen?: readonly TOpen[];\n\t/**\n\t * Open paths whose objects never change once written — keyed by a content\n\t * hash or a file id — so every client may keep them forever: the edge\n\t * sends `Cache-Control: public, max-age=31536000, immutable`, over\n\t * whatever the object was stored with.\n\t *\n\t * A subset of `open`, because a signed object is never served by the edge.\n\t * Only say it of paths whose keys are written once: a client holding an\n\t * immutable copy never asks again.\n\t */\n\timmutable?: readonly string[];\n\t/** Keep previous versions of an object. */\n\tversioned?: boolean;\n\t/**\n\t * Delete the objects under each prefix this many days after they were\n\t * written — `{ 'previews/': { days: 7 } }`. A setting of the bucket this\n\t * server vends, so it cannot be given with `origin`.\n\t */\n\texpire?: ObjectExpiry;\n\t/**\n\t * How long its processors' derivatives are kept in the bucket before it\n\t * deletes them — 30 days unless said. They are remade on the next request,\n\t * so this trades storage for the work of making one again.\n\t */\n\tderived?: { readonly days: number };\n\t/**\n\t * What its processor unit emits — the `Telemetry` construct, as a worker\n\t * takes it: each request is a span, and the deploy hands the unit the\n\t * `OTEL_*` keys its stage resolves.\n\t */\n\ttelemetry?: Telemetry;\n\t/**\n\t * A bucket declared elsewhere, instead of one of this server's own.\n\t *\n\t * `new FileServer('Uploads')` declares both nodes; this fronts a bucket that\n\t * already exists, which is also how the two edges come back when you want\n\t * them separately — a handler depending on the bucket writes, a handler\n\t * depending on the server mints URLs.\n\t */\n\torigin?: Declarable;\n}\n\n/** An immutable pattern that no open pattern admits. */\nexport class ImmutableNotOpen extends Error {\n\tconstructor(\n\t\treadonly id: string,\n\t\treadonly pattern: string,\n\t\treadonly open: readonly string[],\n\t) {\n\t\tsuper(\n\t\t\t`'${id}' says '${pattern}' is immutable, and no open path admits it` +\n\t\t\t\t(open.length ? ` (open: ${open.join(', ')})` : ' — it opens none') +\n\t\t\t\t'. Only an open path is served by the edge, so only one can be ' +\n\t\t\t\t'cached there: add it to `open`, or narrow it to one that is.',\n\t\t);\n\t\tthis.name = 'ImmutableNotOpen';\n\t}\n}\n\n/**\n * A bucket setting given to a file server that fronts a bucket declared\n * elsewhere — where the bucket's own construct is the one that says it.\n */\nexport class BucketOptionOnOrigin extends Error {\n\tconstructor(\n\t\treadonly server: string,\n\t\treadonly origin: string,\n\t\treadonly option: string,\n\t) {\n\t\tsuper(\n\t\t\t`'${server}' fronts '${origin}', which declares itself, so '${option}' ` +\n\t\t\t\t`belongs on the construct that declares '${origin}', not on the file server.`,\n\t\t);\n\t\tthis.name = 'BucketOptionOnOrigin';\n\t}\n}\n\n/** A key was asked to be served unsigned, and no open pattern admits it. */\nexport class NotOpen extends Error {\n\tconstructor(\n\t\treadonly key: string,\n\t\treadonly open: readonly string[],\n\t) {\n\t\tsuper(\n\t\t\t`'${key}' is not served without a signature. ` +\n\t\t\t\t(open.length\n\t\t\t\t\t? `Open paths: ${open.join(', ')}. Use signedUrl() for anything else.`\n\t\t\t\t\t: 'This file server declares no open paths, so every object needs signedUrl().'),\n\t\t);\n\t\tthis.name = 'NotOpen';\n\t}\n}\n\nexport class FileServer<\n\tTName extends string = string,\n\tTOpen extends string = never,\n> implements Construct<TName, FileServerClient<TOpen>>\n{\n\treadonly id: TName;\n\treadonly service: Service<Uncapitalize<TName>, FileServerClient<TOpen>>;\n\n\t/** The id of the surface node, as opposed to the bucket's. */\n\treadonly surfaceId: string;\n\n\tprivate readonly open: readonly string[];\n\tprivate readonly immutable: readonly string[];\n\n\t/** The vended bucket's expiry, checked once: prefix → days. */\n\tprivate readonly expire: Record<string, number> | undefined;\n\n\t/** Days its processors' derivatives are kept, when it says. */\n\tprivate readonly derivedDays: number | undefined;\n\n\t/**\n\t * Declared once and read by both `declare()` and `connect()`, so the keys the\n\t * target publishes and the keys the client reads cannot drift.\n\t */\n\tprivate readonly keys: { origin: string; server: string };\n\n\t/**\n\t * How a processor reaches the bucket: the bucket's own URL and nothing of\n\t * the serving half, which a processor never needs. A field, not a getter:\n\t * services are cached by object identity.\n\t */\n\tprivate readonly origin: Service<string, StorageClient>;\n\n\tconstructor(\n\t\tid: ConstructName<TName>,\n\t\tprivate readonly options: FileServerOptions<TOpen> = {},\n\t) {\n\t\tconst canonical = canonicalId(id as string);\n\n\t\tthis.id = canonical as TName;\n\t\tthis.open = options.open ?? [];\n\t\tthis.immutable = options.immutable ?? [];\n\t\tfor (const pattern of this.immutable) {\n\t\t\tif (!this.covers(pattern)) {\n\t\t\t\tthrow new ImmutableNotOpen(canonical, pattern, this.open);\n\t\t\t}\n\t\t}\n\n\t\t// The surface takes the suffix and the bucket keeps the plain id, which\n\t\t// is what makes \"serve a bucket I already have\" an edit that adds a node\n\t\t// instead of renaming the one holding the data.\n\t\tconst originId = options.origin?.id ?? canonical;\n\t\tif (options.origin && options.expire) {\n\t\t\tthrow new BucketOptionOnOrigin(canonical, originId, 'expire');\n\t\t}\n\t\tthis.expire = expiryDays(canonical, options.expire);\n\t\tthis.derivedDays = options.derived\n\t\t\t? expiryDays(canonical, { [DERIVED_PREFIX]: options.derived })?.[\n\t\t\t\t\tDERIVED_PREFIX\n\t\t\t\t]\n\t\t\t: undefined;\n\t\tthis.surfaceId = `${canonical}Server`;\n\n\t\tthis.keys = {\n\t\t\torigin: provideKey(originId, 'url'),\n\t\t\tserver: provideKey(this.surfaceId, 'url'),\n\t\t};\n\n\t\t// A field, not a getter: consumers cache services by object identity.\n\t\tthis.service = {\n\t\t\tserviceName: serviceKey(canonical) as Uncapitalize<TName>,\n\t\t\tregister: (registerOptions) => this.connect(registerOptions),\n\t\t};\n\n\t\tconst originKey = this.keys.origin;\n\t\tthis.origin = {\n\t\t\tserviceName: `${serviceKey(originId)}ProcessorOrigin`,\n\t\t\tregister: ({ envParser }) => {\n\t\t\t\tconst { url } = envParser\n\t\t\t\t\t.create((get) => ({ url: get(originKey).string() }))\n\t\t\t\t\t.parse();\n\t\t\t\treturn createStorageClient(url);\n\t\t\t},\n\t\t};\n\t}\n\n\t/**\n\t * A processor behind one path pattern of this server: a handler that makes\n\t * a derivative of an open object on read.\n\t *\n\t * ```ts\n\t * export const thumbnails = assets\n\t * .process('/i/:width/*')\n\t * .params(z.object({ width: z.enum(['64', '256', '1024']) }))\n\t * .native(['sharp'])\n\t * .handle(async ({ params, object }) => ({\n\t * body: await sharp(await object.bytes()).resize(Number(params.width)).webp().toBuffer(),\n\t * contentType: 'image/webp',\n\t * cache: 'immutable',\n\t * }));\n\t * ```\n\t *\n\t * Open paths only: a key no `open` pattern admits is answered 404, as an\n\t * unsigned read of it would be.\n\t *\n\t * @throws {ProcessorPatternInvalid} for a pattern the edge cannot route.\n\t */\n\tprocess<TPattern extends `/${string}/*`>(\n\t\tpattern: TPattern,\n\t\toptions: { logger?: Logger } = {},\n\t): ProcessorBuilder<TPattern> {\n\t\treturn new ProcessorBuilder<TPattern>({\n\t\t\tparsed: parsePattern(pattern),\n\t\t\tsurfaceId: this.surfaceId,\n\t\t\tbucketId: this.options.origin?.id ?? this.id,\n\t\t\torigin: this.origin,\n\t\t\tserves: (key) => this.serves(key),\n\t\t\tlogger: (options.logger ?? DEFAULT_LOGGER) as Logger,\n\t\t\tnative: [],\n\t\t\tservices: [],\n\t\t\tconstructs: [],\n\t\t});\n\t}\n\n\t/**\n\t * The served surface, as something an edge can point at.\n\t *\n\t * Depending on the `FileServer` itself points at the **bucket** — its\n\t * canonical node — which is right for a handler: it presigns and writes. But\n\t * a bucket's URL is never public, so a site depending on it gets nothing\n\t * inlined, which is correct and also useless.\n\t *\n\t * This is the other half: `.dependsOn([uploads.server])` points at the\n\t * surface, whose address *is* public, and is what puts\n\t * `VITE_UPLOADS_SERVER_URL` in a bundle. The same accessor shape\n\t * `database.reader()` uses, and for the same reason — one construct owning\n\t * two addresses needs a way to name the second.\n\t */\n\tget server(): Declarable {\n\t\tconst surface = this.declare().find(\n\t\t\t(declaration) => declaration.id === this.surfaceId,\n\t\t);\n\n\t\treturn {\n\t\t\tid: this.surfaceId,\n\t\t\tdeclare: () => (surface ? [surface] : []),\n\t\t};\n\t}\n\n\t/**\n\t * The bucket and the surface over it — or just the surface, when the bucket\n\t * was declared elsewhere.\n\t *\n\t * The surface names its origin through `of`, which is what makes the\n\t * reference check work: an origin resolving to nothing is a build failure,\n\t * the same guarantee `assertDerivations` already gives a reader and a schema\n\t * tenant.\n\t */\n\tdeclare(): Declaration[] {\n\t\tconst origin = this.options.origin;\n\n\t\tconst surface: Declaration = {\n\t\t\tkind: 'file-server',\n\t\t\tid: this.surfaceId,\n\t\t\tof: origin?.id ?? this.id,\n\t\t\t...(this.open.length ? { open: this.open } : {}),\n\t\t\t...(this.immutable.length ? { immutable: this.immutable } : {}),\n\t\t\t...(this.derivedDays !== undefined\n\t\t\t\t? { derivedDays: this.derivedDays }\n\t\t\t\t: {}),\n\t\t\t...(this.options.telemetry\n\t\t\t\t? { telemetry: this.options.telemetry.id }\n\t\t\t\t: {}),\n\t\t\tprovides: [this.keys.server],\n\t\t};\n\n\t\t// A vended bucket is declared here; a given one already declares itself,\n\t\t// and declaring it twice is the duplication the whole model removes.\n\t\tif (origin) return [surface];\n\n\t\treturn [\n\t\t\t{\n\t\t\t\tkind: 'objects',\n\t\t\t\tid: this.id,\n\t\t\t\tprovides: [this.keys.origin],\n\t\t\t\t...(this.options.versioned ? { versioned: true } : {}),\n\t\t\t\t...(this.expire ? { expire: this.expire } : {}),\n\t\t\t},\n\t\t\tsurface,\n\t\t];\n\t}\n\n\t/**\n\t * The bucket's client, with the serving half added.\n\t *\n\t * Both addresses are read here, which is also how the environment reaches a\n\t * handler: the sniffer records what this reads, so `.dependsOn([uploads])`\n\t * carries the bucket URL and the server URL without either being written\n\t * down beside the handler.\n\t */\n\tprivate async connect(\n\t\toptions: ServiceRegisterOptions,\n\t): Promise<FileServerClient<TOpen>> {\n\t\tconst { origin, server } = options.envParser\n\t\t\t.create((get) => ({\n\t\t\t\torigin: get(this.keys.origin).string(),\n\t\t\t\tserver: get(this.keys.server).string(),\n\t\t\t}))\n\t\t\t.parse();\n\n\t\tconst storage = createStorageClient(origin);\n\t\tconst base = server.replace(/\\/+$/, '');\n\n\t\tconst serve = (key: string): string => {\n\t\t\tif (!this.serves(key)) throw new NotOpen(key, this.open);\n\n\t\t\treturn `${base}/${key.replace(/^\\/+/, '')}`;\n\t\t};\n\n\t\t// Delegating rather than copying: the bucket half is whatever the driver\n\t\t// produced, including anything it grows later, and only the serving half\n\t\t// is added on top.\n\t\treturn Object.assign(Object.create(storage) as StorageClient, {\n\t\t\turl: serve as (key: Served<TOpen>) => string,\n\t\t\topenUrl: serve,\n\t\t\tsignedUrl: (key: string, expiresIn?: number) =>\n\t\t\t\tstorage.getDownloadURL({ path: key } as never, expiresIn),\n\t\t\tprocessedUrl: (\n\t\t\t\tprocessor: Processor<string, any, any, any>,\n\t\t\t\tparams: Record<string, string | number>,\n\t\t\t\tkey: string,\n\t\t\t) => {\n\t\t\t\tif (processor.surfaceId !== this.surfaceId) {\n\t\t\t\t\tthrow new ProcessorNotOnThisServer(\n\t\t\t\t\t\tprocessor.pattern,\n\t\t\t\t\t\tprocessor.surfaceId,\n\t\t\t\t\t\tthis.surfaceId,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (!this.serves(key)) throw new NotOpen(key, this.open);\n\t\t\t\treturn `${base}${fillPattern(processor.parsed, params, key)}`;\n\t\t\t},\n\t\t}) as FileServerClient<TOpen>;\n\t}\n\n\t/**\n\t * Whether an open pattern admits a key.\n\t *\n\t * The runtime half of {@link Served}, and stricter than it: here a single\n\t * star stops at a segment boundary and a double star does not, which is the\n\t * distinction a template-literal type cannot draw and the infrastructure\n\t * does.\n\t */\n\tprivate serves(key: string): boolean {\n\t\treturn this.open.some((pattern) => globToRegExp(pattern).test(key));\n\t}\n\n\t/**\n\t * Whether every key a pattern admits is open: the pattern itself, each of\n\t * its wildcards taken as a segment of its own, is a key an open pattern\n\t * serves — `brand/logos/*` under `brand/**`, but not `**` under `brand/**`.\n\t */\n\tprivate covers(pattern: string): boolean {\n\t\treturn this.serves(pattern.replace(/\\*\\*/g, 'x/x').replace(/\\*/g, 'x'));\n\t}\n}\n\n/**\n * One open pattern as an anchored expression.\n *\n * A double star crosses `/`; a single star does not. Both are consumed in one\n * left-to-right pass rather than by substitution, so a pattern cannot be\n * rewritten into a placeholder that a later pass mistakes for literal text.\n */\nfunction globToRegExp(pattern: string): RegExp {\n\tlet source = '';\n\n\tfor (let index = 0; index < pattern.length; index++) {\n\t\tconst character = pattern[index];\n\n\t\tif (character !== '*') {\n\t\t\tsource += character?.replace(/[.+^${}()|[\\]\\\\?]/, '\\\\$&') ?? '';\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (pattern[index + 1] === '*') {\n\t\t\tsource += '.*';\n\t\t\tindex++;\n\t\t\tcontinue;\n\t\t}\n\n\t\tsource += '[^/]*';\n\t}\n\n\treturn new RegExp(`^${source}$`);\n}\n"],"mappings":"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"}