@useairfoil/connector-kit 0.6.0 → 0.7.0

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 (182) hide show
  1. package/LICENSE.txt +201 -0
  2. package/METRICS.md +38 -42
  3. package/README.md +111 -444
  4. package/dist/annotations-vzSzXtr-.d.mts +34 -0
  5. package/dist/arrow-flight-DDQ0ktx9.mjs +251 -0
  6. package/dist/arrow-flight-DDQ0ktx9.mjs.map +1 -0
  7. package/dist/auth/index.d.mts +2 -0
  8. package/dist/{auth-DvU_CZ7C.js → auth/index.mjs} +6 -8
  9. package/dist/auth/index.mjs.map +1 -0
  10. package/dist/connector-app/index.d.mts +2 -0
  11. package/dist/connector-app/index.mjs +2 -0
  12. package/dist/connector-app-C6_dZnhE.mjs +256 -0
  13. package/dist/connector-app-C6_dZnhE.mjs.map +1 -0
  14. package/dist/{constants-D113Ut1x.js → constants-v3UX-hf6.mjs} +5 -5
  15. package/dist/constants-v3UX-hf6.mjs.map +1 -0
  16. package/dist/demo/index.d.mts +2 -0
  17. package/dist/demo/index.mjs +2 -0
  18. package/dist/demo-DqTJ7TWI.mjs +204 -0
  19. package/dist/demo-DqTJ7TWI.mjs.map +1 -0
  20. package/dist/errors/index.d.mts +2 -0
  21. package/dist/errors/index.mjs +2 -0
  22. package/dist/{errors-DHWylqRZ.js → errors-pT0YVJU5.mjs} +2 -3
  23. package/dist/errors-pT0YVJU5.mjs.map +1 -0
  24. package/dist/iceberg/index.d.mts +3 -0
  25. package/dist/iceberg/index.mjs +3 -0
  26. package/dist/iceberg-Cj2PwlNm.mjs +30 -0
  27. package/dist/iceberg-Cj2PwlNm.mjs.map +1 -0
  28. package/dist/index-BTAbexBp.d.mts +55 -0
  29. package/dist/{index-DXa26Ry5.d.ts → index-BX8G7d41.d.mts} +1 -2
  30. package/dist/{index-VpHaZXZw.d.ts → index-Byr8NTdG.d.mts} +11 -13
  31. package/dist/index-CV6V6fyx.d.mts +79 -0
  32. package/dist/{index-C5Ma7V7t.d.ts → index-CZKx2Yce.d.mts} +72 -37
  33. package/dist/{index-DYpEMJf-.d.ts → index-CgvFomO6.d.mts} +6 -7
  34. package/dist/{index-bfjH7Yb0.d.ts → index-DeTwIUKl.d.mts} +9 -10
  35. package/dist/{index-DKoRwyFn.d.ts → index-DkVNO6ks.d.mts} +2 -5
  36. package/dist/{index-BKw7jA2a.d.ts → index-dHS59EZl.d.mts} +10 -8
  37. package/dist/{index-CR9HJDdr.d.ts → index-eFuoMZZ3.d.mts} +18 -20
  38. package/dist/{index-ZSuiZX5H.d.ts → index-fHIJJoRe.d.mts} +25 -30
  39. package/dist/index-uFa2wwYo.d.mts +27 -0
  40. package/dist/index.d.mts +16 -0
  41. package/dist/index.mjs +45 -0
  42. package/dist/index.mjs.map +1 -0
  43. package/dist/ingestion/index.d.mts +2 -0
  44. package/dist/ingestion/index.mjs +2 -0
  45. package/dist/ingestion-DoCUwm5N.mjs +275 -0
  46. package/dist/ingestion-DoCUwm5N.mjs.map +1 -0
  47. package/dist/ingestor/index.d.mts +3 -0
  48. package/dist/ingestor/index.mjs +4 -0
  49. package/dist/ingestor-DjCEzBzZ.mjs +26 -0
  50. package/dist/ingestor-DjCEzBzZ.mjs.map +1 -0
  51. package/dist/instrumented-Bq1WX2L5.mjs +29 -0
  52. package/dist/instrumented-Bq1WX2L5.mjs.map +1 -0
  53. package/dist/manifest/index.d.mts +2 -0
  54. package/dist/manifest/{index.js → index.mjs} +2 -4
  55. package/dist/{manifest-P7kBnch9.js → manifest-CABeIhAG.mjs} +14 -19
  56. package/dist/manifest-CABeIhAG.mjs.map +1 -0
  57. package/dist/{telemetry-j_nxwYfh.js → metrics-C901IIYZ.mjs} +172 -26
  58. package/dist/metrics-C901IIYZ.mjs.map +1 -0
  59. package/dist/{metrics-C4V1pm6v.d.ts → metrics-DoJ0WeKu.d.mts} +18 -13
  60. package/dist/metrics.d.mts +2 -0
  61. package/dist/metrics.mjs +2 -0
  62. package/dist/{chunk-Bp6m_JJh.js → rolldown-runtime-D7D4PA-g.mjs} +4 -4
  63. package/dist/runtime-config/index.d.mts +2 -0
  64. package/dist/runtime-config/index.mjs +3 -0
  65. package/dist/runtime-config-Bn8Fivt1.mjs +100 -0
  66. package/dist/runtime-config-Bn8Fivt1.mjs.map +1 -0
  67. package/dist/server-D1TTN0_D.mjs +144 -0
  68. package/dist/server-D1TTN0_D.mjs.map +1 -0
  69. package/dist/service-BRoy_EId.d.mts +27 -0
  70. package/dist/service-jWxxfEht.mjs +7 -0
  71. package/dist/service-jWxxfEht.mjs.map +1 -0
  72. package/dist/state-store/index.d.mts +2 -0
  73. package/dist/state-store/index.mjs +2 -0
  74. package/dist/{state-store-BdYulQOY.js → state-store-DhSrKVEH.mjs} +15 -22
  75. package/dist/state-store-DhSrKVEH.mjs.map +1 -0
  76. package/dist/status/{index.d.ts → index.d.mts} +1 -4
  77. package/dist/status/{index.js → index.mjs} +2 -6
  78. package/dist/{status-BTTd7AUR.js → status-4h6Oxh-O.mjs} +14 -16
  79. package/dist/status-4h6Oxh-O.mjs.map +1 -0
  80. package/dist/table-CxM09xf7.mjs +307 -0
  81. package/dist/table-CxM09xf7.mjs.map +1 -0
  82. package/dist/types-NtSv6gwl.d.mts +220 -0
  83. package/dist/webhook/index.d.mts +3 -0
  84. package/dist/webhook/index.mjs +12 -0
  85. package/dist/webhook/index.mjs.map +1 -0
  86. package/package.json +72 -58
  87. package/src/catalog/retry.ts +25 -0
  88. package/src/catalog/table.ts +237 -0
  89. package/src/connector-app/create-table.ts +47 -0
  90. package/src/connector-app/demo.ts +47 -0
  91. package/src/connector-app/index.ts +3 -0
  92. package/src/connector-app/setup.ts +69 -0
  93. package/src/connector-app/start.ts +1 -1
  94. package/src/core/builder.ts +15 -23
  95. package/src/core/index.ts +11 -1
  96. package/src/core/types.ts +114 -39
  97. package/src/demo/definition.ts +68 -0
  98. package/src/demo/generate.ts +167 -0
  99. package/src/demo/index.ts +3 -0
  100. package/src/demo/run.ts +172 -0
  101. package/src/iceberg/annotations.ts +49 -0
  102. package/src/iceberg/error.ts +9 -0
  103. package/src/iceberg/index.ts +3 -0
  104. package/src/iceberg/table.ts +461 -0
  105. package/src/index.ts +3 -1
  106. package/src/ingestion/engine.ts +240 -60
  107. package/src/ingestor/arrow-flight.ts +141 -0
  108. package/src/ingestor/arrow.ts +248 -0
  109. package/src/ingestor/console.ts +13 -0
  110. package/src/ingestor/index.ts +13 -0
  111. package/src/ingestor/instrumented.ts +61 -0
  112. package/src/ingestor/service.ts +30 -0
  113. package/src/manifest/config.ts +3 -3
  114. package/src/metrics.ts +66 -20
  115. package/src/runtime-config/config.ts +11 -8
  116. package/src/runtime-config/constants.ts +3 -2
  117. package/src/runtime-config/index.ts +2 -7
  118. package/src/runtime-config/layer.ts +1 -1
  119. package/src/runtime-config/table-bindings.ts +50 -136
  120. package/src/state-store/layer-key-value-store.ts +8 -3
  121. package/src/state-store/layer-memory.ts +1 -1
  122. package/src/state-store/layer-sql.ts +4 -4
  123. package/src/state-store/schema.ts +2 -2
  124. package/src/state-store/state.ts +14 -2
  125. package/src/status/route.ts +2 -2
  126. package/src/telemetry.ts +20 -28
  127. package/src/webhook/server.ts +39 -40
  128. package/dist/auth/index.d.ts +0 -2
  129. package/dist/auth/index.js +0 -3
  130. package/dist/auth-DvU_CZ7C.js.map +0 -1
  131. package/dist/connector-app/index.d.ts +0 -14
  132. package/dist/connector-app/index.js +0 -12
  133. package/dist/connector-app-BljHd0Pe.js +0 -64
  134. package/dist/connector-app-BljHd0Pe.js.map +0 -1
  135. package/dist/constants-D113Ut1x.js.map +0 -1
  136. package/dist/errors/index.d.ts +0 -2
  137. package/dist/errors/index.js +0 -3
  138. package/dist/errors-DHWylqRZ.js.map +0 -1
  139. package/dist/index-BA8ASHKr.d.ts +0 -31
  140. package/dist/index.d.ts +0 -14
  141. package/dist/index.js +0 -60
  142. package/dist/index.js.map +0 -1
  143. package/dist/ingestion/index.d.ts +0 -6
  144. package/dist/ingestion/index.js +0 -11
  145. package/dist/ingestion-QBGKrFr9.js +0 -200
  146. package/dist/ingestion-QBGKrFr9.js.map +0 -1
  147. package/dist/manifest/index.d.ts +0 -2
  148. package/dist/manifest-P7kBnch9.js.map +0 -1
  149. package/dist/metrics-inkCAxKq.js +0 -136
  150. package/dist/metrics-inkCAxKq.js.map +0 -1
  151. package/dist/metrics.d.ts +0 -4
  152. package/dist/metrics.js +0 -5
  153. package/dist/publisher/index.d.ts +0 -6
  154. package/dist/publisher/index.js +0 -8
  155. package/dist/publisher-BdWJOfer.js +0 -138
  156. package/dist/publisher-BdWJOfer.js.map +0 -1
  157. package/dist/runtime-config/index.d.ts +0 -4
  158. package/dist/runtime-config/index.js +0 -5
  159. package/dist/runtime-config-IbyCVpei.js +0 -60
  160. package/dist/runtime-config-IbyCVpei.js.map +0 -1
  161. package/dist/server-D-eLX3yN.js +0 -183
  162. package/dist/server-D-eLX3yN.js.map +0 -1
  163. package/dist/service-CUjGhe-7.d.ts +0 -32
  164. package/dist/service-DiP-XETo.js +0 -8
  165. package/dist/service-DiP-XETo.js.map +0 -1
  166. package/dist/state-store/index.d.ts +0 -3
  167. package/dist/state-store/index.js +0 -5
  168. package/dist/state-store-BdYulQOY.js.map +0 -1
  169. package/dist/status-BTTd7AUR.js.map +0 -1
  170. package/dist/table-bindings-Dp1DhTeS.js +0 -106
  171. package/dist/table-bindings-Dp1DhTeS.js.map +0 -1
  172. package/dist/telemetry-j_nxwYfh.js.map +0 -1
  173. package/dist/types-jVD5Ys43.d.ts +0 -174
  174. package/dist/webhook/index.d.ts +0 -7
  175. package/dist/webhook/index.js +0 -9
  176. package/dist/webhook-MhFcLW-o.js +0 -13
  177. package/dist/webhook-MhFcLW-o.js.map +0 -1
  178. package/src/publisher/console.ts +0 -18
  179. package/src/publisher/index.ts +0 -7
  180. package/src/publisher/instrumented.ts +0 -75
  181. package/src/publisher/service.ts +0 -37
  182. package/src/publisher/wings.ts +0 -204
