@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.
- package/LICENSE.txt +201 -0
- package/METRICS.md +38 -42
- package/README.md +111 -444
- package/dist/annotations-vzSzXtr-.d.mts +34 -0
- package/dist/arrow-flight-DDQ0ktx9.mjs +251 -0
- package/dist/arrow-flight-DDQ0ktx9.mjs.map +1 -0
- package/dist/auth/index.d.mts +2 -0
- package/dist/{auth-DvU_CZ7C.js → auth/index.mjs} +6 -8
- package/dist/auth/index.mjs.map +1 -0
- package/dist/connector-app/index.d.mts +2 -0
- package/dist/connector-app/index.mjs +2 -0
- package/dist/connector-app-C6_dZnhE.mjs +256 -0
- package/dist/connector-app-C6_dZnhE.mjs.map +1 -0
- package/dist/{constants-D113Ut1x.js → constants-v3UX-hf6.mjs} +5 -5
- package/dist/constants-v3UX-hf6.mjs.map +1 -0
- package/dist/demo/index.d.mts +2 -0
- package/dist/demo/index.mjs +2 -0
- package/dist/demo-DqTJ7TWI.mjs +204 -0
- package/dist/demo-DqTJ7TWI.mjs.map +1 -0
- package/dist/errors/index.d.mts +2 -0
- package/dist/errors/index.mjs +2 -0
- package/dist/{errors-DHWylqRZ.js → errors-pT0YVJU5.mjs} +2 -3
- package/dist/errors-pT0YVJU5.mjs.map +1 -0
- package/dist/iceberg/index.d.mts +3 -0
- package/dist/iceberg/index.mjs +3 -0
- package/dist/iceberg-Cj2PwlNm.mjs +30 -0
- package/dist/iceberg-Cj2PwlNm.mjs.map +1 -0
- package/dist/index-BTAbexBp.d.mts +55 -0
- package/dist/{index-DXa26Ry5.d.ts → index-BX8G7d41.d.mts} +1 -2
- package/dist/{index-VpHaZXZw.d.ts → index-Byr8NTdG.d.mts} +11 -13
- package/dist/index-CV6V6fyx.d.mts +79 -0
- package/dist/{index-C5Ma7V7t.d.ts → index-CZKx2Yce.d.mts} +72 -37
- package/dist/{index-DYpEMJf-.d.ts → index-CgvFomO6.d.mts} +6 -7
- package/dist/{index-bfjH7Yb0.d.ts → index-DeTwIUKl.d.mts} +9 -10
- package/dist/{index-DKoRwyFn.d.ts → index-DkVNO6ks.d.mts} +2 -5
- package/dist/{index-BKw7jA2a.d.ts → index-dHS59EZl.d.mts} +10 -8
- package/dist/{index-CR9HJDdr.d.ts → index-eFuoMZZ3.d.mts} +18 -20
- package/dist/{index-ZSuiZX5H.d.ts → index-fHIJJoRe.d.mts} +25 -30
- package/dist/index-uFa2wwYo.d.mts +27 -0
- package/dist/index.d.mts +16 -0
- package/dist/index.mjs +45 -0
- package/dist/index.mjs.map +1 -0
- package/dist/ingestion/index.d.mts +2 -0
- package/dist/ingestion/index.mjs +2 -0
- package/dist/ingestion-DoCUwm5N.mjs +275 -0
- package/dist/ingestion-DoCUwm5N.mjs.map +1 -0
- package/dist/ingestor/index.d.mts +3 -0
- package/dist/ingestor/index.mjs +4 -0
- package/dist/ingestor-DjCEzBzZ.mjs +26 -0
- package/dist/ingestor-DjCEzBzZ.mjs.map +1 -0
- package/dist/instrumented-Bq1WX2L5.mjs +29 -0
- package/dist/instrumented-Bq1WX2L5.mjs.map +1 -0
- package/dist/manifest/index.d.mts +2 -0
- package/dist/manifest/{index.js → index.mjs} +2 -4
- package/dist/{manifest-P7kBnch9.js → manifest-CABeIhAG.mjs} +14 -19
- package/dist/manifest-CABeIhAG.mjs.map +1 -0
- package/dist/{telemetry-j_nxwYfh.js → metrics-C901IIYZ.mjs} +172 -26
- package/dist/metrics-C901IIYZ.mjs.map +1 -0
- package/dist/{metrics-C4V1pm6v.d.ts → metrics-DoJ0WeKu.d.mts} +18 -13
- package/dist/metrics.d.mts +2 -0
- package/dist/metrics.mjs +2 -0
- package/dist/{chunk-Bp6m_JJh.js → rolldown-runtime-D7D4PA-g.mjs} +4 -4
- package/dist/runtime-config/index.d.mts +2 -0
- package/dist/runtime-config/index.mjs +3 -0
- package/dist/runtime-config-Bn8Fivt1.mjs +100 -0
- package/dist/runtime-config-Bn8Fivt1.mjs.map +1 -0
- package/dist/server-D1TTN0_D.mjs +144 -0
- package/dist/server-D1TTN0_D.mjs.map +1 -0
- package/dist/service-BRoy_EId.d.mts +27 -0
- package/dist/service-jWxxfEht.mjs +7 -0
- package/dist/service-jWxxfEht.mjs.map +1 -0
- package/dist/state-store/index.d.mts +2 -0
- package/dist/state-store/index.mjs +2 -0
- package/dist/{state-store-BdYulQOY.js → state-store-DhSrKVEH.mjs} +15 -22
- package/dist/state-store-DhSrKVEH.mjs.map +1 -0
- package/dist/status/{index.d.ts → index.d.mts} +1 -4
- package/dist/status/{index.js → index.mjs} +2 -6
- package/dist/{status-BTTd7AUR.js → status-4h6Oxh-O.mjs} +14 -16
- package/dist/status-4h6Oxh-O.mjs.map +1 -0
- package/dist/table-CxM09xf7.mjs +307 -0
- package/dist/table-CxM09xf7.mjs.map +1 -0
- package/dist/types-NtSv6gwl.d.mts +220 -0
- package/dist/webhook/index.d.mts +3 -0
- package/dist/webhook/index.mjs +12 -0
- package/dist/webhook/index.mjs.map +1 -0
- package/package.json +72 -58
- package/src/catalog/retry.ts +25 -0
- package/src/catalog/table.ts +237 -0
- package/src/connector-app/create-table.ts +47 -0
- package/src/connector-app/demo.ts +47 -0
- package/src/connector-app/index.ts +3 -0
- package/src/connector-app/setup.ts +69 -0
- package/src/connector-app/start.ts +1 -1
- package/src/core/builder.ts +15 -23
- package/src/core/index.ts +11 -1
- package/src/core/types.ts +114 -39
- package/src/demo/definition.ts +68 -0
- package/src/demo/generate.ts +167 -0
- package/src/demo/index.ts +3 -0
- package/src/demo/run.ts +172 -0
- package/src/iceberg/annotations.ts +49 -0
- package/src/iceberg/error.ts +9 -0
- package/src/iceberg/index.ts +3 -0
- package/src/iceberg/table.ts +461 -0
- package/src/index.ts +3 -1
- package/src/ingestion/engine.ts +240 -60
- package/src/ingestor/arrow-flight.ts +141 -0
- package/src/ingestor/arrow.ts +248 -0
- package/src/ingestor/console.ts +13 -0
- package/src/ingestor/index.ts +13 -0
- package/src/ingestor/instrumented.ts +61 -0
- package/src/ingestor/service.ts +30 -0
- package/src/manifest/config.ts +3 -3
- package/src/metrics.ts +66 -20
- package/src/runtime-config/config.ts +11 -8
- package/src/runtime-config/constants.ts +3 -2
- package/src/runtime-config/index.ts +2 -7
- package/src/runtime-config/layer.ts +1 -1
- package/src/runtime-config/table-bindings.ts +50 -136
- package/src/state-store/layer-key-value-store.ts +8 -3
- package/src/state-store/layer-memory.ts +1 -1
- package/src/state-store/layer-sql.ts +4 -4
- package/src/state-store/schema.ts +2 -2
- package/src/state-store/state.ts +14 -2
- package/src/status/route.ts +2 -2
- package/src/telemetry.ts +20 -28
- package/src/webhook/server.ts +39 -40
- package/dist/auth/index.d.ts +0 -2
- package/dist/auth/index.js +0 -3
- package/dist/auth-DvU_CZ7C.js.map +0 -1
- package/dist/connector-app/index.d.ts +0 -14
- package/dist/connector-app/index.js +0 -12
- package/dist/connector-app-BljHd0Pe.js +0 -64
- package/dist/connector-app-BljHd0Pe.js.map +0 -1
- package/dist/constants-D113Ut1x.js.map +0 -1
- package/dist/errors/index.d.ts +0 -2
- package/dist/errors/index.js +0 -3
- package/dist/errors-DHWylqRZ.js.map +0 -1
- package/dist/index-BA8ASHKr.d.ts +0 -31
- package/dist/index.d.ts +0 -14
- package/dist/index.js +0 -60
- package/dist/index.js.map +0 -1
- package/dist/ingestion/index.d.ts +0 -6
- package/dist/ingestion/index.js +0 -11
- package/dist/ingestion-QBGKrFr9.js +0 -200
- package/dist/ingestion-QBGKrFr9.js.map +0 -1
- package/dist/manifest/index.d.ts +0 -2
- package/dist/manifest-P7kBnch9.js.map +0 -1
- package/dist/metrics-inkCAxKq.js +0 -136
- package/dist/metrics-inkCAxKq.js.map +0 -1
- package/dist/metrics.d.ts +0 -4
- package/dist/metrics.js +0 -5
- package/dist/publisher/index.d.ts +0 -6
- package/dist/publisher/index.js +0 -8
- package/dist/publisher-BdWJOfer.js +0 -138
- package/dist/publisher-BdWJOfer.js.map +0 -1
- package/dist/runtime-config/index.d.ts +0 -4
- package/dist/runtime-config/index.js +0 -5
- package/dist/runtime-config-IbyCVpei.js +0 -60
- package/dist/runtime-config-IbyCVpei.js.map +0 -1
- package/dist/server-D-eLX3yN.js +0 -183
- package/dist/server-D-eLX3yN.js.map +0 -1
- package/dist/service-CUjGhe-7.d.ts +0 -32
- package/dist/service-DiP-XETo.js +0 -8
- package/dist/service-DiP-XETo.js.map +0 -1
- package/dist/state-store/index.d.ts +0 -3
- package/dist/state-store/index.js +0 -5
- package/dist/state-store-BdYulQOY.js.map +0 -1
- package/dist/status-BTTd7AUR.js.map +0 -1
- package/dist/table-bindings-Dp1DhTeS.js +0 -106
- package/dist/table-bindings-Dp1DhTeS.js.map +0 -1
- package/dist/telemetry-j_nxwYfh.js.map +0 -1
- package/dist/types-jVD5Ys43.d.ts +0 -174
- package/dist/webhook/index.d.ts +0 -7
- package/dist/webhook/index.js +0 -9
- package/dist/webhook-MhFcLW-o.js +0 -13
- package/dist/webhook-MhFcLW-o.js.map +0 -1
- package/src/publisher/console.ts +0 -18
- package/src/publisher/index.ts +0 -7
- package/src/publisher/instrumented.ts +0 -75
- package/src/publisher/service.ts +0 -37
- 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":"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"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { t as
|
|
2
|
-
import { Cache, Duration, Effect, Exit, Number
|
|
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__ */
|
|
39
|
-
|
|
36
|
+
var auth_exports = /* @__PURE__ */ __exportAll({ makeTokenCache: () => makeTokenCache });
|
|
40
37
|
//#endregion
|
|
41
|
-
export { makeTokenCache
|
|
42
|
-
|
|
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,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
|