@useairfoil/connector-kit 0.5.3 → 0.6.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 (178) hide show
  1. package/METRICS.md +20 -19
  2. package/README.md +127 -430
  3. package/dist/annotations-BE74YmAO.d.mts +39 -0
  4. package/dist/auth/index.d.mts +2 -0
  5. package/dist/{auth-DvU_CZ7C.js → auth/index.mjs} +6 -8
  6. package/dist/auth/index.mjs.map +1 -0
  7. package/dist/connector-app/index.d.mts +2 -0
  8. package/dist/connector-app/index.mjs +2 -0
  9. package/dist/connector-app-C-L4QqG6.mjs +123 -0
  10. package/dist/connector-app-C-L4QqG6.mjs.map +1 -0
  11. package/dist/{constants-BRhIX_rz.js → constants-D-GQzpHZ.mjs} +10 -6
  12. package/dist/constants-D-GQzpHZ.mjs.map +1 -0
  13. package/dist/demo/index.d.mts +2 -0
  14. package/dist/demo/index.mjs +2 -0
  15. package/dist/demo-B30Vlkk2.mjs +191 -0
  16. package/dist/demo-B30Vlkk2.mjs.map +1 -0
  17. package/dist/errors/index.d.mts +2 -0
  18. package/dist/errors/index.mjs +2 -0
  19. package/dist/{errors-DHWylqRZ.js → errors-pT0YVJU5.mjs} +2 -3
  20. package/dist/errors-pT0YVJU5.mjs.map +1 -0
  21. package/dist/iceberg/index.d.mts +3 -0
  22. package/dist/iceberg/index.mjs +3 -0
  23. package/dist/iceberg-D6L8I6vA.mjs +39 -0
  24. package/dist/iceberg-D6L8I6vA.mjs.map +1 -0
  25. package/dist/{index-Cq2IzY7T.d.ts → index-BUHSGm9Q.d.mts} +11 -13
  26. package/dist/{index-CTHVudvQ.d.ts → index-BX8G7d41.d.mts} +1 -2
  27. package/dist/{index-J42AZaXU.d.ts → index-BfXpgxk9.d.mts} +9 -10
  28. package/dist/{index-DKWIh6M1.d.ts → index-CRVAwJ5-.d.mts} +10 -8
  29. package/dist/index-CUPP2A_m.d.mts +27 -0
  30. package/dist/{index-Coh_SkBs.d.ts → index-CgvFomO6.d.mts} +6 -7
  31. package/dist/index-DJhDp4Xc.d.mts +58 -0
  32. package/dist/index-Dk7urvpF.d.mts +76 -0
  33. package/dist/{index-D54ySAKn.d.ts → index-DkVNO6ks.d.mts} +2 -5
  34. package/dist/{index-DtJX_tic.d.ts → index-NnCutIqM.d.mts} +77 -38
  35. package/dist/{index-CBT-JX-T.d.ts → index-eFuoMZZ3.d.mts} +18 -20
  36. package/dist/index-ygpml2kC.d.mts +73 -0
  37. package/dist/index.d.mts +16 -0
  38. package/dist/index.mjs +45 -0
  39. package/dist/index.mjs.map +1 -0
  40. package/dist/ingestion/index.d.mts +2 -0
  41. package/dist/ingestion/index.mjs +2 -0
  42. package/dist/ingestion-q0WwqUSt.mjs +275 -0
  43. package/dist/ingestion-q0WwqUSt.mjs.map +1 -0
  44. package/dist/ingestor/index.d.mts +3 -0
  45. package/dist/ingestor/index.mjs +4 -0
  46. package/dist/ingestor-D4L-HPkg.mjs +26 -0
  47. package/dist/ingestor-D4L-HPkg.mjs.map +1 -0
  48. package/dist/instrumented-Bhp7fyvh.mjs +29 -0
  49. package/dist/instrumented-Bhp7fyvh.mjs.map +1 -0
  50. package/dist/manifest/index.d.mts +2 -0
  51. package/dist/manifest/{index.js → index.mjs} +2 -4
  52. package/dist/{manifest-ByE6YPG0.js → manifest-BGKxmKLy.mjs} +14 -19
  53. package/dist/manifest-BGKxmKLy.mjs.map +1 -0
  54. package/dist/metrics-BaEX__MN.mjs +352 -0
  55. package/dist/metrics-BaEX__MN.mjs.map +1 -0
  56. package/dist/metrics-CVXFL7YT.d.mts +64 -0
  57. package/dist/metrics.d.mts +2 -0
  58. package/dist/metrics.mjs +2 -0
  59. package/dist/{chunk-Bp6m_JJh.js → rolldown-runtime-D7D4PA-g.mjs} +4 -4
  60. package/dist/runtime-config/index.d.mts +2 -0
  61. package/dist/runtime-config/index.mjs +3 -0
  62. package/dist/runtime-config-Oa55gAew.mjs +97 -0
  63. package/dist/runtime-config-Oa55gAew.mjs.map +1 -0
  64. package/dist/{server-CXTQ7odl.js → server-CMnxz3h0.mjs} +21 -60
  65. package/dist/server-CMnxz3h0.mjs.map +1 -0
  66. package/dist/service-DLkhuxYl.d.mts +27 -0
  67. package/dist/service-jWxxfEht.mjs +7 -0
  68. package/dist/service-jWxxfEht.mjs.map +1 -0
  69. package/dist/state-store/index.d.mts +2 -0
  70. package/dist/state-store/index.mjs +2 -0
  71. package/dist/{state-store-yl6LiTQ0.js → state-store-uFvHY7Ax.mjs} +15 -22
  72. package/dist/state-store-uFvHY7Ax.mjs.map +1 -0
  73. package/dist/status/{index.d.ts → index.d.mts} +1 -4
  74. package/dist/status/{index.js → index.mjs} +2 -6
  75. package/dist/{status-B-oZBc33.js → status-CKb9IhQ9.mjs} +14 -16
  76. package/dist/status-CKb9IhQ9.mjs.map +1 -0
  77. package/dist/table-V0tgUC5i.mjs +370 -0
  78. package/dist/table-V0tgUC5i.mjs.map +1 -0
  79. package/dist/types-xZtSmrGA.d.mts +220 -0
  80. package/dist/webhook/index.d.mts +3 -0
  81. package/dist/webhook/index.mjs +12 -0
  82. package/dist/webhook/index.mjs.map +1 -0
  83. package/dist/wings-I56hGtNv.mjs +325 -0
  84. package/dist/wings-I56hGtNv.mjs.map +1 -0
  85. package/package.json +64 -51
  86. package/src/catalog/table.ts +201 -0
  87. package/src/connector-app/create-table.ts +47 -0
  88. package/src/connector-app/demo.ts +42 -0
  89. package/src/connector-app/index.ts +2 -0
  90. package/src/connector-app/start.ts +1 -1
  91. package/src/core/builder.ts +15 -23
  92. package/src/core/index.ts +11 -1
  93. package/src/core/types.ts +114 -39
  94. package/src/demo/definition.ts +68 -0
  95. package/src/demo/generate.ts +167 -0
  96. package/src/demo/index.ts +3 -0
  97. package/src/demo/run.ts +142 -0
  98. package/src/iceberg/annotations.ts +62 -0
  99. package/src/iceberg/error.ts +9 -0
  100. package/src/iceberg/index.ts +3 -0
  101. package/src/iceberg/table.ts +511 -0
  102. package/src/index.ts +3 -1
  103. package/src/ingestion/engine.ts +240 -60
  104. package/src/ingestor/arrow.ts +248 -0
  105. package/src/ingestor/console.ts +13 -0
  106. package/src/ingestor/index.ts +9 -0
  107. package/src/ingestor/instrumented.ts +61 -0
  108. package/src/ingestor/service.ts +30 -0
  109. package/src/ingestor/wings.ts +143 -0
  110. package/src/manifest/config.ts +3 -3
  111. package/src/metrics.ts +125 -33
  112. package/src/runtime-config/config.ts +14 -0
  113. package/src/runtime-config/constants.ts +5 -2
  114. package/src/runtime-config/error.ts +4 -1
  115. package/src/runtime-config/index.ts +2 -0
  116. package/src/runtime-config/layer.ts +1 -1
  117. package/src/runtime-config/table-bindings.ts +92 -0
  118. package/src/state-store/layer-key-value-store.ts +8 -3
  119. package/src/state-store/layer-memory.ts +1 -1
  120. package/src/state-store/layer-sql.ts +4 -4
  121. package/src/state-store/schema.ts +2 -2
  122. package/src/state-store/state.ts +14 -2
  123. package/src/status/route.ts +2 -2
  124. package/src/telemetry.ts +25 -10
  125. package/src/webhook/server.ts +39 -40
  126. package/dist/auth/index.d.ts +0 -2
  127. package/dist/auth/index.js +0 -3
  128. package/dist/auth-DvU_CZ7C.js.map +0 -1
  129. package/dist/connector-app/index.d.ts +0 -14
  130. package/dist/connector-app/index.js +0 -11
  131. package/dist/connector-app-d7Z-SZLQ.js +0 -64
  132. package/dist/connector-app-d7Z-SZLQ.js.map +0 -1
  133. package/dist/constants-BRhIX_rz.js.map +0 -1
  134. package/dist/errors/index.d.ts +0 -2
  135. package/dist/errors/index.js +0 -3
  136. package/dist/errors-DHWylqRZ.js.map +0 -1
  137. package/dist/index-Byr0Lf0C.d.ts +0 -25
  138. package/dist/index-o1-Z3n7V.d.ts +0 -55
  139. package/dist/index.d.ts +0 -14
  140. package/dist/index.js +0 -58
  141. package/dist/index.js.map +0 -1
  142. package/dist/ingestion/index.d.ts +0 -6
  143. package/dist/ingestion/index.js +0 -10
  144. package/dist/ingestion-BQo4cbGw.js +0 -200
  145. package/dist/ingestion-BQo4cbGw.js.map +0 -1
  146. package/dist/manifest/index.d.ts +0 -2
  147. package/dist/manifest-ByE6YPG0.js.map +0 -1
  148. package/dist/metrics-CVAk1ALR.d.ts +0 -53
  149. package/dist/metrics-CWPy2Aqc.js +0 -96
  150. package/dist/metrics-CWPy2Aqc.js.map +0 -1
  151. package/dist/metrics.d.ts +0 -4
  152. package/dist/metrics.js +0 -3
  153. package/dist/publisher/index.d.ts +0 -5
  154. package/dist/publisher/index.js +0 -6
  155. package/dist/publisher-BYT3RLVO.js +0 -131
  156. package/dist/publisher-BYT3RLVO.js.map +0 -1
  157. package/dist/runtime-config/index.d.ts +0 -2
  158. package/dist/runtime-config/index.js +0 -4
  159. package/dist/runtime-config-BpB-d_bF.js +0 -51
  160. package/dist/runtime-config-BpB-d_bF.js.map +0 -1
  161. package/dist/server-CXTQ7odl.js.map +0 -1
  162. package/dist/service-DuFiQwid.js +0 -191
  163. package/dist/service-DuFiQwid.js.map +0 -1
  164. package/dist/service-IE5qaf9x.d.ts +0 -32
  165. package/dist/state-store/index.d.ts +0 -3
  166. package/dist/state-store/index.js +0 -5
  167. package/dist/state-store-yl6LiTQ0.js.map +0 -1
  168. package/dist/status-B-oZBc33.js.map +0 -1
  169. package/dist/types-B35J5YPu.d.ts +0 -174
  170. package/dist/webhook/index.d.ts +0 -6
  171. package/dist/webhook/index.js +0 -8
  172. package/dist/webhook-DI-Afxkg.js +0 -13
  173. package/dist/webhook-DI-Afxkg.js.map +0 -1
  174. package/src/publisher/console.ts +0 -18
  175. package/src/publisher/index.ts +0 -7
  176. package/src/publisher/instrumented.ts +0 -68
  177. package/src/publisher/service.ts +0 -37
  178. package/src/publisher/wings.ts +0 -204
