@rool-dev/client 0.1.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/README.md +186 -0
- package/dist/auth.d.ts +75 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +375 -0
- package/dist/auth.js.map +1 -0
- package/dist/client.d.ts +129 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +343 -0
- package/dist/client.js.map +1 -0
- package/dist/event-emitter.d.ts +38 -0
- package/dist/event-emitter.d.ts.map +1 -0
- package/dist/event-emitter.js +80 -0
- package/dist/event-emitter.js.map +1 -0
- package/dist/graph.d.ts +240 -0
- package/dist/graph.d.ts.map +1 -0
- package/dist/graph.js +573 -0
- package/dist/graph.js.map +1 -0
- package/dist/graphql.d.ts +33 -0
- package/dist/graphql.d.ts.map +1 -0
- package/dist/graphql.js +269 -0
- package/dist/graphql.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/media.d.ts +36 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/media.js +105 -0
- package/dist/media.js.map +1 -0
- package/dist/subscription.d.ts +30 -0
- package/dist/subscription.d.ts.map +1 -0
- package/dist/subscription.js +161 -0
- package/dist/subscription.js.map +1 -0
- package/dist/types.d.ts +180 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/package.json +54 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic node - only `type` and `_meta` are required, all other fields are arbitrary.
|
|
3
|
+
* Consumer applications define their own node schemas.
|
|
4
|
+
*/
|
|
5
|
+
export interface RoolNode {
|
|
6
|
+
type: string;
|
|
7
|
+
_meta: Record<string, unknown>;
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Hyperedge format - supports many-to-many relationships.
|
|
12
|
+
* Sources and targets are arrays of node IDs.
|
|
13
|
+
*/
|
|
14
|
+
export interface RoolEdge {
|
|
15
|
+
sources: string[];
|
|
16
|
+
targets: string[];
|
|
17
|
+
type: string;
|
|
18
|
+
_meta: Record<string, unknown>;
|
|
19
|
+
[key: string]: unknown;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Graph structure - nodes and edges as maps keyed by ID.
|
|
23
|
+
* _meta is preserved but hidden from AI operations.
|
|
24
|
+
*/
|
|
25
|
+
export interface RoolGraphData {
|
|
26
|
+
nodes: Record<string, RoolNode>;
|
|
27
|
+
edges: Record<string, RoolEdge>;
|
|
28
|
+
_meta: Record<string, unknown>;
|
|
29
|
+
}
|
|
30
|
+
export type JSONPatchOp = {
|
|
31
|
+
op: 'add';
|
|
32
|
+
path: string;
|
|
33
|
+
value: unknown;
|
|
34
|
+
} | {
|
|
35
|
+
op: 'remove';
|
|
36
|
+
path: string;
|
|
37
|
+
} | {
|
|
38
|
+
op: 'replace';
|
|
39
|
+
path: string;
|
|
40
|
+
value: unknown;
|
|
41
|
+
} | {
|
|
42
|
+
op: 'move';
|
|
43
|
+
path: string;
|
|
44
|
+
from: string;
|
|
45
|
+
} | {
|
|
46
|
+
op: 'copy';
|
|
47
|
+
path: string;
|
|
48
|
+
from: string;
|
|
49
|
+
} | {
|
|
50
|
+
op: 'test';
|
|
51
|
+
path: string;
|
|
52
|
+
value: unknown;
|
|
53
|
+
};
|
|
54
|
+
export type RoolGraphRole = 'owner' | 'editor' | 'viewer';
|
|
55
|
+
export interface RoolGraphInfo {
|
|
56
|
+
id: string;
|
|
57
|
+
name: string;
|
|
58
|
+
role: RoolGraphRole;
|
|
59
|
+
}
|
|
60
|
+
export interface UserResult {
|
|
61
|
+
id: string;
|
|
62
|
+
email: string;
|
|
63
|
+
}
|
|
64
|
+
export interface RoolGraphUser {
|
|
65
|
+
id: string;
|
|
66
|
+
email: string;
|
|
67
|
+
role: RoolGraphRole;
|
|
68
|
+
}
|
|
69
|
+
export interface Account {
|
|
70
|
+
id: string;
|
|
71
|
+
email: string;
|
|
72
|
+
plan: string;
|
|
73
|
+
creditsBalance: number;
|
|
74
|
+
processedAt: string;
|
|
75
|
+
}
|
|
76
|
+
export interface MediaItem {
|
|
77
|
+
uuid: string;
|
|
78
|
+
url: string;
|
|
79
|
+
contentType: string;
|
|
80
|
+
size: number;
|
|
81
|
+
createdAt: string;
|
|
82
|
+
}
|
|
83
|
+
export interface PromptOptions {
|
|
84
|
+
nodeIds?: string[];
|
|
85
|
+
edgeIds?: string[];
|
|
86
|
+
metadata?: Record<string, unknown>;
|
|
87
|
+
responseSchema?: Record<string, unknown>;
|
|
88
|
+
}
|
|
89
|
+
export interface ImageResult {
|
|
90
|
+
url: string;
|
|
91
|
+
}
|
|
92
|
+
export type ImageAspectRatio = '1:1' | '3:4' | '4:3' | '9:16' | '16:9';
|
|
93
|
+
export type ConnectionState = 'connected' | 'disconnected' | 'reconnecting';
|
|
94
|
+
export type GraphEventType = 'graph_patched' | 'graph_changed' | 'graph_name_changed' | 'graph_created' | 'graph_deleted';
|
|
95
|
+
export interface GraphEvent {
|
|
96
|
+
type: GraphEventType;
|
|
97
|
+
graphId: string;
|
|
98
|
+
timestamp: number;
|
|
99
|
+
patch?: JSONPatchOp[];
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* External auth provider interface for Electron or custom auth flows.
|
|
103
|
+
* When provided, rool-client delegates all auth operations to this provider.
|
|
104
|
+
*/
|
|
105
|
+
export interface AuthProvider {
|
|
106
|
+
/** Get current access token */
|
|
107
|
+
getToken: () => Promise<string | undefined>;
|
|
108
|
+
/** Get user info from current session */
|
|
109
|
+
getUser: () => {
|
|
110
|
+
email: string | null;
|
|
111
|
+
name: string | null;
|
|
112
|
+
};
|
|
113
|
+
/** Check if currently authenticated */
|
|
114
|
+
isAuthenticated?: () => boolean;
|
|
115
|
+
/** Initiate login (optional - some providers handle this externally) */
|
|
116
|
+
login?: () => void;
|
|
117
|
+
/** Logout and clear session (optional) */
|
|
118
|
+
logout?: () => void;
|
|
119
|
+
}
|
|
120
|
+
export interface RoolClientConfig {
|
|
121
|
+
/** Base URL of the Rool server (e.g., 'https://use.rool.dev') */
|
|
122
|
+
baseUrl: string;
|
|
123
|
+
/** localStorage key prefix (default: 'rool_') */
|
|
124
|
+
storagePrefix?: string;
|
|
125
|
+
/**
|
|
126
|
+
* External auth provider (e.g., for Electron).
|
|
127
|
+
* When provided, rool-client uses this instead of built-in web auth.
|
|
128
|
+
*/
|
|
129
|
+
authProvider?: AuthProvider;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Client-level events (graph lifecycle, auth, connection).
|
|
133
|
+
*/
|
|
134
|
+
export interface RoolClientEvents {
|
|
135
|
+
/** Emitted when authentication state changes */
|
|
136
|
+
authStateChanged: (authenticated: boolean) => void;
|
|
137
|
+
/** Emitted when a new graph is created (by any client) */
|
|
138
|
+
graphCreated: (graphId: string) => void;
|
|
139
|
+
/** Emitted when a graph is deleted (by any client) */
|
|
140
|
+
graphDeleted: (graphId: string) => void;
|
|
141
|
+
/** Emitted when a graph is renamed (by any client) */
|
|
142
|
+
graphRenamed: (graphId: string, newName: string) => void;
|
|
143
|
+
/** Emitted when SSE connection state changes */
|
|
144
|
+
connectionStateChanged: (state: ConnectionState) => void;
|
|
145
|
+
/** Emitted on errors */
|
|
146
|
+
error: (error: Error, context?: string) => void;
|
|
147
|
+
/** Index signature for EventEmitter compatibility */
|
|
148
|
+
[key: string]: (...args: any[]) => void;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Graph-level events (content changes within a specific graph).
|
|
152
|
+
*/
|
|
153
|
+
export interface GraphEvents {
|
|
154
|
+
/** Emitted when the graph data changes (any change) */
|
|
155
|
+
changed: (data: RoolGraphData) => void;
|
|
156
|
+
/** Emitted when a node is added */
|
|
157
|
+
nodeAdded: (nodeId: string, node: RoolNode) => void;
|
|
158
|
+
/** Emitted when a node is updated */
|
|
159
|
+
nodeUpdated: (nodeId: string, node: RoolNode) => void;
|
|
160
|
+
/** Emitted when nodes are deleted */
|
|
161
|
+
nodesDeleted: (nodeIds: string[]) => void;
|
|
162
|
+
/** Emitted when an edge is added */
|
|
163
|
+
edgeAdded: (edgeId: string, edge: RoolEdge) => void;
|
|
164
|
+
/** Emitted when an edge is removed */
|
|
165
|
+
edgeRemoved: (edgeId: string) => void;
|
|
166
|
+
/** Emitted when a sync error occurs and the graph resyncs from server */
|
|
167
|
+
syncError: (error: Error) => void;
|
|
168
|
+
/** Index signature for EventEmitter compatibility */
|
|
169
|
+
[key: string]: (...args: any[]) => void;
|
|
170
|
+
}
|
|
171
|
+
export interface AuthTokens {
|
|
172
|
+
accessToken: string;
|
|
173
|
+
refreshToken: string | null;
|
|
174
|
+
expiresAt: number;
|
|
175
|
+
}
|
|
176
|
+
export interface UserInfo {
|
|
177
|
+
email: string | null;
|
|
178
|
+
name: string | null;
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Rool Client Types
|
|
3
|
+
// Generic types for graph-based applications using the Rool server API
|
|
4
|
+
// =============================================================================
|
|
5
|
+
export {};
|
|
6
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,oBAAoB;AACpB,uEAAuE;AACvE,gFAAgF"}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rool-dev/client",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TypeScript client library for the Rool API",
|
|
5
|
+
"packageManager": "pnpm@9.15.1",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc",
|
|
21
|
+
"dev": "tsc --watch",
|
|
22
|
+
"typecheck": "tsc --noEmit",
|
|
23
|
+
"clean": "rm -rf dist"
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/lightpost-one/gcp-backend.git",
|
|
31
|
+
"directory": "rool-client"
|
|
32
|
+
},
|
|
33
|
+
"keywords": [
|
|
34
|
+
"rool",
|
|
35
|
+
"graph",
|
|
36
|
+
"hypergraph",
|
|
37
|
+
"api-client",
|
|
38
|
+
"real-time"
|
|
39
|
+
],
|
|
40
|
+
"author": {
|
|
41
|
+
"name": "Lightpost One",
|
|
42
|
+
"email": "info@lightpost.one",
|
|
43
|
+
"url": "https://lightpost.one"
|
|
44
|
+
},
|
|
45
|
+
"license": "UNLICENSED",
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"fflate": "^0.8.2",
|
|
48
|
+
"graphql-sse": "^2.6.0",
|
|
49
|
+
"immutable-json-patch": "^6.0.2"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"typescript": "^5.9.3"
|
|
53
|
+
}
|
|
54
|
+
}
|