@useairfoil/connector-kit 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/METRICS.md +78 -0
  2. package/README.md +450 -88
  3. package/dist/auth/index.d.ts +2 -0
  4. package/dist/auth/index.js +3 -0
  5. package/dist/auth-DvU_CZ7C.js +42 -0
  6. package/dist/auth-DvU_CZ7C.js.map +1 -0
  7. package/dist/chunk-Bp6m_JJh.js +13 -0
  8. package/dist/connector-app/index.d.ts +14 -0
  9. package/dist/connector-app/index.js +11 -0
  10. package/dist/connector-app-d7Z-SZLQ.js +64 -0
  11. package/dist/connector-app-d7Z-SZLQ.js.map +1 -0
  12. package/dist/constants-BRhIX_rz.js +27 -0
  13. package/dist/constants-BRhIX_rz.js.map +1 -0
  14. package/dist/errors/index.d.ts +2 -0
  15. package/dist/errors/index.js +3 -0
  16. package/dist/errors-DHWylqRZ.js +8 -0
  17. package/dist/errors-DHWylqRZ.js.map +1 -0
  18. package/dist/index-Byr0Lf0C.d.ts +25 -0
  19. package/dist/index-CBT-JX-T.d.ts +173 -0
  20. package/dist/index-CTHVudvQ.d.ts +41 -0
  21. package/dist/index-CTLYQ74P.d.ts +80 -0
  22. package/dist/index-Coh_SkBs.d.ts +95 -0
  23. package/dist/index-D54ySAKn.d.ts +14 -0
  24. package/dist/index-DtJX_tic.d.ts +153 -0
  25. package/dist/index-J42AZaXU.d.ts +38 -0
  26. package/dist/index-fIIBVAai.d.ts +25 -0
  27. package/dist/index-o1-Z3n7V.d.ts +55 -0
  28. package/dist/index.d.ts +14 -165
  29. package/dist/index.js +50 -228
  30. package/dist/index.js.map +1 -1
  31. package/dist/ingestion/index.d.ts +6 -0
  32. package/dist/ingestion/index.js +10 -0
  33. package/dist/ingestion-BQo4cbGw.js +200 -0
  34. package/dist/ingestion-BQo4cbGw.js.map +1 -0
  35. package/dist/manifest/index.d.ts +2 -0
  36. package/dist/manifest/index.js +4 -0
  37. package/dist/manifest-ByE6YPG0.js +221 -0
  38. package/dist/manifest-ByE6YPG0.js.map +1 -0
  39. package/dist/metrics-CVAk1ALR.d.ts +53 -0
  40. package/dist/metrics-CWPy2Aqc.js +96 -0
  41. package/dist/metrics-CWPy2Aqc.js.map +1 -0
  42. package/dist/metrics.d.ts +4 -0
  43. package/dist/metrics.js +3 -0
  44. package/dist/publisher/index.d.ts +5 -0
  45. package/dist/publisher/index.js +6 -0
  46. package/dist/publisher-BYT3RLVO.js +131 -0
  47. package/dist/publisher-BYT3RLVO.js.map +1 -0
  48. package/dist/runtime-config/index.d.ts +2 -0
  49. package/dist/runtime-config/index.js +4 -0
  50. package/dist/runtime-config-BpB-d_bF.js +51 -0
  51. package/dist/runtime-config-BpB-d_bF.js.map +1 -0
  52. package/dist/server-CXTQ7odl.js +183 -0
  53. package/dist/server-CXTQ7odl.js.map +1 -0
  54. package/dist/service-DuFiQwid.js +191 -0
  55. package/dist/service-DuFiQwid.js.map +1 -0
  56. package/dist/service-IE5qaf9x.d.ts +32 -0
  57. package/dist/state-store/index.d.ts +3 -0
  58. package/dist/state-store/index.js +5 -0
  59. package/dist/state-store-yl6LiTQ0.js +198 -0
  60. package/dist/state-store-yl6LiTQ0.js.map +1 -0
  61. package/dist/status/index.d.ts +5 -0
  62. package/dist/status/index.js +6 -0
  63. package/dist/status-B-oZBc33.js +84 -0
  64. package/dist/status-B-oZBc33.js.map +1 -0
  65. package/dist/types-B35J5YPu.d.ts +174 -0
  66. package/dist/webhook/index.d.ts +6 -0
  67. package/dist/webhook/index.js +8 -0
  68. package/dist/webhook-DI-Afxkg.js +13 -0
  69. package/dist/webhook-DI-Afxkg.js.map +1 -0
  70. package/package.json +66 -8
  71. package/src/auth/index.ts +1 -0
  72. package/src/auth/token-cache.ts +54 -0
  73. package/src/connector-app/check.ts +76 -0
  74. package/src/connector-app/index.ts +2 -0
  75. package/src/connector-app/start.ts +42 -0
  76. package/src/core/builder.ts +81 -9
  77. package/src/core/index.ts +22 -0
  78. package/src/core/types.ts +204 -76
  79. package/src/errors/index.ts +1 -0
  80. package/src/index.ts +13 -28
  81. package/src/ingestion/engine.ts +409 -174
  82. package/src/ingestion/index.ts +1 -0
  83. package/src/manifest/config.ts +232 -0
  84. package/src/manifest/index.ts +14 -0
  85. package/src/manifest/manifest.ts +31 -0
  86. package/src/manifest/number-schema.ts +17 -0
  87. package/src/manifest/schema.ts +160 -0
  88. package/src/metrics.ts +128 -0
  89. package/src/publisher/console.ts +18 -0
  90. package/src/publisher/index.ts +7 -0
  91. package/src/publisher/instrumented.ts +68 -0
  92. package/src/publisher/service.ts +30 -13
  93. package/src/publisher/wings.ts +159 -72
  94. package/src/runtime/context.ts +15 -0
  95. package/src/runtime-config/constants.ts +27 -0
  96. package/src/runtime-config/error.ts +13 -0
  97. package/src/runtime-config/index.ts +3 -0
  98. package/src/runtime-config/layer.ts +56 -0
  99. package/src/state-store/index.ts +3 -0
  100. package/src/state-store/keys.ts +22 -0
  101. package/src/state-store/layer-key-value-store.ts +132 -0
  102. package/src/state-store/layer-memory.ts +11 -0
  103. package/src/state-store/layer-sql.ts +50 -0
  104. package/src/state-store/schema.ts +57 -0
  105. package/src/state-store/service.ts +51 -0
  106. package/src/state-store/state.ts +17 -0
  107. package/src/status/index.ts +2 -0
  108. package/src/status/route.ts +77 -0
  109. package/src/status/types.ts +33 -0
  110. package/src/telemetry.ts +288 -0
  111. package/src/webhook/index.ts +8 -0
  112. package/src/webhook/server.ts +209 -39
  113. package/src/webhook/types.ts +6 -17
  114. package/src/ingestion/state-store.ts +0 -36
  115. package/src/streams/pull-stream.ts +0 -54
  116. package/src/streams/webhook-queue.ts +0 -14
  117. /package/src/{core/errors.ts → errors/connector-error.ts} +0 -0
package/dist/index.js CHANGED
@@ -1,236 +1,58 @@
1
- import { Cause, Context, Data, Effect, Layer, Queue, Ref, Schema, Stream } from "effect";
2
- import * as Wings from "@useairfoil/wings";
3
- import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
1
+ import { t as ConnectorError } from "./errors-DHWylqRZ.js";
2
+ import { t as auth_exports } from "./auth-DvU_CZ7C.js";
3
+ import { t as connector_app_exports } from "./connector-app-d7Z-SZLQ.js";
4
+ import { s as metrics_exports } from "./metrics-CWPy2Aqc.js";
5
+ import "./constants-BRhIX_rz.js";
6
+ import { c as telemetry_exports } from "./service-DuFiQwid.js";
7
+ import "./server-CXTQ7odl.js";
8
+ import { t as state_store_exports } from "./state-store-yl6LiTQ0.js";
9
+ import { t as status_exports } from "./status-B-oZBc33.js";
10
+ import { t as ingestion_exports } from "./ingestion-BQo4cbGw.js";
11
+ import { t as manifest_exports } from "./manifest-ByE6YPG0.js";
12
+ import { t as publisher_exports } from "./publisher-BYT3RLVO.js";
13
+ import { t as runtime_config_exports } from "./runtime-config-BpB-d_bF.js";
14
+ import { n as webhook_exports } from "./webhook-DI-Afxkg.js";
15
+ import { DateTime, Effect } from "effect";
4
16
 
5
17
  //#region src/core/builder.ts
