@useairfoil/connector-kit 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/METRICS.md +78 -0
  2. package/README.md +450 -88
  3. package/dist/auth/index.d.ts +2 -0
  4. package/dist/auth/index.js +3 -0
  5. package/dist/auth-DvU_CZ7C.js +42 -0
  6. package/dist/auth-DvU_CZ7C.js.map +1 -0
  7. package/dist/chunk-Bp6m_JJh.js +13 -0
  8. package/dist/connector-app/index.d.ts +14 -0
  9. package/dist/connector-app/index.js +11 -0
  10. package/dist/connector-app-d7Z-SZLQ.js +64 -0
  11. package/dist/connector-app-d7Z-SZLQ.js.map +1 -0
  12. package/dist/constants-BRhIX_rz.js +27 -0
  13. package/dist/constants-BRhIX_rz.js.map +1 -0
  14. package/dist/errors/index.d.ts +2 -0
  15. package/dist/errors/index.js +3 -0
  16. package/dist/errors-DHWylqRZ.js +8 -0
  17. package/dist/errors-DHWylqRZ.js.map +1 -0
  18. package/dist/index-Byr0Lf0C.d.ts +25 -0
  19. package/dist/index-CBT-JX-T.d.ts +173 -0
  20. package/dist/index-CTHVudvQ.d.ts +41 -0
  21. package/dist/index-CTLYQ74P.d.ts +80 -0
  22. package/dist/index-Coh_SkBs.d.ts +95 -0
  23. package/dist/index-D54ySAKn.d.ts +14 -0
  24. package/dist/index-DtJX_tic.d.ts +153 -0
  25. package/dist/index-J42AZaXU.d.ts +38 -0
  26. package/dist/index-fIIBVAai.d.ts +25 -0
  27. package/dist/index-o1-Z3n7V.d.ts +55 -0
  28. package/dist/index.d.ts +14 -165
  29. package/dist/index.js +50 -228
  30. package/dist/index.js.map +1 -1
  31. package/dist/ingestion/index.d.ts +6 -0
  32. package/dist/ingestion/index.js +10 -0
  33. package/dist/ingestion-BQo4cbGw.js +200 -0
  34. package/dist/ingestion-BQo4cbGw.js.map +1 -0
  35. package/dist/manifest/index.d.ts +2 -0
  36. package/dist/manifest/index.js +4 -0
  37. package/dist/manifest-ByE6YPG0.js +221 -0
  38. package/dist/manifest-ByE6YPG0.js.map +1 -0
  39. package/dist/metrics-CVAk1ALR.d.ts +53 -0
  40. package/dist/metrics-CWPy2Aqc.js +96 -0
  41. package/dist/metrics-CWPy2Aqc.js.map +1 -0
  42. package/dist/metrics.d.ts +4 -0
  43. package/dist/metrics.js +3 -0
  44. package/dist/publisher/index.d.ts +5 -0
  45. package/dist/publisher/index.js +6 -0
  46. package/dist/publisher-BYT3RLVO.js +131 -0
  47. package/dist/publisher-BYT3RLVO.js.map +1 -0
  48. package/dist/runtime-config/index.d.ts +2 -0
  49. package/dist/runtime-config/index.js +4 -0
  50. package/dist/runtime-config-BpB-d_bF.js +51 -0
  51. package/dist/runtime-config-BpB-d_bF.js.map +1 -0
  52. package/dist/server-CXTQ7odl.js +183 -0
  53. package/dist/server-CXTQ7odl.js.map +1 -0
  54. package/dist/service-DuFiQwid.js +191 -0
  55. package/dist/service-DuFiQwid.js.map +1 -0
  56. package/dist/service-IE5qaf9x.d.ts +32 -0
  57. package/dist/state-store/index.d.ts +3 -0
  58. package/dist/state-store/index.js +5 -0
  59. package/dist/state-store-yl6LiTQ0.js +198 -0
  60. package/dist/state-store-yl6LiTQ0.js.map +1 -0
  61. package/dist/status/index.d.ts +5 -0
  62. package/dist/status/index.js +6 -0
  63. package/dist/status-B-oZBc33.js +84 -0
  64. package/dist/status-B-oZBc33.js.map +1 -0
  65. package/dist/types-B35J5YPu.d.ts +174 -0
  66. package/dist/webhook/index.d.ts +6 -0
  67. package/dist/webhook/index.js +8 -0
  68. package/dist/webhook-DI-Afxkg.js +13 -0
  69. package/dist/webhook-DI-Afxkg.js.map +1 -0
  70. package/package.json +66 -8
  71. package/src/auth/index.ts +1 -0
  72. package/src/auth/token-cache.ts +54 -0
  73. package/src/connector-app/check.ts +76 -0
  74. package/src/connector-app/index.ts +2 -0
  75. package/src/connector-app/start.ts +42 -0
  76. package/src/core/builder.ts +81 -9
  77. package/src/core/index.ts +22 -0
  78. package/src/core/types.ts +204 -76
  79. package/src/errors/index.ts +1 -0
  80. package/src/index.ts +13 -28
  81. package/src/ingestion/engine.ts +409 -174
  82. package/src/ingestion/index.ts +1 -0
  83. package/src/manifest/config.ts +232 -0
  84. package/src/manifest/index.ts +14 -0
  85. package/src/manifest/manifest.ts +31 -0
  86. package/src/manifest/number-schema.ts +17 -0
  87. package/src/manifest/schema.ts +160 -0
  88. package/src/metrics.ts +128 -0
  89. package/src/publisher/console.ts +18 -0
  90. package/src/publisher/index.ts +7 -0
  91. package/src/publisher/instrumented.ts +68 -0
  92. package/src/publisher/service.ts +30 -13
  93. package/src/publisher/wings.ts +159 -72
  94. package/src/runtime/context.ts +15 -0
  95. package/src/runtime-config/constants.ts +27 -0
  96. package/src/runtime-config/error.ts +13 -0
  97. package/src/runtime-config/index.ts +3 -0
  98. package/src/runtime-config/layer.ts +56 -0
  99. package/src/state-store/index.ts +3 -0
  100. package/src/state-store/keys.ts +22 -0
  101. package/src/state-store/layer-key-value-store.ts +132 -0
  102. package/src/state-store/layer-memory.ts +11 -0
  103. package/src/state-store/layer-sql.ts +50 -0
  104. package/src/state-store/schema.ts +57 -0
  105. package/src/state-store/service.ts +51 -0
  106. package/src/state-store/state.ts +17 -0
  107. package/src/status/index.ts +2 -0
  108. package/src/status/route.ts +77 -0
  109. package/src/status/types.ts +33 -0
  110. package/src/telemetry.ts +288 -0
  111. package/src/webhook/index.ts +8 -0
  112. package/src/webhook/server.ts +209 -39
  113. package/src/webhook/types.ts +6 -17
  114. package/src/ingestion/state-store.ts +0 -36
  115. package/src/streams/pull-stream.ts +0 -54
  116. package/src/streams/webhook-queue.ts +0 -14
  117. /package/src/{core/errors.ts → errors/connector-error.ts} +0 -0
