@synapseone/sdk 0.7.0 → 0.7.1-dev.56

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 CHANGED
@@ -15,7 +15,7 @@ npm install @synapseone/sdk
15
15
  ```ts
16
16
  import { SynapseOneClient } from '@synapseone/sdk';
17
17
 
18
- const c = new SynapseOneClient({ baseUrl: 'http://localhost:8080' });
18
+ const c = new SynapseOneClient(); // defaults to https://synapseone.space (or $SYNAPSEONE_BASE_URL)
19
19
  const { accessToken, user } = await c.iam.login('demo@acme.local');
20
20
  c.setToken(accessToken);
21
21
 
package/dist/_http.d.ts CHANGED
@@ -1,9 +1,34 @@
1
+ /** Public API gateway origin. The console/marketing host is NOT the API. */
2
+ export declare const DEFAULT_BASE_URL = "https://api.synapseone.space";
3
+ /** Default per-request timeout. Long-running namespaces override it. */
4
+ export declare const DEFAULT_TIMEOUT_MS = 15000;
5
+ /** Inference/LLM calls routinely exceed 15s; keep in sync with the gateway route. */
6
+ export declare const INFERENCE_TIMEOUT_MS = 120000;
7
+ /** Public inbound webhooks can run up to the workflow 300s budget. */
8
+ export declare const WEBHOOK_TIMEOUT_MS = 300000;
1
9
  export interface SynapseOneClientOptions {
2
- baseUrl: string;
10
+ baseUrl?: string;
3
11
  token?: string;
4
12
  apiKey?: string;
5
13
  tenant?: string;
6
14
  timeoutMs?: number;
15
+ /** Max attempts for idempotent requests on 429/502/503/504 (default 3, 1 = no retry). */
16
+ maxAttempts?: number;
17
+ /** Base backoff in ms for retries (exponential, default 200). */
18
+ retryBaseMs?: number;
19
+ }
20
+ /** Per-call transport options. */
21
+ export interface RequestOptions {
22
+ /** Override the client/namespace timeout for this call. */
23
+ timeoutMs?: number;
24
+ /** Caller-supplied abort signal. */
25
+ signal?: AbortSignal;
26
+ /** Max total attempts for this call (1 disables retries). */
27
+ maxAttempts?: number;
28
+ /** Force retry eligibility (defaults to idempotent methods). */
29
+ retryable?: boolean;
30
+ /** Extra headers for this call. */
31
+ headers?: Record<string, string>;
7
32
  }
8
33
  /** All SDK failures surface as SynapseOneError (network or HTTP). */
9
34
  export declare class SynapseOneError extends Error {
@@ -11,9 +36,15 @@ export declare class SynapseOneError extends Error {
11
36
  status?: number;
12
37
  /** Parsed error body when the response carried JSON. */
13
38
  body?: unknown;
39
+ /** Machine-readable error code (when known). */
40
+ code?: string;
41
+ /** Base URL the request was sent to (diagnoses console-vs-API misconfiguration). */
42
+ baseUrl?: string;
14
43
  constructor(message: string, opts?: {
15
44
  status?: number;
16
45
  body?: unknown;
46
+ code?: string;
47
+ baseUrl?: string;
17
48
  });
18
49
  }
19
50
  /**
@@ -31,12 +62,34 @@ export declare class HttpClient {
31
62
  apiKey?: string;
32
63
  tenant?: string;
33
64
  timeoutMs: number;
34
- constructor(opts: SynapseOneClientOptions);
65
+ maxAttempts: number;
66
+ retryBaseMs: number;
67
+ constructor(opts?: SynapseOneClientOptions);
35
68
  setToken(t: string): void;
36
69
  setApiKey(k: string): void;
37
70
  setTenant(t: string | undefined): void;
38
71
  headers(extra?: Record<string, string>): Record<string, string>;
39
- req<T>(path: string, init?: RequestInit): Promise<T>;
72
+ /** Low-level fetch with timeout, caller signal and bounded retries. */
73
+ private fetchOnce;
74
+ req<T>(path: string, init?: RequestInit, options?: RequestOptions): Promise<T>;
75
+ /** Upload a binary body and parse the JSON response. */
76
+ upload<T>(path: string, body: Uint8Array | string, options?: RequestOptions & {
77
+ method?: string;
78
+ contentType?: string;
79
+ }): Promise<T>;
80
+ /** HEAD request; returns response headers (no body). */
81
+ head(path: string, options?: RequestOptions): Promise<Record<string, string>>;
82
+ /** Fetch a binary body (bytes). Used by object storage/file downloads. */
83
+ reqBinary(path: string, init?: RequestInit, options?: RequestOptions): Promise<Uint8Array>;
84
+ /**
85
+ * Stream a Server-Sent-Events response as parsed `{event, data}` records.
86
+ * The transport is not buffered; each event is yielded as soon as a blank
87
+ * line terminates it. Pass an AbortSignal to cancel.
88
+ */
89
+ stream(path: string, init?: RequestInit, options?: RequestOptions): AsyncGenerator<{
90
+ event: string;
91
+ data: string;
92
+ }>;
40
93
  /** Generic escape hatch (forward-compat with endpoints added after this SDK). */
41
- call(method: string, path: string, body?: unknown): Promise<unknown>;
94
+ call(method: string, path: string, body?: unknown, options?: RequestOptions): Promise<unknown>;
42
95
  }
package/dist/_http.js CHANGED
@@ -1,23 +1,39 @@
1
1
  "use strict";
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ // SPDX-FileCopyrightText: 2026 VKU.VIC
2
4
  /*
3
5
  * Copyright 2026 SynapseOne Contributors
4
6
  * Licensed under the Apache License, Version 2.0 (the "License");
5
7
  * Shared fetch-based HTTP plumbing for the synapseone JavaScript SDK.
6
8
  */
7
9
  Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.HttpClient = exports.SynapseDXError = exports.SynapseOneError = void 0;
10
+ exports.HttpClient = exports.SynapseDXError = exports.SynapseOneError = exports.WEBHOOK_TIMEOUT_MS = exports.INFERENCE_TIMEOUT_MS = exports.DEFAULT_TIMEOUT_MS = exports.DEFAULT_BASE_URL = void 0;
9
11
  exports.warnDeprecated = warnDeprecated;
12
+ /** Public API gateway origin. The console/marketing host is NOT the API. */
13
+ exports.DEFAULT_BASE_URL = 'https://api.synapseone.space';
14
+ /** Default per-request timeout. Long-running namespaces override it. */
15
+ exports.DEFAULT_TIMEOUT_MS = 15_000;
16
+ /** Inference/LLM calls routinely exceed 15s; keep in sync with the gateway route. */
17
+ exports.INFERENCE_TIMEOUT_MS = 120_000;
18
+ /** Public inbound webhooks can run up to the workflow 300s budget. */
19
+ exports.WEBHOOK_TIMEOUT_MS = 300_000;
10
20
  /** All SDK failures surface as SynapseOneError (network or HTTP). */
11
21
  class SynapseOneError extends Error {
12
22
  /** HTTP status code when the failure came from a response, undefined for network errors. */
13
23
  status;
14
24
  /** Parsed error body when the response carried JSON. */
15
25
  body;
26
+ /** Machine-readable error code (when known). */
27
+ code;
28
+ /** Base URL the request was sent to (diagnoses console-vs-API misconfiguration). */
29
+ baseUrl;
16
30
  constructor(message, opts = {}) {
17
31
  super(message);
18
32
  this.name = 'SynapseOneError';
19
33
  this.status = opts.status;
20
34
  this.body = opts.body;
35
+ this.code = opts.code;
36
+ this.baseUrl = opts.baseUrl;
21
37
  }
22
38
  }
23
39
  exports.SynapseOneError = SynapseOneError;
@@ -32,18 +48,44 @@ function warnDeprecated(name, replacement) {
32
48
  console.warn(message);
33
49
  }
34
50
  }
51
+ const RETRYABLE_STATUS = new Set([429, 502, 503, 504]);
52
+ const IDEMPOTENT_METHODS = new Set(['GET', 'HEAD', 'OPTIONS', 'PUT', 'DELETE']);
53
+ function sleep(ms) {
54
+ return new Promise((resolve) => setTimeout(resolve, ms));
55
+ }
56
+ /** Retry-After is either delta-seconds or an HTTP date. Clamp to a sane max. */
57
+ function retryAfterMs(value) {
58
+ if (!value)
59
+ return undefined;
60
+ const secs = Number(value);
61
+ if (Number.isFinite(secs) && secs >= 0)
62
+ return Math.min(secs * 1000, 30_000);
63
+ const when = Date.parse(value);
64
+ if (Number.isFinite(when))
65
+ return Math.min(Math.max(when - Date.now(), 0), 30_000);
66
+ return undefined;
67
+ }
68
+ function isHtml(body) {
69
+ const head = body.slice(0, 256).trimStart().toLowerCase();
70
+ return head.startsWith('<!doctype html') || head.startsWith('<html');
71
+ }
35
72
  class HttpClient {
36
73
  baseUrl;
37
74
  token;
38
75
  apiKey;
39
76
  tenant;
40
77
  timeoutMs;
41
- constructor(opts) {
42
- this.baseUrl = opts.baseUrl.replace(/\/$/, '');
78
+ maxAttempts;
79
+ retryBaseMs;
80
+ constructor(opts = {}) {
81
+ const raw = opts.baseUrl ?? (typeof process !== 'undefined' ? (process.env?.SYNAPSEONE_BASE_URL || process.env?.SYNAPSEDX_BASE_URL) : undefined) ?? exports.DEFAULT_BASE_URL;
82
+ this.baseUrl = raw.replace(/\/$/, '');
43
83
  this.token = opts.token;
44
84
  this.apiKey = opts.apiKey;
45
85
  this.tenant = opts.tenant;
46
- this.timeoutMs = opts.timeoutMs ?? 15000;
86
+ this.timeoutMs = opts.timeoutMs ?? exports.DEFAULT_TIMEOUT_MS;
87
+ this.maxAttempts = Math.max(1, Math.floor(opts.maxAttempts ?? 3));
88
+ this.retryBaseMs = Math.max(0, Math.floor(opts.retryBaseMs ?? 200));
47
89
  }
48
90
  setToken(t) { this.token = t; }
49
91
  setApiKey(k) { this.apiKey = k; }
@@ -58,35 +100,129 @@ class HttpClient {
58
100
  h['x-synapseone-tenant'] = this.tenant;
59
101
  return h;
60
102
  }
61
- async req(path, init) {
62
- let r;
103
+ /** Low-level fetch with timeout, caller signal and bounded retries. */
104
+ async fetchOnce(path, init, opts, timeoutMs) {
63
105
  const ctl = new AbortController();
64
- const to = setTimeout(() => ctl.abort(), this.timeoutMs);
106
+ const onAbort = () => ctl.abort();
107
+ if (opts.signal) {
108
+ if (opts.signal.aborted)
109
+ ctl.abort();
110
+ else
111
+ opts.signal.addEventListener('abort', onAbort, { once: true });
112
+ }
113
+ const to = timeoutMs > 0 ? setTimeout(() => ctl.abort(), timeoutMs) : undefined;
65
114
  try {
66
- r = await fetch(`${this.baseUrl}${path}`, {
115
+ return await fetch(`${this.baseUrl}${path}`, {
67
116
  ...init,
68
- signal: init?.signal ?? ctl.signal,
69
- headers: this.headers(init?.headers || {}),
117
+ signal: ctl.signal,
118
+ headers: this.headers({ ...init?.headers, ...(opts.headers || {}) }),
70
119
  });
71
120
  }
72
- catch (e) {
73
- throw new SynapseOneError(`synapseone network ${path}: ${e?.message || e}`);
74
- }
75
121
  finally {
76
- clearTimeout(to);
122
+ if (to)
123
+ clearTimeout(to);
124
+ if (opts.signal)
125
+ opts.signal.removeEventListener('abort', onAbort);
77
126
  }
78
- if (!r.ok) {
79
- const text = (await r.text()).slice(0, 2000);
80
- let body;
127
+ }
128
+ async req(path, init, options = {}) {
129
+ const method = String(init?.method || 'GET').toUpperCase();
130
+ const timeoutMs = options.timeoutMs ?? this.timeoutMs;
131
+ const maxAttempts = Math.max(1, Math.floor(options.maxAttempts ?? this.maxAttempts));
132
+ const retryable = options.retryable ?? IDEMPOTENT_METHODS.has(method);
133
+ let attempt = 0;
134
+ let lastErr;
135
+ for (;;) {
136
+ attempt++;
137
+ let r;
81
138
  try {
82
- body = JSON.parse(text);
139
+ r = await this.fetchOnce(path, init, options, timeoutMs);
140
+ }
141
+ catch (e) {
142
+ if (e?.name === 'AbortError') {
143
+ throw new SynapseOneError(`synapseone timeout ${path} after ${timeoutMs}ms (base ${this.baseUrl})`, {
144
+ code: 'timeout',
145
+ baseUrl: this.baseUrl,
146
+ });
147
+ }
148
+ lastErr = new SynapseOneError(`synapseone network ${path}: ${e?.message || e} (base ${this.baseUrl})`, {
149
+ code: 'network_error',
150
+ baseUrl: this.baseUrl,
151
+ });
152
+ if (retryable && attempt < maxAttempts) {
153
+ await sleep(this.retryBaseMs * 2 ** (attempt - 1) + Math.random() * this.retryBaseMs);
154
+ continue;
155
+ }
156
+ throw lastErr;
157
+ }
158
+ if (!r.ok) {
159
+ const text = (await r.text()).slice(0, 2000);
160
+ let body;
161
+ try {
162
+ body = JSON.parse(text);
163
+ }
164
+ catch {
165
+ body = undefined;
166
+ }
167
+ const err = new SynapseOneError(`synapseone ${r.status} ${path}: ${text} (base ${this.baseUrl})`, {
168
+ status: r.status,
169
+ body,
170
+ code: body?.error,
171
+ baseUrl: this.baseUrl,
172
+ });
173
+ if (retryable && RETRYABLE_STATUS.has(r.status) && attempt < maxAttempts) {
174
+ const wait = retryAfterMs(r.headers?.get?.('retry-after') ?? null)
175
+ ?? this.retryBaseMs * 2 ** (attempt - 1) + Math.random() * this.retryBaseMs;
176
+ await sleep(wait);
177
+ lastErr = err;
178
+ continue;
179
+ }
180
+ throw err;
181
+ }
182
+ const contentType = String(r.headers?.get?.('content-type') || '').toLowerCase();
183
+ const text = await r.text();
184
+ if (!text)
185
+ return null;
186
+ if (contentType.includes('text/html') || isHtml(text)) {
187
+ // A 200 SPA/HTML response means the base URL points at the console host,
188
+ // not the API gateway — surface it instead of returning HTML as data.
189
+ throw new SynapseOneError(`synapseone expected JSON from ${this.baseUrl}${path} but received HTML (status ${r.status}). `
190
+ + `The base URL looks like the console/marketing host; use ${exports.DEFAULT_BASE_URL}.`, { status: r.status, code: 'non_json_response', baseUrl: this.baseUrl });
191
+ }
192
+ try {
193
+ return JSON.parse(text);
83
194
  }
84
195
  catch {
85
- body = undefined;
196
+ return text;
86
197
  }
87
- throw new SynapseOneError(`synapseone ${r.status} ${path}: ${text}`, { status: r.status, body });
88
198
  }
199
+ }
200
+ /** Upload a binary body and parse the JSON response. */
201
+ async upload(path, body, options = {}) {
202
+ const timeoutMs = options.timeoutMs ?? this.timeoutMs;
203
+ const r = await this.fetchOnce(path, {
204
+ method: options.method || 'PUT',
205
+ body: body,
206
+ headers: {
207
+ 'Content-Type': options.contentType || 'application/octet-stream',
208
+ ...(options.headers || {}),
209
+ },
210
+ }, options, timeoutMs);
89
211
  const text = await r.text();
212
+ if (!r.ok) {
213
+ let parsed;
214
+ try {
215
+ parsed = JSON.parse(text);
216
+ }
217
+ catch {
218
+ parsed = undefined;
219
+ }
220
+ throw new SynapseOneError(`synapseone ${r.status} ${path}: ${text.slice(0, 500)} (base ${this.baseUrl})`, {
221
+ status: r.status,
222
+ body: parsed,
223
+ baseUrl: this.baseUrl,
224
+ });
225
+ }
90
226
  if (!text)
91
227
  return null;
92
228
  try {
@@ -96,13 +232,124 @@ class HttpClient {
96
232
  return text;
97
233
  }
98
234
  }
235
+ /** HEAD request; returns response headers (no body). */
236
+ async head(path, options = {}) {
237
+ const timeoutMs = options.timeoutMs ?? this.timeoutMs;
238
+ const r = await this.fetchOnce(path, { method: 'HEAD' }, options, timeoutMs);
239
+ if (!r.ok) {
240
+ throw new SynapseOneError(`synapseone ${r.status} ${path} (base ${this.baseUrl})`, {
241
+ status: r.status,
242
+ baseUrl: this.baseUrl,
243
+ });
244
+ }
245
+ const out = {};
246
+ r.headers?.forEach?.((v, k) => { out[k.toLowerCase()] = v; });
247
+ return out;
248
+ }
249
+ /** Fetch a binary body (bytes). Used by object storage/file downloads. */
250
+ async reqBinary(path, init, options = {}) {
251
+ const timeoutMs = options.timeoutMs ?? this.timeoutMs;
252
+ const r = await this.fetchOnce(path, init, options, timeoutMs);
253
+ if (!r.ok) {
254
+ const text = (await r.text()).slice(0, 2000);
255
+ throw new SynapseOneError(`synapseone ${r.status} ${path}: ${text} (base ${this.baseUrl})`, {
256
+ status: r.status,
257
+ baseUrl: this.baseUrl,
258
+ });
259
+ }
260
+ return new Uint8Array(await r.arrayBuffer());
261
+ }
262
+ /**
263
+ * Stream a Server-Sent-Events response as parsed `{event, data}` records.
264
+ * The transport is not buffered; each event is yielded as soon as a blank
265
+ * line terminates it. Pass an AbortSignal to cancel.
266
+ */
267
+ async *stream(path, init, options = {}) {
268
+ const timeoutMs = options.timeoutMs ?? exports.INFERENCE_TIMEOUT_MS;
269
+ const r = await this.fetchOnce(path, init, options, timeoutMs);
270
+ if (!r.ok) {
271
+ const text = (await r.text()).slice(0, 2000);
272
+ let body;
273
+ try {
274
+ body = JSON.parse(text);
275
+ }
276
+ catch {
277
+ body = undefined;
278
+ }
279
+ throw new SynapseOneError(`synapseone ${r.status} ${path}: ${text} (base ${this.baseUrl})`, {
280
+ status: r.status,
281
+ body,
282
+ baseUrl: this.baseUrl,
283
+ });
284
+ }
285
+ const body = r.body;
286
+ if (!body || typeof body.getReader !== 'function') {
287
+ // Non-streaming environment: decode the whole payload and parse SSE lines.
288
+ const text = await r.text();
289
+ yield* parseSse(text);
290
+ return;
291
+ }
292
+ const reader = body.getReader();
293
+ const decoder = new TextDecoder();
294
+ let buffer = '';
295
+ try {
296
+ for (;;) {
297
+ const { done, value } = await reader.read();
298
+ if (done)
299
+ break;
300
+ buffer += decoder.decode(value, { stream: true });
301
+ let idx;
302
+ while ((idx = buffer.search(/\r?\n\r?\n/)) >= 0) {
303
+ const chunk = buffer.slice(0, idx);
304
+ buffer = buffer.slice(idx).replace(/^\r?\n\r?\n/, '');
305
+ for (const ev of parseSse(chunk))
306
+ yield ev;
307
+ }
308
+ }
309
+ buffer += decoder.decode();
310
+ if (buffer.trim())
311
+ for (const ev of parseSse(buffer))
312
+ yield ev;
313
+ }
314
+ finally {
315
+ try {
316
+ reader.releaseLock();
317
+ }
318
+ catch { /* already released */ }
319
+ }
320
+ }
99
321
  /** Generic escape hatch (forward-compat with endpoints added after this SDK). */
100
- call(method, path, body) {
322
+ call(method, path, body, options = {}) {
101
323
  const m = String(method || 'GET').toUpperCase();
102
324
  return this.req(path.startsWith('/') ? path : `/${path}`, {
103
325
  method: m,
104
326
  body: body === undefined || m === 'GET' || m === 'HEAD' ? undefined : JSON.stringify(body),
105
- });
327
+ }, options);
106
328
  }
107
329
  }
108
330
  exports.HttpClient = HttpClient;
331
+ function* parseSse(text) {
332
+ let event = 'message';
333
+ const data = [];
334
+ for (const rawLine of text.split(/\r?\n/)) {
335
+ const line = rawLine.trimEnd();
336
+ if (!line) {
337
+ if (data.length)
338
+ yield { event, data: data.join('\n') };
339
+ event = 'message';
340
+ data.length = 0;
341
+ continue;
342
+ }
343
+ if (line.startsWith(':'))
344
+ continue;
345
+ const idx = line.indexOf(':');
346
+ const field = idx === -1 ? line : line.slice(0, idx);
347
+ const value = idx === -1 ? '' : line.slice(idx + 1).replace(/^ /, '');
348
+ if (field === 'event')
349
+ event = value;
350
+ else if (field === 'data')
351
+ data.push(value);
352
+ }
353
+ if (data.length)
354
+ yield { event, data: data.join('\n') };
355
+ }
package/dist/ai.js CHANGED
@@ -1,4 +1,6 @@
1
1
  "use strict";
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ // SPDX-FileCopyrightText: 2026 VKU.VIC
2
4
  /*
3
5
  * Copyright 2026 SynapseOne Contributors
4
6
  * Licensed under the Apache License, Version 2.0 (the "License");
package/dist/base.d.ts CHANGED
@@ -161,6 +161,17 @@ export declare class BaseService {
161
161
  limit?: number;
162
162
  offset?: number;
163
163
  }): Promise<Array<BaseDocument<T>>>;
164
+ /** Paginated listing with an exact total and next cursor (#29 envelope). */
165
+ listDocumentsPage<T = Record<string, unknown>>(collection: string, opts?: {
166
+ limit?: number;
167
+ offset?: number;
168
+ }): Promise<{
169
+ items: Array<BaseDocument<T>>;
170
+ total: number;
171
+ limit: number;
172
+ offset: number;
173
+ nextCursor: string | null;
174
+ }>;
164
175
  increment<T = Record<string, unknown>>(collection: string, id: string, body: {
165
176
  field: string;
166
177
  by?: number;
package/dist/base.js CHANGED
@@ -1,4 +1,6 @@
1
1
  "use strict";
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ // SPDX-FileCopyrightText: 2026 VKU.VIC
2
4
  /*
3
5
  * Copyright 2026 SynapseOne Contributors
4
6
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -206,6 +208,10 @@ class BaseService {
206
208
  listDocuments(collection, opts = {}) {
207
209
  return this.http.req(`/v1/base/collections/${encodeURIComponent(collection)}/documents${queryString({ limit: opts.limit, offset: opts.offset })}`);
208
210
  }
211
+ /** Paginated listing with an exact total and next cursor (#29 envelope). */
212
+ listDocumentsPage(collection, opts = {}) {
213
+ return this.http.req(`/v1/base/collections/${encodeURIComponent(collection)}/documents${queryString({ limit: opts.limit, offset: opts.offset, withTotal: 1 })}`);
214
+ }
209
215
  increment(collection, id, body) {
210
216
  return this.http.req(`/v1/base/collections/${encodeURIComponent(collection)}/documents/${encodeURIComponent(id)}/increment`, {
211
217
  method: 'POST',
@@ -3,6 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ // SPDX-License-Identifier: Apache-2.0
7
+ // SPDX-FileCopyrightText: 2026 VKU.VIC
6
8
  /*
7
9
  * Copyright 2026 SynapseOne Contributors
8
10
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -44,6 +46,22 @@ let server;
44
46
  strict_1.default.equal(index_1.SynapseDXClient, index_1.SynapseOneClient);
45
47
  strict_1.default.equal(index_1.SynapseDXError, index_1.SynapseOneError);
46
48
  });
49
+ (0, node_test_1.it)('defaults to the public API gateway, not the console host', () => {
50
+ const orig = process.env.SYNAPSEONE_BASE_URL;
51
+ const origLegacy = process.env.SYNAPSEDX_BASE_URL;
52
+ delete process.env.SYNAPSEONE_BASE_URL;
53
+ delete process.env.SYNAPSEDX_BASE_URL;
54
+ try {
55
+ const c = new index_1.SynapseOneClient();
56
+ strict_1.default.equal(c.baseUrl, 'https://api.synapseone.space');
57
+ }
58
+ finally {
59
+ if (orig !== undefined)
60
+ process.env.SYNAPSEONE_BASE_URL = orig;
61
+ if (origLegacy !== undefined)
62
+ process.env.SYNAPSEDX_BASE_URL = origLegacy;
63
+ }
64
+ });
47
65
  (0, node_test_1.it)('trims trailing slash', () => {
48
66
  const c = new index_1.SynapseOneClient({ baseUrl: 'http://x:8080/' });
49
67
  strict_1.default.equal(c.baseUrl, 'http://x:8080');
package/dist/example.js CHANGED
@@ -1,12 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ // SPDX-FileCopyrightText: 2026 VKU.VIC
3
5
  /*
4
6
  * Copyright 2026 SynapseOne Contributors
5
7
  * Licensed under the Apache License, Version 2.0 (the "License");
6
8
  */
7
9
  const index_1 = require("./index");
8
10
  async function main() {
9
- const c = new index_1.SynapseOneClient({ baseUrl: process.env.SYNAPSEONE_BASE_URL || 'http://localhost:8080' });
11
+ const c = new index_1.SynapseOneClient(); // defaults to https://synapseone.space or $SYNAPSEONE_BASE_URL
10
12
  const login = await c.login('demo@acme.local');
11
13
  console.log('login ok:', login.user?.email);
12
14
  c.setToken(login.accessToken);
package/dist/faststore.js CHANGED
@@ -1,4 +1,6 @@
1
1
  "use strict";
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ // SPDX-FileCopyrightText: 2026 VKU.VIC
2
4
  /*
3
5
  * Copyright 2026 SynapseOne Contributors
4
6
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -0,0 +1,54 @@
1
+ import { HttpClient, RequestOptions } from './_http';
2
+ export interface DependencyStatus {
3
+ name: string;
4
+ ok: boolean;
5
+ status?: number;
6
+ ms?: number;
7
+ version?: string;
8
+ error?: string;
9
+ }
10
+ export interface HealthResponse {
11
+ status: string;
12
+ service: string;
13
+ version: string;
14
+ time: string;
15
+ schemaVersion?: string;
16
+ dependencies?: DependencyStatus[];
17
+ }
18
+ export interface ReadyResponse {
19
+ status: 'ready' | 'degraded' | string;
20
+ service: string;
21
+ version: string;
22
+ upstreams: DependencyStatus[];
23
+ time: string;
24
+ schemaVersion?: string;
25
+ }
26
+ export interface StatusResponse {
27
+ ok: boolean;
28
+ tier1: {
29
+ status: string;
30
+ version?: string;
31
+ reachable: boolean;
32
+ httpStatus?: number;
33
+ };
34
+ upstreams: DependencyStatus[];
35
+ time?: string;
36
+ }
37
+ export interface ReadyOptions extends RequestOptions {
38
+ /** Throw on a degraded (503) response instead of returning it (default false). */
39
+ throwOnDegraded?: boolean;
40
+ }
41
+ export declare class HealthService {
42
+ private readonly http;
43
+ constructor(http: HttpClient);
44
+ /** Shallow gateway health (never throws on a degraded dependency). */
45
+ health(opts?: RequestOptions): Promise<HealthResponse>;
46
+ /**
47
+ * Aggregate readiness. A 503 means "degraded", not "unreachable": by default
48
+ * the parsed payload is returned so callers can project the upstream list.
49
+ * Set `throwOnDegraded: true` for strict probe semantics.
50
+ */
51
+ ready(opts?: ReadyOptions): Promise<ReadyResponse>;
52
+ /** Convenience projection used by Tier-2 `/ready` endpoints. */
53
+ status(opts?: ReadyOptions): Promise<StatusResponse>;
54
+ }
package/dist/health.js ADDED
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ // SPDX-FileCopyrightText: 2026 VKU.VIC
4
+ /*
5
+ * Copyright 2026 SynapseOne Contributors
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * Typed gateway health/readiness contract (issue #42).
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.HealthService = void 0;
11
+ const _http_1 = require("./_http");
12
+ class HealthService {
13
+ http;
14
+ constructor(http) {
15
+ this.http = http;
16
+ }
17
+ /** Shallow gateway health (never throws on a degraded dependency). */
18
+ health(opts = {}) {
19
+ return this.http.req('/health', undefined, { maxAttempts: 1, ...opts });
20
+ }
21
+ /**
22
+ * Aggregate readiness. A 503 means "degraded", not "unreachable": by default
23
+ * the parsed payload is returned so callers can project the upstream list.
24
+ * Set `throwOnDegraded: true` for strict probe semantics.
25
+ */
26
+ async ready(opts = {}) {
27
+ try {
28
+ return await this.http.req('/ready', undefined, { maxAttempts: 1, ...opts });
29
+ }
30
+ catch (e) {
31
+ if (e instanceof _http_1.SynapseOneError && e.status === 503 && e.body && !opts.throwOnDegraded) {
32
+ return e.body;
33
+ }
34
+ throw e;
35
+ }
36
+ }
37
+ /** Convenience projection used by Tier-2 `/ready` endpoints. */
38
+ async status(opts = {}) {
39
+ const [health, ready] = await Promise.all([
40
+ this.health(opts).catch((e) => ({ error: e })),
41
+ this.ready(opts).catch((e) => ({ error: e })),
42
+ ]);
43
+ const upstreams = Array.isArray(ready?.upstreams) ? ready.upstreams : [];
44
+ const reachable = !!health?.status && !health?.error;
45
+ return {
46
+ ok: reachable && ready?.status === 'ready',
47
+ tier1: {
48
+ status: health?.status || 'unreachable',
49
+ version: health?.version,
50
+ reachable,
51
+ httpStatus: health?.error?.status,
52
+ },
53
+ upstreams,
54
+ time: ready?.time,
55
+ };
56
+ }
57
+ }
58
+ exports.HealthService = HealthService;