@kubun/connector 0.6.0 → 0.8.0
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/lib/cluster-utils.d.ts +6 -0
- package/lib/cluster-utils.js +1 -0
- package/lib/index.d.ts +3 -7
- package/lib/index.js +1 -1
- package/lib/models/cluster.d.ts +4 -0
- package/lib/models/cluster.js +1 -0
- package/lib/models/external-entity.d.ts +40 -2
- package/lib/types.d.ts +75 -2
- package/package.json +4 -12
- package/lib/manager.d.ts +0 -37
- package/lib/manager.js +0 -1
- package/lib/oauth/routes.d.ts +0 -19
- package/lib/oauth/routes.js +0 -1
- package/lib/oauth/types.d.ts +0 -35
- package/lib/oauth/types.js +0 -1
- package/lib/protocol.d.ts +0 -337
- package/lib/protocol.js +0 -1
- package/lib/registry.d.ts +0 -10
- package/lib/registry.js +0 -1
- package/lib/sync/boundary.d.ts +0 -4
- package/lib/sync/boundary.js +0 -1
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ClustersRecord } from '@kubun/protocol';
|
|
2
|
+
/**
|
|
3
|
+
* Resolve a model name to its content-addressed model ID across clusters.
|
|
4
|
+
* Use this in providers to tag EntityBatch.modelID at module level.
|
|
5
|
+
*/
|
|
6
|
+
export declare function resolveModelID(clusters: ClustersRecord, modelName: string): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function resolveModelID(e,o){for(let r of Object.values(e)){let e=r.models.findIndex(e=>e.name===o);if(-1!==e){for(let[o,t]of Object.entries(r.record))if(t===e)return o}}throw Error(`Model "${o}" not found in any cluster`)}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { resolveModelID } from './cluster-utils.js';
|
|
2
|
+
export { connectorClusters, connectorModelIDs } from './models/cluster.js';
|
|
2
3
|
export { externalEntityDefinition } from './models/external-entity.js';
|
|
3
|
-
export {
|
|
4
|
-
export type { Credential, CredentialProvider, OAuthProviderDefinition, OAuthState, TokenResponse, } from './oauth/types.js';
|
|
5
|
-
export { type ConnectorActionParams, type ConnectorActionResult, type ConnectorAuthCompleteParams, type ConnectorAuthCompleteResult, type ConnectorAuthURLParams, type ConnectorAuthURLResult, type ConnectorProtocol, type ConnectorPushCompleteParams, type ConnectorPushCompleteResult, type ConnectorPushParams, type ConnectorPushResult, type ConnectorStatusParams, type ConnectorStatusResult, type ConnectorSyncParams, type ConnectorSyncReceive, type ConnectorSyncResult, connectorProtocol, } from './protocol.js';
|
|
6
|
-
export { ConnectorRegistry } from './registry.js';
|
|
7
|
-
export { computeEffectiveBoundary, isWithinBoundary, parseDuration, } from './sync/boundary.js';
|
|
8
|
-
export type { ActionDefinition, ActionResult, ClientDataProvider, ClientProviderFactory, ConnectorAuth, ConnectorDefinition, DataProvider, DeviceConnectorAuth, DevicePermission, EntityBatch, EntityRecord, FetchChangesParams, FetchParams, OAuthConnectorAuth, ServerDataProvider, ServerProviderFactory, SyncBoundary, SyncCompleteEvent, SyncErrorEvent, SyncEvent, SyncProgressEvent, UserSyncPreferences, WebhookParams, WebhookRegistration, } from './types.js';
|
|
4
|
+
export type { ActionDefinition, ActionHandler, ActionHandlerFactory, ActionResult, ClientDataProvider, ClientProviderFactory, ConnectorAuth, ConnectorDefinition, ConnectorSyncEventPayload, Credential, CredentialProvider, DataProvider, DeviceConnectorAuth, DevicePermission, EntityBatch, EntityRecord, FetchChangesParams, FetchParams, OAuthConnectorAuth, OAuthProviderDefinition, OAuthState, ServerDataProvider, ServerProviderFactory, SyncBoundary, SyncCompleteEvent, SyncErrorEvent, SyncEvent, SyncProgressEvent, SyncState, SyncStateEntry, SyncStateStore, SyncStatus, TokenResponse, UserSyncPreferences, WebhookParams, WebhookRegistration, } from './types.js';
|
package/lib/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{
|
|
1
|
+
export{resolveModelID}from"./cluster-utils.js";export{connectorClusters,connectorModelIDs}from"./models/cluster.js";export{externalEntityDefinition}from"./models/external-entity.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{GraphBuilder as t,GraphModel as e}from"@kubun/protocol";import{externalEntityDefinition as o}from"./external-entity.js";let r=new t;r.add(o);export const connectorClusters=r.build();let n=e.fromClusters({clusters:connectorClusters});export const connectorModelIDs={ExternalEntity:n.aliases.ExternalEntity};
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { DocumentModelInput } from '@kubun/protocol';
|
|
2
1
|
/**
|
|
3
2
|
* ExternalEntity is the base interface for documents synced from external services.
|
|
4
3
|
*
|
|
@@ -8,4 +7,43 @@ import type { DocumentModelInput } from '@kubun/protocol';
|
|
|
8
7
|
* - sourceURL: Optional URL back to the entity in the source service
|
|
9
8
|
* - lastSyncedAt: Timestamp of when this entity was last synced
|
|
10
9
|
*/
|
|
11
|
-
export declare const externalEntityDefinition:
|
|
10
|
+
export declare const externalEntityDefinition: {
|
|
11
|
+
readonly name: "ExternalEntity";
|
|
12
|
+
readonly behavior: "interface";
|
|
13
|
+
readonly schema: {
|
|
14
|
+
readonly type: "object";
|
|
15
|
+
readonly properties: {
|
|
16
|
+
readonly sourceService: {
|
|
17
|
+
readonly $ref: "#/definitions/SourceService";
|
|
18
|
+
};
|
|
19
|
+
readonly sourceID: {
|
|
20
|
+
readonly $ref: "#/definitions/SourceID";
|
|
21
|
+
};
|
|
22
|
+
readonly sourceURL: {
|
|
23
|
+
readonly $ref: "#/definitions/SourceURL";
|
|
24
|
+
};
|
|
25
|
+
readonly lastSyncedAt: {
|
|
26
|
+
readonly $ref: "#/definitions/DateTime";
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
readonly required: ["sourceService", "sourceID", "lastSyncedAt"];
|
|
30
|
+
readonly additionalProperties: true;
|
|
31
|
+
readonly definitions: {
|
|
32
|
+
readonly SourceService: {
|
|
33
|
+
readonly type: "string";
|
|
34
|
+
readonly title: "SourceService";
|
|
35
|
+
readonly minLength: 1;
|
|
36
|
+
readonly maxLength: 50;
|
|
37
|
+
};
|
|
38
|
+
readonly SourceID: {
|
|
39
|
+
readonly type: "string";
|
|
40
|
+
readonly title: "SourceID";
|
|
41
|
+
readonly minLength: 1;
|
|
42
|
+
readonly maxLength: 500;
|
|
43
|
+
};
|
|
44
|
+
readonly SourceURL: import("json-schema-typed").JSONSchema.String;
|
|
45
|
+
readonly DateTime: import("json-schema-typed").JSONSchema.String;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
readonly fieldsMeta: {};
|
|
49
|
+
};
|
package/lib/types.d.ts
CHANGED
|
@@ -1,5 +1,58 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ClustersRecord } from '@kubun/protocol';
|
|
2
2
|
import type { JSONSchema } from 'json-schema-typed';
|
|
3
|
+
export type OAuthProviderDefinition = {
|
|
4
|
+
name: string;
|
|
5
|
+
authorizationEndpoint: string;
|
|
6
|
+
tokenEndpoint: string;
|
|
7
|
+
baseScopes?: Array<string>;
|
|
8
|
+
clientID?: string;
|
|
9
|
+
clientSecret?: string;
|
|
10
|
+
};
|
|
11
|
+
export type Credential = {
|
|
12
|
+
accessToken: string;
|
|
13
|
+
refreshToken?: string;
|
|
14
|
+
expiresAt?: Date;
|
|
15
|
+
scopes: Array<string>;
|
|
16
|
+
accountLabel?: string;
|
|
17
|
+
metadata?: Record<string, unknown>;
|
|
18
|
+
};
|
|
19
|
+
export type CredentialProvider = {
|
|
20
|
+
get(providerName: string, ownerDID: string): Promise<Credential | null>;
|
|
21
|
+
set(providerName: string, ownerDID: string, credential: Credential): Promise<void>;
|
|
22
|
+
delete(providerName: string, ownerDID: string): Promise<void>;
|
|
23
|
+
};
|
|
24
|
+
export type OAuthState = {
|
|
25
|
+
provider: string;
|
|
26
|
+
redirectURL: string;
|
|
27
|
+
connectors?: Array<string>;
|
|
28
|
+
nonce: string;
|
|
29
|
+
createdAt: number;
|
|
30
|
+
};
|
|
31
|
+
export type TokenResponse = {
|
|
32
|
+
access_token: string;
|
|
33
|
+
refresh_token?: string;
|
|
34
|
+
expires_in?: number;
|
|
35
|
+
token_type: string;
|
|
36
|
+
scope?: string;
|
|
37
|
+
};
|
|
38
|
+
export type SyncStatus = 'idle' | 'syncing' | 'error';
|
|
39
|
+
export type SyncState = {
|
|
40
|
+
checkpoint: unknown;
|
|
41
|
+
lastSyncedAt: string;
|
|
42
|
+
entityCount: number;
|
|
43
|
+
status: SyncStatus;
|
|
44
|
+
error?: string;
|
|
45
|
+
};
|
|
46
|
+
export type SyncStateEntry = SyncState & {
|
|
47
|
+
connectorName: string;
|
|
48
|
+
ownerDID: string;
|
|
49
|
+
};
|
|
50
|
+
export type SyncStateStore = {
|
|
51
|
+
get(connectorName: string, ownerDID: string): Promise<SyncState | null>;
|
|
52
|
+
set(connectorName: string, ownerDID: string, state: SyncState): Promise<void>;
|
|
53
|
+
delete(connectorName: string, ownerDID: string): Promise<void>;
|
|
54
|
+
listForConnector(connectorName: string): Promise<Array<SyncStateEntry>>;
|
|
55
|
+
};
|
|
3
56
|
export type SyncBoundary = {
|
|
4
57
|
maxAge?: string;
|
|
5
58
|
since?: string;
|
|
@@ -16,6 +69,7 @@ export type EntityRecord = Record<string, unknown> & {
|
|
|
16
69
|
sourceID: string;
|
|
17
70
|
};
|
|
18
71
|
export type EntityBatch = {
|
|
72
|
+
modelID: string;
|
|
19
73
|
entities: Array<EntityRecord>;
|
|
20
74
|
deleted?: Array<{
|
|
21
75
|
sourceService: string;
|
|
@@ -59,6 +113,7 @@ export type DevicePermission = {
|
|
|
59
113
|
export type OAuthConnectorAuth = {
|
|
60
114
|
provider: string;
|
|
61
115
|
scopes: Array<string>;
|
|
116
|
+
writeScopes?: Array<string>;
|
|
62
117
|
};
|
|
63
118
|
export type DeviceConnectorAuth = {
|
|
64
119
|
provider: 'device';
|
|
@@ -79,6 +134,13 @@ export type ActionResult = {
|
|
|
79
134
|
documentID?: string;
|
|
80
135
|
deletedDocumentID?: string;
|
|
81
136
|
};
|
|
137
|
+
export type ActionHandler = {
|
|
138
|
+
create(model: string, params: Record<string, unknown>): Promise<EntityRecord>;
|
|
139
|
+
update(model: string, sourceID: string, params: Record<string, unknown>): Promise<EntityRecord>;
|
|
140
|
+
};
|
|
141
|
+
export type ActionHandlerFactory = (config: {
|
|
142
|
+
credential: Credential;
|
|
143
|
+
}) => ActionHandler;
|
|
82
144
|
export type ServerProviderFactory = (config: {
|
|
83
145
|
credential: unknown;
|
|
84
146
|
boundary: SyncBoundary;
|
|
@@ -89,9 +151,10 @@ export type ClientProviderFactory = (config: {
|
|
|
89
151
|
export type ConnectorDefinition = {
|
|
90
152
|
name: string;
|
|
91
153
|
version: string;
|
|
92
|
-
|
|
154
|
+
clusters: ClustersRecord;
|
|
93
155
|
syncStrategies: Array<'polling' | 'webhook' | 'on-demand'>;
|
|
94
156
|
actions?: Array<ActionDefinition>;
|
|
157
|
+
actionHandler?: ActionHandlerFactory;
|
|
95
158
|
serverProvider?: ServerProviderFactory;
|
|
96
159
|
clientProvider?: ClientProviderFactory;
|
|
97
160
|
graphql?: {
|
|
@@ -121,3 +184,13 @@ export type SyncErrorEvent = {
|
|
|
121
184
|
error: string;
|
|
122
185
|
};
|
|
123
186
|
export type SyncEvent = SyncProgressEvent | SyncCompleteEvent | SyncErrorEvent;
|
|
187
|
+
export type ConnectorSyncEventPayload = {
|
|
188
|
+
type: 'started' | 'progress' | 'completed' | 'error';
|
|
189
|
+
connectorName: string;
|
|
190
|
+
entitiesProcessed?: number;
|
|
191
|
+
entitiesFailed?: number;
|
|
192
|
+
totalProcessed?: number;
|
|
193
|
+
totalFailed?: number;
|
|
194
|
+
duration?: number;
|
|
195
|
+
error?: string;
|
|
196
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubun/connector",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"license": "see LICENSE.md",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"type": "module",
|
|
@@ -15,25 +15,17 @@
|
|
|
15
15
|
],
|
|
16
16
|
"sideEffects": false,
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@enkaku/event": "^0.13.0",
|
|
19
|
-
"@enkaku/protocol": "^0.13.0",
|
|
20
|
-
"@enkaku/schema": "^0.13.0",
|
|
21
|
-
"hono": "^4.11.9",
|
|
22
18
|
"json-schema-typed": "^8.0.2",
|
|
23
|
-
"@kubun/
|
|
24
|
-
"@kubun/scalars": "^0.
|
|
25
|
-
"@kubun/models": "^0.6.0",
|
|
26
|
-
"@kubun/protocol": "^0.6.0",
|
|
27
|
-
"@kubun/logger": "^0.6.0"
|
|
19
|
+
"@kubun/protocol": "^0.8.0",
|
|
20
|
+
"@kubun/scalars": "^0.8.0"
|
|
28
21
|
},
|
|
29
|
-
"devDependencies": {},
|
|
30
22
|
"scripts": {
|
|
31
23
|
"build:clean": "del lib",
|
|
32
24
|
"build:js": "swc src -d ./lib --config-file ../../swc.json --strip-leading-paths",
|
|
33
25
|
"build:types": "tsc --emitDeclarationOnly --skipLibCheck",
|
|
34
26
|
"build:types:ci": "tsc --emitDeclarationOnly --declarationMap false",
|
|
35
27
|
"build": "pnpm run build:clean && pnpm run build:js && pnpm run build:types",
|
|
36
|
-
"test:types": "tsc --noEmit",
|
|
28
|
+
"test:types": "tsc --noEmit -p tsconfig.test.json",
|
|
37
29
|
"test:unit": "vitest run",
|
|
38
30
|
"test": "pnpm run test:types && pnpm run test:unit"
|
|
39
31
|
}
|
package/lib/manager.d.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import type { Logger } from '@kubun/logger';
|
|
2
|
-
import type { ConnectorRegistry } from './registry.js';
|
|
3
|
-
import type { SyncBoundary } from './types.js';
|
|
4
|
-
export type ConnectorActivatedEvent = {
|
|
5
|
-
connectorName: string;
|
|
6
|
-
graphID: string;
|
|
7
|
-
};
|
|
8
|
-
export type ConnectorDeactivatedEvent = {
|
|
9
|
-
connectorName: string;
|
|
10
|
-
};
|
|
11
|
-
export type ConnectorManagerEvents = {
|
|
12
|
-
'connector:activated': ConnectorActivatedEvent;
|
|
13
|
-
'connector:deactivated': ConnectorDeactivatedEvent;
|
|
14
|
-
};
|
|
15
|
-
export type ConnectorManagerParams = {
|
|
16
|
-
registry: ConnectorRegistry;
|
|
17
|
-
logger: Logger;
|
|
18
|
-
defaults?: {
|
|
19
|
-
boundary?: SyncBoundary;
|
|
20
|
-
pollingInterval?: string;
|
|
21
|
-
};
|
|
22
|
-
};
|
|
23
|
-
export declare class ConnectorManager {
|
|
24
|
-
#private;
|
|
25
|
-
constructor(params: ConnectorManagerParams);
|
|
26
|
-
get defaults(): {
|
|
27
|
-
boundary: SyncBoundary;
|
|
28
|
-
pollingInterval: string;
|
|
29
|
-
};
|
|
30
|
-
activateForGraph(graphID: string, connectorNames: Array<string>): void;
|
|
31
|
-
deactivateForGraph(graphID: string, connectorNames: Array<string>): void;
|
|
32
|
-
isActive(connectorName: string): boolean;
|
|
33
|
-
getActiveGraphs(connectorName: string): Array<string>;
|
|
34
|
-
listActive(): Array<string>;
|
|
35
|
-
onConnectorActivated(callback: (event: ConnectorActivatedEvent) => void): () => void;
|
|
36
|
-
onConnectorDeactivated(callback: (event: ConnectorDeactivatedEvent) => void): () => void;
|
|
37
|
-
}
|
package/lib/manager.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{EventEmitter as e}from"@enkaku/event";export class ConnectorManager{#e;#t;#r=new Map;#a=new e;#i;constructor(e){this.#e=e.registry,this.#t=e.logger,this.#i={boundary:e.defaults?.boundary??{maxAge:"P90D"},pollingInterval:e.defaults?.pollingInterval??"PT5M"}}get defaults(){return this.#i}activateForGraph(e,t){for(let r of t){if(!this.#e.has(r))throw Error(`Connector "${r}" is not registered`);let t=this.#r.get(r),a=null==t||0===t.size;null==t&&(t=new Set,this.#r.set(r,t)),t.add(e),a&&(this.#t.info("Connector {name} activated by graph {graphID}",{name:r,graphID:e}),this.#a.emit("connector:activated",{connectorName:r,graphID:e}))}}deactivateForGraph(e,t){for(let r of t){let t=this.#r.get(r);null!=t&&(t.delete(e),0===t.size&&(this.#r.delete(r),this.#t.info("Connector {name} deactivated",{name:r}),this.#a.emit("connector:deactivated",{connectorName:r})))}}isActive(e){let t=this.#r.get(e);return null!=t&&t.size>0}getActiveGraphs(e){let t=this.#r.get(e);return t?Array.from(t):[]}listActive(){return Array.from(this.#r.keys()).filter(e=>this.isActive(e))}onConnectorActivated(e){return this.#a.on("connector:activated",e)}onConnectorDeactivated(e){return this.#a.on("connector:deactivated",e)}}
|
package/lib/oauth/routes.d.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { Hono } from 'hono';
|
|
2
|
-
import type { Credential, OAuthProviderDefinition, OAuthState } from './types.js';
|
|
3
|
-
export type StateStore = {
|
|
4
|
-
set(nonce: string, state: OAuthState): Promise<void> | void;
|
|
5
|
-
get(nonce: string): Promise<OAuthState | null> | OAuthState | null;
|
|
6
|
-
delete(nonce: string): Promise<void> | void;
|
|
7
|
-
};
|
|
8
|
-
export type OAuthRoutesParams = {
|
|
9
|
-
providers: Array<OAuthProviderDefinition>;
|
|
10
|
-
stateStore: StateStore;
|
|
11
|
-
/** Maximum age of OAuth state in milliseconds (default: 10 minutes) */
|
|
12
|
-
stateTTL?: number;
|
|
13
|
-
onTokenReceived?: (params: {
|
|
14
|
-
provider: OAuthProviderDefinition;
|
|
15
|
-
state: OAuthState;
|
|
16
|
-
credential: Credential;
|
|
17
|
-
}) => Promise<void> | void;
|
|
18
|
-
};
|
|
19
|
-
export declare function createOAuthRoutes(params: OAuthRoutesParams): Hono;
|
package/lib/oauth/routes.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{Hono as e}from"hono";export function createOAuthRoutes(r){let{providers:t,stateStore:a,stateTTL:i=6e5,onTokenReceived:o}=r,n=new Map(t.map(e=>[e.name,e])),s=new e;return s.get("/auth/:provider/start",async e=>{let r,t=e.req.param("provider"),i=n.get(t);if(!i)return e.json({error:`Unknown provider: ${t}`},404);if(!i.clientID)return e.json({error:`Provider ${t} is not configured with a clientID`},500);let o=e.req.query("redirect_uri");if(!o)return e.json({error:"redirect_uri is required"},400);let s=e.req.query("scope"),c=s?s.split(","):[],d=[...i.baseScopes??[],...c],p=(r=new Uint8Array(32),crypto.getRandomValues(r),Array.from(r,e=>e.toString(16).padStart(2,"0")).join("")),u={provider:t,redirectURL:o,nonce:p,createdAt:Date.now()};await a.set(p,u);let l=new URL(i.authorizationEndpoint);return l.searchParams.set("client_id",i.clientID),l.searchParams.set("redirect_uri",o),l.searchParams.set("response_type","code"),l.searchParams.set("state",p),d.length>0&&l.searchParams.set("scope",d.join(" ")),e.redirect(l.toString())}),s.get("/auth/:provider/callback",async e=>{let r=e.req.param("provider"),t=n.get(r);if(!t)return e.json({error:`Unknown provider: ${r}`},404);let s=e.req.query("code"),c=e.req.query("state"),d=e.req.query("error");if(d){let r=e.req.query("error_description");return e.json({error:d,error_description:r},400)}if(!s||!c)return e.json({error:"Missing code or state"},400);let p=await a.get(c);if(!p)return e.json({error:"Invalid or expired state"},400);if(Date.now()-p.createdAt>i)return await a.delete(c),e.json({error:"State has expired"},400);if(p.provider!==r)return e.json({error:"State provider mismatch"},400);let u=await fetch(t.tokenEndpoint,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",code:s,redirect_uri:p.redirectURL,client_id:t.clientID??"",client_secret:t.clientSecret??""})});if(!u.ok){let r=await u.text();return e.json({error:"Token exchange failed",details:r},500)}let l=await u.json(),h={accessToken:l.access_token,refreshToken:l.refresh_token,expiresAt:l.expires_in?new Date(Date.now()+1e3*l.expires_in):void 0,scopes:l.scope?l.scope.split(" "):[]};await a.delete(c),o&&await o({provider:t,state:p,credential:h});let w=new URL(p.redirectURL);return w.searchParams.set("oauth_status","success"),w.searchParams.set("provider",r),e.redirect(w.toString())}),s}
|
package/lib/oauth/types.d.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
export type OAuthProviderDefinition = {
|
|
2
|
-
name: string;
|
|
3
|
-
authorizationEndpoint: string;
|
|
4
|
-
tokenEndpoint: string;
|
|
5
|
-
baseScopes?: Array<string>;
|
|
6
|
-
clientID?: string;
|
|
7
|
-
clientSecret?: string;
|
|
8
|
-
};
|
|
9
|
-
export type Credential = {
|
|
10
|
-
accessToken: string;
|
|
11
|
-
refreshToken?: string;
|
|
12
|
-
expiresAt?: Date;
|
|
13
|
-
scopes: Array<string>;
|
|
14
|
-
accountLabel?: string;
|
|
15
|
-
metadata?: Record<string, unknown>;
|
|
16
|
-
};
|
|
17
|
-
export type CredentialProvider = {
|
|
18
|
-
get(providerName: string, ownerDID: string): Promise<Credential | null>;
|
|
19
|
-
set(providerName: string, ownerDID: string, credential: Credential): Promise<void>;
|
|
20
|
-
delete(providerName: string, ownerDID: string): Promise<void>;
|
|
21
|
-
};
|
|
22
|
-
export type OAuthState = {
|
|
23
|
-
provider: string;
|
|
24
|
-
redirectURL: string;
|
|
25
|
-
connectors?: Array<string>;
|
|
26
|
-
nonce: string;
|
|
27
|
-
createdAt: number;
|
|
28
|
-
};
|
|
29
|
-
export type TokenResponse = {
|
|
30
|
-
access_token: string;
|
|
31
|
-
refresh_token?: string;
|
|
32
|
-
expires_in?: number;
|
|
33
|
-
token_type: string;
|
|
34
|
-
scope?: string;
|
|
35
|
-
};
|
package/lib/oauth/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export{};
|
package/lib/protocol.d.ts
DELETED
|
@@ -1,337 +0,0 @@
|
|
|
1
|
-
import type { FromSchema } from '@enkaku/schema';
|
|
2
|
-
export declare const connectorProtocol: {
|
|
3
|
-
readonly 'connector/sync': {
|
|
4
|
-
readonly type: "stream";
|
|
5
|
-
readonly param: {
|
|
6
|
-
readonly type: "object";
|
|
7
|
-
readonly properties: {
|
|
8
|
-
readonly connector: {
|
|
9
|
-
readonly type: "string";
|
|
10
|
-
readonly description: "Connector name";
|
|
11
|
-
};
|
|
12
|
-
readonly full: {
|
|
13
|
-
readonly type: "boolean";
|
|
14
|
-
readonly description: "Force full sync instead of incremental";
|
|
15
|
-
};
|
|
16
|
-
};
|
|
17
|
-
readonly required: readonly ["connector"];
|
|
18
|
-
readonly additionalProperties: false;
|
|
19
|
-
};
|
|
20
|
-
readonly receive: {
|
|
21
|
-
readonly type: "object";
|
|
22
|
-
readonly properties: {
|
|
23
|
-
readonly type: {
|
|
24
|
-
readonly type: "string";
|
|
25
|
-
readonly enum: readonly ["progress", "entity", "error"];
|
|
26
|
-
};
|
|
27
|
-
readonly connectorName: {
|
|
28
|
-
readonly type: "string";
|
|
29
|
-
};
|
|
30
|
-
readonly phase: {
|
|
31
|
-
readonly type: "string";
|
|
32
|
-
readonly enum: readonly ["initial", "incremental"];
|
|
33
|
-
};
|
|
34
|
-
readonly entitiesProcessed: {
|
|
35
|
-
readonly type: "number";
|
|
36
|
-
};
|
|
37
|
-
readonly entitiesFailed: {
|
|
38
|
-
readonly type: "number";
|
|
39
|
-
};
|
|
40
|
-
readonly currentBatch: {
|
|
41
|
-
readonly type: "number";
|
|
42
|
-
};
|
|
43
|
-
readonly checkpoint: {};
|
|
44
|
-
readonly entity: {
|
|
45
|
-
readonly type: "object";
|
|
46
|
-
};
|
|
47
|
-
readonly documentID: {
|
|
48
|
-
readonly type: "string";
|
|
49
|
-
};
|
|
50
|
-
readonly error: {
|
|
51
|
-
readonly type: "string";
|
|
52
|
-
};
|
|
53
|
-
};
|
|
54
|
-
readonly additionalProperties: false;
|
|
55
|
-
};
|
|
56
|
-
readonly result: {
|
|
57
|
-
readonly type: "object";
|
|
58
|
-
readonly properties: {
|
|
59
|
-
readonly success: {
|
|
60
|
-
readonly type: "boolean";
|
|
61
|
-
};
|
|
62
|
-
readonly totalProcessed: {
|
|
63
|
-
readonly type: "number";
|
|
64
|
-
};
|
|
65
|
-
readonly totalFailed: {
|
|
66
|
-
readonly type: "number";
|
|
67
|
-
};
|
|
68
|
-
readonly duration: {
|
|
69
|
-
readonly type: "number";
|
|
70
|
-
};
|
|
71
|
-
};
|
|
72
|
-
readonly required: readonly ["success", "totalProcessed", "totalFailed", "duration"];
|
|
73
|
-
readonly additionalProperties: false;
|
|
74
|
-
};
|
|
75
|
-
};
|
|
76
|
-
readonly 'connector/status': {
|
|
77
|
-
readonly type: "request";
|
|
78
|
-
readonly param: {
|
|
79
|
-
readonly type: "object";
|
|
80
|
-
readonly properties: {
|
|
81
|
-
readonly connector: {
|
|
82
|
-
readonly type: "string";
|
|
83
|
-
readonly description: "Optional connector name to filter";
|
|
84
|
-
};
|
|
85
|
-
};
|
|
86
|
-
readonly additionalProperties: false;
|
|
87
|
-
};
|
|
88
|
-
readonly result: {
|
|
89
|
-
readonly type: "object";
|
|
90
|
-
readonly properties: {
|
|
91
|
-
readonly connectors: {
|
|
92
|
-
readonly type: "array";
|
|
93
|
-
readonly items: {
|
|
94
|
-
readonly type: "object";
|
|
95
|
-
readonly properties: {
|
|
96
|
-
readonly name: {
|
|
97
|
-
readonly type: "string";
|
|
98
|
-
};
|
|
99
|
-
readonly lastSyncedAt: {
|
|
100
|
-
readonly type: "string";
|
|
101
|
-
};
|
|
102
|
-
readonly entityCount: {
|
|
103
|
-
readonly type: "number";
|
|
104
|
-
};
|
|
105
|
-
readonly status: {
|
|
106
|
-
readonly type: "string";
|
|
107
|
-
readonly enum: readonly ["active", "inactive", "syncing", "error"];
|
|
108
|
-
};
|
|
109
|
-
readonly error: {
|
|
110
|
-
readonly type: "string";
|
|
111
|
-
};
|
|
112
|
-
};
|
|
113
|
-
readonly required: readonly ["name", "status"];
|
|
114
|
-
readonly additionalProperties: false;
|
|
115
|
-
};
|
|
116
|
-
};
|
|
117
|
-
};
|
|
118
|
-
readonly required: readonly ["connectors"];
|
|
119
|
-
readonly additionalProperties: false;
|
|
120
|
-
};
|
|
121
|
-
};
|
|
122
|
-
readonly 'connector/action': {
|
|
123
|
-
readonly type: "request";
|
|
124
|
-
readonly param: {
|
|
125
|
-
readonly type: "object";
|
|
126
|
-
readonly properties: {
|
|
127
|
-
readonly connector: {
|
|
128
|
-
readonly type: "string";
|
|
129
|
-
};
|
|
130
|
-
readonly action: {
|
|
131
|
-
readonly type: "string";
|
|
132
|
-
};
|
|
133
|
-
readonly input: {
|
|
134
|
-
readonly type: "object";
|
|
135
|
-
};
|
|
136
|
-
};
|
|
137
|
-
readonly required: readonly ["connector", "action", "input"];
|
|
138
|
-
readonly additionalProperties: false;
|
|
139
|
-
};
|
|
140
|
-
readonly result: {
|
|
141
|
-
readonly type: "object";
|
|
142
|
-
readonly properties: {
|
|
143
|
-
readonly raw: {
|
|
144
|
-
readonly type: "object";
|
|
145
|
-
};
|
|
146
|
-
readonly entity: {
|
|
147
|
-
readonly type: "object";
|
|
148
|
-
};
|
|
149
|
-
readonly documentID: {
|
|
150
|
-
readonly type: "string";
|
|
151
|
-
};
|
|
152
|
-
readonly deletedDocumentID: {
|
|
153
|
-
readonly type: "string";
|
|
154
|
-
};
|
|
155
|
-
};
|
|
156
|
-
readonly additionalProperties: false;
|
|
157
|
-
};
|
|
158
|
-
};
|
|
159
|
-
readonly 'connector/push': {
|
|
160
|
-
readonly type: "request";
|
|
161
|
-
readonly param: {
|
|
162
|
-
readonly type: "object";
|
|
163
|
-
readonly properties: {
|
|
164
|
-
readonly connector: {
|
|
165
|
-
readonly type: "string";
|
|
166
|
-
};
|
|
167
|
-
readonly batch: {
|
|
168
|
-
readonly type: "object";
|
|
169
|
-
readonly properties: {
|
|
170
|
-
readonly entities: {
|
|
171
|
-
readonly type: "array";
|
|
172
|
-
readonly items: {
|
|
173
|
-
readonly type: "object";
|
|
174
|
-
};
|
|
175
|
-
};
|
|
176
|
-
readonly deleted: {
|
|
177
|
-
readonly type: "array";
|
|
178
|
-
readonly items: {
|
|
179
|
-
readonly type: "object";
|
|
180
|
-
readonly properties: {
|
|
181
|
-
readonly sourceService: {
|
|
182
|
-
readonly type: "string";
|
|
183
|
-
};
|
|
184
|
-
readonly sourceID: {
|
|
185
|
-
readonly type: "string";
|
|
186
|
-
};
|
|
187
|
-
};
|
|
188
|
-
readonly required: readonly ["sourceService", "sourceID"];
|
|
189
|
-
};
|
|
190
|
-
};
|
|
191
|
-
readonly checkpoint: {};
|
|
192
|
-
readonly hasMore: {
|
|
193
|
-
readonly type: "boolean";
|
|
194
|
-
};
|
|
195
|
-
};
|
|
196
|
-
readonly required: readonly ["entities", "hasMore"];
|
|
197
|
-
readonly additionalProperties: false;
|
|
198
|
-
};
|
|
199
|
-
readonly checkpoint: {};
|
|
200
|
-
};
|
|
201
|
-
readonly required: readonly ["connector", "batch"];
|
|
202
|
-
readonly additionalProperties: false;
|
|
203
|
-
};
|
|
204
|
-
readonly result: {
|
|
205
|
-
readonly type: "object";
|
|
206
|
-
readonly properties: {
|
|
207
|
-
readonly processed: {
|
|
208
|
-
readonly type: "number";
|
|
209
|
-
};
|
|
210
|
-
readonly failed: {
|
|
211
|
-
readonly type: "number";
|
|
212
|
-
};
|
|
213
|
-
readonly documentIDs: {
|
|
214
|
-
readonly type: "array";
|
|
215
|
-
readonly items: {
|
|
216
|
-
readonly type: "string";
|
|
217
|
-
};
|
|
218
|
-
};
|
|
219
|
-
};
|
|
220
|
-
readonly required: readonly ["processed", "failed", "documentIDs"];
|
|
221
|
-
readonly additionalProperties: false;
|
|
222
|
-
};
|
|
223
|
-
};
|
|
224
|
-
readonly 'connector/push-complete': {
|
|
225
|
-
readonly type: "request";
|
|
226
|
-
readonly param: {
|
|
227
|
-
readonly type: "object";
|
|
228
|
-
readonly properties: {
|
|
229
|
-
readonly connector: {
|
|
230
|
-
readonly type: "string";
|
|
231
|
-
};
|
|
232
|
-
readonly checkpoint: {};
|
|
233
|
-
};
|
|
234
|
-
readonly required: readonly ["connector", "checkpoint"];
|
|
235
|
-
readonly additionalProperties: false;
|
|
236
|
-
};
|
|
237
|
-
readonly result: {
|
|
238
|
-
readonly type: "object";
|
|
239
|
-
readonly properties: {
|
|
240
|
-
readonly acknowledged: {
|
|
241
|
-
readonly type: "boolean";
|
|
242
|
-
};
|
|
243
|
-
};
|
|
244
|
-
readonly required: readonly ["acknowledged"];
|
|
245
|
-
readonly additionalProperties: false;
|
|
246
|
-
};
|
|
247
|
-
};
|
|
248
|
-
readonly 'connector/auth-url': {
|
|
249
|
-
readonly type: "request";
|
|
250
|
-
readonly param: {
|
|
251
|
-
readonly type: "object";
|
|
252
|
-
readonly properties: {
|
|
253
|
-
readonly provider: {
|
|
254
|
-
readonly type: "string";
|
|
255
|
-
};
|
|
256
|
-
readonly redirectURL: {
|
|
257
|
-
readonly type: "string";
|
|
258
|
-
};
|
|
259
|
-
readonly connectors: {
|
|
260
|
-
readonly type: "array";
|
|
261
|
-
readonly items: {
|
|
262
|
-
readonly type: "string";
|
|
263
|
-
};
|
|
264
|
-
};
|
|
265
|
-
};
|
|
266
|
-
readonly required: readonly ["provider", "redirectURL"];
|
|
267
|
-
readonly additionalProperties: false;
|
|
268
|
-
};
|
|
269
|
-
readonly result: {
|
|
270
|
-
readonly type: "object";
|
|
271
|
-
readonly properties: {
|
|
272
|
-
readonly url: {
|
|
273
|
-
readonly type: "string";
|
|
274
|
-
};
|
|
275
|
-
readonly state: {
|
|
276
|
-
readonly type: "string";
|
|
277
|
-
};
|
|
278
|
-
};
|
|
279
|
-
readonly required: readonly ["url", "state"];
|
|
280
|
-
readonly additionalProperties: false;
|
|
281
|
-
};
|
|
282
|
-
};
|
|
283
|
-
readonly 'connector/auth-complete': {
|
|
284
|
-
readonly type: "request";
|
|
285
|
-
readonly param: {
|
|
286
|
-
readonly type: "object";
|
|
287
|
-
readonly properties: {
|
|
288
|
-
readonly provider: {
|
|
289
|
-
readonly type: "string";
|
|
290
|
-
};
|
|
291
|
-
readonly code: {
|
|
292
|
-
readonly type: "string";
|
|
293
|
-
};
|
|
294
|
-
readonly state: {
|
|
295
|
-
readonly type: "string";
|
|
296
|
-
};
|
|
297
|
-
};
|
|
298
|
-
readonly required: readonly ["provider", "code", "state"];
|
|
299
|
-
readonly additionalProperties: false;
|
|
300
|
-
};
|
|
301
|
-
readonly result: {
|
|
302
|
-
readonly type: "object";
|
|
303
|
-
readonly properties: {
|
|
304
|
-
readonly providerName: {
|
|
305
|
-
readonly type: "string";
|
|
306
|
-
};
|
|
307
|
-
readonly accountLabel: {
|
|
308
|
-
readonly type: "string";
|
|
309
|
-
};
|
|
310
|
-
readonly scopes: {
|
|
311
|
-
readonly type: "array";
|
|
312
|
-
readonly items: {
|
|
313
|
-
readonly type: "string";
|
|
314
|
-
};
|
|
315
|
-
};
|
|
316
|
-
};
|
|
317
|
-
readonly required: readonly ["providerName", "scopes"];
|
|
318
|
-
readonly additionalProperties: false;
|
|
319
|
-
};
|
|
320
|
-
};
|
|
321
|
-
};
|
|
322
|
-
export type ConnectorProtocol = typeof connectorProtocol;
|
|
323
|
-
export type ConnectorSyncParams = FromSchema<ConnectorProtocol['connector/sync']['param']>;
|
|
324
|
-
export type ConnectorSyncReceive = FromSchema<ConnectorProtocol['connector/sync']['receive']>;
|
|
325
|
-
export type ConnectorSyncResult = FromSchema<ConnectorProtocol['connector/sync']['result']>;
|
|
326
|
-
export type ConnectorStatusParams = FromSchema<ConnectorProtocol['connector/status']['param']>;
|
|
327
|
-
export type ConnectorStatusResult = FromSchema<ConnectorProtocol['connector/status']['result']>;
|
|
328
|
-
export type ConnectorActionParams = FromSchema<ConnectorProtocol['connector/action']['param']>;
|
|
329
|
-
export type ConnectorActionResult = FromSchema<ConnectorProtocol['connector/action']['result']>;
|
|
330
|
-
export type ConnectorPushParams = FromSchema<ConnectorProtocol['connector/push']['param']>;
|
|
331
|
-
export type ConnectorPushResult = FromSchema<ConnectorProtocol['connector/push']['result']>;
|
|
332
|
-
export type ConnectorPushCompleteParams = FromSchema<ConnectorProtocol['connector/push-complete']['param']>;
|
|
333
|
-
export type ConnectorPushCompleteResult = FromSchema<ConnectorProtocol['connector/push-complete']['result']>;
|
|
334
|
-
export type ConnectorAuthURLParams = FromSchema<ConnectorProtocol['connector/auth-url']['param']>;
|
|
335
|
-
export type ConnectorAuthURLResult = FromSchema<ConnectorProtocol['connector/auth-url']['result']>;
|
|
336
|
-
export type ConnectorAuthCompleteParams = FromSchema<ConnectorProtocol['connector/auth-complete']['param']>;
|
|
337
|
-
export type ConnectorAuthCompleteResult = FromSchema<ConnectorProtocol['connector/auth-complete']['result']>;
|
package/lib/protocol.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const connectorProtocol={"connector/sync":{type:"stream",param:{type:"object",properties:{connector:{type:"string",description:"Connector name"},full:{type:"boolean",description:"Force full sync instead of incremental"}},required:["connector"],additionalProperties:!1},receive:{type:"object",properties:{type:{type:"string",enum:["progress","entity","error"]},connectorName:{type:"string"},phase:{type:"string",enum:["initial","incremental"]},entitiesProcessed:{type:"number"},entitiesFailed:{type:"number"},currentBatch:{type:"number"},checkpoint:{},entity:{type:"object"},documentID:{type:"string"},error:{type:"string"}},additionalProperties:!1},result:{type:"object",properties:{success:{type:"boolean"},totalProcessed:{type:"number"},totalFailed:{type:"number"},duration:{type:"number"}},required:["success","totalProcessed","totalFailed","duration"],additionalProperties:!1}},"connector/status":{type:"request",param:{type:"object",properties:{connector:{type:"string",description:"Optional connector name to filter"}},additionalProperties:!1},result:{type:"object",properties:{connectors:{type:"array",items:{type:"object",properties:{name:{type:"string"},lastSyncedAt:{type:"string"},entityCount:{type:"number"},status:{type:"string",enum:["active","inactive","syncing","error"]},error:{type:"string"}},required:["name","status"],additionalProperties:!1}}},required:["connectors"],additionalProperties:!1}},"connector/action":{type:"request",param:{type:"object",properties:{connector:{type:"string"},action:{type:"string"},input:{type:"object"}},required:["connector","action","input"],additionalProperties:!1},result:{type:"object",properties:{raw:{type:"object"},entity:{type:"object"},documentID:{type:"string"},deletedDocumentID:{type:"string"}},additionalProperties:!1}},"connector/push":{type:"request",param:{type:"object",properties:{connector:{type:"string"},batch:{type:"object",properties:{entities:{type:"array",items:{type:"object"}},deleted:{type:"array",items:{type:"object",properties:{sourceService:{type:"string"},sourceID:{type:"string"}},required:["sourceService","sourceID"]}},checkpoint:{},hasMore:{type:"boolean"}},required:["entities","hasMore"],additionalProperties:!1},checkpoint:{}},required:["connector","batch"],additionalProperties:!1},result:{type:"object",properties:{processed:{type:"number"},failed:{type:"number"},documentIDs:{type:"array",items:{type:"string"}}},required:["processed","failed","documentIDs"],additionalProperties:!1}},"connector/push-complete":{type:"request",param:{type:"object",properties:{connector:{type:"string"},checkpoint:{}},required:["connector","checkpoint"],additionalProperties:!1},result:{type:"object",properties:{acknowledged:{type:"boolean"}},required:["acknowledged"],additionalProperties:!1}},"connector/auth-url":{type:"request",param:{type:"object",properties:{provider:{type:"string"},redirectURL:{type:"string"},connectors:{type:"array",items:{type:"string"}}},required:["provider","redirectURL"],additionalProperties:!1},result:{type:"object",properties:{url:{type:"string"},state:{type:"string"}},required:["url","state"],additionalProperties:!1}},"connector/auth-complete":{type:"request",param:{type:"object",properties:{provider:{type:"string"},code:{type:"string"},state:{type:"string"}},required:["provider","code","state"],additionalProperties:!1},result:{type:"object",properties:{providerName:{type:"string"},accountLabel:{type:"string"},scopes:{type:"array",items:{type:"string"}}},required:["providerName","scopes"],additionalProperties:!1}}};
|
package/lib/registry.d.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { ConnectorDefinition } from './types.js';
|
|
2
|
-
export declare class ConnectorRegistry {
|
|
3
|
-
#private;
|
|
4
|
-
register(connector: ConnectorDefinition): void;
|
|
5
|
-
unregister(name: string): void;
|
|
6
|
-
get(name: string): ConnectorDefinition | undefined;
|
|
7
|
-
has(name: string): boolean;
|
|
8
|
-
list(): Array<string>;
|
|
9
|
-
getAll(): Array<ConnectorDefinition>;
|
|
10
|
-
}
|
package/lib/registry.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export class ConnectorRegistry{#e=new Map;register(e){if(this.#e.has(e.name))throw Error(`Connector "${e.name}" is already registered`);this.#e.set(e.name,e)}unregister(e){this.#e.delete(e)}get(e){return this.#e.get(e)}has(e){return this.#e.has(e)}list(){return Array.from(this.#e.keys())}getAll(){return Array.from(this.#e.values())}}
|
package/lib/sync/boundary.d.ts
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { SyncBoundary } from '../types.js';
|
|
2
|
-
export declare function parseDuration(duration: string): number | undefined;
|
|
3
|
-
export declare function computeEffectiveBoundary(serverBoundary: SyncBoundary, userBoundary: SyncBoundary | undefined): SyncBoundary;
|
|
4
|
-
export declare function isWithinBoundary(date: Date, boundary: SyncBoundary): boolean;
|
package/lib/sync/boundary.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
let e=/^P(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?$/;export function parseDuration(n){let i=e.exec(n);if(!i)return;let t=Number.parseInt(i[1]??"0",10),l=Number.parseInt(i[2]??"0",10),a=Number.parseInt(i[3]??"0",10),u=Number.parseInt(i[4]??"0",10);if(0!==t||0!==l||0!==a||0!==u)return((24*t+l)*60+a)*6e4+1e3*u}export function computeEffectiveBoundary(e,n){if(null==n)return e;let i={};if(null!=e.maxAge||null!=n.maxAge){let t=e.maxAge?parseDuration(e.maxAge):void 0,l=n.maxAge?parseDuration(n.maxAge):void 0;null!=t&&null!=l?i.maxAge=t<=l?e.maxAge:n.maxAge:i.maxAge=e.maxAge??n.maxAge}if(null!=e.since||null!=n.since){let t=e.since?new Date(e.since):void 0,l=n.since?new Date(n.since):void 0;null!=t&&null!=l?i.since=t>=l?e.since:n.since:i.since=e.since??n.since}if(null!=e.maxEntities||null!=n.maxEntities){let t=e.maxEntities,l=n.maxEntities;null!=t&&null!=l?i.maxEntities=Math.min(t,l):i.maxEntities=e.maxEntities??n.maxEntities}return i}export function isWithinBoundary(e,n){let i=Date.now();if(null!=n.maxAge){let t=parseDuration(n.maxAge);if(null!=t&&e.getTime()<i-t)return!1}if(null!=n.since){let i=new Date(n.since);if(e.getTime()<i.getTime())return!1}return!0}
|