@useairfoil/connector-kit 0.5.0 → 0.5.2
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/METRICS.md +78 -0
- package/README.md +190 -21
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/index.js +3 -0
- package/dist/auth-DvU_CZ7C.js +42 -0
- package/dist/auth-DvU_CZ7C.js.map +1 -0
- package/dist/connector-app/index.d.ts +14 -9
- package/dist/connector-app/index.js +9 -6
- package/dist/connector-app-d7Z-SZLQ.js +64 -0
- package/dist/connector-app-d7Z-SZLQ.js.map +1 -0
- package/dist/constants-BRhIX_rz.js +27 -0
- package/dist/constants-BRhIX_rz.js.map +1 -0
- package/dist/errors/index.d.ts +1 -1
- package/dist/{index-DK9YcR6W.d.ts → index-Byr0Lf0C.d.ts} +4 -4
- package/dist/index-CBT-JX-T.d.ts +173 -0
- package/dist/index-CTHVudvQ.d.ts +41 -0
- package/dist/index-Coh_SkBs.d.ts +95 -0
- package/dist/index-Cq2IzY7T.d.ts +80 -0
- package/dist/{index-DyuT2jzP.d.ts → index-D54ySAKn.d.ts} +1 -1
- package/dist/{index-CQJsFJs3.d.ts → index-DKWIh6M1.d.ts} +7 -5
- package/dist/{index-BlhJCpEk.d.ts → index-DtJX_tic.d.ts} +39 -10
- package/dist/{index-8KnVpoZp.d.ts → index-J42AZaXU.d.ts} +11 -5
- package/dist/index-o1-Z3n7V.d.ts +55 -0
- package/dist/index.d.ts +14 -9
- package/dist/index.js +14 -8
- package/dist/index.js.map +1 -1
- package/dist/ingestion/index.d.ts +5 -5
- package/dist/ingestion/index.js +7 -4
- package/dist/ingestion-BQo4cbGw.js +200 -0
- package/dist/ingestion-BQo4cbGw.js.map +1 -0
- package/dist/manifest/index.d.ts +2 -0
- package/dist/manifest/index.js +4 -0
- package/dist/manifest-ByE6YPG0.js +221 -0
- package/dist/manifest-ByE6YPG0.js.map +1 -0
- package/dist/metrics-CVAk1ALR.d.ts +53 -0
- package/dist/metrics-CWPy2Aqc.js +96 -0
- package/dist/metrics-CWPy2Aqc.js.map +1 -0
- package/dist/metrics.d.ts +4 -0
- package/dist/metrics.js +3 -0
- package/dist/publisher/index.d.ts +4 -4
- package/dist/publisher/index.js +3 -2
- package/dist/{publisher-DusgYvaV.js → publisher-BYT3RLVO.js} +2 -2
- package/dist/{publisher-DusgYvaV.js.map → publisher-BYT3RLVO.js.map} +1 -1
- package/dist/runtime-config/index.d.ts +2 -0
- package/dist/runtime-config/index.js +4 -0
- package/dist/runtime-config-BpB-d_bF.js +51 -0
- package/dist/runtime-config-BpB-d_bF.js.map +1 -0
- package/dist/server-CXTQ7odl.js +183 -0
- package/dist/server-CXTQ7odl.js.map +1 -0
- package/dist/{telemetry-p0dE5Mdu.js → service-DuFiQwid.js} +30 -10
- package/dist/service-DuFiQwid.js.map +1 -0
- package/dist/{service-B7g_lHMI.d.ts → service-IE5qaf9x.d.ts} +3 -3
- package/dist/state-store/index.d.ts +3 -4
- package/dist/state-store/index.js +4 -2
- package/dist/state-store-yl6LiTQ0.js +198 -0
- package/dist/state-store-yl6LiTQ0.js.map +1 -0
- package/dist/status/index.d.ts +5 -0
- package/dist/status/index.js +6 -0
- package/dist/status-B-oZBc33.js +84 -0
- package/dist/status-B-oZBc33.js.map +1 -0
- package/dist/{types-ZNrA-Dn0.d.ts → types-B35J5YPu.d.ts} +20 -6
- package/dist/webhook/index.d.ts +5 -4
- package/dist/webhook/index.js +5 -3
- package/dist/{webhook-BSA-tXtU.js → webhook-DI-Afxkg.js} +2 -2
- package/dist/{webhook-BSA-tXtU.js.map → webhook-DI-Afxkg.js.map} +1 -1
- package/package.json +36 -7
- package/src/auth/index.ts +1 -0
- package/src/auth/token-cache.ts +54 -0
- package/src/connector-app/check.ts +76 -0
- package/src/connector-app/index.ts +1 -0
- package/src/connector-app/start.ts +4 -0
- package/src/core/builder.ts +8 -3
- package/src/core/types.ts +20 -2
- package/src/index.ts +5 -0
- package/src/ingestion/engine.ts +246 -133
- package/src/manifest/config.ts +232 -0
- package/src/manifest/index.ts +14 -0
- package/src/manifest/manifest.ts +31 -0
- package/src/manifest/number-schema.ts +17 -0
- package/src/manifest/schema.ts +160 -0
- package/src/metrics.ts +128 -0
- package/src/publisher/instrumented.ts +68 -0
- package/src/runtime-config/constants.ts +27 -0
- package/src/runtime-config/error.ts +13 -0
- package/src/runtime-config/index.ts +3 -0
- package/src/runtime-config/layer.ts +56 -0
- package/src/state-store/index.ts +2 -1
- package/src/state-store/keys.ts +22 -0
- package/src/state-store/layer-key-value-store.ts +132 -0
- package/src/state-store/layer-memory.ts +11 -0
- package/src/state-store/layer-sql.ts +50 -0
- package/src/state-store/schema.ts +57 -0
- package/src/state-store/service.ts +39 -6
- package/src/state-store/state.ts +17 -0
- package/src/status/index.ts +2 -0
- package/src/status/route.ts +77 -0
- package/src/status/types.ts +33 -0
- package/src/telemetry.ts +46 -4
- package/src/webhook/server.ts +54 -19
- package/dist/connector-app-CvqbfN6R.js +0 -32
- package/dist/connector-app-CvqbfN6R.js.map +0 -1
- package/dist/index-DaNW63Mv.d.ts +0 -21
- package/dist/ingestion-CMCroK7Y.js +0 -171
- package/dist/ingestion-CMCroK7Y.js.map +0 -1
- package/dist/server-DAeFWT4o.js +0 -113
- package/dist/server-DAeFWT4o.js.map +0 -1
- package/dist/state-store-DG_4QpWG.js +0 -31
- package/dist/state-store-DG_4QpWG.js.map +0 -1
- package/dist/telemetry-p0dE5Mdu.js.map +0 -1
- package/src/state-store/memory.ts +0 -25
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import { t as
|
|
2
|
-
import {
|
|
3
|
-
import { a as
|
|
4
|
-
import "./
|
|
5
|
-
import
|
|
6
|
-
import { t as index_d_exports$
|
|
7
|
-
import { t as index_d_exports$2 } from "./index-
|
|
8
|
-
import { t as index_d_exports$
|
|
9
|
-
|
|
1
|
+
import { t as index_d_exports } from "./index-CTHVudvQ.js";
|
|
2
|
+
import { t as ConnectorError } from "./index-D54ySAKn.js";
|
|
3
|
+
import { S as WebhookRouteContext, _ as ResourceState, a as FetchChangesResult, b as WebhookHandler, c as ResourceBatch, d as ResourceMutation, g as ResourceSchema, h as ResourceRows, l as ResourceDefinition, m as ResourceRow, n as ConnectorDefinition, o as FetchPageResult, p as ResourcePayload, r as Cursor$1, s as PageFetch, t as ChangesFetch, u as ResourceField, x as WebhookRoute, y as WebhookAckMode } from "./types-B35J5YPu.js";
|
|
4
|
+
import { a as telemetry_d_exports, c as Fetch, l as Resource, o as Connector, s as Cursor, t as index_d_exports$1 } from "./index-DtJX_tic.js";
|
|
5
|
+
import "./service-IE5qaf9x.js";
|
|
6
|
+
import { t as index_d_exports$6 } from "./index-Coh_SkBs.js";
|
|
7
|
+
import { t as index_d_exports$2 } from "./index-J42AZaXU.js";
|
|
8
|
+
import { t as index_d_exports$3 } from "./index-CBT-JX-T.js";
|
|
9
|
+
import { g as metrics_d_exports } from "./metrics-CVAk1ALR.js";
|
|
10
|
+
import { t as index_d_exports$4 } from "./index-Byr0Lf0C.js";
|
|
11
|
+
import { t as index_d_exports$5 } from "./index-o1-Z3n7V.js";
|
|
12
|
+
import { t as index_d_exports$7 } from "./index-Cq2IzY7T.js";
|
|
13
|
+
import { t as index_d_exports$8 } from "./index-DKWIh6M1.js";
|
|
14
|
+
export { index_d_exports as Auth, ChangesFetch, Connector, index_d_exports$1 as ConnectorApp, ConnectorDefinition, ConnectorError, Cursor, Cursor$1 as CursorTypes, Fetch, FetchChangesResult, FetchPageResult, index_d_exports$2 as Ingestion, index_d_exports$3 as Manifest, metrics_d_exports as Metrics, PageFetch, index_d_exports$4 as Publisher, Resource, ResourceBatch, ResourceDefinition, ResourceField, ResourceMutation, ResourcePayload, ResourceRow, ResourceRows, ResourceSchema, ResourceState, index_d_exports$5 as RuntimeConfig, index_d_exports$6 as StateStore, index_d_exports$7 as Status, telemetry_d_exports as Telemetry, index_d_exports$8 as Webhook, WebhookAckMode, WebhookHandler, WebhookRoute, WebhookRouteContext };
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { t as ConnectorError } from "./errors-DHWylqRZ.js";
|
|
2
|
-
import {
|
|
3
|
-
import { t as
|
|
4
|
-
import "./
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
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";
|
|
9
15
|
import { DateTime, Effect } from "effect";
|
|
10
16
|
|
|
11
17
|
//#region src/core/builder.ts
|
|
@@ -48,5 +54,5 @@ const Cursor = {
|
|
|
48
54
|
};
|
|
49
55
|
|
|
50
56
|
//#endregion
|
|
51
|
-
export { Connector, connector_app_exports as ConnectorApp, ConnectorError, Cursor, Fetch, ingestion_exports as Ingestion, publisher_exports as Publisher, Resource, state_store_exports as StateStore, telemetry_exports as Telemetry, webhook_exports as Webhook };
|
|
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 };
|
|
52
58
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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:
|
|
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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import "../index-
|
|
2
|
-
import "../types-
|
|
3
|
-
import "../service-
|
|
4
|
-
import "../index-
|
|
5
|
-
import { n as RunOptions, r as run } from "../index-
|
|
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
6
|
export { RunOptions, run };
|
package/dist/ingestion/index.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import "../errors-DHWylqRZ.js";
|
|
2
|
-
import "../
|
|
3
|
-
import "../
|
|
4
|
-
import "../
|
|
5
|
-
import
|
|
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";
|
|
6
9
|
|
|
7
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":";;;;;;;;;;;AAyDA,SAAgB,IAAI,WAAgC,SAAsB;CACxE,MAAM,aAAa,sBAAsB,UAAU,KAAK;CACxD,MAAM,eAAe,SAAS,UAC1B,MAAM,SACJ,YACA,uBAAuB,WAAW,QAAQ,QAAQ,CAAC,KAAK,MAAM,QAAQ,WAAW,CAAC,CACnF,GACD,MAAM;AAEV,QAAO,OAAO,IAAI,aAAa;EAC7B,MAAM,gBACJ,SAAS,kBAAkB,OAAO,SAAS,IAAI,KAAK,OAAO,IAAI,SAAS,UAAU,CAAC;AACrF,SAAO,OAAO,QAAQ,oBAAoB,CAAC,KACzC,OAAO,aAAa;IACjB,KAAK,gBAAgB,UAAU;GAChC,WAAW,UAAU,UAAU;GAChC,CAAC,CACH;EAED,MAAM,YAAY,aAAa,WAAW,cAAc;EACxD,MAAM,aAAa,SAAS,UACxB,kBAAkB,WAAW,QAAQ,QAAQ,OAAO,GACpD,OAAO;AAEX,SAAO,OAAO,OAAO,IAAI,CAAC,WAAW,WAAW,EAAE,EAAE,aAAa,aAAa,CAAC,CAAC,KAC9E,OAAO,OACR;GACD,CAAC,KAAK,OAAO,QAAQ,aAAa,CAAC;;AAGvC,MAAM,yBAAyB,cAC7B,MAAM,OAAO,aAAa,CACxB,OAAO,IAAI,aAAa;CACtB,MAAM,QAAQ,OAAO,MAAM,QAA4B,KAAK;AAC5D,QAAOA,qBAA6B,WAAW,EAAE;AACjD,QAAO,aAAa,GAAG,EAAE,OAAO,CAAC;EACjC,CACH;AAEH,MAAM,qBAAqB,WAAgC,WAAwC;CACjG,MAAM,kBAAkB,OAAO,MAAM,UAAU,MAAM,YAAY,gBAAgB;AACjF,QAAO,OAAO,IACZ,CAAC,kBAAkB,wBAAwB,UAAU,GAAG,OAAO,MAAM,OAAO,MAAM,EAClF,EACE,aAAa,aACd,CACF,CAAC,KAAK,OAAO,OAAO;;AAGvB,MAAM,2BAA2B,cAC/B,OAAO,IAAI,aAAa;CACtB,MAAM,QAAQ,OAAO;AACrB,QAAO,MAAM;EACX,MAAM,QAAQ,OAAO,MAAM,KAAK,MAAM,MAAM;EAC5C,MAAM,QAAQ,OAAO,MAAM,KAAK,MAAM,MAAM;AAC5C,SAAOA,qBAA6B,UAAU,MAAM,MAAM;AAC1D,SAAO,aAAa;GAClB,WAAW,UAAU;GACrB,UAAU,MAAM;GAChB,QAAQ;GACR,OAAO,MAAM;GACd,CAAC;;EAEJ;AAEJ,MAAM,0BACJ,WACA,YAOqD;CACrD,MAAMC,aAAmC,QAAQ,cAAc;CAC/D,MAAM,cAAc,QAAQ,gBAAgB,SAAY,aAAa,QAAQ;CAC7E,MAAM,aAAa,QAAQ,eAAe,SAAY,YAAY,QAAQ;CAC1E,MAAM,MAAM,MAAM,SAChB,OAAO,QAAQ,QAAQ,EAAE,eAAe,UAAU,MAAM,CAAC,EACzD,WAAW,IAAI,OAAO,YAAY,OAAO,QAAQ,mBAAmB,KAAK,KAAK,CAAC,CAAC,EAChF,GAAI,gBAAgB,QAChB,EAAE,GACF,CAAC,cAAc,kBAAkB,UAAU,EAAE,MAAM,aAAa,CAAC,CAAC,EACtE,GAAI,eAAe,QAAQ,EAAE,GAAG,CAAC,YAAY,WAAW,WAAW,CAAC,CACrE;AAED,QAAO,WAAW,MAAM,KAAK,EAC3B,eAAe,QAAQ,qBAAqB,MAC7C,CAAC;;AAGJ,MAAM,gBACJ,WACA,kBAEA,OAAO,QACL,UAAU,YACT,aAAa,mBAAmB,WAAW,UAAU,cAAc,EACpE,EACE,aAAa,aACd,CACF,CAAC,KAAK,OAAO,OAAO;AAEvB,MAAM,2BACJ,UACA,mBACmB;CACnB,GAAG;CACH,SAAS,UAAU,WAAW,EAAE,QAAQ,eAAe;CACvD,UAAU,UAAU,YAAY;EAC9B,QAAQ;EACR,WAAW;EACZ;CACF;AAED,MAAM,sBAAsB,OAAO,WAAW,WAC5C,UACA,eACA;AAGA,QAAO,wBADU,QADH,OAAO,YACS,iBAAiB,SAAS,EACf,cAAc;EACvD;AAEF,MAAM,sBACJ,WACA,UACA,kBACG;CACH,MAAM,OAAO,CACX,SAAS,WACL,qBACE,YAAY,WAAW,UAAU,cAAc,EAC/C,WACA,UACA,WACD,GACD,OAAO,MACX,SAAS,UACL,qBACE,WAAW,WAAW,UAAU,cAAc,EAC9C,WACA,UACA,UACD,GACD,OAAO,KACZ;AAED,QAAO,wBAAwB,WAAW,UAAU,cAAc,CAAC,KACjE,OAAO,QAAQ,OAAO,IAAI,MAAM,EAAE,aAAa,aAAa,CAAC,CAAC,EAC9D,OAAO,QACP,OAAO,YACLC,aAAqB;EAAE,WAAW,UAAU;EAAM,UAAU,SAAS;EAAM,EAAE,QAAQ,CAAC,KACpF,OAAO,QAAQ,OAAO,SAAS,2CAA2C,CAAC,EAC3E,OAAO,aAAa;GACjB,KAAK,gBAAgB,UAAU;EAChC,UAAU,SAAS;EACpB,CAAC,EACF,OAAO,QAAQ,OAAO,MAAM,CAC7B,CACF,CACF;;AAGH,MAAM,uBAEF,WACA,UACA,QACA,eAGA,WAEA,OAAO,KACL,OAAO,eACLA,aAAqB;CAAE,WAAW,UAAU;CAAM,UAAU,SAAS;CAAM,EAAE,QAAQ,CAAC,KACpF,OAAO,QACL,WAAW,KACT,OAAO,SAAS,UAAU,MAAM,iBAAiB,SAAS,MAAM,QAAQ,UAAU,CAAC,CACpF,CACF,CACF,CACF,CACF;AAEL,MAAM,wBACJ,QACA,WACA,UACA,WAGA,OAAO,KACL,OAAO,YACL,OAAO,SAAS,0BAA0B,CAAC,KACzC,OAAO,aAAa;EACjB,KAAK,gBAAgB,UAAU;CAChC,UAAU,SAAS;CACnB;CACD,CAAC,EACF,OAAO,QAAQ,OAAO,MAAM,CAC7B,CACF,CACF;AAEH,MAAM,uBAAuB,OAAO,WAAW,WAC7C,WACA,UACA,eACA;CACA,MAAM,QAAQ,OAAO,oBAAoB,SAAS,MAAM,cAAc;AACtE,QAAOA,aACL;EAAE,WAAW,UAAU;EAAM,UAAU,SAAS;EAAM,EACtD,gBAAgB,UAAU,MAAM,CACjC;AACD,QAAO;EACP;AAEF,MAAM,0BAA0B,OAAO,WAAW,WAChD,WACA,UACA,eACA;CACA,MAAM,QAAQ,OAAO,oBAAoB,SAAS,MAAM,cAAc;AACtE,QAAOA,aACL;EAAE,WAAW,UAAU;EAAM,UAAU,SAAS;EAAM,EACtD,gBAAgB,UAAU,MAAM,CACjC;AACD,KAAI,SAAS,SACX,QAAOC,wBACL;EAAE,WAAW,UAAU;EAAM,UAAU,SAAS;EAAM,QAAQ;EAAY,EAC1E,MAAM,UAAU,cACjB;AAEH,KAAI,SAAS,QACX,QAAOA,wBACL;EAAE,WAAW,UAAU;EAAM,UAAU,SAAS;EAAM,QAAQ;EAAW,EACzE,MAAM,SAAS,cAChB;EAEH;AAEF,MAAM,cAAc,OAAO,WAAW,WACpC,WACA,UACA,eACA;AACA,KAAI,CAAC,SAAS,SAAU;CACxB,MAAM,QAAQ,OAAO;CACrB,IAAI,QAAQ,OAAO,oBAAoB,SAAS,MAAM,cAAc;AAEpE,KAAI,MAAM,UAAU,cAAc,MAAM;AAGtC,SAAO,MACJ,mBAAmB,SAAS,MAAM,WAAW,CAC7C,KAAK,oBAAoB,WAAW,UAAU,YAAY,aAAa,CAAC;AAC3E,SAAO,qBAAqB,WAAW,UAAU,cAAc;AAC/D;;AAGF,QAAO,MAAM,UAAU,cAAc,MAAM;EACzC,MAAM,WAAW,MAAM,YAAY;GAAE,QAAQ;GAAe,WAAW;GAAO;EAC9E,MAAM,qBAAqB,MAAM,WAAW,WAAW;EACvD,MAAM,OAAO,OAAO,SAAS,SAC1B,MAAM;GACL,YAAY,SAAS;GACrB,QAAQ,SAAS;GAClB,CAAC,CACD,KAAK,oBAAoB,WAAW,UAAU,YAAY,QAAQ,CAAC;AAEtE,SAAO,aAAa;GAClB,WAAW,UAAU;GACrB,UAAU,SAAS;GACnB,QAAQ;GACR,OAAO;IACL,QAAQ,KAAK,kBAAkB,SAAS;IACxC,WAAW,KAAK;IACjB;GACF,CAAC,CAAC,KAAK,oBAAoB,WAAW,UAAU,YAAY,UAAU,CAAC;EAExE,MAAM,gBAAgB,OAAO,SAAS,IAAI,KAAK,OAAO,IAAI,SAAS,UAAU,CAAC;AAC9E,UAAQ;GACN,GAAG;GACH,UAAU;IACR,QAAQ,SAAS;IACjB,YAAY,KAAK;IACjB,WAAW,CAAC,KAAK;IACjB;IACD;GACF;AAID,SAAO,OAAO,SACZ,MACG,iBAAiB,SAAS,MAAM;GAC/B,QAAQ,gBAAgB,SAAS,OAAO;GACxC,YACE,KAAK,mBAAmB,SAAY,SAAY,gBAAgB,KAAK,eAAe;GACtF,WAAW,CAAC,KAAK;GACjB;GACD,CAAC,CACD,KACC,OAAO,QACLA,wBACE;GAAE,WAAW,UAAU;GAAM,UAAU,SAAS;GAAM,QAAQ;GAAY,EAC1E,cACD,CACF,EACD,OAAO,QAAQ,MAAM,mBAAmB,SAAS,MAAM,WAAW,CAAC,EACnE,OAAO,UAAU,UAAU,cAAc,aAAa,MAAM,CAAC,CAC9D,EACH,SAAS,UACT,EAAE,YAAY,GAAG,KAAK,WAAW,SAAS,MAAM,EAAE,CACnD,CAAC,KAAK,oBAAoB,WAAW,UAAU,YAAY,aAAa,CAAC;AAG1E,MAAI,sBAAsB,CAAC,KAAK,QAC9B,SAAQ,OAAO,qBAAqB,WAAW,UAAU,cAAc;;EAG3E;AAEF,MAAM,aAAa,OAAO,WAAW,WACnC,WACA,UACA,eACA;AACA,KAAI,CAAC,SAAS,QAAS;CACvB,MAAM,QAAQ,OAAO;AAErB,QAAO,MAAM;EACX,MAAM,QAAQ,OAAO,oBAAoB,SAAS,MAAM,cAAc;EACtE,MAAM,SAAS,MAAM,SAAS,UAAU;EACxC,MAAM,qBAAqB,MAAM,WAAW,WAAW;EACvD,MAAM,OAAO,OAAO,SAAS,QAC1B,MAAM,EAAE,QAAQ,CAAC,CACjB,KAAK,oBAAoB,WAAW,UAAU,WAAW,QAAQ,CAAC;AAErE,SAAO,aAAa;GAClB,WAAW,UAAU;GACrB,UAAU,SAAS;GACnB,QAAQ;GACR,OAAO;IACL,QAAQ,KAAK;IACb,WAAW,KAAK;IACjB;GACF,CAAC,CAAC,KAAK,oBAAoB,WAAW,UAAU,WAAW,UAAU,CAAC;EAEvE,MAAM,gBAAgB,OAAO,SAAS,IAAI,KAAK,OAAO,IAAI,SAAS,UAAU,CAAC;AAE9E,SAAO,OAAO,SACZ,MACG,gBAAgB,SAAS,MAAM;GAC9B,QAAQ,gBAAgB,KAAK,OAAO;GACpC;GACD,CAAC,CACD,KACC,OAAO,QACLA,wBACE;GAAE,WAAW,UAAU;GAAM,UAAU,SAAS;GAAM,QAAQ;GAAW,EACzE,cACD,CACF,EACD,OAAO,QAAQ,MAAM,mBAAmB,SAAS,MAAM,UAAU,CAAC,EAClE,OAAO,UAAU,UAAU,cAAc,aAAa,MAAM,CAAC,CAC9D,EACH,SAAS,UACT,EAAE,YAAY,GAAG,KAAK,WAAW,SAAS,MAAM,EAAE,CACnD,CAAC,KAAK,oBAAoB,WAAW,UAAU,WAAW,aAAa,CAAC;AAEzE,MAAI,mBACF,QAAO,qBAAqB,WAAW,UAAU,cAAc;AAGjE,SAAO,OAAO,MAAM,SAAS,QAAQ,YAAY,WAAW;;EAE9D"}
|
|
@@ -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 };
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { t as __export } from "./chunk-Bp6m_JJh.js";
|
|
2
|
+
import { r as isPlatformRuntimeKey } from "./constants-BRhIX_rz.js";
|
|
3
|
+
import { Config, Option, Predicate, Redacted, Schema, SchemaTransformation, Struct } from "effect";
|
|
4
|
+
|
|
5
|
+
//#region src/manifest/number-schema.ts
|
|
6
|
+
const makeNumberSchema = (constraints) => {
|
|
7
|
+
if (constraints.integer) return constraints.minimum === void 0 ? Schema.Int : Schema.Int.check(Schema.isGreaterThanOrEqualTo(constraints.minimum));
|
|
8
|
+
return constraints.minimum === void 0 ? Schema.Finite : Schema.Finite.check(Schema.isGreaterThanOrEqualTo(constraints.minimum));
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region src/manifest/config.ts
|
|
13
|
+
const configFieldToConfig = Struct.lambda((field) => field.config);
|
|
14
|
+
const withDefault = (config, defaultValue) => defaultValue === void 0 ? config : Config.withDefault(config, defaultValue);
|
|
15
|
+
/** Defines a required non-empty string, optionally with a default. */
|
|
16
|
+
const string = (options) => ({
|
|
17
|
+
config: withDefault(Config.nonEmptyString(options.runtimeKey), options.default),
|
|
18
|
+
spec: {
|
|
19
|
+
runtimeKey: options.runtimeKey,
|
|
20
|
+
type: "string",
|
|
21
|
+
required: options.required ?? true,
|
|
22
|
+
secret: false,
|
|
23
|
+
...options.description !== void 0 ? { description: options.description } : {},
|
|
24
|
+
...options.default !== void 0 ? { default: options.default } : {}
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
/** Defines a finite number with optional integer, minimum, and default constraints. */
|
|
28
|
+
const number = (options) => {
|
|
29
|
+
const schema = makeNumberSchema(options);
|
|
30
|
+
const defaultValue = options.default === void 0 ? void 0 : Schema.decodeUnknownSync(schema)(options.default);
|
|
31
|
+
return {
|
|
32
|
+
config: withDefault(Config.schema(schema, options.runtimeKey), defaultValue),
|
|
33
|
+
spec: {
|
|
34
|
+
runtimeKey: options.runtimeKey,
|
|
35
|
+
type: "number",
|
|
36
|
+
required: options.required ?? true,
|
|
37
|
+
secret: false,
|
|
38
|
+
...options.description !== void 0 ? { description: options.description } : {},
|
|
39
|
+
...defaultValue !== void 0 ? { default: defaultValue } : {},
|
|
40
|
+
...options.integer !== void 0 ? { integer: options.integer } : {},
|
|
41
|
+
...options.minimum !== void 0 ? { minimum: options.minimum } : {}
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
/** Defines a boolean, optionally with a default. */
|
|
46
|
+
const boolean = (options) => ({
|
|
47
|
+
config: withDefault(Config.boolean(options.runtimeKey), options.default),
|
|
48
|
+
spec: {
|
|
49
|
+
runtimeKey: options.runtimeKey,
|
|
50
|
+
type: "boolean",
|
|
51
|
+
required: options.required ?? true,
|
|
52
|
+
secret: false,
|
|
53
|
+
...options.description !== void 0 ? { description: options.description } : {},
|
|
54
|
+
...options.default !== void 0 ? { default: options.default } : {}
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
/** Defines a string literal choice whose values are preserved in the inferred type. */
|
|
58
|
+
const select = (options) => ({
|
|
59
|
+
config: withDefault(Config.literals(options.values, options.runtimeKey), options.default),
|
|
60
|
+
spec: {
|
|
61
|
+
runtimeKey: options.runtimeKey,
|
|
62
|
+
type: "select",
|
|
63
|
+
required: options.required ?? true,
|
|
64
|
+
secret: false,
|
|
65
|
+
values: options.values,
|
|
66
|
+
...options.description !== void 0 ? { description: options.description } : {},
|
|
67
|
+
...options.default !== void 0 ? { default: options.default } : {}
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
/** Defines a required, non-empty secret decoded as `Redacted<string>`. */
|
|
71
|
+
const secret = (options) => ({
|
|
72
|
+
config: Config.schema(Schema.Redacted(Schema.NonEmptyString), options.runtimeKey),
|
|
73
|
+
spec: {
|
|
74
|
+
runtimeKey: options.runtimeKey,
|
|
75
|
+
type: "string",
|
|
76
|
+
required: options.required ?? true,
|
|
77
|
+
secret: true,
|
|
78
|
+
...options.description !== void 0 ? { description: options.description } : {}
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
/** Makes an existing field optional and represents a missing value as `Option.none()`. */
|
|
82
|
+
const optional = (field) => ({
|
|
83
|
+
config: Config.option(field.config),
|
|
84
|
+
spec: {
|
|
85
|
+
...field.spec,
|
|
86
|
+
required: false
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
/**
|
|
90
|
+
* Defines user-facing connector config once for both Effect runtime loading and
|
|
91
|
+
* frontend form generation. The object keys become logical form/API field names;
|
|
92
|
+
* `runtimeKey` remains the key read by Effect Config.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```ts
|
|
96
|
+
* const config = define({
|
|
97
|
+
* apiUrl: string({ runtimeKey: "API_URL" }),
|
|
98
|
+
* token: secret({ runtimeKey: "API_TOKEN" }),
|
|
99
|
+
* });
|
|
100
|
+
*
|
|
101
|
+
* const RuntimeConfig = config.config;
|
|
102
|
+
* const formFields = config.spec;
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
const define = (fields) => {
|
|
106
|
+
const spec = Object.entries(fields).map(([name, field]) => ({
|
|
107
|
+
name,
|
|
108
|
+
...field.spec
|
|
109
|
+
}));
|
|
110
|
+
const platformField = spec.find((field) => isPlatformRuntimeKey(field.runtimeKey));
|
|
111
|
+
if (platformField !== void 0) throw new Error(`Connector runtime key is platform-owned: ${platformField.runtimeKey}`);
|
|
112
|
+
const configFields = Struct.map(fields, configFieldToConfig);
|
|
113
|
+
return {
|
|
114
|
+
fields: configFields,
|
|
115
|
+
config: Config.unwrap(configFields),
|
|
116
|
+
spec
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
//#endregion
|
|
121
|
+
//#region src/manifest/manifest.ts
|
|
122
|
+
/**
|
|
123
|
+
* Defines browser-safe connector metadata while preserving literal names and capabilities.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```ts
|
|
127
|
+
* const manifest = define({
|
|
128
|
+
* name: "producer-example",
|
|
129
|
+
* title: "Example",
|
|
130
|
+
* config: ExampleConfig.spec,
|
|
131
|
+
* resources: [{ name: "orders", capabilities: ["backfill"] }],
|
|
132
|
+
* });
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
const define$1 = (manifest) => manifest;
|
|
136
|
+
|
|
137
|
+
//#endregion
|
|
138
|
+
//#region src/manifest/schema.ts
|
|
139
|
+
const numberFromForm = (field) => {
|
|
140
|
+
const numberSchema = makeNumberSchema(field);
|
|
141
|
+
return Schema.Union([numberSchema, Schema.NonEmptyString.pipe(Schema.decodeTo(numberSchema, SchemaTransformation.numberFromString))]);
|
|
142
|
+
};
|
|
143
|
+
const BooleanFromForm = Schema.Union([Schema.Boolean, Schema.Literals(["true", "false"]).pipe(Schema.decodeTo(Schema.Boolean, SchemaTransformation.transform({
|
|
144
|
+
decode: (value) => value === "true",
|
|
145
|
+
encode: (value) => value ? "true" : "false"
|
|
146
|
+
})))]);
|
|
147
|
+
const EmptyStringAsUndefined = Schema.Literal("").pipe(Schema.decodeTo(Schema.Undefined, SchemaTransformation.transform({
|
|
148
|
+
decode: () => void 0,
|
|
149
|
+
encode: () => ""
|
|
150
|
+
})));
|
|
151
|
+
const baseFieldSchema = (field) => {
|
|
152
|
+
switch (field.type) {
|
|
153
|
+
case "number": return numberFromForm(field);
|
|
154
|
+
case "boolean": return BooleanFromForm;
|
|
155
|
+
case "select": return Schema.Literals(field.values);
|
|
156
|
+
case "string": return Schema.NonEmptyString;
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
const fieldSchema = (field) => field.required ? baseFieldSchema(field) : Schema.optional(Schema.Union([EmptyStringAsUndefined, baseFieldSchema(field)]));
|
|
160
|
+
/**
|
|
161
|
+
* Builds an Effect Schema for logical form/API values from a connector manifest.
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* ```ts
|
|
165
|
+
* const decode = Schema.decodeUnknownEffect(configSchema(manifest));
|
|
166
|
+
* const values = decode({
|
|
167
|
+
* apiUrl: "https://example.com",
|
|
168
|
+
* retries: "3",
|
|
169
|
+
* });
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
const configSchema = (manifest) => {
|
|
173
|
+
const fields = Object.fromEntries(manifest.config.map((field) => [field.name, fieldSchema(field)]));
|
|
174
|
+
return Schema.Struct(fields);
|
|
175
|
+
};
|
|
176
|
+
/** Decodes browser/API config, applies manifest defaults, and rejects undeclared fields. */
|
|
177
|
+
const decodeConfig = (manifest, input) => {
|
|
178
|
+
const defaults = Object.fromEntries(manifest.config.flatMap((field) => field.default === void 0 ? [] : [[field.name, field.default]]));
|
|
179
|
+
const optionalFields = new Set(manifest.config.flatMap((field) => field.required ? [] : [field.name]));
|
|
180
|
+
const normalizedInput = Predicate.isObject(input) ? Object.fromEntries(Object.entries(input).filter(([name, value]) => value !== "" || !optionalFields.has(name))) : input;
|
|
181
|
+
const inputWithDefaults = Predicate.isObject(normalizedInput) ? Object.assign({}, defaults, normalizedInput) : normalizedInput;
|
|
182
|
+
return Schema.decodeUnknownEffect(configSchema(manifest))(inputWithDefaults, { onExcessProperty: "error" });
|
|
183
|
+
};
|
|
184
|
+
/**
|
|
185
|
+
* Maps validated logical values to the flat runtime-key document mounted in a pod.
|
|
186
|
+
* Missing optional values are omitted.
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* ```ts
|
|
190
|
+
* toRuntimeDocument(manifest, { apiUrl: "https://example.com" });
|
|
191
|
+
* // { API_URL: "https://example.com" }
|
|
192
|
+
* ```
|
|
193
|
+
*/
|
|
194
|
+
const toRuntimeDocument = (manifest, values) => {
|
|
195
|
+
const document = {};
|
|
196
|
+
for (const field of manifest.config) {
|
|
197
|
+
const value = Reflect.get(values, field.name);
|
|
198
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") document[field.runtimeKey] = value;
|
|
199
|
+
}
|
|
200
|
+
return document;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
//#endregion
|
|
204
|
+
//#region src/manifest/index.ts
|
|
205
|
+
var manifest_exports = /* @__PURE__ */ __export({
|
|
206
|
+
boolean: () => boolean,
|
|
207
|
+
configSchema: () => configSchema,
|
|
208
|
+
decodeConfig: () => decodeConfig,
|
|
209
|
+
define: () => define$1,
|
|
210
|
+
defineConfig: () => define,
|
|
211
|
+
number: () => number,
|
|
212
|
+
optional: () => optional,
|
|
213
|
+
secret: () => secret,
|
|
214
|
+
select: () => select,
|
|
215
|
+
string: () => string,
|
|
216
|
+
toRuntimeDocument: () => toRuntimeDocument
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
//#endregion
|
|
220
|
+
export { define$1 as a, number as c, select as d, string as f, toRuntimeDocument as i, optional as l, configSchema as n, boolean as o, decodeConfig as r, define as s, manifest_exports as t, secret as u };
|
|
221
|
+
//# sourceMappingURL=manifest-ByE6YPG0.js.map
|