@@ -0,0 +1,251 @@
1
+ import { t as ConnectorError } from "./errors-pT0YVJU5.mjs";
2
+ import { n as compileTable } from "./table-CxM09xf7.mjs";
3
+ import { a as catalog, n as loadTableBindings, o as engineUri, s as flightUri } from "./runtime-config-Bn8Fivt1.mjs";
4
+ import { t as Ingestor } from "./service-jWxxfEht.mjs";
5
+ import { Config, Effect, Layer, Schema } from "effect";
6
+ import { CatalogManager } from "@useairfoil/api";
7
+ import { FetchHttpClient } from "effect/http";
8
+ import { parseDecimalType, parseFixedType } from "iceberg-js";
9
+ import { Bool, DateDay, Decimal, Field, FixedSizeBinary, Float32, Float64, Int32, Int64, LargeBinary, List, Map_, RecordBatch, Schema as Schema$1, Struct as Struct$1, TimeMicrosecond, TimestampMicrosecond, Utf8, makeBuilder } from "apache-arrow";
10
+ //#region src/ingestor/arrow.ts
11
+ const bytes = (length) => Schema.Uint8Array.check(Schema.makeFilter((value) => value.byteLength === length || `expected ${length} bytes`));
12
+ const int32 = Schema.Number.check(Schema.isInt(), Schema.isBetween({
13
+ minimum: -2147483648,
14
+ maximum: 2147483647
15
+ }));
16
+ const int64 = Schema.BigInt.check(Schema.isBetweenBigInt({
17
+ minimum: -(2n ** 63n),
18
+ maximum: 2n ** 63n - 1n
19
+ }));
20
+ const float32 = Schema.Finite.check(Schema.makeFilter((value) => Number.isFinite(Math.fround(value)) || "expected a 32-bit number"));
21
+ const microsecondsInDay = Schema.BigInt.check(Schema.isBetweenBigInt({
22
+ minimum: 0n,
23
+ maximum: 86399999999n
24
+ }));
25
+ const primitives = {
26
+ boolean: {
27
+ type: new Bool(),
28
+ row: Schema.Boolean
29
+ },
30
+ int: {
31
+ type: new Int32(),
32
+ row: int32
33
+ },
34
+ long: {
35
+ type: new Int64(),
36
+ row: int64
37
+ },
38
+ float: {
39
+ type: new Float32(),
40
+ row: float32
41
+ },
42
+ double: {
43
+ type: new Float64(),
44
+ row: Schema.Finite
45
+ },
46
+ string: {
47
+ type: new Utf8(),
48
+ row: Schema.String
49
+ },
50
+ uuid: {
51
+ type: new FixedSizeBinary(16),
52
+ row: bytes(16)
53
+ },
54
+ binary: {
55
+ type: new LargeBinary(),
56
+ row: Schema.Uint8Array
57
+ },
58
+ date: {
59
+ type: new DateDay(),
60
+ row: Schema.Date
61
+ },
62
+ time: {
63
+ type: new TimeMicrosecond(),
64
+ row: microsecondsInDay
65
+ },
66
+ timestamp: {
67
+ type: new TimestampMicrosecond(),
68
+ row: Schema.Date
69
+ },
70
+ timestamptz: {
71
+ type: new TimestampMicrosecond("UTC"),
72
+ row: Schema.Date
73
+ }
74
+ };
75
+ const primitive = (type) => {
76
+ const known = primitives[type];
77
+ if (known) return known;
78
+ const decimal = parseDecimalType(type);
79
+ if (decimal) {
80
+ if (decimal.precision < 1 || decimal.precision > 38 || decimal.scale > decimal.precision) throw new Error(`Invalid Iceberg decimal type ${type}`);
81
+ return {
82
+ type: new Decimal(decimal.scale, decimal.precision),
83
+ row: Schema.instanceOf(Uint32Array).check(Schema.makeFilter((value) => value.length === 4 || "expected four words"))
84
+ };
85
+ }
86
+ const fixed = parseFixedType(type);
87
+ if (fixed) {
88
+ if (fixed.length < 1) throw new Error(`Invalid Iceberg fixed type ${type}`);
89
+ return {
90
+ type: new FixedSizeBinary(fixed.length),
91
+ row: bytes(fixed.length)
92
+ };
93
+ }
94
+ throw new Error(`Unsupported Iceberg type ${type}`);
95
+ };
96
+ /** Absent and null both become null in Arrow. */
97
+ const optional = (row) => Schema.optional(Schema.NullOr(row));
98
+ const fieldMetadata = (id) => /* @__PURE__ */ new Map([["PARQUET:field_id", String(id)]]);
99
+ /** Same as `primitive`, plus struct, list and map. */
100
+ const compileType = (type) => {
101
+ if (typeof type === "string") return primitive(type);
102
+ if (type.type === "struct") {
103
+ const fields = type.fields.map((field) => ({
104
+ ...field,
105
+ compiled: compileType(field.type)
106
+ }));
107
+ return {
108
+ type: new Struct$1(fields.map((field) => new Field(field.name, field.compiled.type, !field.required, fieldMetadata(field.id)))),
109
+ row: Schema.Struct(Object.fromEntries(fields.map((field) => [field.name, field.required ? field.compiled.row : optional(field.compiled.row)])))
110
+ };
111
+ }
112
+ if (type.type === "list") {
113
+ const element = compileType(type.element);
114
+ const required = type["element-required"];
115
+ return {
116
+ type: new List(new Field("element", element.type, !required, fieldMetadata(type["element-id"]))),
117
+ row: Schema.Array(required ? element.row : Schema.NullishOr(element.row))
118
+ };
119
+ }
120
+ const key = compileType(type.key);
121
+ const value = compileType(type.value);
122
+ const required = type["value-required"];
123
+ const entries = new Struct$1([new Field("key", key.type, false, fieldMetadata(type["key-id"])), new Field("value", value.type, !required, fieldMetadata(type["value-id"]))]);
124
+ return {
125
+ type: new Map_(new Field("entries", entries, false)),
126
+ row: Schema.ReadonlyMap(key.row, required ? value.row : Schema.NullishOr(value.row))
127
+ };
128
+ };
129
+ const makeRowEncoder = (tableSchema, version) => Effect.try({
130
+ try: () => {
131
+ const fields = tableSchema.fields.map((field) => ({
132
+ id: field.id,
133
+ name: field.name,
134
+ ...compileType(field.type)
135
+ }));
136
+ const fieldNames = new Set(fields.map((field) => field.name));
137
+ const identifierIds = new Set(tableSchema["identifier-field-ids"]);
138
+ const keys = tableSchema.fields.filter((field) => identifierIds.has(field.id)).map((field) => field.name);
139
+ const required = /* @__PURE__ */ new Set([...keys, version]);
140
+ if (!fieldNames.has(version)) throw new Error(`Missing version column ${version}`);
141
+ const struct = new Struct$1(fields.map((field) => new Field(field.name, field.type, true, fieldMetadata(field.id))));
142
+ const schema = new Schema$1(struct.children);
143
+ const decodeRow = Schema.decodeUnknownSync(Schema.Struct(Object.fromEntries(fields.map((field) => [field.name, required.has(field.name) ? field.row : optional(field.row)]))), { onExcessProperty: "error" });
144
+ const decodeAt = (row, index) => {
145
+ try {
146
+ return decodeRow(row);
147
+ } catch (cause) {
148
+ const detail = cause instanceof Error ? cause.message : String(cause);
149
+ throw new Error(`row ${index}: ${detail}`, { cause });
150
+ }
151
+ };
152
+ return (rows) => Effect.try({
153
+ try: () => {
154
+ const builder = makeBuilder({
155
+ type: struct,
156
+ nullValues: [null, void 0]
157
+ });
158
+ for (const [index, row] of rows.entries()) builder.append(decodeAt(row, index));
159
+ return new RecordBatch(schema, builder.finish().flush());
160
+ },
161
+ catch: (cause) => new ConnectorError({
162
+ message: "Failed to encode rows",
163
+ cause
164
+ })
165
+ });
166
+ },
167
+ catch: (cause) => new ConnectorError({
168
+ message: "Failed to convert Iceberg schema",
169
+ cause
170
+ })
171
+ });
172
+ //#endregion
173
+ //#region src/ingestor/arrow-flight.ts
174
+ const connectorError = (message, cause) => new ConnectorError({
175
+ message,
176
+ cause
177
+ });
178
+ /** Compiles local schemas and opens one Arrow Flight stream per bound resource. */
179
+ const layerArrowFlight = (config) => Layer.effect(Ingestor)(Effect.gen(function* () {
180
+ const manager = yield* CatalogManager.CatalogManager;
181
+ const bindings = config.tables;
182
+ const prepared = [];
183
+ for (const resource of config.connector.resources) {
184
+ const binding = bindings[resource.name];
185
+ if (!binding) continue;
186
+ const { namespace, name } = binding;
187
+ const identifier = {
188
+ namespace,
189
+ name
190
+ };
191
+ const { schema } = yield* compileTable(resource.rowSchema).pipe(Effect.mapError((cause) => connectorError(`Invalid schema for ${resource.name}: ${cause.message}`, cause)));
192
+ const encode = yield* makeRowEncoder(schema, resource.version);
193
+ prepared.push({
194
+ resource: resource.name,
195
+ identifier,
196
+ encode
197
+ });
198
+ }
199
+ const entries = /* @__PURE__ */ new Map();
200
+ for (const { resource, identifier, encode } of prepared) {
201
+ const ingestor = yield* manager.ingestor({
202
+ catalog: config.catalog,
203
+ namespace: identifier.namespace,
204
+ table: identifier.name
205
+ }).pipe(Effect.mapError((cause) => connectorError(`Failed to open ingestor for ${resource}`, cause)));
206
+ entries.set(resource, {
207
+ encode,
208
+ push: (batch) => ingestor.push(batch).pipe(Effect.mapError((cause) => connectorError(`Failed to ingest rows for ${resource}`, cause)))
209
+ });
210
+ }
211
+ const service = {
212
+ resourceNames: prepared.map(({ resource }) => resource),
213
+ ingest: ({ resource, batch }) => {
214
+ const entry = entries.get(resource);
215
+ if (!entry) return Effect.fail(connectorError(`Unknown resource ${resource}`));
216
+ if (batch.rows.length === 0) return Effect.succeed({
217
+ rows: 0,
218
+ bytes: 0
219
+ });
220
+ return entry.encode(batch.rows).pipe(Effect.flatMap((encoded) => entry.push(encoded).pipe(Effect.as({
221
+ rows: batch.rows.length,
222
+ bytes: encoded.data.byteLength
223
+ }))));
224
+ }
225
+ };
226
+ return Ingestor.of(service);
227
+ }));
228
+ /** Configures Arrow Flight ingestion from the hosted URL, catalog and table bindings. */
229
+ const layerArrowFlightConfig = (connector) => Layer.unwrap(Effect.gen(function* () {
230
+ const { catalog: catalog$1, uri, flightUri: flightUri$1 } = yield* Config.all({
231
+ catalog,
232
+ uri: engineUri,
233
+ flightUri
234
+ }).pipe(Effect.mapError((cause) => connectorError("Invalid Arrow Flight configuration", cause)));
235
+ if (uri.protocol !== "http:" && uri.protocol !== "https:") return yield* Effect.fail(connectorError("Invalid Arrow Flight URL"));
236
+ if (flightUri$1.protocol !== "http:" && flightUri$1.protocol !== "https:") return yield* Effect.fail(connectorError("Invalid Arrow Flight URL"));
237
+ const tables = yield* loadTableBindings(connector).pipe(Effect.mapError((cause) => connectorError(cause.message, cause)));
238
+ const manager = CatalogManager.layer({
239
+ engineUri: uri.href,
240
+ flightUri: flightUri$1.href
241
+ }).pipe(Layer.provide(FetchHttpClient.layer));
242
+ return layerArrowFlight({
243
+ connector,
244
+ catalog: catalog$1,
245
+ tables
246
+ }).pipe(Layer.provide(manager));
247
+ }));
248
+ //#endregion
249
+ export { layerArrowFlightConfig as n, layerArrowFlight as t };
250
+
251
+ //# sourceMappingURL=arrow-flight-DDQ0ktx9.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"arrow-flight-DDQ0ktx9.mjs","names":["Struct","ArrowSchema","RuntimeConfig.catalog","RuntimeConfig.engineUri","RuntimeConfig.flightUri","flightUri","RuntimeConfig.loadTableBindings"],"sources":["../src/ingestor/arrow.ts","../src/ingestor/arrow-flight.ts"],"sourcesContent":["import {\n Bool,\n DateDay,\n Decimal,\n Field,\n FixedSizeBinary,\n Float32,\n Float64,\n Int32,\n Int64,\n LargeBinary,\n List,\n Map_,\n RecordBatch,\n Schema as ArrowSchema,\n Struct,\n TimeMicrosecond,\n TimestampMicrosecond,\n Utf8,\n makeBuilder,\n type DataType,\n type TypeMap,\n} from \"apache-arrow\";\nimport { Effect, Schema } from \"effect\";\nimport {\n parseDecimalType,\n parseFixedType,\n type IcebergType,\n type PrimitiveType,\n type TableSchema,\n} from \"iceberg-js\";\n\nimport { ConnectorError } from \"../errors\";\n\n// Nothing converts an Iceberg schema to Arrow for us, so we do it here.\n// Each type also gets a schema for its values, because Arrow does not reject a bad\n// value, it converts it: \"1\" becomes 1, and 1.5 becomes 1.\n\ntype RowSchema = Schema.Codec<unknown, unknown>;\n\n/** One Iceberg type: the Arrow type, and the values it accepts. */\ntype Compiled = {\n readonly type: DataType;\n readonly row: RowSchema;\n};\n\n// Arrow pads or cuts binary values to fit.\nconst bytes = (length: number) =>\n Schema.Uint8Array.check(\n Schema.makeFilter(\n (value: Uint8Array) => value.byteLength === length || `expected ${length} bytes`,\n ),\n );\n\n// Arrow wraps big integers instead of failing, so check the range.\nconst int32 = Schema.Number.check(\n Schema.isInt(),\n Schema.isBetween({ minimum: -2_147_483_648, maximum: 2_147_483_647 }),\n);\n\nconst int64 = Schema.BigInt.check(\n Schema.isBetweenBigInt({ minimum: -(2n ** 63n), maximum: 2n ** 63n - 1n }),\n);\n\n// Arrow turns a number outside the 32-bit range into Infinity.\nconst float32 = Schema.Finite.check(\n Schema.makeFilter(\n (value: number) => Number.isFinite(Math.fround(value)) || \"expected a 32-bit number\",\n ),\n);\n\nconst microsecondsInDay = Schema.BigInt.check(\n Schema.isBetweenBigInt({ minimum: 0n, maximum: 86_399_999_999n }),\n);\n\nconst primitives: Record<string, Compiled> = {\n boolean: { type: new Bool(), row: Schema.Boolean },\n int: { type: new Int32(), row: int32 },\n long: { type: new Int64(), row: int64 },\n float: { type: new Float32(), row: float32 },\n double: { type: new Float64(), row: Schema.Finite },\n string: { type: new Utf8(), row: Schema.String },\n uuid: { type: new FixedSizeBinary(16), row: bytes(16) },\n binary: { type: new LargeBinary(), row: Schema.Uint8Array },\n date: { type: new DateDay(), row: Schema.Date },\n time: { type: new TimeMicrosecond(), row: microsecondsInDay },\n timestamp: { type: new TimestampMicrosecond(), row: Schema.Date },\n timestamptz: { type: new TimestampMicrosecond(\"UTC\"), row: Schema.Date },\n};\n\nconst primitive = (type: PrimitiveType): Compiled => {\n const known = primitives[type];\n\n if (known) return known;\n\n // Nothing checks the catalog metadata, and Arrow takes any precision.\n // Better to fail here than at write time.\n const decimal = parseDecimalType(type);\n\n if (decimal) {\n if (decimal.precision < 1 || decimal.precision > 38 || decimal.scale > decimal.precision) {\n throw new Error(`Invalid Iceberg decimal type ${type}`);\n }\n\n return {\n type: new Decimal(decimal.scale, decimal.precision),\n row: Schema.instanceOf(Uint32Array).check(\n Schema.makeFilter((value: Uint32Array) => value.length === 4 || \"expected four words\"),\n ),\n };\n }\n\n const fixed = parseFixedType(type);\n\n if (fixed) {\n if (fixed.length < 1) throw new Error(`Invalid Iceberg fixed type ${type}`);\n\n return { type: new FixedSizeBinary(fixed.length), row: bytes(fixed.length) };\n }\n\n throw new Error(`Unsupported Iceberg type ${type}`);\n};\n\n/** Absent and null both become null in Arrow. */\nconst optional = (row: RowSchema) => Schema.optional(Schema.NullOr(row));\n\nconst fieldMetadata = (id: number): Map<string, string> =>\n new Map([[\"PARQUET:field_id\", String(id)]]);\n\n/** Same as `primitive`, plus struct, list and map. */\nconst compileType = (type: IcebergType): Compiled => {\n if (typeof type === \"string\") return primitive(type);\n\n if (type.type === \"struct\") {\n const fields = type.fields.map((field) => ({ ...field, compiled: compileType(field.type) }));\n\n return {\n type: new Struct(\n fields.map(\n (field) =>\n new Field(field.name, field.compiled.type, !field.required, fieldMetadata(field.id)),\n ),\n ),\n row: Schema.Struct(\n Object.fromEntries(\n fields.map((field) => [\n field.name,\n field.required ? field.compiled.row : optional(field.compiled.row),\n ]),\n ),\n ),\n };\n }\n\n if (type.type === \"list\") {\n const element = compileType(type.element);\n const required = type[\"element-required\"];\n\n return {\n type: new List(\n new Field(\"element\", element.type, !required, fieldMetadata(type[\"element-id\"])),\n ),\n row: Schema.Array(required ? element.row : Schema.NullishOr(element.row)),\n };\n }\n\n const key = compileType(type.key);\n const value = compileType(type.value);\n const required = type[\"value-required\"];\n const entries = new Struct([\n new Field(\"key\", key.type, false, fieldMetadata(type[\"key-id\"])),\n new Field(\"value\", value.type, !required, fieldMetadata(type[\"value-id\"])),\n ]);\n\n return {\n type: new Map_(new Field(\"entries\", entries, false)),\n row: Schema.ReadonlyMap(key.row, required ? value.row : Schema.NullishOr(value.row)),\n };\n};\n\nexport type RowEncoder = (\n rows: ReadonlyArray<object>,\n) => Effect.Effect<RecordBatch<TypeMap>, ConnectorError>;\n\nexport const makeRowEncoder = (\n tableSchema: TableSchema,\n version: string,\n): Effect.Effect<RowEncoder, ConnectorError> =>\n Effect.try({\n try: () => {\n const fields = tableSchema.fields.map((field) => ({\n id: field.id,\n name: field.name,\n ...compileType(field.type),\n }));\n const fieldNames = new Set(fields.map((field) => field.name));\n const identifierIds = new Set(tableSchema[\"identifier-field-ids\"]);\n const keys = tableSchema.fields\n .filter((field) => identifierIds.has(field.id))\n .map((field) => field.name);\n // Other columns may be missing: a delete only sends the key and version.\n const required = new Set([...keys, version]);\n\n if (!fieldNames.has(version)) throw new Error(`Missing version column ${version}`);\n\n const struct = new Struct(\n fields.map((field) => new Field(field.name, field.type, true, fieldMetadata(field.id))),\n );\n const schema = new ArrowSchema(struct.children);\n\n const decodeRow = Schema.decodeUnknownSync(\n Schema.Struct(\n Object.fromEntries(\n fields.map((field) => [\n field.name,\n required.has(field.name) ? field.row : optional(field.row),\n ]),\n ),\n ),\n { onExcessProperty: \"error\" },\n );\n\n // Schema says which field is wrong. This adds the row number.\n const decodeAt = (row: object, index: number) => {\n try {\n return decodeRow(row) as Record<string, unknown>;\n } catch (cause) {\n const detail = cause instanceof Error ? cause.message : String(cause);\n throw new Error(`row ${index}: ${detail}`, { cause });\n }\n };\n\n return (rows: ReadonlyArray<object>) =>\n Effect.try({\n try: () => {\n const builder = makeBuilder({ type: struct, nullValues: [null, undefined] });\n\n for (const [index, row] of rows.entries()) {\n builder.append(decodeAt(row, index) as Struct<TypeMap>[\"TValue\"]);\n }\n\n return new RecordBatch(schema, builder.finish().flush());\n },\n catch: (cause) => new ConnectorError({ message: \"Failed to encode rows\", cause }),\n });\n },\n catch: (cause) => new ConnectorError({ message: \"Failed to convert Iceberg schema\", cause }),\n });\n","import type { RecordBatch, TypeMap } from \"apache-arrow\";\nimport type { TableIdentifier } from \"iceberg-js\";\n\nimport { CatalogManager } from \"@useairfoil/api\";\nimport { Config, Effect, Layer } from \"effect\";\nimport { FetchHttpClient } from \"effect/http\";\n\nimport type { TableSource } from \"../core/types\";\n\nimport { ConnectorError } from \"../errors\";\nimport { compileTable } from \"../iceberg/table\";\nimport * as RuntimeConfig from \"../runtime-config\";\nimport { makeRowEncoder, type RowEncoder } from \"./arrow\";\nimport { Ingestor, type IngestorService } from \"./service\";\n\n/** One catalog and an Iceberg table for each resource that runs. */\nexport type ArrowFlightIngestorConfig<Source extends TableSource = TableSource> = {\n readonly connector: Source;\n readonly catalog: string;\n readonly tables: RuntimeConfig.ResolvedTableBindings<Source>;\n};\n\ntype IngestorEntry = {\n readonly encode: RowEncoder;\n readonly push: (batch: RecordBatch<TypeMap>) => Effect.Effect<void, ConnectorError>;\n};\n\ntype PreparedTable = {\n readonly resource: string;\n readonly identifier: TableIdentifier;\n readonly encode: RowEncoder;\n};\n\nconst connectorError = (message: string, cause?: unknown) => new ConnectorError({ message, cause });\n\n/** Compiles local schemas and opens one Arrow Flight stream per bound resource. */\nexport const layerArrowFlight = <const Source extends TableSource>(\n config: ArrowFlightIngestorConfig<Source>,\n): Layer.Layer<Ingestor, ConnectorError, CatalogManager.CatalogManager> =>\n Layer.effect(Ingestor)(\n Effect.gen(function* () {\n const manager = yield* CatalogManager.CatalogManager;\n const bindings: Partial<Record<string, RuntimeConfig.TableBinding>> = config.tables;\n\n const prepared: Array<PreparedTable> = [];\n for (const resource of config.connector.resources) {\n const binding = bindings[resource.name];\n if (!binding) continue;\n\n const { namespace, name } = binding;\n const identifier = { namespace, name };\n const { schema } = yield* compileTable(resource.rowSchema).pipe(\n Effect.mapError((cause) =>\n connectorError(`Invalid schema for ${resource.name}: ${cause.message}`, cause),\n ),\n );\n\n const encode = yield* makeRowEncoder(schema, resource.version);\n prepared.push({ resource: resource.name, identifier, encode });\n }\n\n const entries = new Map<string, IngestorEntry>();\n for (const { resource, identifier, encode } of prepared) {\n const ingestor = yield* manager\n .ingestor({\n catalog: config.catalog,\n namespace: identifier.namespace,\n table: identifier.name,\n })\n .pipe(\n Effect.mapError((cause) =>\n connectorError(`Failed to open ingestor for ${resource}`, cause),\n ),\n );\n\n entries.set(resource, {\n encode,\n push: (batch) =>\n ingestor\n .push(batch)\n .pipe(\n Effect.mapError((cause) =>\n connectorError(`Failed to ingest rows for ${resource}`, cause),\n ),\n ),\n });\n }\n\n const service: IngestorService = {\n resourceNames: prepared.map(({ resource }) => resource),\n ingest: ({ resource, batch }) => {\n const entry = entries.get(resource);\n\n if (!entry) return Effect.fail(connectorError(`Unknown resource ${resource}`));\n if (batch.rows.length === 0) return Effect.succeed({ rows: 0, bytes: 0 });\n\n return entry\n .encode(batch.rows)\n .pipe(\n Effect.flatMap((encoded) =>\n entry\n .push(encoded)\n .pipe(Effect.as({ rows: batch.rows.length, bytes: encoded.data.byteLength })),\n ),\n );\n },\n };\n\n return Ingestor.of(service);\n }),\n );\n\n/** Configures Arrow Flight ingestion from the hosted URL, catalog and table bindings. */\nexport const layerArrowFlightConfig = (connector: TableSource) =>\n Layer.unwrap(\n Effect.gen(function* () {\n const { catalog, uri, flightUri } = yield* Config.all({\n catalog: RuntimeConfig.catalog,\n uri: RuntimeConfig.engineUri,\n flightUri: RuntimeConfig.flightUri,\n }).pipe(\n Effect.mapError((cause) => connectorError(\"Invalid Arrow Flight configuration\", cause)),\n );\n\n if (uri.protocol !== \"http:\" && uri.protocol !== \"https:\") {\n return yield* Effect.fail(connectorError(\"Invalid Arrow Flight URL\"));\n }\n if (flightUri.protocol !== \"http:\" && flightUri.protocol !== \"https:\") {\n return yield* Effect.fail(connectorError(\"Invalid Arrow Flight URL\"));\n }\n\n const tables = yield* RuntimeConfig.loadTableBindings(connector).pipe(\n Effect.mapError((cause) => connectorError(cause.message, cause)),\n );\n const manager = CatalogManager.layer({ engineUri: uri.href, flightUri: flightUri.href }).pipe(\n Layer.provide(FetchHttpClient.layer),\n );\n\n return layerArrowFlight({ connector, catalog, tables }).pipe(Layer.provide(manager));\n }),\n );\n"],"mappings":";;;;;;;;;;AA+CA,MAAM,SAAS,WACb,OAAO,WAAW,MAChB,OAAO,YACJ,UAAsB,MAAM,eAAe,UAAU,YAAY,OAAO,OAC3E,CACF;AAGF,MAAM,QAAQ,OAAO,OAAO,MAC1B,OAAO,MAAM,GACb,OAAO,UAAU;CAAE,SAAS;CAAgB,SAAS;AAAc,CAAC,CACtE;AAEA,MAAM,QAAQ,OAAO,OAAO,MAC1B,OAAO,gBAAgB;CAAE,SAAS,EAAE,MAAM;CAAM,SAAS,MAAM,MAAM;AAAG,CAAC,CAC3E;AAGA,MAAM,UAAU,OAAO,OAAO,MAC5B,OAAO,YACJ,UAAkB,OAAO,SAAS,KAAK,OAAO,KAAK,CAAC,KAAK,0BAC5D,CACF;AAEA,MAAM,oBAAoB,OAAO,OAAO,MACtC,OAAO,gBAAgB;CAAE,SAAS;CAAI,SAAS;AAAgB,CAAC,CAClE;AAEA,MAAM,aAAuC;CAC3C,SAAS;EAAE,MAAM,IAAI,KAAK;EAAG,KAAK,OAAO;CAAQ;CACjD,KAAK;EAAE,MAAM,IAAI,MAAM;EAAG,KAAK;CAAM;CACrC,MAAM;EAAE,MAAM,IAAI,MAAM;EAAG,KAAK;CAAM;CACtC,OAAO;EAAE,MAAM,IAAI,QAAQ;EAAG,KAAK;CAAQ;CAC3C,QAAQ;EAAE,MAAM,IAAI,QAAQ;EAAG,KAAK,OAAO;CAAO;CAClD,QAAQ;EAAE,MAAM,IAAI,KAAK;EAAG,KAAK,OAAO;CAAO;CAC/C,MAAM;EAAE,MAAM,IAAI,gBAAgB,EAAE;EAAG,KAAK,MAAM,EAAE;CAAE;CACtD,QAAQ;EAAE,MAAM,IAAI,YAAY;EAAG,KAAK,OAAO;CAAW;CAC1D,MAAM;EAAE,MAAM,IAAI,QAAQ;EAAG,KAAK,OAAO;CAAK;CAC9C,MAAM;EAAE,MAAM,IAAI,gBAAgB;EAAG,KAAK;CAAkB;CAC5D,WAAW;EAAE,MAAM,IAAI,qBAAqB;EAAG,KAAK,OAAO;CAAK;CAChE,aAAa;EAAE,MAAM,IAAI,qBAAqB,KAAK;EAAG,KAAK,OAAO;CAAK;AACzE;AAEA,MAAM,aAAa,SAAkC;CACnD,MAAM,QAAQ,WAAW;CAEzB,IAAI,OAAO,OAAO;CAIlB,MAAM,UAAU,iBAAiB,IAAI;CAErC,IAAI,SAAS;EACX,IAAI,QAAQ,YAAY,KAAK,QAAQ,YAAY,MAAM,QAAQ,QAAQ,QAAQ,WAC7E,MAAM,IAAI,MAAM,gCAAgC,MAAM;EAGxD,OAAO;GACL,MAAM,IAAI,QAAQ,QAAQ,OAAO,QAAQ,SAAS;GAClD,KAAK,OAAO,WAAW,WAAW,CAAC,CAAC,MAClC,OAAO,YAAY,UAAuB,MAAM,WAAW,KAAK,qBAAqB,CACvF;EACF;CACF;CAEA,MAAM,QAAQ,eAAe,IAAI;CAEjC,IAAI,OAAO;EACT,IAAI,MAAM,SAAS,GAAG,MAAM,IAAI,MAAM,8BAA8B,MAAM;EAE1E,OAAO;GAAE,MAAM,IAAI,gBAAgB,MAAM,MAAM;GAAG,KAAK,MAAM,MAAM,MAAM;EAAE;CAC7E;CAEA,MAAM,IAAI,MAAM,4BAA4B,MAAM;AACpD;;AAGA,MAAM,YAAY,QAAmB,OAAO,SAAS,OAAO,OAAO,GAAG,CAAC;AAEvE,MAAM,iBAAiB,uBACrB,IAAI,IAAI,CAAC,CAAC,oBAAoB,OAAO,EAAE,CAAC,CAAC,CAAC;;AAG5C,MAAM,eAAe,SAAgC;CACnD,IAAI,OAAO,SAAS,UAAU,OAAO,UAAU,IAAI;CAEnD,IAAI,KAAK,SAAS,UAAU;EAC1B,MAAM,SAAS,KAAK,OAAO,KAAK,WAAW;GAAE,GAAG;GAAO,UAAU,YAAY,MAAM,IAAI;EAAE,EAAE;EAE3F,OAAO;GACL,MAAM,IAAIA,SACR,OAAO,KACJ,UACC,IAAI,MAAM,MAAM,MAAM,MAAM,SAAS,MAAM,CAAC,MAAM,UAAU,cAAc,MAAM,EAAE,CAAC,CACvF,CACF;GACA,KAAK,OAAO,OACV,OAAO,YACL,OAAO,KAAK,UAAU,CACpB,MAAM,MACN,MAAM,WAAW,MAAM,SAAS,MAAM,SAAS,MAAM,SAAS,GAAG,CACnE,CAAC,CACH,CACF;EACF;CACF;CAEA,IAAI,KAAK,SAAS,QAAQ;EACxB,MAAM,UAAU,YAAY,KAAK,OAAO;EACxC,MAAM,WAAW,KAAK;EAEtB,OAAO;GACL,MAAM,IAAI,KACR,IAAI,MAAM,WAAW,QAAQ,MAAM,CAAC,UAAU,cAAc,KAAK,aAAa,CAAC,CACjF;GACA,KAAK,OAAO,MAAM,WAAW,QAAQ,MAAM,OAAO,UAAU,QAAQ,GAAG,CAAC;EAC1E;CACF;CAEA,MAAM,MAAM,YAAY,KAAK,GAAG;CAChC,MAAM,QAAQ,YAAY,KAAK,KAAK;CACpC,MAAM,WAAW,KAAK;CACtB,MAAM,UAAU,IAAIA,SAAO,CACzB,IAAI,MAAM,OAAO,IAAI,MAAM,OAAO,cAAc,KAAK,SAAS,CAAC,GAC/D,IAAI,MAAM,SAAS,MAAM,MAAM,CAAC,UAAU,cAAc,KAAK,WAAW,CAAC,CAC3E,CAAC;CAED,OAAO;EACL,MAAM,IAAI,KAAK,IAAI,MAAM,WAAW,SAAS,KAAK,CAAC;EACnD,KAAK,OAAO,YAAY,IAAI,KAAK,WAAW,MAAM,MAAM,OAAO,UAAU,MAAM,GAAG,CAAC;CACrF;AACF;AAMA,MAAa,kBACX,aACA,YAEA,OAAO,IAAI;CACT,WAAW;EACT,MAAM,SAAS,YAAY,OAAO,KAAK,WAAW;GAChD,IAAI,MAAM;GACV,MAAM,MAAM;GACZ,GAAG,YAAY,MAAM,IAAI;EAC3B,EAAE;EACF,MAAM,aAAa,IAAI,IAAI,OAAO,KAAK,UAAU,MAAM,IAAI,CAAC;EAC5D,MAAM,gBAAgB,IAAI,IAAI,YAAY,uBAAuB;EACjE,MAAM,OAAO,YAAY,OACtB,QAAQ,UAAU,cAAc,IAAI,MAAM,EAAE,CAAC,CAAC,CAC9C,KAAK,UAAU,MAAM,IAAI;EAE5B,MAAM,2BAAW,IAAI,IAAI,CAAC,GAAG,MAAM,OAAO,CAAC;EAE3C,IAAI,CAAC,WAAW,IAAI,OAAO,GAAG,MAAM,IAAI,MAAM,0BAA0B,SAAS;EAEjF,MAAM,SAAS,IAAIA,SACjB,OAAO,KAAK,UAAU,IAAI,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,cAAc,MAAM,EAAE,CAAC,CAAC,CACxF;EACA,MAAM,SAAS,IAAIC,SAAY,OAAO,QAAQ;EAE9C,MAAM,YAAY,OAAO,kBACvB,OAAO,OACL,OAAO,YACL,OAAO,KAAK,UAAU,CACpB,MAAM,MACN,SAAS,IAAI,MAAM,IAAI,IAAI,MAAM,MAAM,SAAS,MAAM,GAAG,CAC3D,CAAC,CACH,CACF,GACA,EAAE,kBAAkB,QAAQ,CAC9B;EAGA,MAAM,YAAY,KAAa,UAAkB;GAC/C,IAAI;IACF,OAAO,UAAU,GAAG;GACtB,SAAS,OAAO;IACd,MAAM,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;IACpE,MAAM,IAAI,MAAM,OAAO,MAAM,IAAI,UAAU,EAAE,MAAM,CAAC;GACtD;EACF;EAEA,QAAQ,SACN,OAAO,IAAI;GACT,WAAW;IACT,MAAM,UAAU,YAAY;KAAE,MAAM;KAAQ,YAAY,CAAC,MAAM,KAAA,CAAS;IAAE,CAAC;IAE3E,KAAK,MAAM,CAAC,OAAO,QAAQ,KAAK,QAAQ,GACtC,QAAQ,OAAO,SAAS,KAAK,KAAK,CAA8B;IAGlE,OAAO,IAAI,YAAY,QAAQ,QAAQ,OAAO,CAAC,CAAC,MAAM,CAAC;GACzD;GACA,QAAQ,UAAU,IAAI,eAAe;IAAE,SAAS;IAAyB;GAAM,CAAC;EAClF,CAAC;CACL;CACA,QAAQ,UAAU,IAAI,eAAe;EAAE,SAAS;EAAoC;CAAM,CAAC;AAC7F,CAAC;;;ACtNH,MAAM,kBAAkB,SAAiB,UAAoB,IAAI,eAAe;CAAE;CAAS;AAAM,CAAC;;AAGlG,MAAa,oBACX,WAEA,MAAM,OAAO,QAAQ,CAAC,CACpB,OAAO,IAAI,aAAa;CACtB,MAAM,UAAU,OAAO,eAAe;CACtC,MAAM,WAAgE,OAAO;CAE7E,MAAM,WAAiC,CAAC;CACxC,KAAK,MAAM,YAAY,OAAO,UAAU,WAAW;EACjD,MAAM,UAAU,SAAS,SAAS;EAClC,IAAI,CAAC,SAAS;EAEd,MAAM,EAAE,WAAW,SAAS;EAC5B,MAAM,aAAa;GAAE;GAAW;EAAK;EACrC,MAAM,EAAE,WAAW,OAAO,aAAa,SAAS,SAAS,CAAC,CAAC,KACzD,OAAO,UAAU,UACf,eAAe,sBAAsB,SAAS,KAAK,IAAI,MAAM,WAAW,KAAK,CAC/E,CACF;EAEA,MAAM,SAAS,OAAO,eAAe,QAAQ,SAAS,OAAO;EAC7D,SAAS,KAAK;GAAE,UAAU,SAAS;GAAM;GAAY;EAAO,CAAC;CAC/D;CAEA,MAAM,0BAAU,IAAI,IAA2B;CAC/C,KAAK,MAAM,EAAE,UAAU,YAAY,YAAY,UAAU;EACvD,MAAM,WAAW,OAAO,QACrB,SAAS;GACR,SAAS,OAAO;GAChB,WAAW,WAAW;GACtB,OAAO,WAAW;EACpB,CAAC,CAAC,CACD,KACC,OAAO,UAAU,UACf,eAAe,+BAA+B,YAAY,KAAK,CACjE,CACF;EAEF,QAAQ,IAAI,UAAU;GACpB;GACA,OAAO,UACL,SACG,KAAK,KAAK,CAAC,CACX,KACC,OAAO,UAAU,UACf,eAAe,6BAA6B,YAAY,KAAK,CAC/D,CACF;EACN,CAAC;CACH;CAEA,MAAM,UAA2B;EAC/B,eAAe,SAAS,KAAK,EAAE,eAAe,QAAQ;EACtD,SAAS,EAAE,UAAU,YAAY;GAC/B,MAAM,QAAQ,QAAQ,IAAI,QAAQ;GAElC,IAAI,CAAC,OAAO,OAAO,OAAO,KAAK,eAAe,oBAAoB,UAAU,CAAC;GAC7E,IAAI,MAAM,KAAK,WAAW,GAAG,OAAO,OAAO,QAAQ;IAAE,MAAM;IAAG,OAAO;GAAE,CAAC;GAExE,OAAO,MACJ,OAAO,MAAM,IAAI,CAAC,CAClB,KACC,OAAO,SAAS,YACd,MACG,KAAK,OAAO,CAAC,CACb,KAAK,OAAO,GAAG;IAAE,MAAM,MAAM,KAAK;IAAQ,OAAO,QAAQ,KAAK;GAAW,CAAC,CAAC,CAChF,CACF;EACJ;CACF;CAEA,OAAO,SAAS,GAAG,OAAO;AAC5B,CAAC,CACH;;AAGF,MAAa,0BAA0B,cACrC,MAAM,OACJ,OAAO,IAAI,aAAa;CACtB,MAAM,EAAE,SAAA,WAAS,KAAK,WAAA,gBAAc,OAAO,OAAO,IAAI;EAC3CC;EACT,KAAKC;EACMC;CACb,CAAC,CAAC,CAAC,KACD,OAAO,UAAU,UAAU,eAAe,sCAAsC,KAAK,CAAC,CACxF;CAEA,IAAI,IAAI,aAAa,WAAW,IAAI,aAAa,UAC/C,OAAO,OAAO,OAAO,KAAK,eAAe,0BAA0B,CAAC;CAEtE,IAAIC,YAAU,aAAa,WAAWA,YAAU,aAAa,UAC3D,OAAO,OAAO,OAAO,KAAK,eAAe,0BAA0B,CAAC;CAGtE,MAAM,SAAS,OAAOC,kBAAgC,SAAS,CAAC,CAAC,KAC/D,OAAO,UAAU,UAAU,eAAe,MAAM,SAAS,KAAK,CAAC,CACjE;CACA,MAAM,UAAU,eAAe,MAAM;EAAE,WAAW,IAAI;EAAM,WAAWD,YAAU;CAAK,CAAC,CAAC,CAAC,KACvF,MAAM,QAAQ,gBAAgB,KAAK,CACrC;CAEA,OAAO,iBAAiB;EAAE;EAAW,SAAA;EAAS;CAAO,CAAC,CAAC,CAAC,KAAK,MAAM,QAAQ,OAAO,CAAC;AACrF,CAAC,CACH"}
@@ -0,0 +1,2 @@
1
+ import { i as makeTokenCache, n as ExpiringToken, r as TokenCache } from "../index-BX8G7d41.mjs";
2
+ export { ExpiringToken, TokenCache, makeTokenCache };
@@ -1,6 +1,5 @@
1
- import { t as __export } from "./chunk-Bp6m_JJh.js";
2
- import { Cache, Duration, Effect, Exit, Number, Redacted } from "effect";
3
-
1
+ import { t as __exportAll } from "../rolldown-runtime-D7D4PA-g.mjs";
2
+ import { Cache, Duration, Effect, Exit, Number } from "effect";
4
3
  //#region src/auth/token-cache.ts
