@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.
Files changed (110) hide show
  1. package/METRICS.md +78 -0
  2. package/README.md +190 -21
  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/connector-app/index.d.ts +14 -9
  8. package/dist/connector-app/index.js +9 -6
  9. package/dist/connector-app-d7Z-SZLQ.js +64 -0
  10. package/dist/connector-app-d7Z-SZLQ.js.map +1 -0
  11. package/dist/constants-BRhIX_rz.js +27 -0
  12. package/dist/constants-BRhIX_rz.js.map +1 -0
  13. package/dist/errors/index.d.ts +1 -1
  14. package/dist/{index-DK9YcR6W.d.ts → index-Byr0Lf0C.d.ts} +4 -4
  15. package/dist/index-CBT-JX-T.d.ts +173 -0
  16. package/dist/index-CTHVudvQ.d.ts +41 -0
  17. package/dist/index-Coh_SkBs.d.ts +95 -0
  18. package/dist/index-Cq2IzY7T.d.ts +80 -0
  19. package/dist/{index-DyuT2jzP.d.ts → index-D54ySAKn.d.ts} +1 -1
  20. package/dist/{index-CQJsFJs3.d.ts → index-DKWIh6M1.d.ts} +7 -5
  21. package/dist/{index-BlhJCpEk.d.ts → index-DtJX_tic.d.ts} +39 -10
  22. package/dist/{index-8KnVpoZp.d.ts → index-J42AZaXU.d.ts} +11 -5
  23. package/dist/index-o1-Z3n7V.d.ts +55 -0
  24. package/dist/index.d.ts +14 -9
  25. package/dist/index.js +14 -8
  26. package/dist/index.js.map +1 -1
  27. package/dist/ingestion/index.d.ts +5 -5
  28. package/dist/ingestion/index.js +7 -4
  29. package/dist/ingestion-BQo4cbGw.js +200 -0
  30. package/dist/ingestion-BQo4cbGw.js.map +1 -0
  31. package/dist/manifest/index.d.ts +2 -0
  32. package/dist/manifest/index.js +4 -0
  33. package/dist/manifest-ByE6YPG0.js +221 -0
  34. package/dist/manifest-ByE6YPG0.js.map +1 -0
  35. package/dist/metrics-CVAk1ALR.d.ts +53 -0
  36. package/dist/metrics-CWPy2Aqc.js +96 -0
  37. package/dist/metrics-CWPy2Aqc.js.map +1 -0
  38. package/dist/metrics.d.ts +4 -0
  39. package/dist/metrics.js +3 -0
  40. package/dist/publisher/index.d.ts +4 -4
  41. package/dist/publisher/index.js +3 -2
  42. package/dist/{publisher-DusgYvaV.js → publisher-BYT3RLVO.js} +2 -2
  43. package/dist/{publisher-DusgYvaV.js.map → publisher-BYT3RLVO.js.map} +1 -1
  44. package/dist/runtime-config/index.d.ts +2 -0
  45. package/dist/runtime-config/index.js +4 -0
  46. package/dist/runtime-config-BpB-d_bF.js +51 -0
  47. package/dist/runtime-config-BpB-d_bF.js.map +1 -0
  48. package/dist/server-CXTQ7odl.js +183 -0
  49. package/dist/server-CXTQ7odl.js.map +1 -0
  50. package/dist/{telemetry-p0dE5Mdu.js → service-DuFiQwid.js} +30 -10
  51. package/dist/service-DuFiQwid.js.map +1 -0
  52. package/dist/{service-B7g_lHMI.d.ts → service-IE5qaf9x.d.ts} +3 -3
  53. package/dist/state-store/index.d.ts +3 -4
  54. package/dist/state-store/index.js +4 -2
  55. package/dist/state-store-yl6LiTQ0.js +198 -0
  56. package/dist/state-store-yl6LiTQ0.js.map +1 -0
  57. package/dist/status/index.d.ts +5 -0
  58. package/dist/status/index.js +6 -0
  59. package/dist/status-B-oZBc33.js +84 -0
  60. package/dist/status-B-oZBc33.js.map +1 -0
  61. package/dist/{types-ZNrA-Dn0.d.ts → types-B35J5YPu.d.ts} +20 -6
  62. package/dist/webhook/index.d.ts +5 -4
  63. package/dist/webhook/index.js +5 -3
  64. package/dist/{webhook-BSA-tXtU.js → webhook-DI-Afxkg.js} +2 -2
  65. package/dist/{webhook-BSA-tXtU.js.map → webhook-DI-Afxkg.js.map} +1 -1
  66. package/package.json +36 -7
  67. package/src/auth/index.ts +1 -0
  68. package/src/auth/token-cache.ts +54 -0
  69. package/src/connector-app/check.ts +76 -0
  70. package/src/connector-app/index.ts +1 -0
  71. package/src/connector-app/start.ts +4 -0
  72. package/src/core/builder.ts +8 -3
  73. package/src/core/types.ts +20 -2
  74. package/src/index.ts +5 -0
  75. package/src/ingestion/engine.ts +246 -133
  76. package/src/manifest/config.ts +232 -0
  77. package/src/manifest/index.ts +14 -0
  78. package/src/manifest/manifest.ts +31 -0
  79. package/src/manifest/number-schema.ts +17 -0
  80. package/src/manifest/schema.ts +160 -0
  81. package/src/metrics.ts +128 -0
  82. package/src/publisher/instrumented.ts +68 -0
  83. package/src/runtime-config/constants.ts +27 -0
  84. package/src/runtime-config/error.ts +13 -0
  85. package/src/runtime-config/index.ts +3 -0
  86. package/src/runtime-config/layer.ts +56 -0
  87. package/src/state-store/index.ts +2 -1
  88. package/src/state-store/keys.ts +22 -0
  89. package/src/state-store/layer-key-value-store.ts +132 -0
  90. package/src/state-store/layer-memory.ts +11 -0
  91. package/src/state-store/layer-sql.ts +50 -0
  92. package/src/state-store/schema.ts +57 -0
  93. package/src/state-store/service.ts +39 -6
  94. package/src/state-store/state.ts +17 -0
  95. package/src/status/index.ts +2 -0
  96. package/src/status/route.ts +77 -0
  97. package/src/status/types.ts +33 -0
  98. package/src/telemetry.ts +46 -4
  99. package/src/webhook/server.ts +54 -19
  100. package/dist/connector-app-CvqbfN6R.js +0 -32
  101. package/dist/connector-app-CvqbfN6R.js.map +0 -1
  102. package/dist/index-DaNW63Mv.d.ts +0 -21
  103. package/dist/ingestion-CMCroK7Y.js +0 -171
  104. package/dist/ingestion-CMCroK7Y.js.map +0 -1
  105. package/dist/server-DAeFWT4o.js +0 -113
  106. package/dist/server-DAeFWT4o.js.map +0 -1
  107. package/dist/state-store-DG_4QpWG.js +0 -31
  108. package/dist/state-store-DG_4QpWG.js.map +0 -1
  109. package/dist/telemetry-p0dE5Mdu.js.map +0 -1
  110. package/src/state-store/memory.ts +0 -25
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@useairfoil/connector-kit",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "repository": "useairfoil/typescript-sdk",
5
5
  "files": [
6
6
  "dist",
7
7
  "src",
8
+ "METRICS.md",
8
9
  "README.md"
9
10
  ],