@@ -0,0 +1,51 @@
1
+ import { t as __export } from "./chunk-Bp6m_JJh.js";
2
+ import { n as PlatformRuntimeKey, t as PlatformRuntimeDefault } from "./constants-BRhIX_rz.js";
3
+ import { Config, ConfigProvider, Data, Effect, FileSystem, Schema } from "effect";
4
+
5
+ //#region src/runtime-config/error.ts
6
+ /** Expected hosted-config failure with a stable code and diagnostic cause. */
7
+ var RuntimeConfigError = class extends Data.TaggedError("RuntimeConfigError") {};
8
+
9
+ //#endregion
10
+ //#region src/runtime-config/layer.ts
11
+ const providerFromFile = Effect.gen(function* () {
12
+ const configPath = yield* Config.nonEmptyString(PlatformRuntimeKey.configPath).pipe(Effect.mapError((error) => new RuntimeConfigError({
13
+ code: "CONFIG_PATH_MISSING",
14
+ message: `${PlatformRuntimeKey.configPath} is required in hosted mode`,
15
+ cause: error.cause
16
+ })));
17
+ const contents = yield* (yield* FileSystem.FileSystem).readFileString(configPath).pipe(Effect.mapError((cause) => new RuntimeConfigError({
18
+ code: "CONFIG_FILE_UNREADABLE",
19
+ message: "Connector config file could not be read",
20
+ cause
21
+ })));
22
+ const document = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown))(contents).pipe(Effect.mapError((error) => new RuntimeConfigError({
23
+ code: "CONFIG_JSON_INVALID",
24
+ message: "Connector config is not valid JSON",
25
+ cause: error
26
+ })));
27
+ return ConfigProvider.fromUnknown(document, { preserveEmptyStrings: true });
28
+ });
29
+ /**
30
+ * Loads the required JSON file and lets environment values override it per key.
31
+ * Connector field validation remains in the connector's Effect Config.
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * program.pipe(Effect.provide(layerHosted()));
36
+ * ```
37
+ */
38
+ const layerHosted = () => ConfigProvider.layerAdd(providerFromFile);
39
+
40
+ //#endregion
41
+ //#region src/runtime-config/index.ts
42
+ var runtime_config_exports = /* @__PURE__ */ __export({
43
+ PlatformRuntimeDefault: () => PlatformRuntimeDefault,
44
+ PlatformRuntimeKey: () => PlatformRuntimeKey,
45
+ RuntimeConfigError: () => RuntimeConfigError,
46
+ layerHosted: () => layerHosted
47
+ });
48
+
49
+ //#endregion
50
+ export { layerHosted as n, RuntimeConfigError as r, runtime_config_exports as t };
51
+ //# sourceMappingURL=runtime-config-BpB-d_bF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-config-BpB-d_bF.js","names":["providerFromFile: Effect.Effect<\n ConfigProvider.ConfigProvider,\n RuntimeConfigError,\n FileSystem.FileSystem\n>"],"sources":["../src/runtime-config/error.ts","../src/runtime-config/layer.ts","../src/runtime-config/index.ts"],"sourcesContent":["import { Data } from \"effect\";\n\nexport type RuntimeConfigErrorCode =\n | \"CONFIG_PATH_MISSING\"\n | \"CONFIG_FILE_UNREADABLE\"\n | \"CONFIG_JSON_INVALID\";\n\n/** Expected hosted-config failure with a stable code and diagnostic cause. */\nexport class RuntimeConfigError extends Data.TaggedError(\"RuntimeConfigError\")<{\n readonly code: RuntimeConfigErrorCode;\n readonly message: string;\n readonly cause: Error;\n}> {}\n","import { Config, ConfigProvider, Effect, FileSystem, Schema } from \"effect\";\n\nimport { PlatformRuntimeKey } from \"./constants\";\nimport { RuntimeConfigError } from \"./error\";\n\nconst providerFromFile: Effect.Effect<\n ConfigProvider.ConfigProvider,\n RuntimeConfigError,\n FileSystem.FileSystem\n> = Effect.gen(function* () {\n const configPath = yield* Config.nonEmptyString(PlatformRuntimeKey.configPath).pipe(\n Effect.mapError(\n (error) =>\n new RuntimeConfigError({\n code: \"CONFIG_PATH_MISSING\",\n message: `${PlatformRuntimeKey.configPath} is required in hosted mode`,\n cause: error.cause,\n }),\n ),\n );\n const fileSystem = yield* FileSystem.FileSystem;\n const contents = yield* fileSystem.readFileString(configPath).pipe(\n Effect.mapError(\n (cause) =>\n new RuntimeConfigError({\n code: \"CONFIG_FILE_UNREADABLE\",\n message: \"Connector config file could not be read\",\n cause,\n }),\n ),\n );\n const document = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown))(\n contents,\n ).pipe(\n Effect.mapError(\n (error) =>\n new RuntimeConfigError({\n code: \"CONFIG_JSON_INVALID\",\n message: \"Connector config is not valid JSON\",\n cause: error,\n }),\n ),\n );\n return ConfigProvider.fromUnknown(document, { preserveEmptyStrings: true });\n});\n\n/**\n * Loads the required JSON file and lets environment values override it per key.\n * Connector field validation remains in the connector's Effect Config.\n *\n * @example\n * ```ts\n * program.pipe(Effect.provide(layerHosted()));\n * ```\n */\nexport const layerHosted = () => ConfigProvider.layerAdd(providerFromFile);\n","export { RuntimeConfigError, type RuntimeConfigErrorCode } from \"./error\";\nexport { PlatformRuntimeDefault, PlatformRuntimeKey } from \"./constants\";\nexport { layerHosted } from \"./layer\";\n"],"mappings":";;;;;;AAQA,IAAa,qBAAb,cAAwC,KAAK,YAAY,qBAAqB,CAI3E;;;;ACPH,MAAMA,mBAIF,OAAO,IAAI,aAAa;CAC1B,MAAM,aAAa,OAAO,OAAO,eAAe,mBAAmB,WAAW,CAAC,KAC7E,OAAO,UACJ,UACC,IAAI,mBAAmB;EACrB,MAAM;EACN,SAAS,GAAG,mBAAmB,WAAW;EAC1C,OAAO,MAAM;EACd,CAAC,CACL,CACF;CAED,MAAM,WAAW,QADE,OAAO,WAAW,YACF,eAAe,WAAW,CAAC,KAC5D,OAAO,UACJ,UACC,IAAI,mBAAmB;EACrB,MAAM;EACN,SAAS;EACT;EACD,CAAC,CACL,CACF;CACD,MAAM,WAAW,OAAO,OAAO,oBAAoB,OAAO,eAAe,OAAO,QAAQ,CAAC,CACvF,SACD,CAAC,KACA,OAAO,UACJ,UACC,IAAI,mBAAmB;EACrB,MAAM;EACN,SAAS;EACT,OAAO;EACR,CAAC,CACL,CACF;AACD,QAAO,eAAe,YAAY,UAAU,EAAE,sBAAsB,MAAM,CAAC;EAC3E;;;;;;;;;;AAWF,MAAa,oBAAoB,eAAe,SAAS,iBAAiB"}
@@ -0,0 +1,183 @@
1
+ import { t as ConnectorError } from "./errors-DHWylqRZ.js";
2
+ import { a as entitiesUpsertedTotal, c as publishDuration, i as entitiesDeletedTotal, n as batchSize, p as setWebhookQueueDepth, r as batchesTotal, u as recordWebhookRequest } from "./metrics-CWPy2Aqc.js";
3
+ import { a as SpanName, i as EventName, n as Attr, o as addCurrentSpanEvent, r as EventAttr, s as annotateError, t as Publisher } from "./service-DuFiQwid.js";
4
+ import { Cause, Context, Effect, Metric, Queue, Schema } from "effect";
5
+ import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
6
+
7
+ //#region src/publisher/instrumented.ts
8
+ const publishBatch = Effect.fnUntraced(function* (options) {
9
+ const metric = {
10
+ connector: options.connector ?? "unknown",
11
+ resource: options.resource,
12
+ source: options.source
13
+ };
14
+ const upserts = options.batch.mutations.filter((mutation) => mutation.op === "upsert").length;
15
+ const deletes = options.batch.mutations.length - upserts;
16
+ const publisher = yield* Publisher;
17
+ const [duration, ack] = yield* Effect.timed(publisher.publish({
18
+ resource: options.resource,
19
+ source: options.source,
20
+ batch: options.batch
21
+ })).pipe(Effect.catchCause((cause) => Metric.update(Metric.withAttributes(batchesTotal, {
22
+ ...metric,
23
+ outcome: "error"
24
+ }), 1).pipe(Effect.andThen(Effect.failCause(cause)))));
25
+ yield* Metric.update(Metric.withAttributes(publishDuration, metric), duration);
26
+ yield* Metric.update(Metric.withAttributes(batchesTotal, {
27
+ ...metric,
28
+ outcome: ack.status
29
+ }), 1);
30
+ yield* Effect.annotateCurrentSpan({ [Attr.publisherSuccess]: ack.status === "accepted" });
31
+ if (ack.status === "rejected") {
32
+ yield* Effect.annotateCurrentSpan({ [Attr.errorPhase]: "publish" });
33
+ return yield* Effect.fail(new ConnectorError({ message: `Publisher rejected batch for ${options.resource}: ${ack.reason}` }));
34
+ }
35
+ yield* Metric.update(Metric.withAttributes(entitiesUpsertedTotal, metric), upserts);
36
+ yield* Metric.update(Metric.withAttributes(entitiesDeletedTotal, metric), deletes);
37
+ yield* Metric.update(Metric.withAttributes(batchSize, metric), options.batch.mutations.length);
38
+ if (options.batch.cursor !== void 0) yield* addCurrentSpanEvent(EventName.batchCheckpoint, { [EventAttr.batchCursor]: options.batch.cursor });
39
+ });
40
+
41
+ //#endregion
42
+ //#region src/webhook/server.ts
43
+ var WebhookQueue = class extends Context.Service()("@useairfoil/connector-kit/WebhookQueue") {};
44
+ const resourceBatch = (mutations) => ({ mutations });
45
+ const decodeAndHandleWebhook = (resourceName, webhook, payload) => Schema.decodeUnknownEffect(webhook.schema)(payload).pipe(Effect.mapError((cause) => new ConnectorError({
46
+ message: `Invalid webhook payload for resource ${resourceName}`,
47
+ cause
48
+ })), Effect.flatMap((decoded) => webhook.handler({ payload: decoded })));
49
+ const makeRouteContext = (request, rawBody, payload, batches) => ({
50
+ request,
51
+ rawBody,
52
+ payload,
53
+ to: (resource, payload$1) => Effect.gen(function* () {
54
+ if (!resource.webhook) return yield* Effect.fail(new ConnectorError({ message: `Resource ${resource.name} does not define a webhook handler` }));
55
+ const mutations = yield* decodeAndHandleWebhook(resource.name, resource.webhook, payload$1);
56
+ batches.push({
57
+ resource: resource.name,
58
+ batch: resourceBatch(mutations)
59
+ });
60
+ })
61
+ });
62
+ const compactBatches = (batches) => {
63
+ const grouped = /* @__PURE__ */ new Map();
64
+ for (const item of batches) {
65
+ const current = grouped.get(item.resource) ?? [];
66
+ grouped.set(item.resource, [...current, ...item.batch.mutations]);
67
+ }
68
+ return Array.from(grouped.entries()).map(([resource, mutations]) => ({
69
+ resource,
70
+ batch: resourceBatch(mutations)
71
+ }));
72
+ };
73
+ const publishBatches = (batches, options) => Effect.gen(function* () {
74
+ yield* Effect.forEach(batches, (batch) => publishBatch({
75
+ connector: options.connectorName,
76
+ resource: batch.resource,
77
+ source: "webhook",
78
+ batch: batch.batch
79
+ }), { concurrency: "unbounded" });
80
+ });
81
+ const badRequest = (message) => HttpServerResponse.jsonUnsafe({
82
+ ok: false,
83
+ error: message
84
+ }, { status: 400 });
85
+ const readRawBody = (request) => request.arrayBuffer.pipe(Effect.map((buffer) => new Uint8Array(buffer)));
86
+ const parseJson = (rawBody) => Effect.try({
87
+ try: () => JSON.parse(new TextDecoder().decode(rawBody)),
88
+ catch: (cause) => new ConnectorError({
89
+ message: "Invalid webhook JSON",
90
+ cause
91
+ })
92
+ });
93
+ const makeHandler = (route, options) => Effect.gen(function* () {
94
+ const request = yield* HttpServerRequest.HttpServerRequest;
95
+ const collected = [];
96
+ const rawBodyResult = yield* readRawBody(request).pipe(Effect.match({
97
+ onFailure: () => ({
98
+ _tag: "Error",
99
+ message: "Failed to read webhook body"
100
+ }),
101
+ onSuccess: (value) => ({
102
+ _tag: "Success",
103
+ value
104
+ })
105
+ }));
106
+ if (rawBodyResult._tag === "Error") {
107
+ yield* recordWebhookRequest({
108
+ connector: options.connectorName,
109
+ path: String(route.path),
110
+ outcome: "read_error"
111
+ });
112
+ return badRequest(rawBodyResult.message);
113
+ }
114
+ const jsonResult = yield* parseJson(rawBodyResult.value).pipe(Effect.match({
115
+ onFailure: (error) => ({
116
+ _tag: "Error",
117
+ message: error.message
118
+ }),
119
+ onSuccess: (value) => ({
120
+ _tag: "Success",
121
+ value
122
+ })
123
+ }));
124
+ if (jsonResult._tag === "Error") {
125
+ yield* recordWebhookRequest({
126
+ connector: options.connectorName,
127
+ path: String(route.path),
128
+ outcome: "invalid_json"
129
+ });
130
+ return badRequest(jsonResult.message);
131
+ }
132
+ const payloadResult = yield* Schema.decodeUnknownEffect(route.schema)(jsonResult.value).pipe(Effect.match({
133
+ onFailure: () => ({
134
+ _tag: "Error",
135
+ message: "Invalid webhook payload"
136
+ }),
137
+ onSuccess: (value) => ({
138
+ _tag: "Success",
139
+ value
140
+ })
141
+ }));
142
+ if (payloadResult._tag === "Error") {
143
+ yield* recordWebhookRequest({
144
+ connector: options.connectorName,
145
+ path: String(route.path),
146
+ outcome: "invalid_payload"
147
+ });
148
+ return badRequest(payloadResult.message);
149
+ }
150
+ const response = yield* Effect.withSpan(route.handler(makeRouteContext(request, rawBodyResult.value, payloadResult.value, collected)).pipe(Effect.tapError((error) => annotateError("webhook_handle", error))), SpanName.webhookHandle, { attributes: { [Attr.webhookPath]: route.path } });
151
+ const batches = compactBatches(collected);
152
+ if (route.ackMode === "after-enqueue") {
153
+ const queue = yield* WebhookQueue;
154
+ yield* Queue.offerAll(queue.queue, batches);
155
+ const depth = yield* Queue.size(queue.queue);
156
+ yield* setWebhookQueueDepth(options.connectorName, depth);
157
+ yield* recordWebhookRequest({
158
+ connector: options.connectorName,
159
+ path: String(route.path),
160
+ outcome: response.status >= 400 ? "rejected" : "ok"
161
+ });
162
+ return response;
163
+ }
164
+ yield* Effect.withSpan(publishBatches(batches, { connectorName: options.connectorName }).pipe(Effect.tapError((error) => annotateError("webhook_publish", error))), SpanName.webhookHandle, { attributes: { [Attr.webhookPath]: route.path } });
165
+ yield* recordWebhookRequest({
166
+ connector: options.connectorName,
167
+ path: String(route.path),
168
+ outcome: response.status >= 400 ? "rejected" : "ok"
169
+ });
170
+ return response;
171
+ }).pipe(Effect.catchCause((cause) => recordWebhookRequest({
172
+ connector: options.connectorName,
173
+ path: String(route.path),
174
+ outcome: "handler_error"
175
+ }).pipe(Effect.andThen(Effect.logWarning(`Webhook handler error: ${Cause.pretty(cause)}`)), Effect.andThen(Effect.succeed(HttpServerResponse.jsonUnsafe({
176
+ ok: false,
177
+ error: "Webhook handler failed"
178
+ }, { status: 500 }))))));
179
+ const router = (routes, options) => HttpRouter.addAll(routes.map((route) => HttpRouter.route("POST", route.path, makeHandler(route, options))));
180
+
181
+ //#endregion
182
+ export { router as n, publishBatch as r, WebhookQueue as t };
183
+ //# sourceMappingURL=server-CXTQ7odl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-CXTQ7odl.js","names":["Metrics.batchesTotal","Metrics.publishDuration","Metrics.entitiesUpsertedTotal","Metrics.entitiesDeletedTotal","Metrics.batchSize","payload","collected: Array<QueuedWebhookBatch>","Metrics.recordWebhookRequest","Metrics.setWebhookQueueDepth"],"sources":["../src/publisher/instrumented.ts","../src/webhook/server.ts"],"sourcesContent":["import { Effect, Metric } from \"effect\";\n\nimport type { ResourceBatch } from \"../core/types\";\n\nimport { ConnectorError } from \"../errors\";\nimport * as Metrics from \"../metrics\";\nimport { Attr, EventAttr, EventName, addCurrentSpanEvent } from \"../telemetry\";\nimport { Publisher, type PublishSource } from \"./service\";\n\nexport const publishBatch = Effect.fnUntraced(function* (options: {\n readonly connector?: string;\n readonly resource: string;\n readonly source: PublishSource;\n readonly batch: ResourceBatch;\n}) {\n const metric = {\n connector: options.connector ?? \"unknown\",\n resource: options.resource,\n source: options.source,\n };\n const upserts = options.batch.mutations.filter((mutation) => mutation.op === \"upsert\").length;\n const deletes = options.batch.mutations.length - upserts;\n\n const publisher = yield* Publisher;\n const [duration, ack] = yield* Effect.timed(\n publisher.publish({\n resource: options.resource,\n source: options.source,\n batch: options.batch,\n }),\n ).pipe(\n Effect.catchCause((cause) =>\n Metric.update(\n Metric.withAttributes(Metrics.batchesTotal, { ...metric, outcome: \"error\" }),\n 1,\n ).pipe(Effect.andThen(Effect.failCause(cause))),\n ),\n );\n\n yield* Metric.update(Metric.withAttributes(Metrics.publishDuration, metric), duration);\n yield* Metric.update(\n Metric.withAttributes(Metrics.batchesTotal, { ...metric, outcome: ack.status }),\n 1,\n );\n\n yield* Effect.annotateCurrentSpan({ [Attr.publisherSuccess]: ack.status === \"accepted\" });\n if (ack.status === \"rejected\") {\n yield* Effect.annotateCurrentSpan({ [Attr.errorPhase]: \"publish\" });\n return yield* Effect.fail(\n new ConnectorError({\n message: `Publisher rejected batch for ${options.resource}: ${ack.reason}`,\n }),\n );\n }\n\n yield* Metric.update(Metric.withAttributes(Metrics.entitiesUpsertedTotal, metric), upserts);\n yield* Metric.update(Metric.withAttributes(Metrics.entitiesDeletedTotal, metric), deletes);\n yield* Metric.update(\n Metric.withAttributes(Metrics.batchSize, metric),\n options.batch.mutations.length,\n );\n\n if (options.batch.cursor !== undefined) {\n yield* addCurrentSpanEvent(EventName.batchCheckpoint, {\n [EventAttr.batchCursor]: options.batch.cursor,\n });\n }\n});\n","import { Cause, Context, Effect, Queue, Schema } from \"effect\";\nimport { HttpRouter, HttpServerRequest, HttpServerResponse } from \"effect/unstable/http\";\n\nimport type {\n ResourceBatch,\n ResourceDefinition,\n ResourceMutation,\n WebhookHandler,\n WebhookRoute,\n WebhookRouteContext,\n} from \"../core/types\";\n\nimport { ConnectorError } from \"../errors\";\nimport * as Metrics from \"../metrics\";\nimport { publishBatch } from \"../publisher/instrumented\";\nimport { Attr, SpanName, annotateError } from \"../telemetry\";\n\nexport type QueuedWebhookBatch = {\n readonly resource: string;\n readonly batch: ResourceBatch;\n};\n\nexport class WebhookQueue extends Context.Service<\n WebhookQueue,\n {\n readonly queue: Queue.Queue<QueuedWebhookBatch>;\n }\n>()(\"@useairfoil/connector-kit/WebhookQueue\") {}\n\nconst resourceBatch = <Row extends object>(\n mutations: ReadonlyArray<ResourceMutation<Row>>,\n): ResourceBatch<Row> => ({\n mutations,\n});\n\nconst decodeAndHandleWebhook = <Row extends object, Payload>(\n resourceName: string,\n webhook: WebhookHandler<Row, Payload>,\n payload: unknown,\n) =>\n Schema.decodeUnknownEffect(webhook.schema)(payload).pipe(\n Effect.mapError(\n (cause) =>\n new ConnectorError({\n message: `Invalid webhook payload for resource ${resourceName}`,\n cause,\n }),\n ),\n Effect.flatMap((decoded) => webhook.handler({ payload: decoded })),\n );\n\nconst makeRouteContext = (\n request: HttpServerRequest.HttpServerRequest,\n rawBody: Uint8Array,\n payload: unknown,\n batches: Array<QueuedWebhookBatch>,\n): WebhookRouteContext<ReadonlyArray<ResourceDefinition>, unknown> => ({\n request,\n rawBody,\n payload,\n to: (resource, payload) =>\n Effect.gen(function* () {\n if (!resource.webhook) {\n return yield* Effect.fail(\n new ConnectorError({\n message: `Resource ${resource.name} does not define a webhook handler`,\n }),\n );\n }\n\n const mutations = yield* decodeAndHandleWebhook(resource.name, resource.webhook, payload);\n batches.push({\n resource: resource.name,\n batch: resourceBatch(mutations),\n });\n }),\n});\n\nconst compactBatches = (batches: ReadonlyArray<QueuedWebhookBatch>) => {\n const grouped = new Map<string, ResourceMutation[]>();\n for (const item of batches) {\n const current = grouped.get(item.resource) ?? [];\n grouped.set(item.resource, [...current, ...item.batch.mutations]);\n }\n return Array.from(grouped.entries()).map(([resource, mutations]) => ({\n resource,\n batch: resourceBatch(mutations),\n }));\n};\n\nconst publishBatches = (\n batches: ReadonlyArray<QueuedWebhookBatch>,\n options: { readonly connectorName: string },\n) =>\n Effect.gen(function* () {\n yield* Effect.forEach(\n batches,\n (batch) =>\n publishBatch({\n connector: options.connectorName,\n resource: batch.resource,\n source: \"webhook\",\n batch: batch.batch,\n }),\n { concurrency: \"unbounded\" },\n );\n });\n\nconst badRequest = (message: string) =>\n HttpServerResponse.jsonUnsafe({ ok: false, error: message }, { status: 400 });\n\nconst readRawBody = (request: HttpServerRequest.HttpServerRequest) =>\n request.arrayBuffer.pipe(Effect.map((buffer) => new Uint8Array(buffer)));\n\nconst parseJson = (rawBody: Uint8Array) =>\n Effect.try({\n try: () => JSON.parse(new TextDecoder().decode(rawBody)),\n catch: (cause) => new ConnectorError({ message: \"Invalid webhook JSON\", cause }),\n });\n\nconst makeHandler = (route: WebhookRoute, options: { readonly connectorName: string }) =>\n Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const collected: Array<QueuedWebhookBatch> = [];\n\n const rawBodyResult = yield* readRawBody(request).pipe(\n Effect.match({\n onFailure: () => ({ _tag: \"Error\" as const, message: \"Failed to read webhook body\" }),\n onSuccess: (value) => ({ _tag: \"Success\" as const, value }),\n }),\n );\n if (rawBodyResult._tag === \"Error\") {\n yield* Metrics.recordWebhookRequest({\n connector: options.connectorName,\n path: String(route.path),\n outcome: \"read_error\",\n });\n return badRequest(rawBodyResult.message);\n }\n\n const jsonResult = yield* parseJson(rawBodyResult.value).pipe(\n Effect.match({\n onFailure: (error) => ({ _tag: \"Error\" as const, message: error.message }),\n onSuccess: (value) => ({ _tag: \"Success\" as const, value }),\n }),\n );\n if (jsonResult._tag === \"Error\") {\n yield* Metrics.recordWebhookRequest({\n connector: options.connectorName,\n path: String(route.path),\n outcome: \"invalid_json\",\n });\n return badRequest(jsonResult.message);\n }\n\n const payloadResult = yield* Schema.decodeUnknownEffect(route.schema)(jsonResult.value).pipe(\n Effect.match({\n onFailure: () => ({ _tag: \"Error\" as const, message: \"Invalid webhook payload\" }),\n onSuccess: (value) => ({ _tag: \"Success\" as const, value }),\n }),\n );\n if (payloadResult._tag === \"Error\") {\n yield* Metrics.recordWebhookRequest({\n connector: options.connectorName,\n path: String(route.path),\n outcome: \"invalid_payload\",\n });\n return badRequest(payloadResult.message);\n }\n\n const response = yield* Effect.withSpan(\n route\n .handler(makeRouteContext(request, rawBodyResult.value, payloadResult.value, collected))\n .pipe(Effect.tapError((error) => annotateError(\"webhook_handle\", error))),\n SpanName.webhookHandle,\n { attributes: { [Attr.webhookPath]: route.path } },\n );\n\n const batches = compactBatches(collected);\n\n if (route.ackMode === \"after-enqueue\") {\n const queue = yield* WebhookQueue;\n yield* Queue.offerAll(queue.queue, batches);\n const depth = yield* Queue.size(queue.queue);\n yield* Metrics.setWebhookQueueDepth(options.connectorName, depth);\n yield* Metrics.recordWebhookRequest({\n connector: options.connectorName,\n path: String(route.path),\n outcome: response.status >= 400 ? \"rejected\" : \"ok\",\n });\n return response;\n }\n\n yield* Effect.withSpan(\n publishBatches(batches, { connectorName: options.connectorName }).pipe(\n Effect.tapError((error) => annotateError(\"webhook_publish\", error)),\n ),\n SpanName.webhookHandle,\n { attributes: { [Attr.webhookPath]: route.path } },\n );\n\n yield* Metrics.recordWebhookRequest({\n connector: options.connectorName,\n path: String(route.path),\n outcome: response.status >= 400 ? \"rejected\" : \"ok\",\n });\n return response;\n }).pipe(\n Effect.catchCause((cause) =>\n Metrics.recordWebhookRequest({\n connector: options.connectorName,\n path: String(route.path),\n outcome: \"handler_error\",\n }).pipe(\n Effect.andThen(Effect.logWarning(`Webhook handler error: ${Cause.pretty(cause)}`)),\n Effect.andThen(\n Effect.succeed(\n HttpServerResponse.jsonUnsafe(\n { ok: false, error: \"Webhook handler failed\" },\n { status: 500 },\n ),\n ),\n ),\n ),\n ),\n );\n\nexport const router = (\n routes: ReadonlyArray<WebhookRoute>,\n options: { readonly connectorName: string },\n) =>\n HttpRouter.addAll(\n routes.map((route) => HttpRouter.route(\"POST\", route.path, makeHandler(route, options))),\n );\n"],"mappings":"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"}
@@ -0,0 +1,191 @@
1
+ import { t as __export } from "./chunk-Bp6m_JJh.js";
2
+ import { n as PlatformRuntimeKey } from "./constants-BRhIX_rz.js";
3
+ import { Cause, Config, Context, Effect, Layer, Metric, Option } from "effect";
4
+ import { Headers } from "effect/unstable/http";
5
+ import * as Observability from "effect/unstable/observability";
6
+
7
+ //#region src/telemetry.ts
8
+ var telemetry_exports = /* @__PURE__ */ __export({
9
+ Attr: () => Attr,
10
+ EventAttr: () => EventAttr,
11
+ EventName: () => EventName,
12
+ SpanName: () => SpanName,
13
+ addCurrentSpanEvent: () => addCurrentSpanEvent,
14
+ annotateError: () => annotateError,
15
+ layer: () => layer,
16
+ layerConfig: () => layerConfig,
17
+ layerMetricsConsoleDump: () => layerMetricsConsoleDump,
18
+ layerOtlp: () => layerOtlp,
19
+ layerOtlpMetrics: () => layerOtlpMetrics,
20
+ layerOtlpTracing: () => layerOtlpTracing
21
+ });
22
+ /** Canonical span names emitted by connector-kit runtime instrumentation. */
23
+ const SpanName = {
24
+ batchProcess: "connector.batch.process",
25
+ webhookDecode: "connector.webhook.decode",
26
+ webhookHandle: "connector.webhook.handle",
27
+ publish: "connector.publish",
28
+ stateSet: "connector.state.set",
29
+ apiFetch: "connector.api.fetch"
30
+ };
31
+ /** Canonical span attribute names. Keep these stable for collector queries. */
32
+ const Attr = {
33
+ connectorName: "airfoil.connector.name",
34
+ connectorResourcesCount: "airfoil.connector.resources.count",
35
+ resourceName: "airfoil.resource.name",
36
+ resourceSource: "airfoil.resource.source",
37
+ batchMutations: "airfoil.batch.mutations",
38
+ webhookPath: "airfoil.webhook.path",
39
+ publisherSuccess: "airfoil.publisher.success",
40
+ stateKey: "airfoil.state.key",
41
+ apiPath: "airfoil.api.path",
42
+ errorPhase: "airfoil.error.phase",
43
+ errorType: "airfoil.error.type",
44
+ errorMessage: "airfoil.error.message",
45
+ errorDetails: "airfoil.error.details"
46
+ };
47
+ /** Span event names for high-cardinality reproduction details. */
48
+ const EventName = { batchCheckpoint: "airfoil.batch.checkpoint" };
49
+ /** Event-only attributes. These are intentionally not span attributes. */
50
+ const EventAttr = { batchCursor: "airfoil.batch.cursor" };
51
+ const isRecord = (value) => typeof value === "object" && value !== null;
52
+ const maxErrorMessageLength = 500;
53
+ const OtlpEnvConfig = Config.all({
54
+ enabled: Config.boolean(PlatformRuntimeKey.otelEnabled).pipe(Config.withDefault(false)),
55
+ baseUrl: Config.option(Config.string(PlatformRuntimeKey.otelExporterOtlpEndpoint)),
56
+ rawHeaders: Config.option(Config.string(PlatformRuntimeKey.otelExporterOtlpHeaders))
57
+ });
58
+ const defaultRedactedHeaders = [
59
+ "authorization",
60
+ "cookie",
61
+ "set-cookie",
62
+ "x-api-key",
63
+ /api[-_]?key/i,
64
+ /secret/i,
65
+ /signature/i,
66
+ /token/i
67
+ ];
68
+ const parseOtelHeaders = (value) => Object.fromEntries(value.split(",").map((entry) => entry.trim()).filter(Boolean).flatMap((entry) => {
69
+ const separator = entry.indexOf("=");
70
+ if (separator < 1) return [];
71
+ return [[entry.slice(0, separator).trim(), entry.slice(separator + 1).trim()]];
72
+ }));
73
+ const appendPath = (baseUrl, path) => `${baseUrl.replace(/\/$/, "")}/${path.replace(/^\//, "")}`;
74
+ /**
75
+ * Best-effort stable error classifier for span metadata.
76
+ *
77
+ * Effect/Data tagged errors expose `_tag`; that is the most useful value for
78
+ * connector bugs (`ConnectorError`, `InvalidWebhookPayloadError`, etc.). For
79
+ * non-tagged errors we fall back to the JS `Error.name`, then constructor name,
80
+ * then the primitive type.
81
+ */
82
+ const errorType = (error) => {
83
+ if (isRecord(error) && typeof error._tag === "string") return String(error._tag);
84
+ if (error instanceof Error) return error.name;
85
+ if (isRecord(error) && typeof error.constructor?.name === "string") return error.constructor.name;
86
+ return typeof error;
87
+ };
88
+ /**
89
+ * Human-readable error summary for span metadata.
90
+ *
91
+ * Tagged errors in this package carry a `message` field, so this preserves the
92
+ * important diagnostic text. Unknown object failures are intentionally reduced
93
+ * to their type; full structured error payloads belong in logs, not span attrs.
94
+ */
95
+ const errorMessage = (error) => {
96
+ const message = (() => {
97
+ if (isRecord(error) && typeof error.message === "string") return error.message;
98
+ if (typeof error === "string") return error;
99
+ return errorType(error);
100
+ })();
101
+ if (message.length > maxErrorMessageLength) return `${message.slice(0, maxErrorMessageLength)}...`;
102
+ return message;
103
+ };
104
+ const errorDetails = (error) => {
105
+ if (!isRecord(error) || error.cause === void 0) return void 0;
106
+ return Cause.pretty(Cause.die(error.cause));
107
+ };
108
+ /** Annotates the active span with phase-aware error metadata before re-failing. */
109
+ const annotateError = (phase, error) => {
110
+ const details = errorDetails(error);
111
+ return Effect.annotateCurrentSpan({
112
+ [Attr.errorPhase]: phase,
113
+ [Attr.errorType]: errorType(error),
114
+ [Attr.errorMessage]: errorMessage(error),
115
+ ...details !== void 0 ? { [Attr.errorDetails]: details } : {}
116
+ });
117
+ };
118
+ /** Adds an event to the active span, or no-ops when no span is active. */
119
+ const addCurrentSpanEvent = (name, attributes) => Effect.currentSpan.pipe(Effect.flatMap((span) => Effect.clockWith((clock) => Effect.sync(() => span.event(name, clock.currentTimeNanosUnsafe(), attributes)))), Effect.ignore);
120
+ const buildOtlpTracingLayer = (config, options) => Effect.gen(function* () {
121
+ const allRedacted = [...defaultRedactedHeaders, ...options.redactedHeaders ?? []];
122
+ const RedactionLayer = Layer.succeed(Headers.CurrentRedactedNames)(allRedacted);
123
+ if (!config.enabled) return RedactionLayer;
124
+ if (!config.endpoint) return yield* Effect.fail(/* @__PURE__ */ new Error("enabled=true requires endpoint"));
125
+ yield* Effect.logInfo("telemetry enabled").pipe(Effect.annotateLogs({
126
+ endpoint: config.endpoint,
127
+ headers: config.headers ? Object.keys(config.headers) : []
128
+ }));
129
+ return Layer.mergeAll(RedactionLayer, Observability.OtlpTracer.layer({
130
+ url: appendPath(config.endpoint, "/v1/traces"),
131
+ headers: config.headers
132
+ }).pipe(Layer.provide(Observability.OtlpSerialization.layerJson)));
133
+ });
134
+ /**
135
+ * Trace-only OTLP export with sensitive HTTP header redaction.
136
+ * Takes direct config values — no `ConfigProvider` required.
137
+ */
138
+ const layer = (config = {}, options = {}) => Layer.unwrap(buildOtlpTracingLayer(config, options));
139
+ /**
140
+ * Trace-only OTLP export with sensitive HTTP header redaction.
141
+ * Takes Effect Config-wrapped values — reads from `ConfigProvider`.
142
+ *
143
+ * @example
144
+ * Telemetry.layerConfig({
145
+ * enabled: Config.boolean("MY_OTEL_ENABLED"),
146
+ * endpoint: Config.string("MY_COLLECTOR_URL"),
147
+ * })
148
+ */
149
+ const layerConfig = (config, options = {}) => Layer.unwrap(Effect.gen(function* () {
150
+ return yield* buildOtlpTracingLayer(yield* Config.unwrap(config), options);
151
+ }));
152
+ /**
153
+ * Trace-only OTLP export with sensitive HTTP header redaction.
154
+ * Zero-config shortcut that reads the standard `OTEL_*` env var names.
155
+ *
156
+ * Reads `OTEL_ENABLED`, `OTEL_EXPORTER_OTLP_ENDPOINT`, and
157
+ * `OTEL_EXPORTER_OTLP_HEADERS`. Effect's OTLP resource handling reads
158
+ * `OTEL_SERVICE_NAME`, `OTEL_SERVICE_VERSION`, and `OTEL_RESOURCE_ATTRIBUTES`.
159
+ */
160
+ const layerOtlpTracing = (options = {}) => Layer.unwrap(Effect.gen(function* () {
161
+ const raw = yield* OtlpEnvConfig;
162
+ return yield* buildOtlpTracingLayer({
163
+ enabled: raw.enabled,
164
+ endpoint: Option.getOrUndefined(raw.baseUrl),
165
+ headers: Option.isSome(raw.rawHeaders) ? parseOtelHeaders(raw.rawHeaders.value) : void 0
166
+ }, options);
167
+ }));
168
+ const layerOtlpMetrics = (options = {}) => Layer.unwrap(Effect.gen(function* () {
169
+ const raw = yield* OtlpEnvConfig;
170
+ if (!raw.enabled) return Layer.empty;
171
+ const endpoint = Option.getOrUndefined(raw.baseUrl);
172
+ if (!endpoint) return yield* Effect.fail(/* @__PURE__ */ new Error("OTEL_ENABLED=true requires OTEL_EXPORTER_OTLP_ENDPOINT"));
173
+ return Observability.OtlpMetrics.layer({
174
+ url: appendPath(endpoint, "/v1/metrics"),
175
+ headers: Option.isSome(raw.rawHeaders) ? parseOtelHeaders(raw.rawHeaders.value) : void 0,
176
+ exportInterval: options.exportInterval,
177
+ temporality: "cumulative"
178
+ }).pipe(Layer.provide(Observability.OtlpSerialization.layerJson));
179
+ }));
180
+ const layerOtlp = (options = {}) => Layer.mergeAll(layerOtlpTracing(options), layerOtlpMetrics(options));
181
+ const layerMetricsConsoleDump = (interval = "30 seconds") => Layer.effectDiscard(Effect.gen(function* () {
182
+ yield* Effect.forkScoped(Effect.forever(Metric.dump.pipe(Effect.flatMap((snapshot) => Effect.logInfo(snapshot)), Effect.andThen(Effect.sleep(interval)))));
183
+ }));
184
+
185
+ //#endregion
186
+ //#region src/publisher/service.ts
187
+ var Publisher = class extends Context.Service()("@useairfoil/connector-kit/Publisher") {};
188
+
189
+ //#endregion
190
+ export { SpanName as a, telemetry_exports as c, EventName as i, Attr as n, addCurrentSpanEvent as o, EventAttr as r, annotateError as s, Publisher as t };
191
+ //# sourceMappingURL=service-DuFiQwid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-DuFiQwid.js","names":["defaultRedactedHeaders: ReadonlyArray<string | RegExp>"],"sources":["../src/telemetry.ts","../src/publisher/service.ts"],"sourcesContent":["import { Cause, Config, Effect, Layer, Metric, Option, type Duration } from \"effect\";\nimport { Headers } from \"effect/unstable/http\";\nimport * as Observability from \"effect/unstable/observability\";\n\nimport { PlatformRuntimeKey } from \"./runtime-config/constants\";\n\n/** Canonical span names emitted by connector-kit runtime instrumentation. */\nexport const SpanName = {\n batchProcess: \"connector.batch.process\",\n webhookDecode: \"connector.webhook.decode\",\n webhookHandle: \"connector.webhook.handle\",\n publish: \"connector.publish\",\n stateSet: \"connector.state.set\",\n apiFetch: \"connector.api.fetch\",\n} as const;\n\n/** Canonical span attribute names. Keep these stable for collector queries. */\nexport const Attr = {\n connectorName: \"airfoil.connector.name\",\n connectorResourcesCount: \"airfoil.connector.resources.count\",\n resourceName: \"airfoil.resource.name\",\n resourceSource: \"airfoil.resource.source\",\n batchMutations: \"airfoil.batch.mutations\",\n webhookPath: \"airfoil.webhook.path\",\n publisherSuccess: \"airfoil.publisher.success\",\n stateKey: \"airfoil.state.key\",\n apiPath: \"airfoil.api.path\",\n errorPhase: \"airfoil.error.phase\",\n errorType: \"airfoil.error.type\",\n errorMessage: \"airfoil.error.message\",\n errorDetails: \"airfoil.error.details\",\n} as const;\n\n/** Span event names for high-cardinality reproduction details. */\nexport const EventName = {\n batchCheckpoint: \"airfoil.batch.checkpoint\",\n} as const;\n\n/** Event-only attributes. These are intentionally not span attributes. */\nexport const EventAttr = {\n batchCursor: \"airfoil.batch.cursor\",\n} as const;\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null;\n\nconst maxErrorMessageLength = 500;\n\nconst OtlpEnvConfig = Config.all({\n enabled: Config.boolean(PlatformRuntimeKey.otelEnabled).pipe(Config.withDefault(false)),\n baseUrl: Config.option(Config.string(PlatformRuntimeKey.otelExporterOtlpEndpoint)),\n rawHeaders: Config.option(Config.string(PlatformRuntimeKey.otelExporterOtlpHeaders)),\n});\n\nconst defaultRedactedHeaders: ReadonlyArray<string | RegExp> = [\n \"authorization\",\n \"cookie\",\n \"set-cookie\",\n \"x-api-key\",\n /api[-_]?key/i,\n /secret/i,\n /signature/i,\n /token/i,\n];\n\nconst parseOtelHeaders = (value: string): Record<string, string> =>\n Object.fromEntries(\n value\n .split(\",\")\n .map((entry) => entry.trim())\n .filter(Boolean)\n .flatMap((entry) => {\n const separator = entry.indexOf(\"=\");\n if (separator < 1) return [];\n return [[entry.slice(0, separator).trim(), entry.slice(separator + 1).trim()]];\n }),\n );\n\nconst appendPath = (baseUrl: string, path: string) =>\n `${baseUrl.replace(/\\/$/, \"\")}/${path.replace(/^\\//, \"\")}`;\n\n/**\n * Best-effort stable error classifier for span metadata.\n *\n * Effect/Data tagged errors expose `_tag`; that is the most useful value for\n * connector bugs (`ConnectorError`, `InvalidWebhookPayloadError`, etc.). For\n * non-tagged errors we fall back to the JS `Error.name`, then constructor name,\n * then the primitive type.\n */\nconst errorType = (error: unknown) => {\n if (isRecord(error) && typeof error._tag === \"string\") {\n return String(error._tag);\n }\n if (error instanceof Error) {\n return error.name;\n }\n if (isRecord(error) && typeof error.constructor?.name === \"string\") {\n return error.constructor.name;\n }\n return typeof error;\n};\n\n/**\n * Human-readable error summary for span metadata.\n *\n * Tagged errors in this package carry a `message` field, so this preserves the\n * important diagnostic text. Unknown object failures are intentionally reduced\n * to their type; full structured error payloads belong in logs, not span attrs.\n */\nconst errorMessage = (error: unknown) => {\n const message = (() => {\n if (isRecord(error) && typeof error.message === \"string\") {\n return error.message;\n }\n if (typeof error === \"string\") {\n return error;\n }\n return errorType(error);\n })();\n\n if (message.length > maxErrorMessageLength) {\n return `${message.slice(0, maxErrorMessageLength)}...`;\n }\n\n return message;\n};\n\nconst errorDetails = (error: unknown): string | undefined => {\n if (!isRecord(error) || error.cause === undefined) return undefined;\n return Cause.pretty(Cause.die(error.cause));\n};\n\n/** Annotates the active span with phase-aware error metadata before re-failing. */\nexport const annotateError = (phase: string, error: unknown) => {\n const details = errorDetails(error);\n return Effect.annotateCurrentSpan({\n [Attr.errorPhase]: phase,\n [Attr.errorType]: errorType(error),\n [Attr.errorMessage]: errorMessage(error),\n ...(details !== undefined ? { [Attr.errorDetails]: details } : {}),\n });\n};\n\n/** Adds an event to the active span, or no-ops when no span is active. */\nexport const addCurrentSpanEvent = (name: string, attributes?: Record<string, unknown>) =>\n Effect.currentSpan.pipe(\n Effect.flatMap((span) =>\n Effect.clockWith((clock) =>\n Effect.sync(() => span.event(name, clock.currentTimeNanosUnsafe(), attributes)),\n ),\n ),\n Effect.ignore,\n );\n\n/** Transport configuration for OTLP trace export. All fields are direct values. */\nexport type OtlpTracingConfig = {\n readonly enabled?: boolean;\n readonly endpoint?: string;\n readonly headers?: Record<string, string>;\n};\n\n/** Runtime options — not config-wrappable since RegExp cannot live in Config. */\nexport type OtlpTracingOptions = {\n /** Additional connector-specific header names or regexes to redact from HTTP logs/traces. */\n readonly redactedHeaders?: ReadonlyArray<string | RegExp>;\n};\n\nexport type OtlpMetricsOptions = {\n readonly exportInterval?: Duration.Input;\n};\n\nconst buildOtlpTracingLayer = (config: OtlpTracingConfig, options: OtlpTracingOptions) =>\n Effect.gen(function* () {\n const allRedacted = [...defaultRedactedHeaders, ...(options.redactedHeaders ?? [])];\n const RedactionLayer = Layer.succeed(Headers.CurrentRedactedNames)(allRedacted);\n\n if (!config.enabled) {\n return RedactionLayer;\n }\n\n if (!config.endpoint) {\n return yield* Effect.fail(new Error(\"enabled=true requires endpoint\"));\n }\n\n yield* Effect.logInfo(\"telemetry enabled\").pipe(\n Effect.annotateLogs({\n endpoint: config.endpoint,\n headers: config.headers ? Object.keys(config.headers) : [],\n }),\n );\n\n return Layer.mergeAll(\n RedactionLayer,\n Observability.OtlpTracer.layer({\n url: appendPath(config.endpoint, \"/v1/traces\"),\n headers: config.headers,\n }).pipe(Layer.provide(Observability.OtlpSerialization.layerJson)),\n );\n });\n\n/**\n * Trace-only OTLP export with sensitive HTTP header redaction.\n * Takes direct config values — no `ConfigProvider` required.\n */\nexport const layer = (config: OtlpTracingConfig = {}, options: OtlpTracingOptions = {}) =>\n Layer.unwrap(buildOtlpTracingLayer(config, options));\n\n/**\n * Trace-only OTLP export with sensitive HTTP header redaction.\n * Takes Effect Config-wrapped values — reads from `ConfigProvider`.\n *\n * @example\n * Telemetry.layerConfig({\n * enabled: Config.boolean(\"MY_OTEL_ENABLED\"),\n * endpoint: Config.string(\"MY_COLLECTOR_URL\"),\n * })\n */\nexport const layerConfig = (\n config: Config.Wrap<OtlpTracingConfig>,\n options: OtlpTracingOptions = {},\n) =>\n Layer.unwrap(\n Effect.gen(function* () {\n const resolved = yield* Config.unwrap(config);\n return yield* buildOtlpTracingLayer(resolved, options);\n }),\n );\n\n/**\n * Trace-only OTLP export with sensitive HTTP header redaction.\n * Zero-config shortcut that reads the standard `OTEL_*` env var names.\n *\n * Reads `OTEL_ENABLED`, `OTEL_EXPORTER_OTLP_ENDPOINT`, and\n * `OTEL_EXPORTER_OTLP_HEADERS`. Effect's OTLP resource handling reads\n * `OTEL_SERVICE_NAME`, `OTEL_SERVICE_VERSION`, and `OTEL_RESOURCE_ATTRIBUTES`.\n */\nexport const layerOtlpTracing = (options: OtlpTracingOptions = {}) =>\n Layer.unwrap(\n Effect.gen(function* () {\n const raw = yield* OtlpEnvConfig;\n return yield* buildOtlpTracingLayer(\n {\n enabled: raw.enabled,\n endpoint: Option.getOrUndefined(raw.baseUrl),\n headers: Option.isSome(raw.rawHeaders)\n ? parseOtelHeaders(raw.rawHeaders.value)\n : undefined,\n },\n options,\n );\n }),\n );\n\nexport const layerOtlpMetrics = (options: OtlpMetricsOptions = {}) =>\n Layer.unwrap(\n Effect.gen(function* () {\n const raw = yield* OtlpEnvConfig;\n if (!raw.enabled) return Layer.empty;\n const endpoint = Option.getOrUndefined(raw.baseUrl);\n if (!endpoint)\n return yield* Effect.fail(\n new Error(\"OTEL_ENABLED=true requires OTEL_EXPORTER_OTLP_ENDPOINT\"),\n );\n return Observability.OtlpMetrics.layer({\n url: appendPath(endpoint, \"/v1/metrics\"),\n headers: Option.isSome(raw.rawHeaders) ? parseOtelHeaders(raw.rawHeaders.value) : undefined,\n exportInterval: options.exportInterval,\n temporality: \"cumulative\",\n }).pipe(Layer.provide(Observability.OtlpSerialization.layerJson));\n }),\n );\n\nexport const layerOtlp = (options: OtlpTracingOptions & OtlpMetricsOptions = {}) =>\n Layer.mergeAll(layerOtlpTracing(options), layerOtlpMetrics(options));\n\nexport const layerMetricsConsoleDump = (interval: Duration.Input = \"30 seconds\") =>\n Layer.effectDiscard(\n Effect.gen(function* () {\n yield* Effect.forkScoped(\n Effect.forever(\n Metric.dump.pipe(\n Effect.flatMap((snapshot) => Effect.logInfo(snapshot)),\n Effect.andThen(Effect.sleep(interval)),\n ),\n ),\n );\n }),\n );\n","import type * as Wings from \"@useairfoil/wings\";\nimport type * as Effect from \"effect/Effect\";\n\nimport { Context } from \"effect\";\n\nimport type { ResourceBatch } from \"../core/types\";\nimport type { ConnectorError } from \"../errors\";\n\nexport type PublishSource = \"backfill\" | \"changes\" | \"webhook\";\n\nexport type PublishAck =\n | {\n readonly status: \"accepted\";\n readonly resource: string;\n readonly partition?: Wings.PartitionValue.PartitionValue;\n }\n | {\n readonly status: \"rejected\";\n readonly resource: string;\n readonly reason: string;\n readonly rejectedRows?: number;\n readonly partition?: Wings.PartitionValue.PartitionValue;\n };\n\nexport type PublishOptions = {\n readonly resource: string;\n readonly source: PublishSource;\n readonly batch: ResourceBatch;\n};\n\nexport interface PublisherService {\n readonly publish: (options: PublishOptions) => Effect.Effect<PublishAck, ConnectorError>;\n}\n\nexport class Publisher extends Context.Service<Publisher, PublisherService>()(\n \"@useairfoil/connector-kit/Publisher\",\n) {}\n"],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import { t as ConnectorError } from "./index-D54ySAKn.js";
2
+ import { c as ResourceBatch } from "./types-B35J5YPu.js";
3
+ import { Context } from "effect";
4
+ import * as Wings from "@useairfoil/wings";
5
+ import * as Effect$1 from "effect/Effect";
6
+
7
+ //#region src/publisher/service.d.ts
8
+ type PublishSource = "backfill" | "changes" | "webhook";
9
+ type PublishAck = {
10
+ readonly status: "accepted";
11
+ readonly resource: string;
12
+ readonly partition?: Wings.PartitionValue.PartitionValue;
13
+ } | {
14
+ readonly status: "rejected";
15
+ readonly resource: string;
16
+ readonly reason: string;
17
+ readonly rejectedRows?: number;
18
+ readonly partition?: Wings.PartitionValue.PartitionValue;
19
+ };
20
+ type PublishOptions = {
21
+ readonly resource: string;
22
+ readonly source: PublishSource;
23
+ readonly batch: ResourceBatch;
24
+ };
25
+ interface PublisherService {
26
+ readonly publish: (options: PublishOptions) => Effect$1.Effect<PublishAck, ConnectorError>;
27
+ }
28
+ declare const Publisher_base: Context.ServiceClass<Publisher, "@useairfoil/connector-kit/Publisher", PublisherService>;
29
+ declare class Publisher extends Publisher_base {}
30
+ //#endregion
31
+ export { PublisherService as a, Publisher as i, PublishOptions as n, PublishSource as r, PublishAck as t };
32
+ //# sourceMappingURL=service-IE5qaf9x.d.ts.map
@@ -0,0 +1,3 @@
1
+ import "../index-D54ySAKn.js";
2
+ import { a as StateStoreService, i as StateStore, n as layerSql, r as layerMemory } from "../index-Coh_SkBs.js";
3
+ export { StateStore, StateStoreService, layerMemory, layerSql };
@@ -0,0 +1,5 @@
1
+ import "../errors-DHWylqRZ.js";
2
+ import "../constants-BRhIX_rz.js";
3
+ import { n as layerSql, o as StateStore, r as layerMemory } from "../state-store-yl6LiTQ0.js";
4
+
5
+ export { StateStore, layerMemory, layerSql };