6
- const defineConnector = (definition) => definition;
7
- const defineEntity = (definition) => definition;
8
- const defineEvent = (definition) => definition;
9
-
10
- //#endregion
11
- //#region src/core/errors.ts
12
- var ConnectorError = class extends Data.TaggedError("ConnectorError") {};
13
-
14
- //#endregion
15
- //#region src/publisher/service.ts
16
- var Publisher = class extends Context.Service()("Publisher") {};
17
-
18
- //#endregion
19
- //#region src/ingestion/state-store.ts
20
- var StateStore = class extends Context.Service()("StateStore") {};
21
- const StateStoreInMemory = Layer.effect(StateStore)(Effect.gen(function* () {
22
- const ref = yield* Ref.make(/* @__PURE__ */ new Map());
23
- return {
24
- getState: (key) => Effect.map(Ref.get(ref), (map) => map.get(key)),
25
- setState: (key, state) => Effect.map(Ref.update(ref, (map) => {
26
- const next = new Map(map);
27
- next.set(key, state);
28
- return next;
29
- }), () => void 0)
30
- };
31
- }));
32
-
33
- //#endregion
34
- //#region src/ingestion/engine.ts
35
- const runConnector = (connector, initialCutoff) => Effect.gen(function* () {
36
- const entityRuns = connector.entities.map((entity) => runEntity(entity, initialCutoff));
37
- const eventRuns = connector.events.map((event) => runEvent(event, initialCutoff));
38
- yield* Effect.all([...entityRuns, ...eventRuns], { concurrency: "unbounded" });
39
- });
40
- const createInitialState = (cutoff) => ({
41
- backfill: { cutoff },
42
- live: { cutoff }
43
- });
44
- const makeStateRef = (key, initialCutoff) => Effect.gen(function* () {
45
- const initial = (yield* (yield* StateStore).getState(key)) ?? createInitialState(initialCutoff);
46
- return yield* Ref.make(initial);
47
- });
48
- const runEntity = (entity, initialCutoff) => Effect.gen(function* () {
49
- const stateRef = yield* makeStateRef(entity.name, initialCutoff);
50
- const seenRef = yield* Ref.make(/* @__PURE__ */ new Set());
51
- const liveStream = resolveLiveStream(entity.live);
52
- const tagLive = (batch) => ({
53
- source: "live",
54
- batch
55
- });
56
- const updateSeen = (rows) => Ref.update(seenRef, (seen) => {
57
- const next = new Set(seen);
58
- for (const row of rows) {
59
- const key = String(row[entity.primaryKey]);
60
- next.add(key);
61
- }
62
- return next;
63
- });
64
- const backfillTagged = Stream.mapEffect(entity.backfill, (batch) => Ref.get(seenRef).pipe(Effect.map((seen) => {
65
- const filtered = batch.rows.filter((row) => {
66
- const key = String(row[entity.primaryKey]);
67
- return !seen.has(key);
68
- });
69
- return {
70
- source: "backfill",
71
- batch: {
72
- cursor: batch.cursor,
73
- rows: filtered
74
- }
75
- };
76
- }))).pipe(Stream.tap(({ batch }) => updateSeen(batch.rows)));
77
- if (isWebhookStream(entity.live)) {
78
- const firstBatch = yield* Queue.take(entity.live.queue);
79
- yield* updateSeen(firstBatch.rows);
80
- yield* processTaggedStream(Stream.make({
81
- source: "live",
82
- batch: firstBatch
83
- }), entity.name, entity.transform, stateRef);
84
- const liveTailTagged = Stream.map(liveStream, tagLive).pipe(Stream.tap(({ batch }) => updateSeen(batch.rows)));
85
- yield* processTaggedStream(Stream.merge(liveTailTagged, backfillTagged), entity.name, entity.transform, stateRef);
86
- return;
87
- }
88
- const liveTagged = Stream.map(liveStream, tagLive).pipe(Stream.tap(({ batch }) => updateSeen(batch.rows)));
89
- yield* processTaggedStream(Stream.merge(liveTagged, backfillTagged), entity.name, entity.transform, stateRef);
90
- });
91
- const runEvent = (event, initialCutoff) => Effect.gen(function* () {
92
- const stateRef = yield* makeStateRef(event.name, initialCutoff);
93
- const liveStream = resolveLiveStream(event.live);
94
- if (event.backfill) yield* processTaggedStream(Stream.map(event.backfill, (batch) => ({
95
- source: "backfill",
96
- batch
97
- })), event.name, event.transform, stateRef);
98
- yield* processTaggedStream(Stream.map(liveStream, (batch) => ({
99
- source: "live",
100
- batch
101
- })), event.name, event.transform, stateRef);
102
- });
103
- const updateState = (state, source, cursor) => source === "live" ? {
104
- ...state,
105
- live: {
106
- ...state.live,
107
- current: cursor
108
- }
109
- } : {
110
- ...state,
111
- backfill: {
112
- ...state.backfill,
113
- current: cursor
114
- }
18
+ const Connector = { define: (definition) => definition };
19
+ const Resource = {
20
+ entity: (definition) => definition,
21
+ webhook: (definition) => definition,
22
+ upsert: (row) => ({
23
+ op: "upsert",
24
+ row
25
+ }),
26
+ delete: (options) => ({
27
+ op: "delete",
28
+ key: options.key,
29
+ version: options.version
30
+ })
115
31
  };
116
- const resolveLiveStream = (source) => isWebhookStream(source) ? source.stream : source;
117
- const isWebhookStream = (source) => typeof source === "object" && source !== null && "queue" in source && "stream" in source;
118
- const processTaggedStream = (stream, name, transform, stateRef) => Stream.runForEach(stream, ({ source, batch }) => Effect.gen(function* () {
119
- const rows = transform ? yield* Effect.forEach(batch.rows, transform) : batch.rows;
120
- yield* (yield* Publisher).publish({
121
- name,
122
- batch: {
123
- cursor: batch.cursor,
124
- rows
32
+ const Fetch = {
33
+ page: (definition) => definition,
34
+ changes: (definition) => definition
35
+ };
36
+ const decodeFailure = (kind, value) => new ConnectorError({ message: `Invalid ${kind} cursor value: ${String(value)}` });
37
+ const Cursor = {
38
+ string: () => ({
39
+ kind: "string",
40
+ decode: (value) => typeof value === "string" ? Effect.succeed(value) : Effect.fail(decodeFailure("string", value))
41
+ }),
42
+ number: () => ({
43
+ kind: "number",
44
+ decode: (value) => typeof value === "number" && Number.isFinite(value) ? Effect.succeed(value) : Effect.fail(decodeFailure("number", value))
45
+ }),
46
+ isoDateTime: () => ({
47
+ kind: "isoDateTime",
48
+ decode: (value) => {
49
+ if (typeof value !== "string" || Number.isNaN(Date.parse(value))) return Effect.fail(decodeFailure("ISO datetime", value));
50
+ return Effect.succeed(value);
125
51
  }
126
- });
127
- const nextState = yield* Ref.updateAndGet(stateRef, (state) => updateState(state, source, batch.cursor));
128
- yield* (yield* StateStore).setState(name, nextState);
129
- }));
130
-
131
- //#endregion
132
- //#region src/publisher/wings.ts
133
- const buildRecordBatch = (rows) => {
134
- const [batch] = Wings.tableFromJSON(Array.from(rows)).batches;
135
- if (!batch) throw new ConnectorError({ message: "No rows to publish" });
136
- return batch;
52
+ }),
53
+ nowIsoDateTime: Effect.map(DateTime.now, DateTime.formatIso)
137
54
  };
138
- const WingsPublisherLayer = (config) => Layer.effect(Publisher)(Effect.gen(function* () {
139
- const entries = /* @__PURE__ */ new Map();
140
- for (const def of [...config.connector.entities, ...config.connector.events]) {
141
- const topic = config.topics[def.name];
142
- if (!topic) return yield* Effect.fail(new ConnectorError({ message: `Missing topic for ${def.name}` }));
143
- const partitionIndex = topic.partitionKey !== void 0 ? topic.schema.fields.findIndex((field) => field.id === topic.partitionKey) : void 0;
144
- const partitionField = partitionIndex !== void 0 && partitionIndex >= 0 ? topic.schema.fields[partitionIndex]?.name : void 0;
145
- const publisher = yield* Wings.WingsClient.publisher({
146
- topic,
147
- partitionValue: config.partitionValues?.[def.name]
148
- }).pipe(Effect.mapError((error) => new ConnectorError({
149
- message: error.message,
150
- cause: error
151
- })));
152
- entries.set(def.name, {
153
- publisher,
154
- partitionField,
155
- partitionValue: config.partitionValues?.[def.name]
156
- });
157
- }
158
- return { publish: ({ name, batch }) => Effect.gen(function* () {
159
- const entry = entries.get(name);
160
- if (!entry) return yield* Effect.fail(new ConnectorError({ message: `Unknown stream ${name}` }));
161
- if (batch.rows.length === 0) return { success: true };
162
- const recordBatch = buildRecordBatch(batch.rows);
163
- const result = yield* entry.publisher.push({
164
- batch: recordBatch,
165
- partitionValue: entry.partitionValue
166
- }).pipe(Effect.mapError((error) => new ConnectorError({
167
- message: error.message,
168
- cause: error
169
- })));
170
- return { success: !!(result.result && result.result.$case === "accepted") };
171
- }) };
172
- }));
173
-
174
- //#endregion
175
- //#region src/streams/pull-stream.ts
176
- const makePullStream = (options) => Stream.unfold({
177
- cursor: options.initialCursor,
178
- done: false
179
- }, (state) => Effect.gen(function* () {
180
- if (state.done) return;
181
- let nextCursor = state.cursor;
182
- while (true) {
183
- const page = yield* options.fetchPage(nextCursor);
184
- if (page.rows.length > 0) return [{
185
- cursor: page.cursor,
186
- rows: page.rows
187
- }, page.hasMore ? {
188
- cursor: page.cursor,
189
- done: false
190
- } : {
191
- cursor: void 0,
192
- done: true
193
- }];
194
- if (!page.hasMore) return;
195
- nextCursor = page.cursor;
196
- }
197
- }));
198
-
199
- //#endregion
200
- //#region src/streams/webhook-queue.ts
201
- const makeWebhookQueue = (options) => Effect.gen(function* () {
202
- const capacity = options?.capacity ?? 1024;
203
- const queue = yield* Queue.bounded(capacity);
204
- return {
205
- queue,
206
- stream: Stream.fromQueue(queue)
207
- };
208
- });
209
-
210
- //#endregion
211
- //#region src/webhook/server.ts
212
- var InvalidWebhookPayloadError = class extends Data.TaggedError("InvalidWebhookPayloadError") {};
213
- const makeHandler = (route) => Effect.gen(function* () {
214
- const request = yield* HttpServerRequest.HttpServerRequest;
215
- const rawBuffer = yield* request.arrayBuffer.pipe(Effect.mapError(() => new InvalidWebhookPayloadError({ message: "Failed to read request body" })));
216
- const rawBody = new Uint8Array(rawBuffer);
217
- const rawText = new TextDecoder().decode(rawBody);
218
- const rawJson = yield* Effect.try({
219
- try: () => JSON.parse(rawText),
220
- catch: () => new InvalidWebhookPayloadError({ message: "Invalid JSON body" })
221
- });
222
- const payload = yield* Schema.decodeUnknownEffect(route.schema)(rawJson).pipe(Effect.mapError(() => new InvalidWebhookPayloadError({ message: "Invalid webhook payload" })));
223
- yield* route.handle(payload, request, rawBody);
224
- return HttpServerResponse.jsonUnsafe({ ok: true });
225
- }).pipe(Effect.catchTag("InvalidWebhookPayloadError", () => Effect.succeed(HttpServerResponse.jsonUnsafe({
226
- ok: false,
227
- error: "Invalid webhook payload"
228
- }, { status: 400 }))), Effect.catchCause((cause) => Effect.logWarning(`Webhook handler error: ${Cause.pretty(cause)}`).pipe(Effect.andThen(Effect.succeed(HttpServerResponse.jsonUnsafe({
229
- ok: false,
230
- error: "Webhook handler failed"
231
- }, { status: 500 }))))));
232
- const buildWebhookRouter = (routes) => HttpRouter.addAll(routes.map((route) => HttpRouter.route("POST", route.path, makeHandler(route))));
233
55
 
234
56
  //#endregion
235
- export { ConnectorError, Publisher, StateStore, StateStoreInMemory, WingsPublisherLayer, buildWebhookRouter, defineConnector, defineEntity, defineEvent, makePullStream, makeWebhookQueue, runConnector };
57
+ export { auth_exports as Auth, Connector, connector_app_exports as ConnectorApp, ConnectorError, Cursor, Fetch, ingestion_exports as Ingestion, manifest_exports as Manifest, metrics_exports as Metrics, publisher_exports as Publisher, Resource, runtime_config_exports as RuntimeConfig, state_store_exports as StateStore, status_exports as Status, telemetry_exports as Telemetry, webhook_exports as Webhook };
236
58
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["nextCursor: Cursor | undefined"],"sources":["../src/core/builder.ts","../src/core/errors.ts","../src/publisher/service.ts","../src/ingestion/state-store.ts","../src/ingestion/engine.ts","../src/publisher/wings.ts","../src/streams/pull-stream.ts","../src/streams/webhook-queue.ts","../src/webhook/server.ts"],"sourcesContent":["import type { ConnectorDefinition, EntityDefinition, EntitySchema, EventDefinition } from \"./types\";\n\nexport const defineConnector = <\n const Entities extends ReadonlyArray<EntityDefinition<EntitySchema>>,\n const Events extends ReadonlyArray<EventDefinition<EntitySchema>>,\n>(\n definition: ConnectorDefinition<Entities, Events>,\n) => definition;\n\nexport const defineEntity = <S extends EntitySchema>(definition: EntityDefinition<S>) => definition;\n\nexport const defineEvent = <S extends EntitySchema>(definition: EventDefinition<S>) => definition;\n","import { Data } from \"effect\";\n\nexport class ConnectorError extends Data.TaggedError(\"ConnectorError\")<{\n readonly message: string;\n readonly cause?: unknown;\n}> {}\n","import type * as Effect from \"effect/Effect\";\n\nimport { Context } from \"effect\";\n\nimport type { ConnectorError } from \"../core/errors\";\nimport type { Batch } from \"../core/types\";\n\nexport type PublishAck = {\n readonly success: boolean;\n};\n\nexport class Publisher extends Context.Service<\n Publisher,\n {\n readonly publish: (options: {\n readonly name: string;\n readonly batch: Batch<Record<string, unknown>>;\n }) => Effect.Effect<PublishAck, ConnectorError>;\n }\n>()(\"Publisher\") {}\n","import { Context, Effect, Layer, Ref } from \"effect\";\n\nimport type { ConnectorError } from \"../core/errors\";\nimport type { Cursor, IngestionState } from \"../core/types\";\n\nexport class StateStore extends Context.Service<\n StateStore,\n {\n readonly getState: (\n key: string,\n ) => Effect.Effect<IngestionState<Cursor> | undefined, ConnectorError>;\n readonly setState: (\n key: string,\n state: IngestionState<Cursor>,\n ) => Effect.Effect<void, ConnectorError>;\n }\n>()(\"StateStore\") {}\n\nexport const StateStoreInMemory = Layer.effect(StateStore)(\n Effect.gen(function* () {\n const ref = yield* Ref.make(new Map<string, IngestionState<Cursor>>());\n\n return {\n getState: (key) => Effect.map(Ref.get(ref), (map) => map.get(key)),\n setState: (key, state) =>\n Effect.map(\n Ref.update(ref, (map) => {\n const next = new Map(map);\n next.set(key, state);\n return next;\n }),\n () => undefined,\n ),\n };\n }),\n);\n","import { Effect, Queue, Ref, Stream } from \"effect\";\n\nimport type { ConnectorError } from \"../core/errors\";\nimport type {\n Batch,\n ConnectorDefinition,\n Cursor,\n EntityDefinition,\n EntityRow,\n EntitySchema,\n EventDefinition,\n IngestionState,\n LiveSource,\n Transform,\n WebhookStream,\n} from \"../core/types\";\n\nimport { Publisher } from \"../publisher/service\";\nimport { StateStore } from \"./state-store\";\n\ntype TaggedBatch<T> = {\n readonly source: \"live\" | \"backfill\";\n readonly batch: Batch<T>;\n};\n\nexport const runConnector = (\n connector: ConnectorDefinition,\n initialCutoff: Cursor,\n): Effect.Effect<void, ConnectorError, StateStore | Publisher> =>\n Effect.gen(function* () {\n // Start ingestion for every entity and event in parallel.\n const entityRuns = connector.entities.map((entity) => runEntity(entity, initialCutoff));\n const eventRuns = connector.events.map((event) => runEvent(event, initialCutoff));\n // main runner\n yield* Effect.all([...entityRuns, ...eventRuns], {\n concurrency: \"unbounded\",\n });\n });\n\nconst createInitialState = (cutoff: Cursor): IngestionState<Cursor> => ({\n backfill: { cutoff },\n live: { cutoff },\n});\n\nconst makeStateRef = (\n key: string,\n initialCutoff: Cursor,\n): Effect.Effect<Ref.Ref<IngestionState<Cursor>>, ConnectorError, StateStore> =>\n Effect.gen(function* () {\n // Load persisted state or initialize a new one for this stream.\n const store = yield* StateStore;\n const existing = yield* store.getState(key);\n const initial = existing ?? createInitialState(initialCutoff);\n return yield* Ref.make(initial);\n });\n\nconst runEntity = <S extends EntitySchema>(\n entity: EntityDefinition<S>,\n initialCutoff: Cursor,\n): Effect.Effect<void, ConnectorError, StateStore | Publisher> =>\n Effect.gen(function* () {\n type Row = EntityRow<S>;\n const stateRef = yield* makeStateRef(entity.name, initialCutoff);\n // Tracks which primary keys have already been emitted.\n const seenRef = yield* Ref.make(new Set<string>());\n\n const liveStream = resolveLiveStream(entity.live);\n const tagLive = (batch: Batch<Row>) => ({\n source: \"live\" as const,\n batch,\n });\n const updateSeen = (rows: ReadonlyArray<Row>) =>\n Ref.update(seenRef, (seen) => {\n const next = new Set(seen);\n for (const row of rows) {\n const key = String(row[entity.primaryKey]);\n next.add(key);\n }\n return next;\n });\n\n // Entities are upserts, so live and backfill can overlap. We keep an in-memory\n // seen set (primary keys) so backfill does not re-emit rows already observed live.\n const backfillTagged = Stream.mapEffect(entity.backfill, (batch) =>\n Ref.get(seenRef).pipe(\n Effect.map((seen) => {\n const filtered = batch.rows.filter((row) => {\n const key = String(row[entity.primaryKey]);\n return !seen.has(key);\n });\n return {\n source: \"backfill\" as const,\n batch: { cursor: batch.cursor, rows: filtered },\n };\n }),\n ),\n ).pipe(Stream.tap(({ batch }) => updateSeen(batch.rows)));\n\n // For webhook live sources, we wait for the first live batch before starting\n // backfill. That first batch establishes the cutoff timestamp and seeds the\n // seen set so backfill can de-dupe correctly.\n // Queue-backed streams are single-consumer; splitting with take/drop would\n // consume and discard elements. Take the first element directly from the\n // queue, then let Stream.fromQueue continue from element #2.\n if (isWebhookStream(entity.live)) {\n const firstBatch = yield* Queue.take(entity.live.queue);\n yield* updateSeen(firstBatch.rows);\n yield* processTaggedStream(\n Stream.make({ source: \"live\" as const, batch: firstBatch }),\n entity.name,\n entity.transform,\n stateRef,\n );\n\n // liveStream is Stream.fromQueue on the same queue, continues from element #2.\n const liveTailTagged = Stream.map(liveStream, tagLive).pipe(\n Stream.tap(({ batch }) => updateSeen(batch.rows)),\n );\n const merged = Stream.merge(liveTailTagged, backfillTagged);\n yield* processTaggedStream(merged, entity.name, entity.transform, stateRef);\n return;\n }\n\n // For pull-based live sources, we can merge immediately because there is no\n // webhook cutoff gating and the live stream is not queue-backed.\n const liveTagged = Stream.map(liveStream, tagLive).pipe(\n Stream.tap(({ batch }) => updateSeen(batch.rows)),\n );\n\n const merged = Stream.merge(liveTagged, backfillTagged);\n yield* processTaggedStream(merged, entity.name, entity.transform, stateRef);\n });\n\nconst runEvent = <S extends EntitySchema>(\n event: EventDefinition<S>,\n initialCutoff: Cursor,\n): Effect.Effect<void, ConnectorError, StateStore | Publisher> =>\n Effect.gen(function* () {\n type Row = EntityRow<S>;\n const stateRef = yield* makeStateRef(event.name, initialCutoff);\n const liveStream = resolveLiveStream<Row>(event.live);\n\n // Events must backfill first to preserve ordering.\n if (event.backfill) {\n const backfillTagged = Stream.map(event.backfill, (batch) => ({\n source: \"backfill\" as const,\n batch,\n }));\n yield* processTaggedStream(backfillTagged, event.name, event.transform, stateRef);\n }\n\n const liveTagged = Stream.map(liveStream, (batch) => ({\n source: \"live\" as const,\n batch,\n }));\n yield* processTaggedStream(liveTagged, event.name, event.transform, stateRef);\n });\n\nconst updateState = (\n state: IngestionState<Cursor>,\n source: \"live\" | \"backfill\",\n cursor: Cursor,\n): IngestionState<Cursor> =>\n source === \"live\"\n ? { ...state, live: { ...state.live, current: cursor } }\n : { ...state, backfill: { ...state.backfill, current: cursor } };\n\nconst resolveLiveStream = <T>(source: LiveSource<T>): Stream.Stream<Batch<T>, ConnectorError> =>\n isWebhookStream(source) ? source.stream : source;\n\nconst isWebhookStream = <T>(source: LiveSource<T>): source is WebhookStream<T> =>\n typeof source === \"object\" && source !== null && \"queue\" in source && \"stream\" in source;\n\nconst processTaggedStream = <T extends Record<string, unknown>>(\n stream: Stream.Stream<TaggedBatch<T>, ConnectorError>,\n name: string,\n transform: Transform<T> | undefined,\n stateRef: Ref.Ref<IngestionState<Cursor>>,\n): Effect.Effect<void, ConnectorError, StateStore | Publisher> =>\n Stream.runForEach(stream, ({ source, batch }) =>\n Effect.gen(function* () {\n // Optional per-row transformation.\n const rows = transform ? yield* Effect.forEach(batch.rows, transform) : batch.rows;\n\n // Publish before updating cursor state.\n const publisher = yield* Publisher;\n yield* publisher.publish({\n name,\n batch: {\n cursor: batch.cursor,\n rows,\n },\n });\n\n // Persist state only after publish succeeds.\n const nextState = yield* Ref.updateAndGet(stateRef, (state) =>\n updateState(state, source, batch.cursor),\n );\n\n const store = yield* StateStore;\n yield* store.setState(name, nextState);\n }),\n );\n","import type { PartitionValue } from \"@useairfoil/wings\";\n\nimport * as Wings from \"@useairfoil/wings\";\nimport { Effect, Layer } from \"effect\";\n\nimport type { ConnectorDefinition } from \"../core/types\";\n\nimport { ConnectorError } from \"../core/errors\";\nimport { Publisher } from \"./service\";\n\ntype Rows = Record<string, unknown>;\n\nexport type WingsPublisherConfig = {\n readonly connector: ConnectorDefinition;\n /** Map of entity/event name to Wings topic. */\n readonly topics: Record<string, Wings.Cluster.Topic.Topic>;\n /** per-stream partition value (key is entity/event name). */\n readonly partitionValues?: Record<string, PartitionValue>;\n};\n\n/** Publisher entry for a single entity/event. */\ntype PublisherEntry = {\n /** Wings publisher for the entity/event. */\n readonly publisher: Wings.WingsClient.Publisher;\n /** Partition field name (if any). */\n readonly partitionField?: string;\n /** Partition value (if any). */\n readonly partitionValue?: PartitionValue;\n};\n\nconst buildRecordBatch = (rows: ReadonlyArray<Rows>) => {\n // Convert JSON rows into an Arrow RecordBatch for Wings.\n const table = Wings.tableFromJSON(Array.from(rows));\n const [batch] = table.batches;\n\n if (!batch) {\n throw new ConnectorError({ message: \"No rows to publish\" });\n }\n\n return batch;\n};\n\nexport const WingsPublisherLayer = (\n config: WingsPublisherConfig,\n): Layer.Layer<Publisher, ConnectorError, Wings.WingsClient.WingsClient> =>\n Layer.effect(Publisher)(\n Effect.gen(function* () {\n const entries = new Map<string, PublisherEntry>();\n\n // create and store a wings publisher for each entity/event.\n for (const def of [...config.connector.entities, ...config.connector.events]) {\n const topic = config.topics[def.name];\n if (!topic) {\n return yield* Effect.fail(\n new ConnectorError({ message: `Missing topic for ${def.name}` }),\n );\n }\n\n const partitionIndex =\n topic.partitionKey !== undefined\n ? topic.schema.fields.findIndex((field) => field.id === topic.partitionKey)\n : undefined;\n\n const partitionField =\n partitionIndex !== undefined && partitionIndex >= 0\n ? topic.schema.fields[partitionIndex]?.name\n : undefined;\n\n const publisher = yield* Wings.WingsClient.publisher({\n topic,\n partitionValue: config.partitionValues?.[def.name],\n }).pipe(\n Effect.mapError((error) => new ConnectorError({ message: error.message, cause: error })),\n );\n\n entries.set(def.name, {\n publisher,\n partitionField,\n partitionValue: config.partitionValues?.[def.name],\n });\n }\n\n return {\n publish: ({ name, batch }) =>\n Effect.gen(function* () {\n const entry = entries.get(name);\n if (!entry) {\n return yield* Effect.fail(new ConnectorError({ message: `Unknown stream ${name}` }));\n }\n\n if (batch.rows.length === 0) {\n return { success: true };\n }\n\n const recordBatch = buildRecordBatch(batch.rows);\n const result = yield* entry.publisher\n .push({\n batch: recordBatch,\n partitionValue: entry.partitionValue,\n })\n .pipe(\n Effect.mapError(\n (error) =>\n new ConnectorError({\n message: error.message,\n cause: error,\n }),\n ),\n );\n\n return {\n success: !!(result.result && result.result.$case === \"accepted\"),\n };\n }),\n };\n }),\n );\n","import { Effect, Stream } from \"effect\";\n\nimport type { ConnectorError } from \"../core/errors\";\nimport type { Batch, Cursor } from \"../core/types\";\n\nexport type PullPage<T> = {\n readonly cursor: Cursor;\n readonly rows: ReadonlyArray<T>;\n readonly hasMore: boolean;\n};\n\nexport type PullFetcher<T, R = never> = (\n cursor: Cursor | undefined,\n) => Effect.Effect<PullPage<T>, ConnectorError, R>;\n\nexport type PullStreamOptions<T, R = never> = {\n readonly initialCursor?: Cursor;\n readonly fetchPage: PullFetcher<T, R>;\n};\n\nexport const makePullStream = <T, R = never>(\n options: PullStreamOptions<T, R>,\n): Stream.Stream<Batch<T>, ConnectorError, R> =>\n Stream.unfold({ cursor: options.initialCursor, done: false }, (state) =>\n Effect.gen(function* () {\n if (state.done) {\n return undefined;\n }\n\n let nextCursor: Cursor | undefined = state.cursor;\n\n while (true) {\n const page = yield* options.fetchPage(nextCursor);\n\n if (page.rows.length > 0) {\n const batch: Batch<T> = {\n cursor: page.cursor,\n rows: page.rows,\n };\n\n return [\n batch,\n page.hasMore ? { cursor: page.cursor, done: false } : { cursor: undefined, done: true },\n ] as const;\n }\n\n if (!page.hasMore) {\n return undefined;\n }\n\n nextCursor = page.cursor;\n }\n }),\n );\n","import { Effect, Queue, Stream } from \"effect\";\n\nimport type { ConnectorError } from \"../core/errors\";\nimport type { Batch, WebhookStream } from \"../core/types\";\n\nexport const makeWebhookQueue = <T>(options?: {\n readonly capacity?: number;\n}): Effect.Effect<WebhookStream<T>, never> =>\n Effect.gen(function* () {\n const capacity = options?.capacity ?? 1024;\n const queue = yield* Queue.bounded<Batch<T>>(capacity);\n const stream: Stream.Stream<Batch<T>, ConnectorError> = Stream.fromQueue(queue);\n return { queue, stream };\n });\n","import { Cause, Data, Effect, Schema } from \"effect\";\nimport { HttpRouter, HttpServerRequest, HttpServerResponse } from \"effect/unstable/http\";\n\nimport type { WebhookRoute } from \"./types\";\n\nclass InvalidWebhookPayloadError extends Data.TaggedError(\"InvalidWebhookPayloadError\")<{\n readonly message: string;\n}> {}\n\nconst makeHandler = <TPayload>(route: WebhookRoute<TPayload>) =>\n Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const rawBuffer = yield* request.arrayBuffer.pipe(\n Effect.mapError(\n () =>\n new InvalidWebhookPayloadError({\n message: \"Failed to read request body\",\n }),\n ),\n );\n const rawBody = new Uint8Array(rawBuffer);\n const rawText = new TextDecoder().decode(rawBody);\n const rawJson = yield* Effect.try({\n try: () => JSON.parse(rawText) as unknown,\n catch: () => new InvalidWebhookPayloadError({ message: \"Invalid JSON body\" }),\n });\n const payload = yield* Schema.decodeUnknownEffect(route.schema)(rawJson).pipe(\n Effect.mapError(\n () =>\n new InvalidWebhookPayloadError({\n message: \"Invalid webhook payload\",\n }),\n ),\n );\n yield* route.handle(payload, request, rawBody);\n // unsafeJson serializes synchronously — no Effect, no HttpBodyError\n return HttpServerResponse.jsonUnsafe({ ok: true });\n }).pipe(\n Effect.catchTag(\"InvalidWebhookPayloadError\", () =>\n Effect.succeed(\n HttpServerResponse.jsonUnsafe(\n { ok: false, error: \"Invalid webhook payload\" },\n { status: 400 },\n ),\n ),\n ),\n Effect.catchCause((cause) =>\n Effect.logWarning(`Webhook handler error: ${Cause.pretty(cause)}`).pipe(\n Effect.andThen(\n Effect.succeed(\n HttpServerResponse.jsonUnsafe(\n { ok: false, error: \"Webhook handler failed\" },\n { status: 500 },\n ),\n ),\n ),\n ),\n ),\n );\n\nexport const buildWebhookRouter = <TPayload>(routes: ReadonlyArray<WebhookRoute<TPayload>>) =>\n HttpRouter.addAll(\n routes.map((route) => HttpRouter.route(\"POST\", route.path, makeHandler(route))),\n );\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/core/builder.ts"],"sourcesContent":["import { DateTime, Effect } from \"effect\";\n\nimport type {\n ChangesFetch,\n ConnectorDefinition,\n Cursor as CursorType,\n DeleteValue,\n PageFetch,\n ResourceDefinition,\n ResourceMutation,\n ResourceSchema,\n WebhookHandler,\n} from \"./types\";\n\nimport { ConnectorError } from \"../errors\";\n\nexport const Connector = {\n define: <const Resources extends ReadonlyArray<ResourceDefinition>>(\n definition: ConnectorDefinition<Resources>,\n ) => definition,\n};\n\nexport const Resource = {\n entity: <\n S extends ResourceSchema,\n Payload = never,\n R = never,\n const Name extends string = string,\n >(\n definition: ResourceDefinition<S, Payload, R, Name>,\n ): ResourceDefinition<S, Payload, R, Name> => definition,\n\n webhook: <Payload, Row extends object = never>(definition: WebhookHandler<Row, Payload>) =>\n definition,\n\n upsert: <Row extends object>(row: Row): ResourceMutation<Row> => ({ op: \"upsert\", row }),\n\n delete: (options: {\n readonly key: DeleteValue;\n readonly version: DeleteValue;\n }): ResourceMutation<never> => ({\n op: \"delete\",\n key: options.key,\n version: options.version,\n }),\n};\n\nexport const Fetch = {\n page: <Row extends object = never, R = never>(definition: PageFetch<Row, R>) => definition,\n changes: <Row extends object = never, R = never>(definition: ChangesFetch<Row, R>) => definition,\n};\n\nconst decodeFailure = (kind: string, value: unknown) =>\n new ConnectorError({ message: `Invalid ${kind} cursor value: ${String(value)}` });\n\nexport const Cursor = {\n string: (): CursorType.Definition<string> => ({\n kind: \"string\",\n decode: (value) =>\n typeof value === \"string\"\n ? Effect.succeed(value)\n : Effect.fail(decodeFailure(\"string\", value)),\n }),\n\n number: (): CursorType.Definition<number> => ({\n kind: \"number\",\n decode: (value) =>\n typeof value === \"number\" && Number.isFinite(value)\n ? Effect.succeed(value)\n : Effect.fail(decodeFailure(\"number\", value)),\n }),\n\n isoDateTime: (): CursorType.Definition<string> => ({\n kind: \"isoDateTime\",\n decode: (value) => {\n if (typeof value !== \"string\" || Number.isNaN(Date.parse(value))) {\n return Effect.fail(decodeFailure(\"ISO datetime\", value));\n }\n return Effect.succeed(value);\n },\n }),\n\n nowIsoDateTime: Effect.map(DateTime.now, DateTime.formatIso),\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AAgBA,MAAa,YAAY,EACvB,SACE,eACG,YACN;AAED,MAAa,WAAW;CACtB,SAME,eAC4C;CAE9C,UAA+C,eAC7C;CAEF,SAA6B,SAAqC;EAAE,IAAI;EAAU;EAAK;CAEvF,SAAS,aAGuB;EAC9B,IAAI;EACJ,KAAK,QAAQ;EACb,SAAS,QAAQ;EAClB;CACF;AAED,MAAa,QAAQ;CACnB,OAA8C,eAAkC;CAChF,UAAiD,eAAqC;CACvF;AAED,MAAM,iBAAiB,MAAc,UACnC,IAAI,eAAe,EAAE,SAAS,WAAW,KAAK,iBAAiB,OAAO,MAAM,IAAI,CAAC;AAEnF,MAAa,SAAS;CACpB,eAA8C;EAC5C,MAAM;EACN,SAAS,UACP,OAAO,UAAU,WACb,OAAO,QAAQ,MAAM,GACrB,OAAO,KAAK,cAAc,UAAU,MAAM,CAAC;EAClD;CAED,eAA8C;EAC5C,MAAM;EACN,SAAS,UACP,OAAO,UAAU,YAAY,OAAO,SAAS,MAAM,GAC/C,OAAO,QAAQ,MAAM,GACrB,OAAO,KAAK,cAAc,UAAU,MAAM,CAAC;EAClD;CAED,oBAAmD;EACjD,MAAM;EACN,SAAS,UAAU;AACjB,OAAI,OAAO,UAAU,YAAY,OAAO,MAAM,KAAK,MAAM,MAAM,CAAC,CAC9D,QAAO,OAAO,KAAK,cAAc,gBAAgB,MAAM,CAAC;AAE1D,UAAO,OAAO,QAAQ,MAAM;;EAE/B;CAED,gBAAgB,OAAO,IAAI,SAAS,KAAK,SAAS,UAAU;CAC7D"}
@@ -0,0 +1,6 @@
1
+ import "../index-D54ySAKn.js";
2
+ import "../types-B35J5YPu.js";
3
+ import "../service-IE5qaf9x.js";
4
+ import "../index-Coh_SkBs.js";
5
+ import { n as RunOptions, r as run } from "../index-J42AZaXU.js";
6
+ export { RunOptions, run };
@@ -0,0 +1,10 @@
1
+ import "../errors-DHWylqRZ.js";
2
+ import "../metrics-CWPy2Aqc.js";
3
+ import "../constants-BRhIX_rz.js";
4
+ import "../service-DuFiQwid.js";
5
+ import "../server-CXTQ7odl.js";
6
+ import "../state-store-yl6LiTQ0.js";
7
+ import "../status-B-oZBc33.js";
8
+ import { n as run } from "../ingestion-BQo4cbGw.js";
9
+
10
+ export { run };
@@ -0,0 +1,200 @@
1
+ import { t as __export } from "./chunk-Bp6m_JJh.js";
2
+ import { d as setLastSuccessTimestamp, f as setSyncState, p as setWebhookQueueDepth } from "./metrics-CWPy2Aqc.js";
3
+ import { a as SpanName, n as Attr, s as annotateError } from "./service-DuFiQwid.js";
4
+ import { n as router, r as publishBatch, t as WebhookQueue } from "./server-CXTQ7odl.js";
5
+ import { o as StateStore } from "./state-store-yl6LiTQ0.js";
6
+ import { c as deriveSyncState, l as normalizeCursor, s as statusRoute } from "./status-B-oZBc33.js";
7
+ import { DateTime, Effect, Layer, Queue } from "effect";
8
+ import { HttpRouter, HttpServerResponse } from "effect/unstable/http";
9
+ import * as Observability from "effect/unstable/observability";
10
+
11
+ //#region src/ingestion/engine.ts
12
+ function run(connector, options) {
13
+ const queueLayer = makeWebhookQueueLayer(connector.name);
14
+ const runtimeLayer = options?.webhook ? Layer.mergeAll(queueLayer, makeWebhookServerLayer(connector, options.webhook).pipe(Layer.provide(queueLayer))) : Layer.empty;
15
+ return Effect.gen(function* () {
16
+ const initialCutoff = options?.initialCutoff ?? (yield* DateTime.now.pipe(Effect.map(DateTime.formatIso)));
17
+ yield* Effect.logInfo("Connector started").pipe(Effect.annotateLogs({
18
+ [Attr.connectorName]: connector.name,
19
+ resources: connector.resources.length
20
+ }));
21
+ const sourceRun = runIngestion(connector, initialCutoff);
22
+ const webhookRun = options?.webhook ? runWebhookRuntime(connector, options.webhook.routes) : Effect.void;
23
+ return yield* Effect.all([sourceRun, webhookRun], { concurrency: "unbounded" }).pipe(Effect.asVoid);
24
+ }).pipe(Effect.provide(runtimeLayer));
25
+ }
26
+ const makeWebhookQueueLayer = (connector) => Layer.effect(WebhookQueue)(Effect.gen(function* () {
27
+ const queue = yield* Queue.bounded(1024);
28
+ yield* setWebhookQueueDepth(connector, 0);
29
+ return WebhookQueue.of({ queue });
30
+ }));
31
+ const runWebhookRuntime = (connector, routes) => {
32
+ const hasAfterEnqueue = routes.some((route) => route.ackMode === "after-enqueue");
33
+ return Effect.all([hasAfterEnqueue ? runWebhookQueueConsumer(connector) : Effect.void, Effect.never], { concurrency: "unbounded" }).pipe(Effect.asVoid);
34
+ };
35
+ const runWebhookQueueConsumer = (connector) => Effect.gen(function* () {
36
+ const queue = yield* WebhookQueue;
37
+ while (true) {
38
+ const batch = yield* Queue.take(queue.queue);
39
+ const depth = yield* Queue.size(queue.queue);
40
+ yield* setWebhookQueueDepth(connector.name, depth);
41
+ yield* publishBatch({
42
+ connector: connector.name,
43
+ resource: batch.resource,
44
+ source: "webhook",
45
+ batch: batch.batch
46
+ });
47
+ }
48
+ });
49
+ const makeWebhookServerLayer = (connector, options) => {
50
+ const healthPath = options.healthPath ?? "/health";
51
+ const metricsPath = options.metricsPath === void 0 ? "/metrics" : options.metricsPath;
52
+ const statusPath = options.statusPath === void 0 ? "/status" : options.statusPath;
53
+ const app = Layer.mergeAll(router(options.routes, { connectorName: connector.name }), HttpRouter.add("GET", healthPath, Effect.succeed(HttpServerResponse.text("ok"))), ...metricsPath === false ? [] : [Observability.PrometheusMetrics.layerHttp({ path: metricsPath })], ...statusPath === false ? [] : [statusRoute(connector, statusPath)]);
54
+ return HttpRouter.serve(app, { disableLogger: options.disableHttpLogger ?? true });
55
+ };
56
+ const runIngestion = (connector, initialCutoff) => Effect.forEach(connector.resources, (resource) => runResourceSources(connector, resource, initialCutoff), { concurrency: "unbounded" }).pipe(Effect.asVoid);
57
+ const initializeResourceState = (existing, initialCutoff) => ({
58
+ ...existing,
59
+ changes: existing?.changes ?? { cursor: initialCutoff },
60
+ backfill: existing?.backfill ?? {
61
+ cutoff: initialCutoff,
62
+ completed: false
63
+ }
64
+ });
65
+ const getInitializedState = Effect.fnUntraced(function* (resource, initialCutoff) {
66
+ return initializeResourceState(yield* (yield* StateStore).getResourceState(resource), initialCutoff);
67
+ });
68
+ const runResourceSources = (connector, resource, initialCutoff) => {
69
+ const runs = [resource.backfill ? isolateSourceFailure(runBackfill(connector, resource, initialCutoff), connector, resource, "backfill") : Effect.void, resource.changes ? isolateSourceFailure(runChanges(connector, resource, initialCutoff), connector, resource, "changes") : Effect.void];
70
+ return initializeRuntimeStatus(connector, resource, initialCutoff).pipe(Effect.andThen(Effect.all(runs, { concurrency: "unbounded" })), Effect.asVoid, Effect.catch(() => setSyncState({
71
+ connector: connector.name,
72
+ resource: resource.name
73
+ }, "error").pipe(Effect.andThen(Effect.logError("Connector resource initialization failed")), Effect.annotateLogs({
74
+ [Attr.connectorName]: connector.name,
75
+ resource: resource.name
76
+ }), Effect.andThen(Effect.never))));
77
+ };
78
+ const recordResourceError = (connector, resource, source, operation) => (effect) => effect.pipe(Effect.tapError(() => setSyncState({
79
+ connector: connector.name,
80
+ resource: resource.name
81
+ }, "error").pipe(Effect.andThen(StateStore.pipe(Effect.flatMap((store) => store.setResourceError(resource.name, source, operation)))))));
82
+ const isolateSourceFailure = (effect, connector, resource, source) => effect.pipe(Effect.catch(() => Effect.logError("Connector source parked").pipe(Effect.annotateLogs({
83
+ [Attr.connectorName]: connector.name,
84
+ resource: resource.name,
85
+ source
86
+ }), Effect.andThen(Effect.never))));
87
+ const refreshRuntimeStatus = Effect.fnUntraced(function* (connector, resource, initialCutoff) {
88
+ const state = yield* getInitializedState(resource.name, initialCutoff);
89
+ yield* setSyncState({
90
+ connector: connector.name,
91
+ resource: resource.name
92
+ }, deriveSyncState(resource, state));
93
+ return state;
94
+ });
95
+ const initializeRuntimeStatus = Effect.fnUntraced(function* (connector, resource, initialCutoff) {
96
+ const state = yield* getInitializedState(resource.name, initialCutoff);
97
+ yield* setSyncState({
98
+ connector: connector.name,
99
+ resource: resource.name
100
+ }, deriveSyncState(resource, state));
101
+ if (resource.backfill) yield* setLastSuccessTimestamp({
102
+ connector: connector.name,
103
+ resource: resource.name,
104
+ source: "backfill"
105
+ }, state.backfill?.lastSuccessAt);
106
+ if (resource.changes) yield* setLastSuccessTimestamp({
107
+ connector: connector.name,
108
+ resource: resource.name,
109
+ source: "changes"
110
+ }, state.changes?.lastSuccessAt);
111
+ });
112
+ const runBackfill = Effect.fnUntraced(function* (connector, resource, initialCutoff) {
113
+ if (!resource.backfill) return;
114
+ const store = yield* StateStore;
115
+ let state = yield* getInitializedState(resource.name, initialCutoff);
116
+ if (state.backfill?.completed === true) {
117
+ yield* store.clearResourceError(resource.name, "backfill").pipe(recordResourceError(connector, resource, "backfill", "checkpoint"));
118
+ yield* refreshRuntimeStatus(connector, resource, initialCutoff);
119
+ return;
120
+ }
121
+ while (state.backfill?.completed !== true) {
122
+ const backfill = state.backfill ?? {
123
+ cutoff: initialCutoff,
124
+ completed: false
125
+ };
126
+ const clearsVisibleError = state.lastError?.source === "backfill";
127
+ const page = yield* resource.backfill.fetch({
128
+ pageCursor: backfill.pageCursor,
129
+ cutoff: backfill.cutoff
130
+ }).pipe(recordResourceError(connector, resource, "backfill", "fetch"));
131
+ yield* publishBatch({
132
+ connector: connector.name,
133
+ resource: resource.name,
134
+ source: "backfill",
135
+ batch: {
136
+ cursor: page.nextPageCursor ?? backfill.cutoff,
137
+ mutations: page.mutations
138
+ }
139
+ }).pipe(recordResourceError(connector, resource, "backfill", "publish"));
140
+ const lastSuccessAt = yield* DateTime.now.pipe(Effect.map(DateTime.formatIso));
141
+ state = {
142
+ ...state,
143
+ backfill: {
144
+ cutoff: backfill.cutoff,
145
+ pageCursor: page.nextPageCursor,
146
+ completed: !page.hasMore,
147
+ lastSuccessAt
148
+ }
149
+ };
150
+ yield* Effect.withSpan(store.setBackfillState(resource.name, {
151
+ cutoff: normalizeCursor(backfill.cutoff),
152
+ pageCursor: page.nextPageCursor === void 0 ? void 0 : normalizeCursor(page.nextPageCursor),
153
+ completed: !page.hasMore,
154
+ lastSuccessAt
155
+ }).pipe(Effect.andThen(setLastSuccessTimestamp({
156
+ connector: connector.name,
157
+ resource: resource.name,
158
+ source: "backfill"
159
+ }, lastSuccessAt)), Effect.andThen(store.clearResourceError(resource.name, "backfill")), Effect.tapError((error) => annotateError("state_set", error))), SpanName.stateSet, { attributes: { [Attr.stateKey]: resource.name } }).pipe(recordResourceError(connector, resource, "backfill", "checkpoint"));
160
+ if (clearsVisibleError || !page.hasMore) state = yield* refreshRuntimeStatus(connector, resource, initialCutoff);
161
+ }
162
+ });
163
+ const runChanges = Effect.fnUntraced(function* (connector, resource, initialCutoff) {
164
+ if (!resource.changes) return;
165
+ const store = yield* StateStore;
166
+ while (true) {
167
+ const state = yield* getInitializedState(resource.name, initialCutoff);
168
+ const cursor = state.changes?.cursor ?? initialCutoff;
169
+ const clearsVisibleError = state.lastError?.source === "changes";
170
+ const page = yield* resource.changes.fetch({ cursor }).pipe(recordResourceError(connector, resource, "changes", "fetch"));
171
+ yield* publishBatch({
172
+ connector: connector.name,
173
+ resource: resource.name,
174
+ source: "changes",
175
+ batch: {
176
+ cursor: page.cursor,
177
+ mutations: page.mutations
178
+ }
179
+ }).pipe(recordResourceError(connector, resource, "changes", "publish"));
180
+ const lastSuccessAt = yield* DateTime.now.pipe(Effect.map(DateTime.formatIso));
181
+ yield* Effect.withSpan(store.setChangesState(resource.name, {
182
+ cursor: normalizeCursor(page.cursor),
183
+ lastSuccessAt
184
+ }).pipe(Effect.andThen(setLastSuccessTimestamp({
185
+ connector: connector.name,
186
+ resource: resource.name,
187
+ source: "changes"
188
+ }, lastSuccessAt)), Effect.andThen(store.clearResourceError(resource.name, "changes")), Effect.tapError((error) => annotateError("state_set", error))), SpanName.stateSet, { attributes: { [Attr.stateKey]: resource.name } }).pipe(recordResourceError(connector, resource, "changes", "checkpoint"));
189
+ if (clearsVisibleError) yield* refreshRuntimeStatus(connector, resource, initialCutoff);
190
+ yield* Effect.sleep(resource.changes.interval ?? "1 minute");
191
+ }
192
+ });
193
+
194
+ //#endregion
195
+ //#region src/ingestion/index.ts
196
+ var ingestion_exports = /* @__PURE__ */ __export({ run: () => run });
197
+
198
+ //#endregion
199
+ export { run as n, ingestion_exports as t };
200
+ //# sourceMappingURL=ingestion-BQo4cbGw.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingestion-BQo4cbGw.js","names":["Metrics.setWebhookQueueDepth","healthPath: HttpRouter.PathInput","Metrics.setSyncState","Metrics.setLastSuccessTimestamp"],"sources":["../src/ingestion/engine.ts","../src/ingestion/index.ts"],"sourcesContent":["import { DateTime, Effect, Layer, Queue } from \"effect\";\nimport { HttpRouter, type HttpServer, HttpServerResponse } from \"effect/unstable/http\";\nimport * as Observability from \"effect/unstable/observability\";\n\nimport type {\n ConnectorDefinition,\n Cursor,\n ResourceDefinition,\n ResourceState,\n WebhookRoute,\n} from \"../core/types\";\nimport type { StateOperation, StateSource } from \"../state-store/schema\";\n\nimport { ConnectorError } from \"../errors\";\nimport * as Metrics from \"../metrics\";\nimport { publishBatch } from \"../publisher/instrumented\";\nimport { Publisher } from \"../publisher/service\";\nimport { StateStore } from \"../state-store\";\nimport { deriveSyncState, normalizeCursor } from \"../state-store/state\";\nimport { statusRoute } from \"../status\";\nimport { Attr, SpanName, annotateError } from \"../telemetry\";\nimport { router, WebhookQueue, type QueuedWebhookBatch } from \"../webhook/server\";\n\ntype RunBaseOptions = {\n readonly initialCutoff?: Cursor.Value;\n};\n\nexport type RunOptions = RunBaseOptions & {\n readonly webhook?: {\n readonly routes: ReadonlyArray<WebhookRoute>;\n readonly healthPath?: HttpRouter.PathInput;\n readonly metricsPath?: HttpRouter.PathInput | false;\n readonly statusPath?: HttpRouter.PathInput | false;\n readonly disableHttpLogger?: boolean;\n };\n};\n\ntype RunNoWebhookOptions = RunBaseOptions & {\n readonly webhook?: undefined;\n};\n\ntype RunWebhookOptions = RunOptions & {\n readonly webhook: NonNullable<RunOptions[\"webhook\"]>;\n};\n\n/**\n * Runs all connector ingestion loops. Callers provide the Publisher and\n * StateStore layers; webhook mode additionally requires an HttpServer layer.\n */\nexport function run<const Resources extends ReadonlyArray<ResourceDefinition>>(\n connector: ConnectorDefinition<Resources>,\n options?: RunNoWebhookOptions,\n): Effect.Effect<void, ConnectorError, StateStore | Publisher>;\nexport function run<const Resources extends ReadonlyArray<ResourceDefinition>>(\n connector: ConnectorDefinition<Resources>,\n options: RunWebhookOptions,\n): Effect.Effect<void, ConnectorError, StateStore | Publisher | HttpServer.HttpServer>;\nexport function run(connector: ConnectorDefinition, options?: RunOptions) {\n const queueLayer = makeWebhookQueueLayer(connector.name);\n const runtimeLayer = options?.webhook\n ? Layer.mergeAll(\n queueLayer,\n makeWebhookServerLayer(connector, options.webhook).pipe(Layer.provide(queueLayer)),\n )\n : Layer.empty;\n\n return Effect.gen(function* () {\n const initialCutoff =\n options?.initialCutoff ?? (yield* DateTime.now.pipe(Effect.map(DateTime.formatIso)));\n yield* Effect.logInfo(\"Connector started\").pipe(\n Effect.annotateLogs({\n [Attr.connectorName]: connector.name,\n resources: connector.resources.length,\n }),\n );\n\n const sourceRun = runIngestion(connector, initialCutoff);\n const webhookRun = options?.webhook\n ? runWebhookRuntime(connector, options.webhook.routes)\n : Effect.void;\n\n return yield* Effect.all([sourceRun, webhookRun], { concurrency: \"unbounded\" }).pipe(\n Effect.asVoid,\n );\n }).pipe(Effect.provide(runtimeLayer));\n}\n\nconst makeWebhookQueueLayer = (connector: string): Layer.Layer<WebhookQueue> =>\n Layer.effect(WebhookQueue)(\n Effect.gen(function* () {\n const queue = yield* Queue.bounded<QueuedWebhookBatch>(1024);\n yield* Metrics.setWebhookQueueDepth(connector, 0);\n return WebhookQueue.of({ queue });\n }),\n );\n\nconst runWebhookRuntime = (connector: ConnectorDefinition, routes: ReadonlyArray<WebhookRoute>) => {\n const hasAfterEnqueue = routes.some((route) => route.ackMode === \"after-enqueue\");\n return Effect.all(\n [hasAfterEnqueue ? runWebhookQueueConsumer(connector) : Effect.void, Effect.never],\n {\n concurrency: \"unbounded\",\n },\n ).pipe(Effect.asVoid);\n};\n\nconst runWebhookQueueConsumer = (connector: ConnectorDefinition) =>\n Effect.gen(function* () {\n const queue = yield* WebhookQueue;\n while (true) {\n const batch = yield* Queue.take(queue.queue);\n const depth = yield* Queue.size(queue.queue);\n yield* Metrics.setWebhookQueueDepth(connector.name, depth);\n yield* publishBatch({\n connector: connector.name,\n resource: batch.resource,\n source: \"webhook\",\n batch: batch.batch,\n });\n }\n });\n\nconst makeWebhookServerLayer = (\n connector: ConnectorDefinition,\n options: {\n readonly routes: ReadonlyArray<WebhookRoute>;\n readonly healthPath?: HttpRouter.PathInput;\n readonly metricsPath?: HttpRouter.PathInput | false;\n readonly statusPath?: HttpRouter.PathInput | false;\n readonly disableHttpLogger?: boolean;\n },\n): Layer.Layer<never, never, HttpServer.HttpServer> => {\n const healthPath: HttpRouter.PathInput = options.healthPath ?? \"/health\";\n const metricsPath = options.metricsPath === undefined ? \"/metrics\" : options.metricsPath;\n const statusPath = options.statusPath === undefined ? \"/status\" : options.statusPath;\n const app = Layer.mergeAll(\n router(options.routes, { connectorName: connector.name }),\n HttpRouter.add(\"GET\", healthPath, Effect.succeed(HttpServerResponse.text(\"ok\"))),\n ...(metricsPath === false\n ? []\n : [Observability.PrometheusMetrics.layerHttp({ path: metricsPath })]),\n ...(statusPath === false ? [] : [statusRoute(connector, statusPath)]),\n );\n\n return HttpRouter.serve(app, {\n disableLogger: options.disableHttpLogger ?? true,\n });\n};\n\nconst runIngestion = (\n connector: ConnectorDefinition,\n initialCutoff: Cursor.Value,\n): Effect.Effect<void, ConnectorError, StateStore | Publisher> =>\n Effect.forEach(\n connector.resources,\n (resource) => runResourceSources(connector, resource, initialCutoff),\n {\n concurrency: \"unbounded\",\n },\n ).pipe(Effect.asVoid);\n\nconst initializeResourceState = (\n existing: ResourceState | undefined,\n initialCutoff: Cursor.Value,\n): ResourceState => ({\n ...existing,\n changes: existing?.changes ?? { cursor: initialCutoff },\n backfill: existing?.backfill ?? {\n cutoff: initialCutoff,\n completed: false,\n },\n});\n\nconst getInitializedState = Effect.fnUntraced(function* (\n resource: string,\n initialCutoff: Cursor.Value,\n) {\n const store = yield* StateStore;\n const existing = yield* store.getResourceState(resource);\n return initializeResourceState(existing, initialCutoff);\n});\n\nconst runResourceSources = (\n connector: ConnectorDefinition,\n resource: ResourceDefinition,\n initialCutoff: Cursor.Value,\n) => {\n const runs = [\n resource.backfill\n ? isolateSourceFailure(\n runBackfill(connector, resource, initialCutoff),\n connector,\n resource,\n \"backfill\",\n )\n : Effect.void,\n resource.changes\n ? isolateSourceFailure(\n runChanges(connector, resource, initialCutoff),\n connector,\n resource,\n \"changes\",\n )\n : Effect.void,\n ];\n\n return initializeRuntimeStatus(connector, resource, initialCutoff).pipe(\n Effect.andThen(Effect.all(runs, { concurrency: \"unbounded\" })),\n Effect.asVoid,\n Effect.catch(() =>\n Metrics.setSyncState({ connector: connector.name, resource: resource.name }, \"error\").pipe(\n Effect.andThen(Effect.logError(\"Connector resource initialization failed\")),\n Effect.annotateLogs({\n [Attr.connectorName]: connector.name,\n resource: resource.name,\n }),\n Effect.andThen(Effect.never),\n ),\n ),\n );\n};\n\nconst recordResourceError =\n (\n connector: ConnectorDefinition,\n resource: ResourceDefinition,\n source: StateSource,\n operation: StateOperation,\n ) =>\n <A, R>(\n effect: Effect.Effect<A, ConnectorError, R>,\n ): Effect.Effect<A, ConnectorError, R | StateStore> =>\n effect.pipe(\n Effect.tapError(() =>\n Metrics.setSyncState({ connector: connector.name, resource: resource.name }, \"error\").pipe(\n Effect.andThen(\n StateStore.pipe(\n Effect.flatMap((store) => store.setResourceError(resource.name, source, operation)),\n ),\n ),\n ),\n ),\n );\n\nconst isolateSourceFailure = <A, R>(\n effect: Effect.Effect<A, ConnectorError, R>,\n connector: ConnectorDefinition,\n resource: ResourceDefinition,\n source: StateSource,\n): Effect.Effect<A, never, R> =>\n // Only typed source errors are isolated. Defects and interruption still stop the runtime.\n effect.pipe(\n Effect.catch(() =>\n Effect.logError(\"Connector source parked\").pipe(\n Effect.annotateLogs({\n [Attr.connectorName]: connector.name,\n resource: resource.name,\n source,\n }),\n Effect.andThen(Effect.never),\n ),\n ),\n );\n\nconst refreshRuntimeStatus = Effect.fnUntraced(function* (\n connector: ConnectorDefinition,\n resource: ResourceDefinition,\n initialCutoff: Cursor.Value,\n) {\n const state = yield* getInitializedState(resource.name, initialCutoff);\n yield* Metrics.setSyncState(\n { connector: connector.name, resource: resource.name },\n deriveSyncState(resource, state),\n );\n return state;\n});\n\nconst initializeRuntimeStatus = Effect.fnUntraced(function* (\n connector: ConnectorDefinition,\n resource: ResourceDefinition,\n initialCutoff: Cursor.Value,\n) {\n const state = yield* getInitializedState(resource.name, initialCutoff);\n yield* Metrics.setSyncState(\n { connector: connector.name, resource: resource.name },\n deriveSyncState(resource, state),\n );\n if (resource.backfill) {\n yield* Metrics.setLastSuccessTimestamp(\n { connector: connector.name, resource: resource.name, source: \"backfill\" },\n state.backfill?.lastSuccessAt,\n );\n }\n if (resource.changes) {\n yield* Metrics.setLastSuccessTimestamp(\n { connector: connector.name, resource: resource.name, source: \"changes\" },\n state.changes?.lastSuccessAt,\n );\n }\n});\n\nconst runBackfill = Effect.fnUntraced(function* (\n connector: ConnectorDefinition,\n resource: ResourceDefinition,\n initialCutoff: Cursor.Value,\n) {\n if (!resource.backfill) return;\n const store = yield* StateStore;\n let state = yield* getInitializedState(resource.name, initialCutoff);\n\n if (state.backfill?.completed === true) {\n // The checkpoint may have committed before the previous process cleared\n // its error marker. It may also be hidden by a newer changes error.\n yield* store\n .clearResourceError(resource.name, \"backfill\")\n .pipe(recordResourceError(connector, resource, \"backfill\", \"checkpoint\"));\n yield* refreshRuntimeStatus(connector, resource, initialCutoff);\n return;\n }\n\n while (state.backfill?.completed !== true) {\n const backfill = state.backfill ?? { cutoff: initialCutoff, completed: false };\n const clearsVisibleError = state.lastError?.source === \"backfill\";\n const page = yield* resource.backfill\n .fetch({\n pageCursor: backfill.pageCursor,\n cutoff: backfill.cutoff,\n })\n .pipe(recordResourceError(connector, resource, \"backfill\", \"fetch\"));\n\n yield* publishBatch({\n connector: connector.name,\n resource: resource.name,\n source: \"backfill\",\n batch: {\n cursor: page.nextPageCursor ?? backfill.cutoff,\n mutations: page.mutations,\n },\n }).pipe(recordResourceError(connector, resource, \"backfill\", \"publish\"));\n\n const lastSuccessAt = yield* DateTime.now.pipe(Effect.map(DateTime.formatIso));\n state = {\n ...state,\n backfill: {\n cutoff: backfill.cutoff,\n pageCursor: page.nextPageCursor,\n completed: !page.hasMore,\n lastSuccessAt,\n },\n };\n\n // Checkpoint only after publish succeeds so a crash cannot skip an\n // unacknowledged page; replay therefore remains at-least-once.\n yield* Effect.withSpan(\n store\n .setBackfillState(resource.name, {\n cutoff: normalizeCursor(backfill.cutoff),\n pageCursor:\n page.nextPageCursor === undefined ? undefined : normalizeCursor(page.nextPageCursor),\n completed: !page.hasMore,\n lastSuccessAt,\n })\n .pipe(\n Effect.andThen(\n Metrics.setLastSuccessTimestamp(\n { connector: connector.name, resource: resource.name, source: \"backfill\" },\n lastSuccessAt,\n ),\n ),\n Effect.andThen(store.clearResourceError(resource.name, \"backfill\")),\n Effect.tapError((error) => annotateError(\"state_set\", error)),\n ),\n SpanName.stateSet,\n { attributes: { [Attr.stateKey]: resource.name } },\n ).pipe(recordResourceError(connector, resource, \"backfill\", \"checkpoint\"));\n\n // Completion or clearing an error can change the resource-wide sync state.\n if (clearsVisibleError || !page.hasMore) {\n state = yield* refreshRuntimeStatus(connector, resource, initialCutoff);\n }\n }\n});\n\nconst runChanges = Effect.fnUntraced(function* (\n connector: ConnectorDefinition,\n resource: ResourceDefinition,\n initialCutoff: Cursor.Value,\n) {\n if (!resource.changes) return;\n const store = yield* StateStore;\n\n while (true) {\n const state = yield* getInitializedState(resource.name, initialCutoff);\n const cursor = state.changes?.cursor ?? initialCutoff;\n const clearsVisibleError = state.lastError?.source === \"changes\";\n const page = yield* resource.changes\n .fetch({ cursor })\n .pipe(recordResourceError(connector, resource, \"changes\", \"fetch\"));\n\n yield* publishBatch({\n connector: connector.name,\n resource: resource.name,\n source: \"changes\",\n batch: {\n cursor: page.cursor,\n mutations: page.mutations,\n },\n }).pipe(recordResourceError(connector, resource, \"changes\", \"publish\"));\n\n const lastSuccessAt = yield* DateTime.now.pipe(Effect.map(DateTime.formatIso));\n // Advance the durable cursor only after Wings accepts the page.\n yield* Effect.withSpan(\n store\n .setChangesState(resource.name, {\n cursor: normalizeCursor(page.cursor),\n lastSuccessAt,\n })\n .pipe(\n Effect.andThen(\n Metrics.setLastSuccessTimestamp(\n { connector: connector.name, resource: resource.name, source: \"changes\" },\n lastSuccessAt,\n ),\n ),\n Effect.andThen(store.clearResourceError(resource.name, \"changes\")),\n Effect.tapError((error) => annotateError(\"state_set\", error)),\n ),\n SpanName.stateSet,\n { attributes: { [Attr.stateKey]: resource.name } },\n ).pipe(recordResourceError(connector, resource, \"changes\", \"checkpoint\"));\n\n if (clearsVisibleError) {\n yield* refreshRuntimeStatus(connector, resource, initialCutoff);\n }\n\n yield* Effect.sleep(resource.changes.interval ?? \"1 minute\");\n }\n});\n","export { run, type RunOptions } from \"./engine\";\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { _ as number, a as configSchema, b as select, c as ConnectorManifest, d as ConfigField, f as ConfigFieldSpec, g as define$1, h as boolean, i as RuntimeConfigDocument, l as ResourceCapability, m as ConnectorConfigDef, n as ConnectorConfigSchema, o as decodeConfig, p as ConfigValuesOf, r as ConnectorConfigValues, s as toRuntimeDocument, u as define, v as optional, x as string, y as secret } from "../index-CBT-JX-T.js";
2
+ export { ConfigField, ConfigFieldSpec, ConfigValuesOf, ConnectorConfigDef, ConnectorConfigSchema, ConnectorConfigValues, ConnectorManifest, ResourceCapability, RuntimeConfigDocument, boolean, configSchema, decodeConfig, define, define$1 as defineConfig, number, optional, secret, select, string, toRuntimeDocument };
@@ -0,0 +1,4 @@
1
+ import "../constants-BRhIX_rz.js";
2
+ import { a as define, c as number, d as select, f as string, i as toRuntimeDocument, l as optional, n as configSchema, o as boolean, r as decodeConfig, s as define$1, u as secret } from "../manifest-ByE6YPG0.js";
3
+
4
+ export { boolean, configSchema, decodeConfig, define, define$1 as defineConfig, number, optional, secret, select, string, toRuntimeDocument };