10
11
  "type": "module",
@@ -16,6 +17,11 @@
16
17
  "import": "./dist/index.js",
17
18
  "default": "./dist/index.js"
18
19
  },
20
+ "./auth": {
21
+ "types": "./dist/auth/index.d.ts",
22
+ "import": "./dist/auth/index.js",
23
+ "default": "./dist/auth/index.js"
24
+ },
19
25
  "./connector-app": {
20
26
  "types": "./dist/connector-app/index.d.ts",
21
27
  "import": "./dist/connector-app/index.js",
@@ -26,16 +32,36 @@
26
32
  "import": "./dist/ingestion/index.js",
27
33
  "default": "./dist/ingestion/index.js"
28
34
  },
35
+ "./manifest": {
36
+ "types": "./dist/manifest/index.d.ts",
37
+ "import": "./dist/manifest/index.js",
38
+ "default": "./dist/manifest/index.js"
39
+ },
40
+ "./metrics": {
41
+ "types": "./dist/metrics.d.ts",
42
+ "import": "./dist/metrics.js",
43
+ "default": "./dist/metrics.js"
44
+ },
29
45
  "./publisher": {
30
46
  "types": "./dist/publisher/index.d.ts",
31
47
  "import": "./dist/publisher/index.js",
32
48
  "default": "./dist/publisher/index.js"
33
49
  },