5
4
  /**
6
5
  * Creates a lazy, in-memory token cache around an acquisition effect.
@@ -32,11 +31,10 @@ const makeTokenCache = (options) => Effect.gen(function* () {
32
31
  invalidate: Cache.invalidate(cache, void 0)
33
32
  };
34
33
  });
35
-
36
34
  //#endregion
37
35
  //#region src/auth/index.ts
38
- var auth_exports = /* @__PURE__ */ __export({ makeTokenCache: () => makeTokenCache });
39
-
36
+ var auth_exports = /* @__PURE__ */ __exportAll({ makeTokenCache: () => makeTokenCache });
40
37
  //#endregion
41
- export { makeTokenCache as n, auth_exports as t };
42
- //# sourceMappingURL=auth-DvU_CZ7C.js.map
38
+ export { makeTokenCache, auth_exports as t };
39
+
40
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/auth/token-cache.ts","../../src/auth/index.ts"],"sourcesContent":["import { Cache, Duration, Effect, Exit, Number, Redacted } from \"effect\";\n\nexport interface ExpiringToken {\n /** The credential kept in memory by the cache. */\n readonly value: Redacted.Redacted<string>;\n /** The lifetime reported by the provider. */\n readonly expiresIn: Duration.Duration;\n}\n\nexport interface TokenCache<E> {\n /** Returns the cached token or performs one shared acquisition. */\n readonly get: Effect.Effect<Redacted.Redacted<string>, E>;\n /** Removes the cached token so the next `get` reacquires it. */\n readonly invalidate: Effect.Effect<void>;\n}\n\n/**\n * Creates a lazy, in-memory token cache around an acquisition effect.\n *\n * `refreshFactor` controls how much of the provider lifetime is cached. It is\n * clamped between `0` and `1`.\n *\n * @example\n * ```ts\n * const cache = yield* makeTokenCache({\n * acquire: exchangeCredentials,\n * refreshFactor: 0.5,\n * })\n *\n * const token = yield* cache.get\n * ```\n */\nexport const makeTokenCache = <E, R>(options: {\n readonly acquire: Effect.Effect<ExpiringToken, E, R>;\n readonly refreshFactor?: number;\n}): Effect.Effect<TokenCache<E>, never, R> =>\n Effect.gen(function* () {\n const refreshFactor = Number.clamp(options.refreshFactor ?? 0.5, {\n minimum: 0,\n maximum: 1,\n });\n const cache = yield* Cache.makeWith((_: void) => options.acquire, {\n capacity: 1,\n timeToLive: (exit) =>\n Exit.isSuccess(exit)\n ? Duration.millis(Duration.toMillis(exit.value.expiresIn) * refreshFactor)\n : Duration.zero,\n });\n\n return {\n get: Cache.get(cache, undefined).pipe(Effect.map((token) => token.value)),\n invalidate: Cache.invalidate(cache, undefined),\n };\n });\n","export * from \"./token-cache\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAgCA,MAAa,kBAAwB,YAInC,OAAO,IAAI,aAAa;CACtB,MAAM,gBAAgB,OAAO,MAAM,QAAQ,iBAAiB,IAAK;EAC/D,SAAS;EACT,SAAS;CACX,CAAC;CACD,MAAM,QAAQ,OAAO,MAAM,UAAU,MAAY,QAAQ,SAAS;EAChE,UAAU;EACV,aAAa,SACX,KAAK,UAAU,IAAI,IACf,SAAS,OAAO,SAAS,SAAS,KAAK,MAAM,SAAS,IAAI,aAAa,IACvE,SAAS;CACjB,CAAC;CAED,OAAO;EACL,KAAK,MAAM,IAAI,OAAO,KAAA,CAAS,CAAC,CAAC,KAAK,OAAO,KAAK,UAAU,MAAM,KAAK,CAAC;EACxE,YAAY,MAAM,WAAW,OAAO,KAAA,CAAS;CAC/C;AACF,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { _ as check, a as start, c as makeCreateTableCommand, g as ResourceCheckResult, h as CheckResult, i as StartOptions, m as CheckOptions, n as makeSetupCommand, o as makeDemoCommand, r as App, s as parseSize } from "../index-CZKx2Yce.mjs";
2
+ export { type App, type CheckOptions, type CheckResult, type ResourceCheckResult, type StartOptions, check, makeCreateTableCommand, makeDemoCommand, makeSetupCommand, parseSize, start };
@@ -0,0 +1,2 @@
1
+ import { a as parseSize, i as makeDemoCommand, n as makeSetupCommand, o as makeCreateTableCommand, r as start, s as check } from "../connector-app-C6_dZnhE.mjs";
2
+ export { check, makeCreateTableCommand, makeDemoCommand, makeSetupCommand, parseSize, start };
@@ -0,0 +1,256 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
+ import { t as ConnectorError } from "./errors-pT0YVJU5.mjs";
3
+ import { a as IcebergSchemaError, n as compileTable, r as makeCreateRequest } from "./table-CxM09xf7.mjs";
4
+ import { a as catalog, n as loadTableBindings, o as engineUri } from "./runtime-config-Bn8Fivt1.mjs";
5
+ import { n as run } from "./demo-DqTJ7TWI.mjs";
6
+ import { n as layerArrowFlightConfig } from "./arrow-flight-DDQ0ktx9.mjs";
7
+ import { n as run$1 } from "./ingestion-DoCUwm5N.mjs";
8
+ import { DateTime, Duration, Effect, Option, Schedule, Schema, Tuple } from "effect";
9
+ import { Argument, CliError, Command, Flag } from "effect/cli";
10
+ import { CatalogManager } from "@useairfoil/api";
11
+ import "effect/http";
12
+ import { getCurrentSchema } from "iceberg-js";
13
+ import humanFormat from "human-format";
14
+ import { NodeHttpServer } from "@effect/platform-node";
15
+ import { createServer } from "node:http";
16
+ //#region src/connector-app/check.ts
17
+ const ok = { _tag: "ok" };
18
+ const error = (message) => ({
19
+ _tag: "error",
20
+ message
21
+ });
22
+ const fromEntries = (entries) => Object.fromEntries(entries);
23
+ const resultsFor = (names, result) => fromEntries(names.map((name) => Tuple.make(name, result)));
24
+ /**
25
+ * Builds the connector service from its layer and validates only the selected
26
+ * resources. The layer remains open until every check finishes.
27
+ */
28
+ function check(service, layer, options) {
29
+ if (options.resources.length === 0) return Effect.succeed(fromEntries([]));
30
+ return service.pipe(Effect.flatMap((connector) => Effect.forEach(options.resources, (name) => {
31
+ const resource = connector.resources.find((candidate) => candidate.name === name);
32
+ return (resource ? resource.check.pipe(Effect.match({
33
+ onFailure: (failure) => error(failure.message),
34
+ onSuccess: () => ok
35
+ })) : Effect.succeed(error(`Unknown connector resource: ${name}`))).pipe(Effect.map((result) => Tuple.make(name, result)));
36
+ }).pipe(Effect.map(fromEntries))), Effect.provide(layer), Effect.match({
37
+ onFailure: (failure) => resultsFor(options.resources, error(failure.message)),
38
+ onSuccess: (results) => results
39
+ }));
40
+ }
41
+ //#endregion
42
+ //#region src/catalog/retry.ts
43
+ /** Retries transient catalog responses at the request boundary. */
44
+ const retryCatalogRequest = (request, options) => options === void 0 ? request : request.pipe(Effect.retry({
45
+ schedule: Schedule.spaced(options.interval),
46
+ times: options.times,
47
+ while: (error) => error.status === 408 || error.status === 429 || error.status !== void 0 && error.status >= 500 && error.status <= 599
48
+ }));
49
+ //#endregion
50
+ //#region src/catalog/table.ts
51
+ const tableError = (cause) => {
52
+ if (cause instanceof ConnectorError) return cause;
53
+ const message = cause instanceof Error ? cause.message : "Failed to prepare Iceberg table";
54
+ return new ConnectorError({
55
+ message: cause instanceof IcebergSchemaError ? `${cause.path}: ${message}` : message,
56
+ cause
57
+ });
58
+ };
59
+ const compareTypes = (expected, actual, path) => {
60
+ if (typeof expected === "string" || typeof actual === "string") return expected === actual ? void 0 : path;
61
+ if (expected.type !== actual.type) return path;
62
+ switch (expected.type) {
63
+ case "struct":
64
+ if (actual.type !== "struct") return path;
65
+ return compareFields(expected.fields, actual.fields, path);
66
+ case "list":
67
+ if (actual.type !== "list" || expected["element-required"] !== actual["element-required"]) return path;
68
+ return compareTypes(expected.element, actual.element, `${path}.element`);
69
+ case "map":
70
+ if (actual.type !== "map" || expected["value-required"] !== actual["value-required"]) return path;
71
+ return compareTypes(expected.key, actual.key, `${path}.key`) ?? compareTypes(expected.value, actual.value, `${path}.value`);
72
+ }
73
+ };
74
+ const compareFields = (expected, actual, path) => {
75
+ if (expected.length !== actual.length) return path;
76
+ for (const field of expected) {
77
+ const fieldPath = `${path}.${field.name}`;
78
+ const other = actual.find((candidate) => candidate.name === field.name);
79
+ if (!other || field.required !== other.required) return fieldPath;
80
+ const mismatch = compareTypes(field.type, other.type, fieldPath);
81
+ if (mismatch) return mismatch;
82
+ }
83
+ };
84
+ const verifyTable = (metadata, expected) => Effect.try({
85
+ try: () => {
86
+ if (!Array.isArray(metadata.schemas)) throw new Error("Invalid table metadata");
87
+ if (metadata["format-version"] !== 3) throw new Error("Storage-engine tables must use Iceberg format v3. Migrate the table before ingesting.");
88
+ const partitionSpec = metadata["partition-specs"].find((spec) => spec["spec-id"] === metadata["default-spec-id"]);
89
+ if (!partitionSpec) throw new Error("Table has no default partition spec");
90
+ if (partitionSpec.fields.length > 0) throw new Error("Storage-engine tables must be unpartitioned.");
91
+ if (metadata.properties?.["encryption.key-id"] !== void 0) throw new Error("Storage-engine tables must be unencrypted.");
92
+ const actual = getCurrentSchema(metadata);
93
+ if (!actual) throw new Error("Table has no current schema");
94
+ const path = compareFields(expected.fields, actual.fields, "$");
95
+ if (path) throw new Error(`Table schema does not match at ${path}. Migrate the table or revert the schema change.`);
96
+ const identifierNames = (schema) => new Set(schema.fields.filter((field) => schema["identifier-field-ids"]?.includes(field.id)).map((field) => field.name));
97
+ const expectedNames = identifierNames(expected);
98
+ const actualNames = identifierNames(actual);
99
+ if (expectedNames.size !== actualNames.size || [...expectedNames].some((name) => !actualNames.has(name))) throw new Error("Table identifier fields do not match. Recreate the table or revert the schema change.");
100
+ return actual;
101
+ },
102
+ catch: (cause) => new ConnectorError({
103
+ message: cause instanceof Error ? cause.message : "Invalid table metadata",
104
+ cause
105
+ })
106
+ });
107
+ /** Creates a missing table through an engine catalog and verifies its schema. */
108
+ const ensureTableFromEngine = (schema, options) => Effect.scoped(Effect.gen(function* () {
109
+ const catalog = yield* (yield* CatalogManager.make({ engineUri: options.engineUri })).getIcebergCatalog(options.catalogId);
110
+ return yield* ensureTable(schema, {
111
+ catalog,
112
+ identifier: options.identifier,
113
+ location: options.location,
114
+ properties: options.properties
115
+ });
116
+ })).pipe(Effect.mapError(tableError));
117
+ /** Loads a table, creating it when it is missing. */
118
+ const ensureTable = (schema, options) => Effect.gen(function* () {
119
+ const table = yield* compileTable(schema);
120
+ const request = (effect) => retryCatalogRequest(effect, options.retry);
121
+ const metadata = yield* request(options.catalog.loadTable(options.identifier)).pipe(Effect.catchIf((error) => error.status === 404, () => request(options.catalog.createNamespaceIfNotExists({ namespace: options.identifier.namespace })).pipe(Effect.andThen(request(options.catalog.createTable({ namespace: options.identifier.namespace }, makeCreateRequest(table, {
122
+ name: options.identifier.name,
123
+ location: options.location,
124
+ properties: options.properties
125
+ }))))).pipe(Effect.catchIf((error) => error.status === 409 || error.status === 412, () => Effect.void), Effect.flatMap(() => request(options.catalog.loadTable(options.identifier))))));
126
+ return yield* verifyTable(metadata, table.schema);
127
+ }).pipe(Effect.mapError(tableError));
128
+ //#endregion
129
+ //#region src/connector-app/create-table.ts
130
+ /** Creates the table command; existing tables must match the connector's row schemas. */
131
+ const makeCreateTableCommand = (schemas) => Command.make("create-table", {
132
+ table: Argument.String("table"),
133
+ catalog: Flag.String("catalog"),
134
+ location: Flag.String("location").pipe(Flag.optional),
135
+ resource: Flag.String("resource").pipe(Flag.optional)
136
+ }, ({ table, catalog, location, resource }) => Effect.gen(function* () {
137
+ const namespace = table.split(".");
138
+ const name = namespace.pop();
139
+ if (!name || namespace.length === 0 || namespace.some((part) => part.length === 0)) return yield* new CliError.UserError({
140
+ cause: "Invalid table name",
141
+ userMessage: "Use a table name like my.namespace.table"
142
+ });
143
+ const resourceName = Option.getOrElse(resource, () => name);
144
+ const schema = Object.hasOwn(schemas, resourceName) ? schemas[resourceName] : void 0;
145
+ if (!schema) return yield* new CliError.UserError({
146
+ cause: "Unknown resource",
147
+ userMessage: `Unknown resource: ${resourceName}`
148
+ });
149
+ const uri = yield* engineUri;
150
+ yield* ensureTableFromEngine(schema, {
151
+ engineUri: uri.href,
152
+ catalogId: catalog,
153
+ identifier: {
154
+ namespace,
155
+ name
156
+ },
157
+ location: Option.getOrUndefined(location)
158
+ });
159
+ yield* Effect.log(`Prepared ${table}`);
160
+ })).pipe(Command.withDescription("Create or verify an Iceberg table for a connector resource"));
161
+ //#endregion
162
+ //#region src/connector-app/demo.ts
163
+ /** Reads byte sizes such as `512kB`, `128MB` or `1.5GB`. */
164
+ const parseSize = (input) => Option.liftThrowable(() => humanFormat.parse.bytes(input))().pipe(Option.filter((bytes) => Number.isSafeInteger(bytes) && bytes > 0));
165
+ /**
166
+ * Creates the command that writes demo data over Arrow Flight, then exits.
167
+ * It reads hosted config and table bindings, so provide `RuntimeConfig.layerHosted()`.
168
+ */
169
+ const makeDemoCommand = (demo) => Command.make("demo", {
170
+ size: Flag.String("size").pipe(Flag.filterMap(parseSize, () => "a size like 512kB, 128MB or 1.5GB")),
171
+ seed: Flag.Int("seed").pipe(Flag.optional),
172
+ batchSize: Flag.Int("batch-size").pipe(Flag.withSchema(Schema.Int.check(Schema.isGreaterThan(0))), Flag.withDescription("Maximum rows per ingestion batch (default: 500)"), Flag.optional)
173
+ }, ({ size, seed, batchSize }) => Effect.gen(function* () {
174
+ const now = yield* DateTime.nowAsDate;
175
+ yield* run(demo, {
176
+ bytes: size,
177
+ seed: Option.getOrUndefined(seed),
178
+ batchSize: Option.getOrUndefined(batchSize),
179
+ now
180
+ }).pipe(Effect.provide(layerArrowFlightConfig({ resources: [...demo.setup.tables, ...demo.group.tables] })));
181
+ })).pipe(Command.withDescription("Write demo data for the bound tables, then exit"));
182
+ //#endregion
183
+ //#region src/connector-app/start.ts
184
+ const start = (app, options) => Effect.gen(function* () {
185
+ const initialCutoff = options.initialCutoff ?? (yield* DateTime.now.pipe(Effect.map(DateTime.formatIso)));
186
+ const routes = app.webhooks?.map((route) => route.path) ?? [];
187
+ yield* Effect.logInfo("webhook server ready").pipe(Effect.annotateLogs({
188
+ port: options.port,
189
+ routes
190
+ }));
191
+ return yield* run$1(app, {
192
+ initialCutoff,
193
+ webhook: {
194
+ routes: app.webhooks ?? [],
195
+ healthPath: options.healthPath ?? "/health",
196
+ metricsPath: options.metricsPath,
197
+ statusPath: options.statusPath,
198
+ disableHttpLogger: options.disableHttpLogger ?? true
199
+ }
200
+ }).pipe(Effect.provide(NodeHttpServer.layer(createServer, { port: options.port })));
201
+ });
202
+ //#endregion
203
+ //#region src/connector-app/setup.ts
204
+ /** Creates or verifies all bound tables without loading provider credentials. */
205
+ const makeSetupCommand = (schemas) => Command.make("setup", {
206
+ checkpointIntervalSec: Flag.Int("checkpoint-interval-sec").pipe(Flag.withSchema(Schema.Int.check(Schema.isBetween({
207
+ minimum: 0,
208
+ maximum: 900
209
+ }))), Flag.withDescription("Checkpoint interval for new tables in seconds (0 disables the interval delay)"), Flag.optional),
210
+ retryInterval: Flag.String("retry-interval").pipe(Flag.withDefault("5 seconds"), Flag.withSchema(Schema.DurationFromString), Flag.filter((duration) => Duration.isPositive(duration) && Duration.isFinite(duration), () => "a positive duration, such as 5 seconds"), Flag.withDescription("Delay between transient catalog retries")),
211
+ retries: Flag.Int("retries").pipe(Flag.withDefault(10), Flag.filter((value) => value >= 0, () => "a non-negative number of retries"), Flag.withDescription("Maximum retries per catalog request after the initial attempt"))
212
+ }, ({ retryInterval, retries, checkpointIntervalSec }) => Effect.gen(function* () {
213
+ const source = { resources: Object.entries(schemas).map(([name, rowSchema]) => ({
214
+ name,
215
+ rowSchema
216
+ })) };
217
+ const tables = yield* loadTableBindings(source);
218
+ const uri = yield* engineUri;
219
+ const catalogId = yield* catalog;
220
+ const manager = yield* CatalogManager.make({ engineUri: uri.href });
221
+ const retry = {
222
+ interval: retryInterval,
223
+ times: retries
224
+ };
225
+ const properties = Option.match(checkpointIntervalSec, {
226
+ onNone: () => void 0,
227
+ onSome: (seconds) => ({ "airfoil.checkpoint.interval-sec": String(seconds) })
228
+ });
229
+ const catalog$1 = yield* retryCatalogRequest(manager.getIcebergCatalog(catalogId), retry);
230
+ for (const resource of source.resources) {
231
+ const binding = tables[resource.name];
232
+ if (!binding) continue;
233
+ yield* ensureTable(resource.rowSchema, {
234
+ catalog: catalog$1,
235
+ identifier: binding,
236
+ location: binding.location,
237
+ retry,
238
+ properties
239
+ });
240
+ yield* Effect.logInfo(`Prepared ${[...binding.namespace, binding.name].join(".")}`);
241
+ }
242
+ })).pipe(Command.withDescription("Create or verify the Iceberg tables in AIRFOIL_TABLE_BINDINGS"));
243
+ //#endregion
244
+ //#region src/connector-app/index.ts
245
+ var connector_app_exports = /* @__PURE__ */ __exportAll({
246
+ check: () => check,
247
+ makeCreateTableCommand: () => makeCreateTableCommand,
248
+ makeDemoCommand: () => makeDemoCommand,
249
+ makeSetupCommand: () => makeSetupCommand,
250
+ parseSize: () => parseSize,
251
+ start: () => start
252
+ });
253
+ //#endregion
254
+ export { parseSize as a, makeDemoCommand as i, makeSetupCommand as n, makeCreateTableCommand as o, start as r, check as s, connector_app_exports as t };
255
+
256
+ //# sourceMappingURL=connector-app-C6_dZnhE.mjs.map