@jskit-ai/connectors-core 0.2.2 → 0.2.4

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.
@@ -116,8 +116,8 @@ const MESSAGE_KEY_SOURCE = Symbol.for("@jskit-ai/connectors-core.messageKeySourc
116
116
 
117
117
  // Provider definitions carry message keys, which connectors-web translates. Code that
118
118
  // shows a definition's texts itself gets the same definitions with every key (and every
119
- // { key, params } a function returns) rendered to the catalog's English, exactly as
120
- // before keys. Each English object keeps its keyed original under MESSAGE_KEY_SOURCE.
119
+ // { key, params } a function returns) rendered to the catalog's English text. Each
120
+ // English object keeps its keyed original under MESSAGE_KEY_SOURCE.
121
121
  function createEnglishProviderTexts(catalog) {
122
122
  const rendered = new WeakMap();
123
123
  const englishMessage = (key, params) => {
@@ -168,25 +168,31 @@ function providerGrantTypes(provider) {
168
168
  }
169
169
 
170
170
  function getProviderSettingsSchema(provider, settings = {}) {
171
+ // readable-check: allow method-probe - data: a provider definition gives its settings schema as a schema or as a function of the settings
171
172
  return typeof provider.settingsSchema === "function" ? provider.settingsSchema(settings) : provider.settingsSchema;
172
173
  }
173
174
 
174
175
  function getProviderAuthenticationMethods(provider, settings = {}) {
175
- return provider.authenticationMethodsForSettings ? provider.authenticationMethodsForSettings(settings) : provider.authenticationMethods;
176
+ // readable-check: allow method-probe - optional operation: authenticationMethodsForSettings is an optional hook of the provider definition
177
+ return typeof provider.authenticationMethodsForSettings === "function" ? provider.authenticationMethodsForSettings(settings) : provider.authenticationMethods;
176
178
  }
177
179
 
178
180
  function getProviderAccountModes(provider, settings = {}) {
179
- return provider.accountModesForSettings ? provider.accountModesForSettings(settings) : provider.accountModes;
181
+ // readable-check: allow method-probe - optional operation: accountModesForSettings is an optional hook of the provider definition
182
+ return typeof provider.accountModesForSettings === "function" ? provider.accountModesForSettings(settings) : provider.accountModes;
180
183
  }
181
184
 
182
185
  function getProviderScopes(provider, settings = {}, grantType = DEFAULT_GRANT_TYPE, authenticationMethod) {
183
- const scopes = provider.scopesForGrantType ? provider.scopesForGrantType(grantType, settings)
184
- : provider.scopesForSettings ? provider.scopesForSettings(settings) : provider.scopes;
186
+ // readable-check: allow method-probe - optional operation: scopesForGrantType is an optional hook of the provider definition
187
+ const scopes = typeof provider.scopesForGrantType === "function" ? provider.scopesForGrantType(grantType, settings)
188
+ // readable-check: allow method-probe - optional operation: scopesForSettings is an optional hook of the provider definition
189
+ : typeof provider.scopesForSettings === "function" ? provider.scopesForSettings(settings) : provider.scopes;
185
190
  return authenticationMethod ? scopes.filter(scope => !scope.authenticationMethods || scope.authenticationMethods.includes(authenticationMethod)) : scopes;
186
191
  }
187
192
 
188
193
  function getProviderClientAuthenticationMethods(provider, grantType = DEFAULT_GRANT_TYPE, settings = {}) {
189
194
  const methods = provider.oauthClientAuthenticationMethods;
195
+ // readable-check: allow method-probe - data: a provider definition gives its client authentication methods as a list or as a function of the grant type
190
196
  return typeof methods === "function" ? methods(grantType, settings) : methods || [DEFAULT_TOKEN_ENDPOINT_AUTH_METHOD];
191
197
  }
192
198
 
@@ -194,12 +200,15 @@ function getProviderClientAuthenticationMethods(provider, grantType = DEFAULT_GR
194
200
  function getProviderSetup(provider, settings = {}, authenticationMethod) {
195
201
  const setup = provider.setup || {};
196
202
  return {
203
+ // readable-check: allow optional-call - data: a provider definition's setup may give its URL for the settings
197
204
  url: setup.urlForSettings?.(settings, authenticationMethod) || setup.urlByAuthentication?.[authenticationMethod] || setup.url,
205
+ // readable-check: allow optional-call - data: a provider definition's setup may give its steps for the settings
198
206
  steps: setup.stepsForSettings?.(settings, authenticationMethod) || setup.stepsByAuthentication?.[authenticationMethod] || setup.steps
199
207
  };
200
208
  }
201
209
 
202
210
  function isProviderConfigurationOnly(provider, settings = {}) {
211
+ // readable-check: allow optional-call - data: a provider definition may say per settings whether it is configuration only
203
212
  return Boolean(provider.configurationOnly || provider.configurationOnlyForSettings?.(settings));
204
213
  }
205
214
 
@@ -296,15 +305,16 @@ function validateIntegrationConfiguration(input, { providers = [], allowUnknownP
296
305
  Object.assign(fieldErrorCodes, codes);
297
306
  }
298
307
  }
308
+ const settings = integration.settings || {};
299
309
  for (const field of provider.settingsFields || []) {
300
- if (field.authenticationMethods && !field.authenticationMethods.includes(auth.method) && Object.hasOwn(integration.settings || {}, field.name)) {
310
+ if (field.authenticationMethods && !field.authenticationMethods.includes(auth.method) && Object.hasOwn(settings, field.name)) {
301
311
  fieldErrors[`${path}.settings.${field.name}`] = FIELD_ERRORS.otherCredentialModeSetting;
302
312
  }
303
313
  }
304
- if (!getProviderAccountModes(provider, integration.settings || {}).includes(integration.accountMode)) {
314
+ if (!getProviderAccountModes(provider, settings).includes(integration.accountMode)) {
305
315
  fieldErrors[`${path}.accountMode`] = FIELD_ERRORS.accountModeUnsupported;
306
316
  }
307
- if (!getProviderAuthenticationMethods(provider, integration.settings).includes(auth.method)) {
317
+ if (!getProviderAuthenticationMethods(provider, settings).includes(auth.method)) {
308
318
  fieldErrors[`${path}.authentication.method`] = FIELD_ERRORS.credentialModeUnsupported;
309
319
  }
310
320
  if (auth.method === "oauth2" && registration?.source === "own") {
@@ -312,16 +322,17 @@ function validateIntegrationConfiguration(input, { providers = [], allowUnknownP
312
322
  if (!providerGrantTypes(provider).includes(grantType)) {
313
323
  fieldErrors[`${registrationPath}.grantType`] = FIELD_ERRORS.oauthFlowUnsupported;
314
324
  }
315
- const methods = getProviderClientAuthenticationMethods(provider, grantType, integration.settings || {});
325
+ const methods = getProviderClientAuthenticationMethods(provider, grantType, settings);
316
326
  if (!methods.includes(registrationClientAuthentication(registration))) {
317
327
  fieldErrors[`${registrationPath}.tokenEndpointAuthMethod`] = FIELD_ERRORS.clientAuthenticationUnsupported;
318
328
  }
319
- if (registration.clientId && provider.validateClientId) {
329
+ // readable-check: allow method-probe - optional operation: validateClientId is an optional hook of the provider definition
330
+ if (registration.clientId && typeof provider.validateClientId === "function") {
320
331
  const result = provider.validateClientId(registration.clientId);
321
332
  if (result !== true) fieldErrors[`${registrationPath}.clientId`] = result || FIELD_ERRORS.clientId;
322
333
  }
323
334
  }
324
- const scopes = getProviderScopes(provider, integration.settings || {}, grantType, auth.method);
335
+ const scopes = getProviderScopes(provider, settings, grantType, auth.method);
325
336
  const allowedScopes = new Set(scopes.map((scope) => scope.value));
326
337
  if (scopes.some((scope) => scope.required && !integration.scopes.includes(scope.value))) {
327
338
  fieldErrors[`${path}.scopes`] = FIELD_ERRORS.requiredScopes;
@@ -5,7 +5,9 @@ import assert from "node:assert/strict";
5
5
  import test from "node:test";
6
6
  import Fastify from "fastify";
7
7
  import { createCapabilityRuntime, defineProvider } from "@jskit-ai/kernel/shared/capabilities";
8
- import { createActionProvider } from "@jskit-ai/kernel/server/actions";
8
+ import { ActionProvider } from "@jskit-ai/kernel/server/actions";
9
+ import { EventProvider } from "@jskit-ai/kernel/server/runtime";
10
+ import { silentLogger } from "@jskit-ai/kernel/server/runtime/logger";
9
11
  import { googleCalendarProvider } from "../../connector-google-calendar/src/server/provider.js";
10
12
  import { createConnectionService, createConnectorsFeature, createEnvironmentReferenceResolver } from "../src/server/index.js";
11
13
  import { integrationsSchema, parseIntegrationConfiguration, validateIntegrationConfiguration } from "../src/shared/configuration.js";
@@ -111,8 +113,8 @@ test("the ordinary credential-free Feature action verifies without resolving ref
111
113
  return Response.json({ meta: [], data: [{ ok: 1, user: "default" }], rows: 1 });
112
114
  }
113
115
  };
114
- const runtime = createCapabilityRuntime({ providers: [
115
- createActionProvider(), createConnectorsFeature(options),
116
+ const runtime = createCapabilityRuntime({ inputs: { "runtime.logger": silentLogger }, providers: [
117
+ EventProvider, ActionProvider, createConnectorsFeature(options),
116
118
  defineProvider({ id: "test.credential-free-observer", requires: { catalogue: "runtime.actions" }, setup({ catalogue }) { actions = catalogue; } })
117
119
  ] });
118
120
  await runtime.start();
@@ -395,7 +397,7 @@ test("cancelling a service token exchange preserves the previous grant without r
395
397
  test("the client credentials Feature action uses the ordinary application policy and runtime", async () => {
396
398
  const { options } = serviceAccountSetup();
397
399
  let actions;
398
- const runtime = createCapabilityRuntime({ providers: [createActionProvider(), createConnectorsFeature(options),
400
+ const runtime = createCapabilityRuntime({ inputs: { "runtime.logger": silentLogger }, providers: [EventProvider, ActionProvider, createConnectorsFeature(options),
399
401
  defineProvider({ id: "test.service-observer", requires: { catalogue: "runtime.actions" }, setup({ catalogue }) { actions = catalogue; } })] });
400
402
  await runtime.start();
401
403
  try {
@@ -828,8 +830,8 @@ test("configuration rejects gateway registrations before constructing a runtime"
828
830
  test("the Feature exposes ordinary JSKIT actions with application authorization", async () => {
829
831
  const { options } = setup();
830
832
  let actions;
831
- const runtime = createCapabilityRuntime({ providers: [
832
- createActionProvider(), createConnectorsFeature(options),
833
+ const runtime = createCapabilityRuntime({ inputs: { "runtime.logger": silentLogger }, providers: [
834
+ EventProvider, ActionProvider, createConnectorsFeature(options),
833
835
  defineProvider({
834
836
  id: "test.observer", requires: { catalogue: "runtime.actions" },
835
837
  setup({ catalogue }) { actions = catalogue; }
@@ -997,8 +999,8 @@ test("JSON-body credentials replace supplied values, preserve input and reach th
997
999
  }
998
1000
  };
999
1001
  let actions;
1000
- const runtime = createCapabilityRuntime({ providers: [
1001
- createActionProvider(), createConnectorsFeature(options),
1002
+ const runtime = createCapabilityRuntime({ inputs: { "runtime.logger": silentLogger }, providers: [
1003
+ EventProvider, ActionProvider, createConnectorsFeature(options),
1002
1004
  defineProvider({ id: "test.body-observer", requires: { catalogue: "runtime.actions" }, setup({ catalogue }) { actions = catalogue; } })
1003
1005
  ] });
1004
1006
  await runtime.start();
@@ -5,7 +5,9 @@ import { tmpdir } from "node:os";
5
5
  import path from "node:path";
6
6
  import { createSchema } from "json-rest-schema";
7
7
  import { createCapabilityRuntime, defineProvider } from "@jskit-ai/kernel/shared/capabilities";
8
- import { createActionProvider } from "@jskit-ai/kernel/server/actions";
8
+ import { ActionProvider } from "@jskit-ai/kernel/server/actions";
9
+ import { EventProvider } from "@jskit-ai/kernel/server/runtime";
10
+ import { silentLogger } from "@jskit-ai/kernel/server/runtime/logger";
9
11
  import { createConnectionService, createConnectorsFeature } from "../src/server/index.js";
10
12
  import { ConnectorError } from "../src/server/errors.js";
11
13
  import { createFileConnectionStore, createCredentialProtection } from "../src/server/fileStorage.js";
@@ -182,7 +184,7 @@ test("service grants honor cancellation and deadlines without replay or partial
182
184
 
183
185
  test("the service-account Feature action uses the same application policy and verified runtime", async (t) => {
184
186
  const f = await fixture(t); let actions;
185
- const runtime = createCapabilityRuntime({ providers: [createActionProvider(), createConnectorsFeature(f.options),
187
+ const runtime = createCapabilityRuntime({ inputs: { "runtime.logger": silentLogger }, providers: [EventProvider, ActionProvider, createConnectorsFeature(f.options),
186
188
  defineProvider({ id: "test.service-observer", requires: { catalogue: "runtime.actions" }, setup({ catalogue }) { actions = catalogue; } })] });
187
189
  await runtime.start();
188
190
  try {
@@ -16,9 +16,10 @@ const guide = await readFile(new URL("../docs/setup-command.md", import.meta.url
16
16
  const source = guide.match(/```js\n([\s\S]*?)\n```/u)?.[1];
17
17
  assert.ok(source, "The setup guide must contain its dispatch example.");
18
18
  const dispatchSetup = new Function(`${source}\nreturn dispatchSetup;`)();
19
+ const protocol = "example-host.integration-setup.command.v1";
19
20
  const scope = "https://www.googleapis.com/auth/calendar.calendarlist.readonly";
20
21
  const callback = "http://127.0.0.1:8080/integrations/google/callback";
21
- const context = { applicationId: "dogandgroom-dev", subjectId: "business-account" };
22
+ const context = { applicationId: "bookings-dev", subjectId: "business-account" };
22
23
 
23
24
  async function fixture(t) {
24
25
  const directory = await mkdtemp(path.join(tmpdir(), "setup-composition-"));
@@ -72,9 +73,9 @@ async function fixture(t) {
72
73
  }
73
74
  });
74
75
  const dispatch = async (operation, integrationId, extra = {}, identity = context) => {
75
- const result = await dispatchSetup({ protocol: "vibe64.integration-setup.command.v1",
76
+ const result = await dispatchSetup({ protocol,
76
77
  requestId: "fixture-request", operation, integrationId, ...extra },
77
- { configuration, connections: restart(), context: identity });
78
+ { protocol, configuration, connections: restart(), context: identity });
78
79
  assert.equal(result.requestId, "fixture-request");
79
80
  assert.equal(JSON.stringify(result).includes("private-"), false);
80
81
  return result;
@@ -123,14 +124,14 @@ try {
123
124
  return Response.json({ object: "list", data: [], has_more: false });
124
125
  }
125
126
  });
126
- const result = await dispatchSetup(JSON.parse(input), { configuration, connections, context });
127
+ const result = await dispatchSetup(JSON.parse(input), { protocol: ${JSON.stringify(protocol)}, configuration, connections, context });
127
128
  process.stdout.write(JSON.stringify(result) + "\\n");
128
129
  } catch {
129
130
  process.stderr.write("Integration setup failed.\\n");
130
131
  process.exitCode = 1;
131
132
  }
132
133
  `);
133
- const request = { protocol: "vibe64.integration-setup.command.v1", requestId: "cli-request", integrationId: "mail" };
134
+ const request = { protocol, requestId: "cli-request", integrationId: "mail" };
134
135
  for (const [operation, key, applicationId, expected, allowCheck] of [
135
136
  ["status", "MISSING", "first-app", "unconfigured", false],
136
137
  ["connect", "MISSING", "first-app", "unconfigured", false],
@@ -220,7 +221,7 @@ test("moving application state preserves grants and refresh while invalidating c
220
221
  const moved = `${directory}-relocated`;
221
222
  t.after(() => rm(moved, { recursive: true, force: true }));
222
223
  await rename(directory, moved);
223
- env.CALLBACK = "https://dogandgroom.example/integrations/google/callback";
224
+ env.CALLBACK = "https://bookings.example/integrations/google/callback";
224
225
  const relocated = restart(moved);
225
226
  const requestCount = requests.length;
226
227
  assert.equal(await relocated.resumeAuthorization(input), null);
@@ -235,7 +236,7 @@ test("moving application state preserves grants and refresh while invalidating c
235
236
  "oauth2.googleapis.com", "www.googleapis.com"
236
237
  ]);
237
238
  assert.equal((await restart(moved).status(input)).status, "connected");
238
- const otherEnvironment = { ...context, applicationId: "dogandgroom-production" };
239
+ const otherEnvironment = { ...context, applicationId: "bookings-production" };
239
240
  assert.equal((await relocated.status({ ...input, context: otherEnvironment })).status, "disconnected");
240
241
  await relocated.disconnect({ ...input, context: otherEnvironment });
241
242
  assert.equal((await relocated.status(input)).status, "connected");
@@ -259,7 +260,7 @@ test("documented OAuth setup resumes across restarts, preserves a grant on cance
259
260
  const callbackUrl = `${callback}?code=fixture-code&state=${pending.attemptId}`;
260
261
  await restart().completeAuthorization({ context, integrationId: "calendar", callbackUrl });
261
262
  assert.equal((await dispatch("status", "calendar")).status, "connected");
262
- env.CALLBACK = "https://dogandgroom.example/integrations/google/callback";
263
+ env.CALLBACK = "https://bookings.example/integrations/google/callback";
263
264
  assert.equal((await dispatch("status", "calendar")).callbackUrl, env.CALLBACK);
264
265
  env.CALLBACK = callback;
265
266
  const replacement = await dispatch("connect", "calendar");
File without changes