50
+ "./runtime-config": {
51
+ "types": "./dist/runtime-config/index.d.ts",
52
+ "import": "./dist/runtime-config/index.js",
53
+ "default": "./dist/runtime-config/index.js"
54
+ },
34
55
  "./state-store": {
35
56
  "types": "./dist/state-store/index.d.ts",
36
57
  "import": "./dist/state-store/index.js",
37
58
  "default": "./dist/state-store/index.js"
38
59
  },
60
+ "./status": {
61
+ "types": "./dist/status/index.d.ts",
62
+ "import": "./dist/status/index.js",
63
+ "default": "./dist/status/index.js"
64
+ },
39
65
  "./webhook": {
40
66
  "types": "./dist/webhook/index.d.ts",
41
67
  "import": "./dist/webhook/index.js",
@@ -54,18 +80,21 @@
54
80
  "typecheck": "tsc --noEmit"
55
81
  },
56
82
  "dependencies": {
57
- "@effect/platform-node": "4.0.0-beta.83",
58
- "@useairfoil/flight": "0.4.3",
59
- "@useairfoil/wings": "0.4.1"
83
+ "@effect/platform-node": "^4.0.0-rc.108",
84
+ "@useairfoil/flight": "0.4.5",
85
+ "@useairfoil/wings": "0.4.3"
60
86
  },
61
87
  "devDependencies": {
62
- "@effect/vitest": "4.0.0-beta.83",
88
+ "@effect/sql-pg": "^4.0.0-rc.108",
89
+ "@effect/vitest": "^4.0.0-rc.108",
63
90
  "@types/node": "^24.2.0",
91
+ "effect": "^4.0.0-rc.108",
92
+ "testcontainers": "^11.14.0",
64
93
  "tsdown": "^0.15.6",
65
- "vitest": "^3.2.4"
94
+ "vitest": "^4.1.0"
66
95
  },
67
96
  "peerDependencies": {
68
- "effect": "4.0.0-beta.83"
97
+ "effect": "^4.0.0-rc.108"
69
98
  },
