@zhin.js/adapter 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,78 @@
1
+ import { type CapabilityId, type GenerationAdmissionGate } from '@zhin.js/plugin-runtime';
2
+ import type { AdapterContext, EndpointSendRequest } from './definition.js';
3
+ import type { EndpointManagement } from './endpoint-management.js';
4
+ import type { EndpointControl } from './endpoint-control.js';
5
+ import type { EndpointContentPort } from './endpoint-content.js';
6
+ /** The one inbound event boundary shared by every platform Endpoint. */
7
+ export interface EndpointEventGateway {
8
+ receive(event: EndpointEvent): Promise<unknown>;
9
+ }
10
+ /** Callback shape used by protocol normalizers which feed Endpoint.emit(). */
11
+ export type EndpointEventEmitter = <TPayload>(name: string, payload: TPayload) => Promise<unknown>;
12
+ /** Generation-bound gateway injected into Endpoint by AdapterIndex. */
13
+ export declare const endpointEventGatewayToken: import("@zhin.js/plugin-runtime").Token<EndpointEventGateway>;
14
+ /** Stable identity of the Endpoint which produced an event. */
15
+ export interface EndpointIdentity {
16
+ readonly id: CapabilityId;
17
+ readonly adapter: string;
18
+ }
19
+ /**
20
+ * The single event context delivered from an Endpoint into Core and plugins.
21
+ * `client` is the actual platform SDK/protocol client owned by the Endpoint.
22
+ */
23
+ export interface EndpointEvent<TPayload = unknown, TClient = unknown, TName extends string = string> {
24
+ readonly name: TName;
25
+ readonly payload: TPayload;
26
+ readonly endpoint: EndpointIdentity;
27
+ readonly client: TClient;
28
+ }
29
+ /** Lossless native event delivered before any optional canonical projection. */
30
+ export interface PlatformEvent<TEvent = unknown, TName extends string = string> {
31
+ /** Native SDK/protocol event name, for example `guild_member_add`. */
32
+ readonly name: TName;
33
+ /** Native SDK/protocol payload without canonicalization. */
34
+ readonly event: TEvent;
35
+ }
36
+ declare const endpointBrand: unique symbol;
37
+ declare const endpointBind: unique symbol;
38
+ /**
39
+ * Deep platform boundary.
40
+ *
41
+ * Platform implementations inherit this class, expose the platform-native
42
+ * `client`, own account/transport lifecycle, and normalize every inbound SDK
43
+ * callback through `emit()`. Core owns dispatch, admission and plugin context.
44
+ */
45
+ export declare abstract class Endpoint<TClient = unknown> {
46
+ #private;
47
+ readonly [endpointBrand] = true;
48
+ /**
49
+ * Platform SDK or protocol client exposed to plugin code.
50
+ * It must be a distinct object: Endpoint owns framework lifecycle, Client
51
+ * owns platform operations.
52
+ */
53
+ abstract readonly client: TClient;
54
+ readonly management?: EndpointManagement;
55
+ readonly control?: EndpointControl;
56
+ readonly content?: EndpointContentPort;
57
+ /** @internal Bound exactly once by the generation-owned AdapterIndex. */
58
+ [endpointBind](context: AdapterContext, admission?: GenerationAdmissionGate): void;
59
+ /** The identity is available after AdapterDefinition.create returns. */
60
+ get identity(): EndpointIdentity;
61
+ /** The only legal platform-to-framework event ingress. */
62
+ protected emit<TPayload, TName extends string>(name: TName, payload: TPayload): Promise<unknown>;
63
+ /**
64
+ * Lossless native-event projection. Adapters call this before deriving
65
+ * message/notice/request/system events, including for unknown event kinds.
66
+ */
67
+ protected emitPlatform<TEvent, TName extends string>(name: TName, event: TEvent): Promise<unknown>;
68
+ abstract start(signal: AbortSignal): void | Promise<void>;
69
+ abstract open(): void;
70
+ abstract close(): void | Promise<void>;
71
+ abstract stop(): void | Promise<void>;
72
+ send?(_request: EndpointSendRequest): string | Promise<string>;
73
+ }
74
+ /** @internal AdapterIndex binding hook; deliberately not exported by name. */
75
+ export declare function bindEndpoint(endpoint: Endpoint, context: AdapterContext, admission?: GenerationAdmissionGate): void;
76
+ /** @internal Cross-generation Endpoint check that survives ESM module re-evaluation. */
77
+ export declare function isEndpoint(value: unknown): value is Endpoint;
78
+ export {};
@@ -0,0 +1,100 @@
1
+ import { createToken, } from '@zhin.js/plugin-runtime';
2
+ /** Generation-bound gateway injected into Endpoint by AdapterIndex. */
3
+ export const endpointEventGatewayToken = createToken('zhin.adapter.endpoint-events');
4
+ const endpointBrand = Symbol.for('zhin.adapter.endpoint/1');
5
+ const endpointBind = Symbol.for('zhin.adapter.endpoint-bind/1');
6
+ const PRE_ADMISSION_EVENT_LIMIT = 256;
7
+ /**
8
+ * Deep platform boundary.
9
+ *
10
+ * Platform implementations inherit this class, expose the platform-native
11
+ * `client`, own account/transport lifecycle, and normalize every inbound SDK
12
+ * callback through `emit()`. Core owns dispatch, admission and plugin context.
13
+ */
14
+ export class Endpoint {
15
+ [endpointBrand] = true;
16
+ management;
17
+ control;
18
+ content;
19
+ #identity;
20
+ #events;
21
+ #admissionState = 'unbound';
22
+ #pendingEvents = [];
23
+ /** @internal Bound exactly once by the generation-owned AdapterIndex. */
24
+ [endpointBind](context, admission) {
25
+ if (this.#events)
26
+ throw new Error(`Endpoint ${context.id} is already bound`);
27
+ this.#identity = Object.freeze({ id: context.id, adapter: context.name });
28
+ this.#events = context.use(endpointEventGatewayToken);
29
+ if (!admission) {
30
+ this.#admissionState = 'active';
31
+ return;
32
+ }
33
+ this.#admissionState = 'pending';
34
+ admission.onActivate(() => {
35
+ if (this.#admissionState !== 'pending')
36
+ return;
37
+ this.#admissionState = 'active';
38
+ admission.onDeactivate(() => {
39
+ this.#admissionState = 'retired';
40
+ this.#pendingEvents.length = 0;
41
+ });
42
+ const pending = this.#pendingEvents.splice(0);
43
+ for (const event of pending) {
44
+ void this.#events?.receive(event).catch(() => undefined);
45
+ }
46
+ });
47
+ }
48
+ /** The identity is available after AdapterDefinition.create returns. */
49
+ get identity() {
50
+ if (!this.#identity)
51
+ throw new Error('Endpoint is not bound to a runtime generation');
52
+ return this.#identity;
53
+ }
54
+ /** The only legal platform-to-framework event ingress. */
55
+ emit(name, payload) {
56
+ if (!this.#events || !this.#identity) {
57
+ throw new Error('Endpoint emitted before it was bound to a runtime generation');
58
+ }
59
+ const event = Object.freeze({
60
+ name,
61
+ payload,
62
+ endpoint: this.#identity,
63
+ client: this.client,
64
+ });
65
+ if (this.#admissionState === 'pending') {
66
+ if (this.#pendingEvents.length >= PRE_ADMISSION_EVENT_LIMIT) {
67
+ this.#pendingEvents.shift();
68
+ }
69
+ this.#pendingEvents.push(event);
70
+ return Promise.resolve(undefined);
71
+ }
72
+ if (this.#admissionState === 'retired')
73
+ return Promise.resolve(undefined);
74
+ return this.#events.receive(event);
75
+ }
76
+ /**
77
+ * Lossless native-event projection. Adapters call this before deriving
78
+ * message/notice/request/system events, including for unknown event kinds.
79
+ */
80
+ emitPlatform(name, event) {
81
+ return this.emit('platform.receive', Object.freeze({ name, event }));
82
+ }
83
+ }
84
+ /** @internal AdapterIndex binding hook; deliberately not exported by name. */
85
+ export function bindEndpoint(endpoint, context, admission) {
86
+ endpoint[endpointBind](context, admission);
87
+ }
88
+ /** @internal Cross-generation Endpoint check that survives ESM module re-evaluation. */
89
+ export function isEndpoint(value) {
90
+ if (!value || typeof value !== 'object')
91
+ return false;
92
+ const candidate = value;
93
+ return candidate[endpointBrand] === true
94
+ && typeof candidate[endpointBind] === 'function'
95
+ && typeof candidate.start === 'function'
96
+ && typeof candidate.open === 'function'
97
+ && typeof candidate.close === 'function'
98
+ && typeof candidate.stop === 'function'
99
+ && 'client' in candidate;
100
+ }
package/lib/index.d.ts CHANGED
@@ -1,12 +1,18 @@
1
+ /**
2
+ * Adapter authoring contracts and platform-neutral Endpoint capabilities.
3
+ * @module @zhin.js/adapter
4
+ */
1
5
  /** @internal 适配器 projection(AdapterIndex),框架内部机制,不承诺不 break。 */
2
6
  export * from './adapter-index.js';
3
7
  export * from './credentials.js';
4
8
  /** @public 用户侧创作面:`defineAdapter`(`adapters/` 约定目录默认导出,承诺 semver)。 */
5
9
  export * from './definition.js';
10
+ export * from './endpoint.js';
6
11
  export * from './endpoint-commands.js';
7
12
  export * from './endpoint-lifecycle.js';
8
13
  export * from './endpoint-management.js';
9
14
  export * from './endpoint-control.js';
10
15
  export * from './endpoint-content.js';
16
+ export * from './endpoint-client.js';
11
17
  export * from './provider.js';
12
18
  export { default } from './provider.js';
package/lib/index.js CHANGED
@@ -1,12 +1,18 @@
1
+ /**
2
+ * Adapter authoring contracts and platform-neutral Endpoint capabilities.
3
+ * @module @zhin.js/adapter
4
+ */
1
5
  /** @internal 适配器 projection(AdapterIndex),框架内部机制,不承诺不 break。 */
2
6
  export * from './adapter-index.js';
3
7
  export * from './credentials.js';
4
8
  /** @public 用户侧创作面:`defineAdapter`(`adapters/` 约定目录默认导出,承诺 semver)。 */
5
9
  export * from './definition.js';
10
+ export * from './endpoint.js';
6
11
  export * from './endpoint-commands.js';
7
12
  export * from './endpoint-lifecycle.js';
8
13
  export * from './endpoint-management.js';
9
14
  export * from './endpoint-control.js';
10
15
  export * from './endpoint-content.js';
16
+ export * from './endpoint-client.js';
11
17
  export * from './provider.js';
12
18
  export { default } from './provider.js';
package/lib/provider.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { AdapterIndex } from './adapter-index.js';
2
2
  export declare const adapterFeatureId: import("@zhin.js/plugin-runtime").FeatureId;
3
- declare const adapterFeature: Readonly<import("@zhin.js/feature-kit").FeatureProvider<import("./definition.js").AdapterDefinition<unknown>, AdapterIndex>>;
3
+ declare const adapterFeature: Readonly<import("@zhin.js/feature-kit").FeatureProvider<import("./definition.js").AdapterDefinition<unknown, unknown>, AdapterIndex>>;
4
4
  export { adapterFeature };
5
5
  export default adapterFeature;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Convention-based Adapter and Endpoint Feature for Zhin Plugin Runtime",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -18,15 +18,15 @@
18
18
  ],
19
19
  "dependencies": {
20
20
  "yaml": "^2.9.0",
21
- "@zhin.js/feature-kit": "1.0.12",
21
+ "@zhin.js/feature-kit": "1.0.13",
22
22
  "@zhin.js/im-contract": "1.0.4",
23
- "@zhin.js/logger": "1.0.76",
24
- "@zhin.js/plugin-runtime": "1.1.7"
23
+ "@zhin.js/logger": "1.0.77",
24
+ "@zhin.js/plugin-runtime": "1.1.8"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/node": "^26.1.2",
28
28
  "typescript": "^6.0.3",
29
- "@zhin.js/command": "1.0.15"
29
+ "@zhin.js/command": "1.0.16"
30
30
  },
31
31
  "zhin": {
32
32
  "protocol": 1,
@@ -17,9 +17,9 @@ import {
17
17
  type AdapterDefinition,
18
18
  type AdapterOperation,
19
19
  type AdapterSegmentPolicy,
20
- type EndpointInstance,
21
20
  type EndpointSendRequest,
22
21
  } from './definition.js';
22
+ import { bindEndpoint, isEndpoint, type Endpoint } from './endpoint.js';
23
23
  import {
24
24
  listEndpointManagementCapabilities,
25
25
  type EndpointManagementCapability,
@@ -57,7 +57,7 @@ export type AdapterEndpointPhase =
57
57
  'pending' | 'starting' | 'online';
58
58
 
59
59
  interface AdapterRecord extends AdapterDescriptor {
60
- readonly endpoint: EndpointInstance;
60
+ readonly endpoint: Endpoint;
61
61
  readonly segments?: AdapterSegmentPolicy;
62
62
  started: boolean;
63
63
  open: boolean;
@@ -157,15 +157,49 @@ export class AdapterIndex {
157
157
  return exact?.id ?? matches[0]?.id;
158
158
  }
159
159
 
160
- /**
161
- * Resolve a live EndpointInstance for Host-side side channels (reactions, etc.).
162
- */
163
- instance(adapter: string, endpointKey: string): EndpointInstance | undefined {
160
+ /** Resolve the framework-owned Endpoint for internal Host control ports. */
161
+ connection(adapter: string, endpointKey: string): Endpoint | undefined {
164
162
  const id = this.resolve(adapter, endpointKey);
165
163
  if (!id) return undefined;
166
164
  return this.#records.get(id)?.endpoint;
167
165
  }
168
166
 
167
+ /** Resolve the platform-native client owned by one active Endpoint. */
168
+ client<TClient>(adapter: string, endpointKey: string): TClient {
169
+ const id = this.resolve(adapter, endpointKey);
170
+ const record = id ? this.#records.get(id) : undefined;
171
+ if (!record) throw new Error(`Endpoint ${adapter}/${endpointKey} does not exist`);
172
+ if (!record.started || record.stopped) {
173
+ throw new Error(`Endpoint ${adapter}/${endpointKey} is not active`);
174
+ }
175
+ return record.endpoint.client as TClient;
176
+ }
177
+
178
+ /** Resolve the Client directly from a generation-stable CapabilityId. */
179
+ clientById<TClient>(id: CapabilityId): TClient {
180
+ const record = this.#records.get(id);
181
+ if (!record) throw new Error(`Unknown Adapter Endpoint: ${id}`);
182
+ if (!record.started || record.stopped) {
183
+ throw new Error(`Adapter Endpoint ${id} is not active`);
184
+ }
185
+ return record.endpoint.client as TClient;
186
+ }
187
+
188
+ /** Literal adapter name used by authoring-context type discrimination. */
189
+ clientAdapter(id: CapabilityId): string {
190
+ const record = this.#records.get(id);
191
+ if (!record) throw new Error(`Unknown Adapter Endpoint: ${id}`);
192
+ return record.endpoint.identity.adapter;
193
+ }
194
+
195
+ /** Optional Client lookup for cross-platform middleware and routing. */
196
+ findClient<TClient>(adapter: string, endpointKey: string): TClient | undefined {
197
+ const id = this.resolve(adapter, endpointKey);
198
+ const record = id ? this.#records.get(id) : undefined;
199
+ if (!record || !record.started || record.stopped) return undefined;
200
+ return record.endpoint.client as TClient;
201
+ }
202
+
169
203
  owner(id: CapabilityId): PluginId {
170
204
  const record = this.#records.get(id);
171
205
  if (!record) throw new Error(`Unknown Adapter Endpoint: ${id}`);
@@ -228,6 +262,11 @@ export class AdapterIndex {
228
262
  }
229
263
  signal.throwIfAborted();
230
264
  if (record.stopped) throw new Error(`Adapter Endpoint stopped during start: ${record.id}`);
265
+ if (record.endpoint.client === record.endpoint) {
266
+ throw new TypeError(
267
+ `Adapter Endpoint ${record.id} must expose a distinct platform client`,
268
+ );
269
+ }
231
270
  record.started = true;
232
271
  }
233
272
  } catch (error) {
@@ -335,7 +374,7 @@ function matchesEndpoint(
335
374
  || record.id.endsWith(`/${adapter}`)
336
375
  || record.owner === adapter
337
376
  || record.owner.endsWith(`/${adapter}`);
338
- // Live EndpointInstance.name is the bot runtime id (e.g. ICQQ uin). Host /
377
+ // The live Endpoint identity is the bot runtime id (e.g. ICQQ uin). Host /
339
378
  // activity-feedback resolve with that id; slot.localName alone is not enough
340
379
  // when multiple plugin instances share localName "icqq".
341
380
  const liveName = endpointLiveName(record.endpoint);
@@ -346,7 +385,7 @@ function matchesEndpoint(
346
385
  return adapterOk && endpointOk;
347
386
  }
348
387
 
349
- function endpointLiveName(endpoint: EndpointInstance): string | undefined {
388
+ function endpointLiveName(endpoint: Endpoint): string | undefined {
350
389
  const name = (endpoint as { readonly name?: unknown }).name;
351
390
  return typeof name === 'string' && name.length > 0 ? name : undefined;
352
391
  }
@@ -357,9 +396,9 @@ function endpointPhase(record: AdapterRecord): AdapterEndpointPhase {
357
396
  return 'pending';
358
397
  }
359
398
 
360
- function assertEndpoint(value: unknown, id: CapabilityId): asserts value is EndpointInstance {
361
- if (!value || typeof value !== 'object') {
362
- throw new TypeError(`Adapter ${id} create() must return an Endpoint instance`);
399
+ function assertEndpoint(value: unknown, id: CapabilityId): asserts value is Endpoint {
400
+ if (!isEndpoint(value)) {
401
+ throw new TypeError(`Adapter ${id} create() must return an Endpoint subclass`);
363
402
  }
364
403
  }
365
404
 
@@ -425,7 +464,7 @@ async function createEndpoint(
425
464
  admission: GenerationAdmissionGate,
426
465
  signal: AbortSignal,
427
466
  expansion?: EndpointExpansion,
428
- ): Promise<Readonly<{ endpoint: EndpointInstance; operations: readonly AdapterOperation[] }>> {
467
+ ): Promise<Readonly<{ endpoint: Endpoint; operations: readonly AdapterOperation[] }>> {
429
468
  const context = Object.freeze({
430
469
  ...createCapabilityContext(snapshot, slot.owner, admission, signal),
431
470
  ...(expansion?.config ? { config: expansion.config } : {}),
@@ -435,6 +474,7 @@ async function createEndpoint(
435
474
  const operations = resolveAdapterOperations(slot.definition, context);
436
475
  const endpoint = await slot.definition.create(context);
437
476
  assertEndpoint(endpoint, expansion?.id ?? slot.id);
477
+ bindEndpoint(endpoint, context, admission);
438
478
  if (slot.definition.capabilities.includes('outbound') && typeof endpoint.send !== 'function') {
439
479
  throw new TypeError(
440
480
  `Adapter Endpoint ${String(expansion?.id ?? slot.id)} declares outbound but send() is missing`,
package/src/definition.ts CHANGED
@@ -1,3 +1,7 @@
1
+ /**
2
+ * Adapter authoring API consumed from `zhin.js/adapter`.
3
+ * @module zhin.js/adapter
4
+ */
1
5
  import type { CapabilityId } from '@zhin.js/plugin-runtime';
2
6
  import type { CapabilityContext } from '@zhin.js/feature-kit';
3
7
  import type {
@@ -5,9 +9,19 @@ import type {
5
9
  EndpointCapabilities,
6
10
  EndpointOperation,
7
11
  } from '@zhin.js/im-contract';
8
- import type { EndpointManagement } from './endpoint-management.js';
9
- import type { EndpointControl } from './endpoint-control.js';
10
- import type { EndpointContentPort } from './endpoint-content.js';
12
+ import type { Endpoint } from './endpoint.js';
13
+
14
+ export { Endpoint } from './endpoint.js';
15
+ export type {
16
+ EndpointEvent,
17
+ EndpointIdentity,
18
+ PlatformEvent,
19
+ } from './endpoint.js';
20
+ export { defineEndpointClient } from './endpoint-client.js';
21
+ export type {
22
+ EndpointClientContext,
23
+ EndpointClientToken,
24
+ } from './endpoint-client.js';
11
25
 
12
26
  const adapterBrand = 'zhin.adapter/1' as const;
13
27
 
@@ -35,25 +49,9 @@ export interface EndpointSendRequest {
35
49
  readonly payload: unknown;
36
50
  }
37
51
 
38
- export interface EndpointInstance {
39
- /** Optional platform-neutral Console/Host management surface. */
40
- readonly management?: EndpointManagement;
41
- /** Optional platform-neutral control surface for existing messages. */
42
- readonly control?: EndpointControl;
43
- /** Optional canonical resolver for message, merged-forward and media references. */
44
- readonly content?: EndpointContentPort;
45
- /** Required readiness; must observe abort and settle before rollback returns. */
46
- start?(signal: AbortSignal): void | Promise<void>;
47
- /** Opens Endpoint-local flow behind the candidate generation admission gate. */
48
- open?(): void;
49
- /** Stops new inbound events while preserving in-flight work. */
50
- close?(): void | Promise<void>;
51
- /** Releases transport resources. Calls must be idempotent. */
52
- stop?(): void | Promise<void>;
53
- /** Platform message id. Core wraps it in the canonical MessageRef/DeliveryReceipt. */
54
- send?(request: EndpointSendRequest): string | Promise<string>;
55
- }
56
-
52
+ /**
53
+ * Generation-owned construction context for one runtime Endpoint.
54
+ */
57
55
  export interface AdapterContext<TConfig = unknown> extends CapabilityContext<TConfig> {
58
56
  readonly id: CapabilityId;
59
57
  readonly name: string;
@@ -100,7 +98,8 @@ const OUTBOUND_MEDIA_FORMS: readonly AdapterOutboundMedia[] = [
100
98
 
101
99
  const ADAPTER_OPERATIONS: readonly AdapterOperation[] = ['recall', 'edit', 'reaction', 'typing'];
102
100
 
103
- export interface AdapterDefinition<TConfig = unknown> {
101
+ export interface AdapterDefinition<TConfig = unknown, TClient = unknown> {
102
+ /** @internal Runtime feature brand. */
104
103
  readonly $feature: typeof adapterBrand;
105
104
  readonly capabilities: readonly AdapterCapability[];
106
105
  /**
@@ -113,7 +112,7 @@ export interface AdapterDefinition<TConfig = unknown> {
113
112
  readonly segments?: AdapterSegmentPolicy;
114
113
  create(
115
114
  context: AdapterContext<TConfig>,
116
- ): EndpointInstance | Promise<EndpointInstance>;
115
+ ): Endpoint<TClient> | Promise<Endpoint<TClient>>;
117
116
  }
118
117
 
119
118
  declare module '@zhin.js/plugin-runtime' {
@@ -125,6 +124,23 @@ declare module '@zhin.js/plugin-runtime' {
125
124
  }
126
125
  }
127
126
 
127
+ /**
128
+ * Define an Adapter module for the `adapters/` convention directory.
129
+ *
130
+ * The returned definition is immutable and declares capabilities before an
131
+ * Endpoint is created, so Runtime admission can fail closed.
132
+ *
133
+ * @public
134
+ * @example
135
+ * ```ts
136
+ * import { defineAdapter } from 'zhin.js/adapter';
137
+ *
138
+ * export default defineAdapter({
139
+ * capabilities: ['inbound', 'outbound'],
140
+ * create: () => new MyPlatformEndpoint(),
141
+ * });
142
+ * ```
143
+ */
128
144
  export function defineAdapter<TConfig = unknown>(
129
145
  definition: Omit<AdapterDefinition<TConfig>, '$feature'>,
130
146
  ): Readonly<AdapterDefinition<TConfig>> {
@@ -151,7 +167,10 @@ export function defineAdapter<TConfig = unknown>(
151
167
  });
152
168
  }
153
169
 
154
- /** Converts the definition's compact authoring form into the public contract. */
170
+ /**
171
+ * Converts the definition's compact authoring form into the Runtime contract.
172
+ * @internal Adapter projection helper.
173
+ */
155
174
  export function endpointCapabilitiesOf(
156
175
  definition: Pick<AdapterDefinition, 'capabilities' | 'operations'>,
157
176
  resolvedOperations?: readonly AdapterOperation[],
@@ -172,7 +191,10 @@ export function endpointCapabilitiesOf(
172
191
  });
173
192
  }
174
193
 
175
- /** Resolve and validate the operation declaration for one concrete Endpoint. */
194
+ /**
195
+ * Resolve and validate the operation declaration for one concrete Endpoint.
196
+ * @internal Adapter projection helper.
197
+ */
176
198
  export function resolveAdapterOperations<TConfig>(
177
199
  definition: Pick<AdapterDefinition<TConfig>, 'operations'>,
178
200
  context: AdapterContext<TConfig>,
@@ -251,6 +273,7 @@ function normalizeSegmentPolicy(
251
273
  });
252
274
  }
253
275
 
276
+ /** @internal Runtime validation for convention-discovered modules. */
254
277
  export function parseAdapterDefinition(value: unknown): AdapterDefinition {
255
278
  if (!value || typeof value !== 'object') throw invalidAdapter();
256
279
  const definition = value as Partial<AdapterDefinition>;