@@ -1,131 +0,0 @@
1
- import { t as __export } from "./chunk-Bp6m_JJh.js";
2
- import { t as ConnectorError } from "./errors-DHWylqRZ.js";
3
- import { a as SpanName, t as Publisher } from "./service-DuFiQwid.js";
4
- import { Effect, Layer } from "effect";
5
- import * as Wings from "@useairfoil/wings";
6
-
7
- //#region src/publisher/console.ts
8
- const layerConsole = Layer.succeed(Publisher)({ publish: ({ resource, source, batch }) => Effect.gen(function* () {
9
- yield* Effect.logInfo(`[publisher] -> Source: ${source} | Resource: ${resource}`).pipe(Effect.annotateLogs({
10
- mutations: batch.mutations.length,
11
- cursor: batch.cursor,
12
- source
13
- }));
14
- return {
15
- status: "accepted",
16
- resource
17
- };
18
- }) });
19
-
20
- //#endregion
21
- //#region src/publisher/wings.ts
22
- const normalizeMapping = (mapping) => typeof mapping === "string" ? { name: mapping } : mapping;
23
- const buildRecordBatch = (rows) => {
24
- const [batch] = Wings.Arrow.tableFromJSON(Array.from(rows)).batches;
25
- return batch ? Effect.succeed(batch) : Effect.fail(new ConnectorError({ message: "No rows to publish" }));
26
- };
27
- const validateTableMapping = (options) => Effect.try({
28
- try: () => {
29
- const keyField = Wings.TableUtils.getKeyField(options.table);
30
- const versionField = Wings.TableUtils.getVersionField(options.table);
31
- const partitionField = Wings.TableUtils.getPartitionField(options.table);
32
- if (keyField.name !== options.resource.key) throw new Error(`Resource ${options.resource.name} key '${options.resource.key}' does not match Wings table key '${keyField.name}'`);
33
- if (versionField.name !== options.resource.version) throw new Error(`Resource ${options.resource.name} version '${options.resource.version}' does not match Wings table version '${versionField.name}'`);
34
- if (options.resource.partition?.required === true && !options.partitionValue) throw new Error(`Resource ${options.resource.name} requires a partition value`);
35
- if (partitionField && !options.partitionValue) throw new Error(`Wings table ${options.table.name} is partitioned but no partition value was provided`);
36
- if (!partitionField && options.partitionValue) throw new Error(`Wings table ${options.table.name} is not partitioned but a partition value was provided`);
37
- return {
38
- keyField,
39
- versionField
40
- };
41
- },
42
- catch: (cause) => new ConnectorError({
43
- message: `Invalid Wings table mapping for ${options.resource.name}`,
44
- cause
45
- })
46
- });
47
- const accepted = (entry) => ({
48
- status: "accepted",
49
- resource: entry.resource.name,
50
- partition: entry.partitionValue
51
- });
52
- const rejected = (entry, reason, rejectedRows) => ({
53
- status: "rejected",
54
- resource: entry.resource.name,
55
- reason,
56
- rejectedRows,
57
- partition: entry.partitionValue
58
- });
59
- const layerWings = (config) => Layer.effect(Publisher)(Effect.gen(function* () {
60
- const entries = /* @__PURE__ */ new Map();
61
- const clusterClient = yield* Wings.WingsClient.clusterClient;
62
- for (const resource of config.connector.resources) {
63
- const mappingInput = config.tables[resource.name];
64
- if (!mappingInput) return yield* Effect.fail(new ConnectorError({ message: `Missing table for ${resource.name}` }));
65
- const mapping = normalizeMapping(mappingInput);
66
- const table = yield* clusterClient.getTable({ name: mapping.name }).pipe(Effect.mapError((error) => new ConnectorError({
67
- message: `Failed to resolve Wings table for ${resource.name}`,
68
- cause: error
69
- })));
70
- const { keyField, versionField } = yield* validateTableMapping({
71
- resource,
72
- table,
73
- partitionValue: mapping.partitionValue
74
- });
75
- const publisher = yield* Wings.WingsClient.publisher({
76
- table,
77
- partitionValue: mapping.partitionValue
78
- }).pipe(Effect.mapError((error) => new ConnectorError({
79
- message: error.message,
80
- cause: error
81
- })));
82
- entries.set(resource.name, {
83
- resource,
84
- table,
85
- publisher,
86
- partitionValue: mapping.partitionValue,
87
- keyField,
88
- versionField
89
- });
90
- }
91
- const pushRows = (entry, operation, rows) => Effect.gen(function* () {
92
- if (rows.length === 0) return accepted(entry);
93
- const batch = yield* buildRecordBatch(rows);
94
- return yield* entry.publisher.push({
95
- operation,
96
- batch,
97
- partitionValue: entry.partitionValue
98
- }).pipe(Effect.matchEffect({
99
- onFailure: (error) => Effect.succeed(rejected(entry, error.message, rows.length)),
100
- onSuccess: () => Effect.succeed(accepted(entry))
101
- }));
102
- });
103
- const service = { publish: Effect.fn(SpanName.publish, { kind: "producer" })(function* ({ resource, source: _source, batch }) {
104
- const entry = entries.get(resource);
105
- if (!entry) return yield* Effect.fail(new ConnectorError({ message: `Unknown resource ${resource}` }));
106
- if (batch.mutations.length === 0) return accepted(entry);
107
- const upserts = [];
108
- const deletes = [];
109
- for (const mutation of batch.mutations) if (mutation.op === "upsert") upserts.push(mutation.row);
110
- else deletes.push({
111
- [entry.keyField.name]: mutation.key,
112
- [entry.versionField.name]: mutation.version
113
- });
114
- const upsertAck = yield* pushRows(entry, "upsert", upserts);
115
- if (upsertAck.status === "rejected") return upsertAck;
116
- return yield* pushRows(entry, "delete", deletes);
117
- }) };
118
- return Publisher.of(service);
119
- }));
120
-
121
- //#endregion
122
- //#region src/publisher/index.ts
123
- var publisher_exports = /* @__PURE__ */ __export({
124
- Publisher: () => Publisher,
125
- layerConsole: () => layerConsole,
126
- layerWings: () => layerWings
127
- });
128
-
129
- //#endregion
130
- export { layerWings as n, layerConsole as r, publisher_exports as t };
131
- //# sourceMappingURL=publisher-BYT3RLVO.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"publisher-BYT3RLVO.js","names":["service: PublisherService","upserts: Rows[]","deletes: Rows[]"],"sources":["../src/publisher/console.ts","../src/publisher/wings.ts","../src/publisher/index.ts"],"sourcesContent":["import { Effect, Layer } from \"effect\";\n\nimport { Publisher } from \"./service\";\n\nexport const layerConsole = Layer.succeed(Publisher)({\n publish: ({ resource, source, batch }) =>\n Effect.gen(function* () {\n yield* Effect.logInfo(`[publisher] -> Source: ${source} | Resource: ${resource}`).pipe(\n Effect.annotateLogs({\n mutations: batch.mutations.length,\n cursor: batch.cursor,\n source,\n }),\n );\n\n return { status: \"accepted\" as const, resource };\n }),\n});\n","import * as Wings from \"@useairfoil/wings\";\nimport { Effect, Layer } from \"effect\";\n\nimport type { ConnectorDefinition, ResourceDefinition } from \"../core/types\";\n\nimport { ConnectorError } from \"../errors\";\nimport { SpanName } from \"../telemetry\";\nimport { Publisher, type PublishAck, type PublisherService } from \"./service\";\n\ntype Rows = Record<string, unknown>;\n\nexport type WingsTableMapping = {\n readonly name: string;\n readonly partitionValue?: Wings.PartitionValue.PartitionValue;\n};\n\nexport type WingsPublisherConfig = {\n readonly connector: ConnectorDefinition;\n readonly tables: Record<string, string | WingsTableMapping>;\n};\n\ntype PublisherEntry = {\n readonly resource: ResourceDefinition;\n readonly table: Wings.Cluster.Table.Table;\n readonly publisher: Wings.WingsClient.Publisher;\n readonly partitionValue?: Wings.PartitionValue.PartitionValue;\n readonly keyField: Wings.Cluster.ArrowType.Field;\n readonly versionField: Wings.Cluster.ArrowType.Field;\n};\n\nconst normalizeMapping = (mapping: string | WingsTableMapping): WingsTableMapping =>\n typeof mapping === \"string\" ? { name: mapping } : mapping;\n\nconst buildRecordBatch = (rows: ReadonlyArray<Rows>) => {\n const table = Wings.Arrow.tableFromJSON(Array.from(rows));\n const [batch] = table.batches;\n return batch\n ? Effect.succeed(batch)\n : Effect.fail(new ConnectorError({ message: \"No rows to publish\" }));\n};\n\nconst validateTableMapping = (options: {\n readonly resource: ResourceDefinition;\n readonly table: Wings.Cluster.Table.Table;\n readonly partitionValue?: Wings.PartitionValue.PartitionValue;\n}) =>\n Effect.try({\n try: () => {\n const keyField = Wings.TableUtils.getKeyField(options.table);\n const versionField = Wings.TableUtils.getVersionField(options.table);\n const partitionField = Wings.TableUtils.getPartitionField(options.table);\n\n if (keyField.name !== options.resource.key) {\n throw new Error(\n `Resource ${options.resource.name} key '${options.resource.key}' does not match Wings table key '${keyField.name}'`,\n );\n }\n if (versionField.name !== options.resource.version) {\n throw new Error(\n `Resource ${options.resource.name} version '${options.resource.version}' does not match Wings table version '${versionField.name}'`,\n );\n }\n if (options.resource.partition?.required === true && !options.partitionValue) {\n throw new Error(`Resource ${options.resource.name} requires a partition value`);\n }\n if (partitionField && !options.partitionValue) {\n throw new Error(\n `Wings table ${options.table.name} is partitioned but no partition value was provided`,\n );\n }\n if (!partitionField && options.partitionValue) {\n throw new Error(\n `Wings table ${options.table.name} is not partitioned but a partition value was provided`,\n );\n }\n\n return { keyField, versionField };\n },\n catch: (cause) =>\n new ConnectorError({\n message: `Invalid Wings table mapping for ${options.resource.name}`,\n cause,\n }),\n });\n\nconst accepted = (entry: PublisherEntry): PublishAck => ({\n status: \"accepted\",\n resource: entry.resource.name,\n partition: entry.partitionValue,\n});\n\nconst rejected = (entry: PublisherEntry, reason: string, rejectedRows: number): PublishAck => ({\n status: \"rejected\",\n resource: entry.resource.name,\n reason,\n rejectedRows,\n partition: entry.partitionValue,\n});\n\nexport const layerWings = (\n config: WingsPublisherConfig,\n): Layer.Layer<Publisher, ConnectorError, Wings.WingsClient.WingsClient> =>\n Layer.effect(Publisher)(\n Effect.gen(function* () {\n const entries = new Map<string, PublisherEntry>();\n const clusterClient = yield* Wings.WingsClient.clusterClient;\n\n for (const resource of config.connector.resources) {\n const mappingInput = config.tables[resource.name];\n if (!mappingInput) {\n return yield* Effect.fail(\n new ConnectorError({ message: `Missing table for ${resource.name}` }),\n );\n }\n\n const mapping = normalizeMapping(mappingInput);\n const table = yield* clusterClient.getTable({ name: mapping.name }).pipe(\n Effect.mapError(\n (error) =>\n new ConnectorError({\n message: `Failed to resolve Wings table for ${resource.name}`,\n cause: error,\n }),\n ),\n );\n const { keyField, versionField } = yield* validateTableMapping({\n resource,\n table,\n partitionValue: mapping.partitionValue,\n });\n\n const publisher = yield* Wings.WingsClient.publisher({\n table,\n partitionValue: mapping.partitionValue,\n }).pipe(\n Effect.mapError((error) => new ConnectorError({ message: error.message, cause: error })),\n );\n\n entries.set(resource.name, {\n resource,\n table,\n publisher,\n partitionValue: mapping.partitionValue,\n keyField,\n versionField,\n });\n }\n\n const pushRows = (\n entry: PublisherEntry,\n operation: \"upsert\" | \"delete\",\n rows: ReadonlyArray<Rows>,\n ) =>\n Effect.gen(function* () {\n if (rows.length === 0) return accepted(entry);\n const batch = yield* buildRecordBatch(rows);\n return yield* entry.publisher\n .push({ operation, batch, partitionValue: entry.partitionValue })\n .pipe(\n Effect.matchEffect({\n onFailure: (error) => Effect.succeed(rejected(entry, error.message, rows.length)),\n onSuccess: () => Effect.succeed(accepted(entry)),\n }),\n );\n });\n\n const service: PublisherService = {\n publish: Effect.fn(SpanName.publish, { kind: \"producer\" })(function* ({\n resource,\n source: _source,\n batch,\n }) {\n const entry = entries.get(resource);\n if (!entry) {\n return yield* Effect.fail(\n new ConnectorError({ message: `Unknown resource ${resource}` }),\n );\n }\n\n if (batch.mutations.length === 0) return accepted(entry);\n\n const upserts: Rows[] = [];\n const deletes: Rows[] = [];\n for (const mutation of batch.mutations) {\n if (mutation.op === \"upsert\") {\n upserts.push(mutation.row as Rows);\n } else {\n deletes.push({\n [entry.keyField.name]: mutation.key,\n [entry.versionField.name]: mutation.version,\n });\n }\n }\n\n const upsertAck = yield* pushRows(entry, \"upsert\", upserts);\n if (upsertAck.status === \"rejected\") return upsertAck;\n\n return yield* pushRows(entry, \"delete\", deletes);\n }),\n };\n\n return Publisher.of(service);\n }),\n );\n","export type { ResourceBatch } from \"../core/types\";\nexport type { PublishAck, PublishOptions, PublisherService, PublishSource } from \"./service\";\nexport type { WingsPublisherConfig } from \"./wings\";\n\nexport { layerConsole } from \"./console\";\nexport { Publisher } from \"./service\";\nexport { layerWings } from \"./wings\";\n"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- import { a as RuntimeConfigError, i as PlatformRuntimeKey, n as layerHosted, o as RuntimeConfigErrorCode, r as PlatformRuntimeDefault } from "../index-o1-Z3n7V.js";
2
- export { PlatformRuntimeDefault, PlatformRuntimeKey, RuntimeConfigError, RuntimeConfigErrorCode, layerHosted };
@@ -1,4 +0,0 @@
1
- import { n as PlatformRuntimeKey, t as PlatformRuntimeDefault } from "../constants-BRhIX_rz.js";
2
- import { n as layerHosted, r as RuntimeConfigError } from "../runtime-config-BpB-d_bF.js";
3
-
4
- export { PlatformRuntimeDefault, PlatformRuntimeKey, RuntimeConfigError, layerHosted };
@@ -1,51 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1 +0,0 @@
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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"}
@@ -1,191 +0,0 @@
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
@@ -1 +0,0 @@
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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"}
@@ -1,32 +0,0 @@
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
@@ -1,3 +0,0 @@
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 };
@@ -1,5 +0,0 @@
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 };
@@ -1 +0,0 @@
1
- {"version":3,"file":"state-store-yl6LiTQ0.js","names":["layerKeyValueStore: Layer.Layer<StateStore, never, KeyValueStore.KeyValueStore>","layerMemory: Layer.Layer<StateStore>"],"sources":["../src/state-store/service.ts","../src/state-store/keys.ts","../src/state-store/schema.ts","../src/state-store/layer-key-value-store.ts","../src/state-store/layer-memory.ts","../src/state-store/layer-sql.ts","../src/state-store/index.ts"],"sourcesContent":["import { Context, Effect } from \"effect\";\n\nimport type {\n PersistedBackfillState,\n PersistedChangesState,\n PersistedResourceState,\n StateOperation,\n StateSource,\n} from \"./schema\";\n\nimport { ConnectorError } from \"../errors\";\n\n/** Connector-facing durable state operations, independent of the storage backend. */\nexport interface StateStoreService {\n readonly getResourceState: (\n resource: string,\n ) => Effect.Effect<PersistedResourceState | undefined, ConnectorError>;\n readonly setBackfillState: (\n resource: string,\n state: PersistedBackfillState,\n ) => Effect.Effect<void, ConnectorError>;\n readonly setChangesState: (\n resource: string,\n state: PersistedChangesState,\n ) => Effect.Effect<void, ConnectorError>;\n readonly setResourceError: (\n resource: string,\n source: StateSource,\n operation: StateOperation,\n ) => Effect.Effect<void, ConnectorError>;\n readonly clearResourceError: (\n resource: string,\n source: StateSource,\n ) => Effect.Effect<void, ConnectorError>;\n}\n\n/**\n * Effect service used by ingestion and status logic for connector checkpoints.\n * Production provides `layerSql`; sandboxes and tests may provide `layerMemory`.\n *\n * @example\n * ```ts\n * const program = Effect.gen(function* () {\n * const state = yield* StateStore;\n * yield* state.setChangesState(\"orders\", { cursor: \"next-page\" });\n * });\n * ```\n */\nexport class StateStore extends Context.Service<StateStore, StateStoreService>()(\n \"@useairfoil/connector-kit/StateStore\",\n) {}\n","import type { StateSource } from \"./schema\";\n\ntype StateComponent = StateSource | `error:${StateSource}`;\n\nexport const resourceComponentKey = (resource: string, component: StateComponent): string =>\n `resource:${resource}:${component}`;\n\nexport const resourceErrorKey = (resource: string, source: StateSource): string =>\n resourceComponentKey(resource, `error:${source}`);\n\n/**\n * Prefix for all state owned by one connector instance. The platform-owned ID\n * stays visible so operators can inspect and clean up shared-table rows directly.\n *\n * @example\n * ```ts\n * connectorInstanceKeyPrefix(\"team-a/orders\");\n * // \"connector-instance:team-a/orders:\"\n * ```\n */\nexport const connectorInstanceKeyPrefix = (connectorInstanceId: string): string =>\n `connector-instance:${connectorInstanceId}:`;\n","import { Schema } from \"effect\";\n\nconst CursorValueSchema = Schema.Union([Schema.String, Schema.Number]);\n\n/** Durable progress for a finite historical backfill. */\nexport const BackfillStateSchema = Schema.Struct({\n cutoff: CursorValueSchema,\n pageCursor: Schema.optional(CursorValueSchema),\n completed: Schema.Boolean,\n lastSuccessAt: Schema.optional(Schema.String),\n});\n\n/** Durable cursor for an ongoing incremental changes stream. */\nexport const ChangesStateSchema = Schema.Struct({\n cursor: CursorValueSchema,\n lastSuccessAt: Schema.optional(Schema.String),\n});\n\nexport const StateSourceSchema = Schema.Literals([\"backfill\", \"changes\"]);\n\nexport const StateOperationSchema = Schema.Literals([\"fetch\", \"publish\", \"checkpoint\"]);\n\nexport const StateErrorCodeSchema = Schema.Literals([\n \"fetch_failed\",\n \"publish_failed\",\n \"checkpoint_failed\",\n]);\n\n/** SDK-owned error context safe to persist and expose through status. */\nexport const ResourceErrorContextSchema = Schema.Struct({\n source: StateSourceSchema,\n operation: StateOperationSchema,\n code: StateErrorCodeSchema,\n message: Schema.String,\n});\n\n/** Stable public error summary persisted for status reporting; never a raw cause. */\nexport const ResourceErrorSchema = Schema.Struct({\n ...ResourceErrorContextSchema.fields,\n at: Schema.String,\n});\n\n/** Complete persisted state assembled from independently stored components. */\nexport const ResourceStateSchema = Schema.Struct({\n backfill: Schema.optional(BackfillStateSchema),\n changes: Schema.optional(ChangesStateSchema),\n lastError: Schema.optional(ResourceErrorSchema),\n});\n\nexport type PersistedBackfillState = Schema.Schema.Type<typeof BackfillStateSchema>;\nexport type PersistedChangesState = Schema.Schema.Type<typeof ChangesStateSchema>;\nexport type StateSource = Schema.Schema.Type<typeof StateSourceSchema>;\nexport type StateOperation = Schema.Schema.Type<typeof StateOperationSchema>;\nexport type StateErrorCode = Schema.Schema.Type<typeof StateErrorCodeSchema>;\nexport type ResourceErrorContext = Schema.Schema.Type<typeof ResourceErrorContextSchema>;\nexport type PersistedResourceError = Schema.Schema.Type<typeof ResourceErrorSchema>;\nexport type PersistedResourceState = Schema.Schema.Type<typeof ResourceStateSchema>;\n","import { DateTime, Effect, Layer, Option, Schema } from \"effect\";\nimport { KeyValueStore } from \"effect/unstable/persistence\";\n\nimport { ConnectorError } from \"../errors\";\nimport { resourceComponentKey, resourceErrorKey } from \"./keys\";\nimport {\n BackfillStateSchema,\n ChangesStateSchema,\n ResourceErrorSchema,\n type PersistedBackfillState,\n type PersistedChangesState,\n type PersistedResourceError,\n type ResourceErrorContext,\n type StateOperation,\n type StateSource,\n} from \"./schema\";\nimport { StateStore } from \"./service\";\n\nconst mapStoreError = (operation: \"read\" | \"write\" | \"clear\", resource: string) =>\n Effect.mapError(\n (cause: KeyValueStore.KeyValueStoreError | Schema.SchemaError) =>\n new ConnectorError({\n message: `Failed to ${operation} state for resource ${resource}`,\n cause,\n }),\n );\n\nconst resourceErrorContext = (\n source: StateSource,\n operation: StateOperation,\n): ResourceErrorContext => {\n const sourceName = source === \"backfill\" ? \"Backfill\" : \"Changes\";\n switch (operation) {\n case \"fetch\":\n return { source, operation, code: \"fetch_failed\", message: `${sourceName} fetch failed` };\n case \"publish\":\n return { source, operation, code: \"publish_failed\", message: `${sourceName} publish failed` };\n case \"checkpoint\":\n return {\n source,\n operation,\n code: \"checkpoint_failed\",\n message: `${sourceName} checkpoint failed`,\n };\n }\n};\n\nconst latestError = (\n backfill: Option.Option<PersistedResourceError>,\n changes: Option.Option<PersistedResourceError>,\n): Option.Option<PersistedResourceError> => {\n if (Option.isNone(backfill)) return changes;\n if (Option.isNone(changes)) return backfill;\n return backfill.value.at >= changes.value.at ? backfill : changes;\n};\n\n/**\n * Implements StateStore over any Effect KeyValueStore. `toSchemaStore` owns\n * serialization and validates persisted values when they are read and written.\n * The caller owns instance scoping; `layerSql` supplies the production prefix.\n */\nexport const layerKeyValueStore: Layer.Layer<StateStore, never, KeyValueStore.KeyValueStore> =\n Layer.effect(StateStore)(\n Effect.gen(function* () {\n const keyValueStore = yield* KeyValueStore.KeyValueStore;\n const backfillStore = KeyValueStore.toSchemaStore(keyValueStore, BackfillStateSchema);\n const changesStore = KeyValueStore.toSchemaStore(keyValueStore, ChangesStateSchema);\n const errorStore = KeyValueStore.toSchemaStore(keyValueStore, ResourceErrorSchema);\n\n const getResourceState = (resource: string) =>\n // Components use separate keys so a cursor update cannot overwrite the\n // independently managed error or backfill state.\n Effect.all(\n {\n backfill: backfillStore.get(resourceComponentKey(resource, \"backfill\")),\n changes: changesStore.get(resourceComponentKey(resource, \"changes\")),\n backfillError: errorStore.get(resourceErrorKey(resource, \"backfill\")),\n changesError: errorStore.get(resourceErrorKey(resource, \"changes\")),\n },\n { concurrency: \"unbounded\" },\n ).pipe(\n Effect.map(({ backfill, changes, backfillError, changesError }) => {\n const lastError = latestError(backfillError, changesError);\n if (Option.isNone(backfill) && Option.isNone(changes) && Option.isNone(lastError)) {\n return undefined;\n }\n return {\n ...(Option.isSome(backfill) ? { backfill: backfill.value } : {}),\n ...(Option.isSome(changes) ? { changes: changes.value } : {}),\n ...(Option.isSome(lastError) ? { lastError: lastError.value } : {}),\n };\n }),\n mapStoreError(\"read\", resource),\n );\n\n const setBackfillState = (resource: string, state: PersistedBackfillState) =>\n backfillStore\n .set(resourceComponentKey(resource, \"backfill\"), state)\n .pipe(mapStoreError(\"write\", resource));\n\n const setChangesState = (resource: string, state: PersistedChangesState) =>\n changesStore\n .set(resourceComponentKey(resource, \"changes\"), state)\n .pipe(mapStoreError(\"write\", resource));\n\n const setResourceError = (resource: string, source: StateSource, operation: StateOperation) =>\n DateTime.now.pipe(\n Effect.map(DateTime.formatIso),\n Effect.flatMap((at) => {\n const error = resourceErrorContext(source, operation);\n return errorStore.set(resourceErrorKey(resource, source), {\n ...error,\n at,\n });\n }),\n mapStoreError(\"write\", resource),\n );\n\n const clearResourceError = (resource: string, source: StateSource) =>\n errorStore\n .remove(resourceErrorKey(resource, source))\n .pipe(mapStoreError(\"clear\", resource));\n\n return StateStore.of({\n getResourceState,\n setBackfillState,\n setChangesState,\n setResourceError,\n clearResourceError,\n });\n }),\n );\n","import { Layer } from \"effect\";\nimport { KeyValueStore } from \"effect/unstable/persistence\";\n\nimport type { StateStore } from \"./service\";\n\nimport { layerKeyValueStore } from \"./layer-key-value-store\";\n\n/** Process-local state for tests and explicit local sandbox commands. */\nexport const layerMemory: Layer.Layer<StateStore> = layerKeyValueStore.pipe(\n Layer.provide(KeyValueStore.layerMemory),\n);\n","import { Config, Effect, Layer } from \"effect\";\nimport { KeyValueStore } from \"effect/unstable/persistence\";\nimport { SqlClient } from \"effect/unstable/sql\";\n\nimport type { StateStore } from \"./service\";\n\nimport { PlatformRuntimeDefault, PlatformRuntimeKey } from \"../runtime-config/constants\";\nimport { connectorInstanceKeyPrefix } from \"./keys\";\nimport { layerKeyValueStore } from \"./layer-key-value-store\";\n\nconst scopedKeyValueStore = (connectorInstanceId: string) =>\n Layer.effect(KeyValueStore.KeyValueStore)(\n KeyValueStore.KeyValueStore.pipe(\n Effect.map(KeyValueStore.prefix(connectorInstanceKeyPrefix(connectorInstanceId))),\n ),\n );\n\n/**\n * Shared SQL state using the immutable connector instance ID supplied by\n * platform-owned Effect configuration. The table defaults to\n * `_airfoil_connectors_state` and can be overridden with `AIRFOIL_STATE_TABLE`.\n * Effect's SQL KeyValueStore owns the table, while `KeyValueStore.prefix`\n * provides application-level isolation between connector instances in that\n * table. The resulting layer exposes only StateStore, never the unscoped\n * KeyValueStore.\n *\n * @example\n * ```ts\n * const StateStoreLive = layerSql().pipe(Layer.provide(PostgresLive));\n * ```\n */\nexport const layerSql = (): Layer.Layer<StateStore, Config.ConfigError, SqlClient.SqlClient> =>\n Layer.unwrap(\n Config.all({\n connectorInstanceId: Config.nonEmptyString(PlatformRuntimeKey.connectorInstanceId),\n table: Config.nonEmptyString(PlatformRuntimeKey.stateTable).pipe(\n Config.withDefault(PlatformRuntimeDefault.stateTable),\n ),\n }).pipe(\n Effect.map(({ connectorInstanceId, table }) =>\n layerKeyValueStore.pipe(\n Layer.provide(\n scopedKeyValueStore(connectorInstanceId).pipe(\n Layer.provide(KeyValueStore.layerSql({ table })),\n ),\n ),\n ),\n ),\n ),\n );\n","export { StateStore, type StateStoreService } from \"./service\";\nexport { layerMemory } from \"./layer-memory\";\nexport { layerSql } from \"./layer-sql\";\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"status-B-oZBc33.js","names":["ResourceErrorSchema","StateResourceErrorSchema"],"sources":["../src/state-store/state.ts","../src/status/route.ts","../src/status/types.ts","../src/status/index.ts"],"sourcesContent":["import type { Cursor, ResourceDefinition, ResourceState, SyncState } from \"../core/types\";\n\n/** Normalizes a cursor value to its persisted/wire representation (never a Date). */\nexport const normalizeCursor = (value: Cursor.Value): string | number =>\n value instanceof Date ? value.toISOString() : value;\n\n/** Derives connector-facing sync state from durable state and resource capabilities. */\nexport const deriveSyncState = (\n resource: ResourceDefinition,\n state: ResourceState | undefined,\n): SyncState => {\n if (state?.lastError) return \"error\";\n if (!resource.backfill && !resource.changes) return \"live\";\n if (!state) return \"pending\";\n if (resource.backfill && (!state.backfill || !state.backfill.completed)) return \"backfilling\";\n return \"live\";\n};\n","import { Cause, Effect } from \"effect\";\nimport { HttpRouter, HttpServerResponse } from \"effect/unstable/http\";\n\nimport type { ConnectorDefinition } from \"../core/types\";\nimport type { ResourceStatus, StatusUnavailableResponse } from \"./types\";\n\nimport { StateStore } from \"../state-store\";\nimport { deriveSyncState, normalizeCursor } from \"../state-store/state\";\n\nexport const statusRoute = (\n connector: ConnectorDefinition,\n path: HttpRouter.PathInput = \"/status\",\n) =>\n HttpRouter.add(\n \"GET\",\n path,\n Effect.gen(function* () {\n const store = yield* StateStore;\n\n const resources = yield* Effect.forEach(\n connector.resources,\n (resource) =>\n store.getResourceState(resource.name).pipe(\n Effect.map(\n (state): ResourceStatus => ({\n name: resource.name,\n state: deriveSyncState(resource, state),\n ...(state?.backfill\n ? {\n backfill: {\n completed: state.backfill.completed,\n cutoff: normalizeCursor(state.backfill.cutoff),\n ...(state.backfill.pageCursor !== undefined\n ? { pageCursor: normalizeCursor(state.backfill.pageCursor) }\n : {}),\n ...(state.backfill.lastSuccessAt !== undefined\n ? { lastSuccessAt: state.backfill.lastSuccessAt }\n : {}),\n },\n }\n : {}),\n ...(state?.changes\n ? {\n changes: {\n cursor: normalizeCursor(state.changes.cursor),\n ...(state.changes.lastSuccessAt !== undefined\n ? { lastSuccessAt: state.changes.lastSuccessAt }\n : {}),\n },\n }\n : {}),\n ...(state?.lastError ? { lastError: state.lastError } : {}),\n }),\n ),\n ),\n { concurrency: \"unbounded\" },\n );\n\n return HttpServerResponse.jsonUnsafe({ connector: connector.name, resources });\n }).pipe(\n Effect.catchCause((cause) =>\n Effect.logWarning(`Status route failed: ${Cause.pretty(cause)}`).pipe(\n Effect.andThen(\n Effect.succeed(\n HttpServerResponse.jsonUnsafe(\n {\n ok: false,\n error: \"Failed to compute status\",\n } satisfies StatusUnavailableResponse,\n { status: 500 },\n ),\n ),\n ),\n ),\n ),\n ),\n );\n","import { Schema } from \"effect\";\n\nimport {\n ResourceErrorSchema as StateResourceErrorSchema,\n ResourceStateSchema,\n} from \"../state-store/schema\";\n\nexport const SyncStateSchema = Schema.Literals([\"pending\", \"backfilling\", \"live\", \"error\"]);\nexport type SyncState = Schema.Schema.Type<typeof SyncStateSchema>;\n\n/** Safe source failure returned by the status endpoint. */\nexport const ResourceErrorSchema = StateResourceErrorSchema;\nexport type ResourceError = Schema.Schema.Type<typeof ResourceErrorSchema>;\n\nexport const ResourceStatusSchema = Schema.Struct({\n name: Schema.String,\n state: SyncStateSchema,\n ...ResourceStateSchema.fields,\n});\nexport type ResourceStatus = Schema.Schema.Type<typeof ResourceStatusSchema>;\n\nexport const StatusResponseSchema = Schema.Struct({\n connector: Schema.String,\n resources: Schema.Array(ResourceStatusSchema),\n});\nexport type StatusResponse = Schema.Schema.Type<typeof StatusResponseSchema>;\n\n/** Returned when durable resource status cannot be read. */\nexport const StatusUnavailableResponseSchema = Schema.Struct({\n ok: Schema.Literal(false),\n error: Schema.String,\n});\nexport type StatusUnavailableResponse = Schema.Schema.Type<typeof StatusUnavailableResponseSchema>;\n","export * from \"./route\";\nexport * from \"./types\";\n"],"mappings":";;;;;;;AAGA,MAAa,mBAAmB,UAC9B,iBAAiB,OAAO,MAAM,aAAa,GAAG;;AAGhD,MAAa,mBACX,UACA,UACc;AACd,KAAI,OAAO,UAAW,QAAO;AAC7B,KAAI,CAAC,SAAS,YAAY,CAAC,SAAS,QAAS,QAAO;AACpD,KAAI,CAAC,MAAO,QAAO;AACnB,KAAI,SAAS,aAAa,CAAC,MAAM,YAAY,CAAC,MAAM,SAAS,WAAY,QAAO;AAChF,QAAO;;;;;ACNT,MAAa,eACX,WACA,OAA6B,cAE7B,WAAW,IACT,OACA,MACA,OAAO,IAAI,aAAa;CACtB,MAAM,QAAQ,OAAO;CAErB,MAAM,YAAY,OAAO,OAAO,QAC9B,UAAU,YACT,aACC,MAAM,iBAAiB,SAAS,KAAK,CAAC,KACpC,OAAO,KACJ,WAA2B;EAC1B,MAAM,SAAS;EACf,OAAO,gBAAgB,UAAU,MAAM;EACvC,GAAI,OAAO,WACP,EACE,UAAU;GACR,WAAW,MAAM,SAAS;GAC1B,QAAQ,gBAAgB,MAAM,SAAS,OAAO;GAC9C,GAAI,MAAM,SAAS,eAAe,SAC9B,EAAE,YAAY,gBAAgB,MAAM,SAAS,WAAW,EAAE,GAC1D,EAAE;GACN,GAAI,MAAM,SAAS,kBAAkB,SACjC,EAAE,eAAe,MAAM,SAAS,eAAe,GAC/C,EAAE;GACP,EACF,GACD,EAAE;EACN,GAAI,OAAO,UACP,EACE,SAAS;GACP,QAAQ,gBAAgB,MAAM,QAAQ,OAAO;GAC7C,GAAI,MAAM,QAAQ,kBAAkB,SAChC,EAAE,eAAe,MAAM,QAAQ,eAAe,GAC9C,EAAE;GACP,EACF,GACD,EAAE;EACN,GAAI,OAAO,YAAY,EAAE,WAAW,MAAM,WAAW,GAAG,EAAE;EAC3D,EACF,CACF,EACH,EAAE,aAAa,aAAa,CAC7B;AAED,QAAO,mBAAmB,WAAW;EAAE,WAAW,UAAU;EAAM;EAAW,CAAC;EAC9E,CAAC,KACD,OAAO,YAAY,UACjB,OAAO,WAAW,wBAAwB,MAAM,OAAO,MAAM,GAAG,CAAC,KAC/D,OAAO,QACL,OAAO,QACL,mBAAmB,WACjB;CACE,IAAI;CACJ,OAAO;CACR,EACD,EAAE,QAAQ,KAAK,CAChB,CACF,CACF,CACF,CACF,CACF,CACF;;;;ACrEH,MAAa,kBAAkB,OAAO,SAAS;CAAC;CAAW;CAAe;CAAQ;CAAQ,CAAC;;AAI3F,MAAaA,wBAAsBC;AAGnC,MAAa,uBAAuB,OAAO,OAAO;CAChD,MAAM,OAAO;CACb,OAAO;CACP,GAAG,oBAAoB;CACxB,CAAC;AAGF,MAAa,uBAAuB,OAAO,OAAO;CAChD,WAAW,OAAO;CAClB,WAAW,OAAO,MAAM,qBAAqB;CAC9C,CAAC;;AAIF,MAAa,kCAAkC,OAAO,OAAO;CAC3D,IAAI,OAAO,QAAQ,MAAM;CACzB,OAAO,OAAO;CACf,CAAC"}