70
99
  "peerDependenciesMeta": {
71
100
  "effect": {
@@ -0,0 +1 @@
1
+ export * from "./token-cache";
@@ -0,0 +1,54 @@
1
+ import { Cache, Duration, Effect, Exit, Number, Redacted } from "effect";
2
+
3
+ export interface ExpiringToken {
4
+ /** The credential kept in memory by the cache. */
5
+ readonly value: Redacted.Redacted<string>;
6
+ /** The lifetime reported by the provider. */
7
+ readonly expiresIn: Duration.Duration;
8
+ }
9
+
10
+ export interface TokenCache<E> {
11
+ /** Returns the cached token or performs one shared acquisition. */
12
+ readonly get: Effect.Effect<Redacted.Redacted<string>, E>;
13
+ /** Removes the cached token so the next `get` reacquires it. */
14
+ readonly invalidate: Effect.Effect<void>;
15
+ }
16
+
17
+ /**
18
+ * Creates a lazy, in-memory token cache around an acquisition effect.
19
+ *
20
+ * `refreshFactor` controls how much of the provider lifetime is cached. It is
21
+ * clamped between `0` and `1`.
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * const cache = yield* makeTokenCache({
26
+ * acquire: exchangeCredentials,
27
+ * refreshFactor: 0.5,
28
+ * })
29
+ *
30
+ * const token = yield* cache.get
31
+ * ```
32
+ */
33
+ export const makeTokenCache = <E, R>(options: {
34
+ readonly acquire: Effect.Effect<ExpiringToken, E, R>;
35
+ readonly refreshFactor?: number;
36
+ }): Effect.Effect<TokenCache<E>, never, R> =>
37
+ Effect.gen(function* () {
38
+ const refreshFactor = Number.clamp(options.refreshFactor ?? 0.5, {
39
+ minimum: 0,
40
+ maximum: 1,
41
+ });
42
+ const cache = yield* Cache.makeWith((_: void) => options.acquire, {
43
+ capacity: 1,
44
+ timeToLive: (exit) =>
45
+ Exit.isSuccess(exit)
46
+ ? Duration.millis(Duration.toMillis(exit.value.expiresIn) * refreshFactor)
47
+ : Duration.zero,
48
+ });
49
+
50
+ return {
51
+ get: Cache.get(cache, undefined).pipe(Effect.map((token) => token.value)),
52
+ invalidate: Cache.invalidate(cache, undefined),
53
+ };
54
+ });
@@ -0,0 +1,76 @@
1
+ import { Context, Effect, Layer, Tuple } from "effect";
2
+
3
+ import type { ConnectorDefinition, ResourceDefinition, ResourceName } from "../core/types";
4
+
5
+ export type ResourceCheckResult =
6
+ | { readonly _tag: "ok" }
7
+ | { readonly _tag: "error"; readonly message: string };
8
+
9
+ export type CheckOptions<Name extends string> = {
10
+ readonly resources: ReadonlyArray<Name>;
11
+ };
12
+
13
+ type ResourceCheckResults<Name extends PropertyKey> = {
14
+ readonly [Key in Name]: ResourceCheckResult;
15
+ };
16
+
17
+ export type CheckResult<
18
+ Resources extends ReadonlyArray<ResourceDefinition>,
19
+ Name extends ResourceName<Resources> = ResourceName<Resources>,
20
+ > = ResourceCheckResults<Name>;
21
+
22
+ type CheckFailure = { readonly message: string };
23
+
24
+ const ok: ResourceCheckResult = { _tag: "ok" };
25
+
26
+ const error = (message: string): ResourceCheckResult => ({ _tag: "error", message });
27
+
28
+ const fromEntries = <Name extends string>(
29
+ entries: ReadonlyArray<readonly [Name, ResourceCheckResult]>,
30
+ ): ResourceCheckResults<Name> => Object.fromEntries(entries) as ResourceCheckResults<Name>;
31
+
32
+ const resultsFor = <Name extends string>(names: ReadonlyArray<Name>, result: ResourceCheckResult) =>
33
+ fromEntries(names.map((name) => Tuple.make(name, result)));
34
+
35
+ /**
36
+ * Builds the connector service from its layer and validates only the selected
37
+ * resources. The layer remains open until every check finishes.
38
+ */
39
+ export function check<
40
+ Identifier,
41
+ const Resources extends ReadonlyArray<ResourceDefinition>,
42
+ E extends CheckFailure,
43
+ R,
44
+ const Name extends ResourceName<Resources>,
45
+ >(
46
+ service: Context.Key<Identifier, ConnectorDefinition<Resources>>,
47
+ layer: Layer.Layer<Identifier, E, R>,
48
+ options: CheckOptions<Name>,
49
+ ): Effect.Effect<CheckResult<Resources, Name>, never, R> {
50
+ if (options.resources.length === 0) {
51
+ return Effect.succeed(fromEntries<Name>([]));
52
+ }
53
+
54
+ return service.pipe(
55
+ Effect.flatMap((connector) =>
56
+ Effect.forEach(options.resources, (name) => {
57
+ const resource = connector.resources.find((candidate) => candidate.name === name);
58
+ const result = resource
59
+ ? resource.check.pipe(
60
+ Effect.match({
61
+ onFailure: (failure) => error(failure.message),
62
+ onSuccess: () => ok,
63
+ }),
64
+ )
65
+ : Effect.succeed(error(`Unknown connector resource: ${name}`));
66
+
67
+ return result.pipe(Effect.map((result) => Tuple.make(name, result)));
68
+ }).pipe(Effect.map(fromEntries)),
69
+ ),
70
+ Effect.provide(layer),
71
+ Effect.match({
72
+ onFailure: (failure) => resultsFor(options.resources, error(failure.message)),
73
+ onSuccess: (results) => results,
74
+ }),
75
+ );
76
+ }
@@ -1 +1,2 @@
1
+ export { check, type CheckOptions, type CheckResult, type ResourceCheckResult } from "./check";
1
2
  export { start, type App, type StartOptions } from "./start";
@@ -14,6 +14,8 @@ export type StartOptions = {
14
14
  readonly port: number;
15
15
  readonly initialCutoff?: Cursor.Value;
16
16
  readonly healthPath?: HttpRouter.PathInput;
17
+ readonly metricsPath?: HttpRouter.PathInput | false;
18
+ readonly statusPath?: HttpRouter.PathInput | false;
17
19
  readonly disableHttpLogger?: boolean;
18
20
  };
19
21
 
@@ -32,6 +34,8 @@ export const start = (app: App, options: StartOptions) =>
32
34
  webhook: {
33
35
  routes: app.webhooks ?? [],
34
36
  healthPath: options.healthPath ?? "/health",
37
+ metricsPath: options.metricsPath,
38
+ statusPath: options.statusPath,
35
39
  disableHttpLogger: options.disableHttpLogger ?? true,
36
40
  },
37
41
  }).pipe(Effect.provide(NodeHttpServer.layer(createServer, { port: options.port })));
@@ -21,9 +21,14 @@ export const Connector = {
21
21
  };
22
22
 
