@fatagnus/dink-web 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 ADDED
@@ -0,0 +1,133 @@
1
+ # @fatagnus/dink-web
2
+
3
+ Browser SDK for connecting to [dinkd](https://github.com/ozanturksever/dink) via NATS WebSocket.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @fatagnus/dink-web
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import { DinkWebClient } from '@fatagnus/dink-web';
15
+
16
+ const client = new DinkWebClient({
17
+ url: 'wss://dinkd.example.com:9222',
18
+ token: 'dk_dev_wb_...',
19
+ appId: 'my-app',
20
+ });
21
+
22
+ await client.connect();
23
+
24
+ // Call an edge service
25
+ const result = await client.call('edge-1', 'SensorService', 'ReadTemperature', {
26
+ sensorId: 'temp-1',
27
+ });
28
+
29
+ // Call a center (cloud) service
30
+ const user = await client.callCenter('UserService', 'GetProfile', { userId: '123' });
31
+
32
+ // Subscribe to streaming updates
33
+ const sub = await client.subscribe(
34
+ 'edge-1', 'SensorService', 'WatchTemperature',
35
+ { sensorId: 'temp-1' },
36
+ (update) => console.log('Temperature:', update),
37
+ );
38
+
39
+ // Later
40
+ sub.unsubscribe();
41
+ await client.close();
42
+ ```
43
+
44
+ ## Token Refresh
45
+
46
+ Web tokens expire (default 1 hour, max 24 hours). The SDK can auto-refresh them:
47
+
48
+ ```typescript
49
+ const client = new DinkWebClient({
50
+ url: 'wss://dinkd.example.com:9222',
51
+ token: initialToken,
52
+ expiresAt: initialExpiresAt,
53
+ appId: 'my-app',
54
+
55
+ // Called 1 minute before expiry (configurable via refreshBeforeExpiryMs)
56
+ tokenRefresher: async () => {
57
+ const resp = await fetch('/api/keys/web-token', {
58
+ method: 'POST',
59
+ headers: { 'X-API-Key': backendApiKey },
60
+ body: JSON.stringify({ app_id: 'my-app' }),
61
+ });
62
+ return resp.json(); // { token, ws_url, expires_at }
63
+ },
64
+ });
65
+ ```
66
+
67
+ The client will drain the old connection and reconnect with the new token seamlessly.
68
+
69
+ ## Connection Quality
70
+
71
+ ```typescript
72
+ // Check stats at any time
73
+ const { status, reconnectCount, rttMs, tokenExpiresAt } = client.stats;
74
+ console.log(`RTT: ${rttMs}ms, reconnects: ${reconnectCount}`);
75
+
76
+ // React to status changes
77
+ const client = new DinkWebClient({
78
+ // ...
79
+ onStatusChange: (status) => {
80
+ if (status === 'error') showOfflineBanner();
81
+ if (status === 'connected') hideOfflineBanner();
82
+ },
83
+ });
84
+ ```
85
+
86
+ RTT is measured every 30 seconds via NATS flush.
87
+
88
+ ## Generated Clients
89
+
90
+ Use `dink codegen --output-mode web-client` to generate typed clients:
91
+
92
+ ```typescript
93
+ import { DinkWebClient } from '@fatagnus/dink-web';
94
+ import { SensorServiceClient } from './generated/sensorservice.client';
95
+
96
+ const client = new DinkWebClient({ url, token, appId });
97
+ await client.connect();
98
+
99
+ const sensor = new SensorServiceClient('edge-1', client);
100
+ const reading = await sensor.ReadTemperature({ sensorId: 'temp-1' });
101
+ ```
102
+
103
+ ## API
104
+
105
+ ### `DinkWebClient`
106
+
107
+ | Method | Description |
108
+ |--------|-------------|
109
+ | `connect()` | Establish WebSocket connection |
110
+ | `close()` | Drain connection and close |
111
+ | `call(edgeId, service, method, req)` | Request/reply to edge service |
112
+ | `callCenter(service, method, req)` | Request/reply to center service |
113
+ | `subscribe(edgeId, service, method, req, handler)` | Stream from edge service |
114
+ | `service(edgeId, ClientClass)` | Create typed service client |
115
+ | `status` | Current connection status |
116
+ | `stats` | Connection statistics (RTT, reconnects, token expiry) |
117
+
118
+ ### Config
119
+
120
+ | Option | Type | Default | Description |
121
+ |--------|------|---------|-------------|
122
+ | `url` | `string` | required | WebSocket URL |
123
+ | `token` | `string` | required | Web token |
124
+ | `appId` | `string` | required | App ID |
125
+ | `expiresAt` | `string \| Date` | - | Token expiry (enables auto-refresh) |
126
+ | `timeout` | `number` | 30000 | Request timeout (ms) |
127
+ | `onStatusChange` | `function` | - | Status change callback |
128
+ | `tokenRefresher` | `function` | - | Returns fresh token |
129
+ | `refreshBeforeExpiryMs` | `number` | 60000 | Refresh lead time (ms) |
130
+
131
+ ## License
132
+
133
+ Apache-2.0
@@ -0,0 +1,57 @@
1
+ import type { DinkWebConfig, ServiceCaller, Subscription, ConnectionStatus, ConnectionStats } from './types.js';
2
+ /**
3
+ * DinkWebClient connects to dinkd via NATS WebSocket from the browser.
4
+ * Implements ServiceCaller so generated client code works unchanged.
5
+ *
6
+ * Features:
7
+ * - Auto token refresh before expiry (requires tokenRefresher config)
8
+ * - Connection quality monitoring (RTT, reconnect count)
9
+ * - Automatic reconnection (built into nats.ws)
10
+ * - Center and edge service calls
11
+ */
12
+ export declare class DinkWebClient implements ServiceCaller {
13
+ private nc;
14
+ private config;
15
+ private _status;
16
+ private _reconnectCount;
17
+ private _rttMs;
18
+ private _tokenExpiresAt;
19
+ private _refreshTimer;
20
+ private _rttInterval;
21
+ constructor(config: DinkWebConfig);
22
+ get status(): ConnectionStatus;
23
+ /** Current connection statistics */
24
+ get stats(): ConnectionStats;
25
+ connect(): Promise<void>;
26
+ close(): Promise<void>;
27
+ /**
28
+ * Call an edge service via request/reply.
29
+ * Subject: edge.{appId}.{edgeId}.services.{service}.{method}
30
+ */
31
+ call<Req, Resp>(edgeId: string, service: string, method: string, req: Req): Promise<Resp>;
32
+ /**
33
+ * Call a center (cloud) service via request/reply.
34
+ * Subject: center.{appId}.services.{service}.{method}
35
+ */
36
+ callCenter<Req, Resp>(service: string, method: string, req: Req): Promise<Resp>;
37
+ /**
38
+ * Subscribe to a streaming edge service.
39
+ * Subject: edge.{appId}.{edgeId}.services.{service}.{method}.stream
40
+ */
41
+ subscribe<Req, Resp>(edgeId: string, service: string, method: string, req: Req, handler: (resp: Resp) => void): Promise<Subscription>;
42
+ /**
43
+ * Create a typed service client for an edge.
44
+ * Usage: const svc = client.service('edge-1', MyServiceClient);
45
+ */
46
+ service<T>(edgeId: string, factory: new (edgeId: string, caller: ServiceCaller) => T): T;
47
+ private scheduleTokenRefresh;
48
+ private refreshToken;
49
+ private startRttProbe;
50
+ private monitorStatus;
51
+ private stopTimers;
52
+ private setStatus;
53
+ private ensureConnected;
54
+ private encode;
55
+ private decode;
56
+ }
57
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACX,aAAa,EACb,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,eAAe,EAEf,MAAM,YAAY,CAAC;AAKpB;;;;;;;;;GASG;AACH,qBAAa,aAAc,YAAW,aAAa;IAClD,OAAO,CAAC,EAAE,CAA+B;IACzC,OAAO,CAAC,MAAM,CAA2E;IACzF,OAAO,CAAC,OAAO,CAAkC;IACjD,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,eAAe,CAAqB;IAC5C,OAAO,CAAC,aAAa,CAA8C;IACnE,OAAO,CAAC,YAAY,CAA+C;gBAEvD,MAAM,EAAE,aAAa;IAQjC,IAAI,MAAM,IAAI,gBAAgB,CAE7B;IAED,oCAAoC;IACpC,IAAI,KAAK,IAAI,eAAe,CAO3B;IAEK,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAoBxB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAS5B;;;OAGG;IACG,IAAI,CAAC,GAAG,EAAE,IAAI,EACnB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,GAAG,GACN,OAAO,CAAC,IAAI,CAAC;IAUhB;;;OAGG;IACG,UAAU,CAAC,GAAG,EAAE,IAAI,EACzB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,GAAG,GACN,OAAO,CAAC,IAAI,CAAC;IAUhB;;;OAGG;IACG,SAAS,CAAC,GAAG,EAAE,IAAI,EACxB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,GAAG,EACR,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,GAC3B,OAAO,CAAC,YAAY,CAAC;IAyBxB;;;OAGG;IACH,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,KAAK,CAAC,GAAG,CAAC;IAMxF,OAAO,CAAC,oBAAoB;YAiBd,YAAY;IAwC1B,OAAO,CAAC,aAAa;IAoBrB,OAAO,CAAC,aAAa;IAqBrB,OAAO,CAAC,UAAU;IAWlB,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,eAAe;IAMvB,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,MAAM;CAGd"}
package/dist/client.js ADDED
@@ -0,0 +1,244 @@
1
+ import { connect } from 'nats.ws';
2
+ const DEFAULT_TIMEOUT = 30_000;
3
+ const DEFAULT_REFRESH_BEFORE_EXPIRY_MS = 60_000; // 1 minute before expiry
4
+ /**
5
+ * DinkWebClient connects to dinkd via NATS WebSocket from the browser.
6
+ * Implements ServiceCaller so generated client code works unchanged.
7
+ *
8
+ * Features:
9
+ * - Auto token refresh before expiry (requires tokenRefresher config)
10
+ * - Connection quality monitoring (RTT, reconnect count)
11
+ * - Automatic reconnection (built into nats.ws)
12
+ * - Center and edge service calls
13
+ */
14
+ export class DinkWebClient {
15
+ nc = null;
16
+ config;
17
+ _status = 'connecting';
18
+ _reconnectCount = 0;
19
+ _rttMs = null;
20
+ _tokenExpiresAt = null;
21
+ _refreshTimer = null;
22
+ _rttInterval = null;
23
+ constructor(config) {
24
+ this.config = { timeout: DEFAULT_TIMEOUT, ...config };
25
+ if (config.expiresAt) {
26
+ this._tokenExpiresAt =
27
+ config.expiresAt instanceof Date ? config.expiresAt : new Date(config.expiresAt);
28
+ }
29
+ }
30
+ get status() {
31
+ return this._status;
32
+ }
33
+ /** Current connection statistics */
34
+ get stats() {
35
+ return {
36
+ status: this._status,
37
+ reconnectCount: this._reconnectCount,
38
+ rttMs: this._rttMs,
39
+ tokenExpiresAt: this._tokenExpiresAt,
40
+ };
41
+ }
42
+ async connect() {
43
+ this.setStatus('connecting');
44
+ try {
45
+ this.nc = await connect({
46
+ servers: this.config.url,
47
+ token: this.config.token,
48
+ maxReconnectAttempts: -1,
49
+ reconnectTimeWait: 2000,
50
+ });
51
+ this.setStatus('connected');
52
+ this.monitorStatus();
53
+ this.scheduleTokenRefresh();
54
+ this.startRttProbe();
55
+ }
56
+ catch (err) {
57
+ this.setStatus('error');
58
+ throw err;
59
+ }
60
+ }
61
+ async close() {
62
+ this.stopTimers();
63
+ if (this.nc) {
64
+ await this.nc.drain();
65
+ this.nc = null;
66
+ }
67
+ this.setStatus('closed');
68
+ }
69
+ /**
70
+ * Call an edge service via request/reply.
71
+ * Subject: edge.{appId}.{edgeId}.services.{service}.{method}
72
+ */
73
+ async call(edgeId, service, method, req) {
74
+ this.ensureConnected();
75
+ const subject = `edge.${this.config.appId}.${edgeId}.services.${service}.${method}`;
76
+ const payload = this.encode(req);
77
+ const msg = await this.nc.request(subject, payload, {
78
+ timeout: this.config.timeout ?? DEFAULT_TIMEOUT,
79
+ });
80
+ return this.decode(msg);
81
+ }
82
+ /**
83
+ * Call a center (cloud) service via request/reply.
84
+ * Subject: center.{appId}.services.{service}.{method}
85
+ */
86
+ async callCenter(service, method, req) {
87
+ this.ensureConnected();
88
+ const subject = `center.${this.config.appId}.services.${service}.${method}`;
89
+ const payload = this.encode(req);
90
+ const msg = await this.nc.request(subject, payload, {
91
+ timeout: this.config.timeout ?? DEFAULT_TIMEOUT,
92
+ });
93
+ return this.decode(msg);
94
+ }
95
+ /**
96
+ * Subscribe to a streaming edge service.
97
+ * Subject: edge.{appId}.{edgeId}.services.{service}.{method}.stream
98
+ */
99
+ async subscribe(edgeId, service, method, req, handler) {
100
+ this.ensureConnected();
101
+ const subject = `edge.${this.config.appId}.${edgeId}.services.${service}.${method}.stream`;
102
+ const inbox = this.nc.subscribe(subject);
103
+ const payload = this.encode(req);
104
+ this.nc.publish(subject, payload);
105
+ (async () => {
106
+ for await (const msg of inbox) {
107
+ try {
108
+ const resp = this.decode(msg);
109
+ handler(resp);
110
+ }
111
+ catch {
112
+ // Skip malformed messages
113
+ }
114
+ }
115
+ })().catch(() => { });
116
+ return {
117
+ unsubscribe: () => {
118
+ inbox.unsubscribe();
119
+ },
120
+ };
121
+ }
122
+ /**
123
+ * Create a typed service client for an edge.
124
+ * Usage: const svc = client.service('edge-1', MyServiceClient);
125
+ */
126
+ service(edgeId, factory) {
127
+ return new factory(edgeId, this);
128
+ }
129
+ // ── Token refresh ───────────────────────────────────────────────
130
+ scheduleTokenRefresh() {
131
+ if (!this.config.tokenRefresher || !this._tokenExpiresAt)
132
+ return;
133
+ const refreshBeforeMs = this.config.refreshBeforeExpiryMs ?? DEFAULT_REFRESH_BEFORE_EXPIRY_MS;
134
+ const msUntilExpiry = this._tokenExpiresAt.getTime() - Date.now();
135
+ const msUntilRefresh = msUntilExpiry - refreshBeforeMs;
136
+ if (msUntilRefresh <= 0) {
137
+ // Already past refresh window — refresh immediately
138
+ this.refreshToken();
139
+ return;
140
+ }
141
+ this._refreshTimer = setTimeout(() => this.refreshToken(), msUntilRefresh);
142
+ }
143
+ async refreshToken() {
144
+ if (!this.config.tokenRefresher)
145
+ return;
146
+ try {
147
+ const resp = await this.config.tokenRefresher();
148
+ this._tokenExpiresAt = new Date(resp.expires_at);
149
+ // Reconnect with new token
150
+ const wasConnected = this._status === 'connected';
151
+ if (this.nc) {
152
+ await this.nc.drain();
153
+ this.nc = null;
154
+ }
155
+ this.config.token = resp.token;
156
+ if (resp.ws_url) {
157
+ this.config.url = resp.ws_url;
158
+ }
159
+ if (wasConnected) {
160
+ this.nc = await connect({
161
+ servers: this.config.url,
162
+ token: this.config.token,
163
+ maxReconnectAttempts: -1,
164
+ reconnectTimeWait: 2000,
165
+ });
166
+ this.setStatus('connected');
167
+ this.monitorStatus();
168
+ }
169
+ // Schedule next refresh
170
+ this.scheduleTokenRefresh();
171
+ }
172
+ catch {
173
+ // Retry in 10 seconds on failure
174
+ this._refreshTimer = setTimeout(() => this.refreshToken(), 10_000);
175
+ }
176
+ }
177
+ // ── Connection quality ──────────────────────────────────────────
178
+ startRttProbe() {
179
+ // Measure RTT every 30s using a NATS flush (round-trip to server)
180
+ const probe = async () => {
181
+ if (!this.nc || this._status !== 'connected')
182
+ return;
183
+ try {
184
+ const start = performance.now();
185
+ await this.nc.flush();
186
+ this._rttMs = Math.round(performance.now() - start);
187
+ }
188
+ catch {
189
+ this._rttMs = null;
190
+ }
191
+ };
192
+ // Initial probe
193
+ probe();
194
+ this._rttInterval = setInterval(probe, 30_000);
195
+ }
196
+ // ── Internal ────────────────────────────────────────────────────
197
+ monitorStatus() {
198
+ if (!this.nc)
199
+ return;
200
+ (async () => {
201
+ for await (const s of this.nc.status()) {
202
+ switch (s.type) {
203
+ case 'reconnecting':
204
+ this.setStatus('reconnecting');
205
+ break;
206
+ case 'reconnect':
207
+ this._reconnectCount++;
208
+ this.setStatus('connected');
209
+ break;
210
+ case 'disconnect':
211
+ case 'error':
212
+ this.setStatus('error');
213
+ break;
214
+ }
215
+ }
216
+ })().catch(() => { });
217
+ }
218
+ stopTimers() {
219
+ if (this._refreshTimer) {
220
+ clearTimeout(this._refreshTimer);
221
+ this._refreshTimer = null;
222
+ }
223
+ if (this._rttInterval) {
224
+ clearInterval(this._rttInterval);
225
+ this._rttInterval = null;
226
+ }
227
+ }
228
+ setStatus(status) {
229
+ this._status = status;
230
+ this.config.onStatusChange?.(status);
231
+ }
232
+ ensureConnected() {
233
+ if (!this.nc) {
234
+ throw new Error('DinkWebClient is not connected. Call connect() first.');
235
+ }
236
+ }
237
+ encode(data) {
238
+ return new TextEncoder().encode(JSON.stringify(data));
239
+ }
240
+ decode(msg) {
241
+ return JSON.parse(new TextDecoder().decode(msg.data));
242
+ }
243
+ }
244
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export { DinkWebClient } from './client.js';
2
+ export type { DinkWebConfig, ServiceCaller, Subscription, ServiceDefinition, ConnectionStatus, ConnectionStats, WebTokenResponse, TokenRefresher, } from './types.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,YAAY,EACX,aAAa,EACb,aAAa,EACb,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,cAAc,GACd,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ // @fatagnus/dink-web - Browser SDK for Dink edge mesh via NATS WebSocket
2
+ export { DinkWebClient } from './client.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,60 @@
1
+ export interface ServiceCaller {
2
+ call<Req, Resp>(edgeId: string, service: string, method: string, req: Req): Promise<Resp>;
3
+ callCenter<Req, Resp>(service: string, method: string, req: Req): Promise<Resp>;
4
+ subscribe<Req, Resp>(edgeId: string, service: string, method: string, req: Req, handler: (resp: Resp) => void): Promise<Subscription>;
5
+ }
6
+ export interface Subscription {
7
+ unsubscribe(): void;
8
+ }
9
+ export interface ServiceDefinition {
10
+ name: string;
11
+ version: string;
12
+ methods: string[];
13
+ }
14
+ /** Response from POST /api/keys/web-token */
15
+ export interface WebTokenResponse {
16
+ token: string;
17
+ ws_url: string;
18
+ expires_at: string;
19
+ }
20
+ /**
21
+ * Callback that fetches a fresh web token.
22
+ * Called automatically when the current token is about to expire.
23
+ * Should POST to /api/keys/web-token and return the response.
24
+ */
25
+ export type TokenRefresher = () => Promise<WebTokenResponse>;
26
+ export interface DinkWebConfig {
27
+ /** WebSocket URL (e.g., "wss://host:9222") */
28
+ url: string;
29
+ /** Authentication token (from /api/keys/web-token) */
30
+ token: string;
31
+ /** Token expiry time (ISO 8601 string or Date). Enables auto-refresh when set with tokenRefresher. */
32
+ expiresAt?: string | Date;
33
+ /** App ID for subject routing */
34
+ appId: string;
35
+ /** Request timeout in ms (default: 30000) */
36
+ timeout?: number;
37
+ /** Called when connection state changes */
38
+ onStatusChange?: (status: ConnectionStatus) => void;
39
+ /**
40
+ * Called to obtain a fresh token before the current one expires.
41
+ * If not set, the client will not auto-refresh tokens.
42
+ */
43
+ tokenRefresher?: TokenRefresher;
44
+ /**
45
+ * How many ms before expiry to trigger a refresh (default: 60000 = 1 minute).
46
+ */
47
+ refreshBeforeExpiryMs?: number;
48
+ }
49
+ export interface ConnectionStats {
50
+ /** Current connection status */
51
+ status: ConnectionStatus;
52
+ /** Number of reconnections since connect() */
53
+ reconnectCount: number;
54
+ /** Last measured round-trip time in ms, or null if not yet measured */
55
+ rttMs: number | null;
56
+ /** When the current token expires, or null if unknown */
57
+ tokenExpiresAt: Date | null;
58
+ }
59
+ export type ConnectionStatus = 'connecting' | 'connected' | 'reconnecting' | 'closed' | 'error';
60
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,aAAa;IAC7B,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1F,UAAU,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChF,SAAS,CAAC,GAAG,EAAE,IAAI,EAClB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,GAAG,EACR,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,GAC3B,OAAO,CAAC,YAAY,CAAC,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC5B,WAAW,IAAI,IAAI,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,6CAA6C;AAC7C,MAAM,WAAW,gBAAgB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,OAAO,CAAC,gBAAgB,CAAC,CAAC;AAE7D,MAAM,WAAW,aAAa;IAC7B,8CAA8C;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,sDAAsD;IACtD,KAAK,EAAE,MAAM,CAAC;IACd,sGAAsG;IACtG,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,iCAAiC;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC;IACpD;;;OAGG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC;;OAEG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,eAAe;IAC/B,gCAAgC;IAChC,MAAM,EAAE,gBAAgB,CAAC;IACzB,8CAA8C;IAC9C,cAAc,EAAE,MAAM,CAAC;IACvB,uEAAuE;IACvE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,yDAAyD;IACzD,cAAc,EAAE,IAAI,GAAG,IAAI,CAAC;CAC5B;AAED,MAAM,MAAM,gBAAgB,GAAG,YAAY,GAAG,WAAW,GAAG,cAAc,GAAG,QAAQ,GAAG,OAAO,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ // @fatagnus/dink-web - Shared types for browser SDK
2
+ export {};
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,oDAAoD"}
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@fatagnus/dink-web",
3
+ "version": "0.1.0",
4
+ "description": "Browser SDK for Dink edge mesh platform via NATS WebSocket",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./types": {
14
+ "types": "./dist/types.d.ts",
15
+ "import": "./dist/types.js"
16
+ }
17
+ },
18
+ "typesVersions": {
19
+ "*": {
20
+ "types": ["dist/types.d.ts"]
21
+ }
22
+ },
23
+ "scripts": {
24
+ "build": "tsc",
25
+ "clean": "rm -rf dist",
26
+ "prebuild": "npm run clean",
27
+ "prepublishOnly": "npm run build",
28
+ "typecheck": "tsc --noEmit"
29
+ },
30
+ "keywords": [
31
+ "dink",
32
+ "nats",
33
+ "websocket",
34
+ "edge",
35
+ "browser",
36
+ "real-time"
37
+ ],
38
+ "author": "Ozan Turksever",
39
+ "license": "Apache-2.0",
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "git+https://github.com/ozanturksever/dink.git",
43
+ "directory": "sdk/web"
44
+ },
45
+ "homepage": "https://github.com/ozanturksever/dink#readme",
46
+ "bugs": {
47
+ "url": "https://github.com/ozanturksever/dink/issues"
48
+ },
49
+ "publishConfig": {
50
+ "access": "public"
51
+ },
52
+ "sideEffects": false,
53
+ "dependencies": {
54
+ "nats.ws": "^1.29.2"
55
+ },
56
+ "devDependencies": {
57
+ "@types/node": "^20.10.0",
58
+ "typescript": "^5.3.0"
59
+ },
60
+ "engines": {
61
+ "node": ">=18.0.0"
62
+ },
63
+ "files": [
64
+ "dist",
65
+ "README.md",
66
+ "LICENSE"
67
+ ]
68
+ }