23
23
  export const Resource = {
24
- entity: <S extends ResourceSchema, Payload = never, R = never>(
25
- definition: ResourceDefinition<S, Payload, R>,
26
- ): ResourceDefinition<S, Payload, R> => definition,
24
+ entity: <
25
+ S extends ResourceSchema,
26
+ Payload = never,
27
+ R = never,
28
+ const Name extends string = string,
29
+ >(
30
+ definition: ResourceDefinition<S, Payload, R, Name>,
31
+ ): ResourceDefinition<S, Payload, R, Name> => definition,
27
32
 
28
33
  webhook: <Payload, Row extends object = never>(definition: WebhookHandler<Row, Payload>) =>
29
34
  definition,
package/src/core/types.ts CHANGED
@@ -37,12 +37,23 @@ export type ResourceState = {
37
37
  readonly cutoff: Cursor.Value;
38
38
  readonly pageCursor?: Cursor.Value;
39
39
  readonly completed: boolean;
40
+ readonly lastSuccessAt?: string;
40
41
  };
41
42
  readonly changes?: {
42
43
  readonly cursor: Cursor.Value;
44
+ readonly lastSuccessAt?: string;
45
+ };
46
+ readonly lastError?: {
47
+ readonly source: "backfill" | "changes";
48
+ readonly operation: "fetch" | "publish" | "checkpoint";
49
+ readonly code: "fetch_failed" | "publish_failed" | "checkpoint_failed";
50
+ readonly message: string;
51
+ readonly at: string;
43
52
  };
44
53
  };
45
54
 
55
+ export type SyncState = "pending" | "backfilling" | "live" | "error";
56
+
46
57
  export type ResourceSchema = Schema.Decoder<object>;
47
58
 
48
59
  /**
@@ -112,14 +123,17 @@ export type ResourceDefinition<
112
123
  S extends ResourceSchema = ResourceSchema,
113
124
  Payload = unknown,
114
125
  R = never,
126
+ Name extends string = string,
115
127
  > = {
116
- readonly name: string;
128
+ readonly name: Name;
117
129
  readonly schema: S;
118
130
  readonly key: ResourceField<NoInfer<S>>;
119
131
  readonly version: ResourceField<NoInfer<S>>;
120
132
  readonly partition?: {
121
133
  readonly required: boolean;
122
134
  };
135
+ /** Read-only validation used before provisioning this resource. */
136
+ readonly check: Effect.Effect<void, ConnectorError>;
123
137
  readonly backfill?: PageFetch<ResourceRow<NoInfer<S>>, R>;
124
138
  readonly changes?: ChangesFetch<ResourceRow<NoInfer<S>>, R>;
125
139
  readonly webhook?: WebhookHandler<ResourceRow<NoInfer<S>>, Payload>;
@@ -153,6 +167,10 @@ export type ResourceRows<Resources extends ReadonlyArray<ResourceDefinition>> =
153
167
  : never;
154
168
  };
155
169
 
170
+ /** Resource names preserved from a connector's resource tuple. */
171
+ export type ResourceName<Resources extends ReadonlyArray<ResourceDefinition>> =
172
+ keyof ResourceRows<Resources> & string;
173
+
156
174
  /**
157
175
  * Takes a resource definition and returns the payload type accepted by
158
176
  * `to(resource, payload)`.
@@ -192,7 +210,7 @@ export type WebhookRoute<
192
210
  readonly schema: Schema.Decoder<Payload>;
193
211
  handler(
194
212
  context: WebhookRouteContext<Resources, Payload>,
195
- ): Effect.Effect<HttpServerResponse.HttpServerResponse, unknown>;
213
+ ): Effect.Effect<HttpServerResponse.HttpServerResponse, ConnectorError>;
196
214
  };
197
215
 
198
216
  export type WebhookRouteInput<
package/src/index.ts CHANGED
@@ -1,8 +1,13 @@
1
1
  export * from "./core";
2
+ export * as Auth from "./auth";
2
3
  export * as ConnectorApp from "./connector-app";
3
4
  export * as Ingestion from "./ingestion";
5
+ export * as Manifest from "./manifest";
6
+ export * as Metrics from "./metrics";
4
7
  export * as Publisher from "./publisher";
8
+ export * as RuntimeConfig from "./runtime-config";
5
9
  export * as StateStore from "./state-store";
10
+ export * as Status from "./status";
6
11
  export * as Telemetry from "./telemetry";
7
12
  export * as Webhook from "./webhook";
8
13
  export * from "./errors";