@secondlayer/sdk 3.5.2 → 3.5.4
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 +19 -1
- package/dist/index.d.ts +42 -2
- package/dist/index.js +100 -37
- package/dist/index.js.map +6 -6
- package/dist/subgraphs/index.d.ts +39 -1
- package/dist/subgraphs/index.js +100 -37
- package/dist/subgraphs/index.js.map +6 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -172,6 +172,17 @@ for await (const transfer of sl.index.ftTransfers.walk({
|
|
|
172
172
|
|
|
173
173
|
Deploy and query app-specific L3 tables.
|
|
174
174
|
|
|
175
|
+
Subgraphs and subscriptions live on per-tenant containers (`https://<slug>.api.secondlayer.tools`), not on the platform `api.secondlayer.tools`. The tenant containers expect a short-lived HS256 JWT, not your platform `sk-sl_*` key — so the SDK transparently mints one on the first subgraph or subscription call (POST `/api/tenants/me/keys/mint-ephemeral`), caches the apiUrl + JWT for the session's lifetime, and refreshes 30 s before expiry. You don't need to know the URL or manage tokens — just pass your normal `apiKey`.
|
|
176
|
+
|
|
177
|
+
If you already know your tenant URL (OSS, staging, or a custom routing setup), skip the lookup with `tenantBaseUrl`:
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
const sl = new SecondLayer({
|
|
181
|
+
apiKey: "sk-sl_...",
|
|
182
|
+
tenantBaseUrl: "https://myslug.api.secondlayer.tools", // optional
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
175
186
|
```typescript
|
|
176
187
|
// List
|
|
177
188
|
const { data } = await sl.subgraphs.list();
|
|
@@ -243,7 +254,14 @@ try {
|
|
|
243
254
|
} catch (err) {
|
|
244
255
|
if (err instanceof ApiError) {
|
|
245
256
|
console.log(err.status); // 404
|
|
246
|
-
console.log(err.
|
|
257
|
+
console.log(err.code); // "NOT_FOUND" (from API's {error, code} envelope, if present)
|
|
258
|
+
console.log(err.message); // "Subgraph not found"
|
|
259
|
+
console.log(err.body); // full parsed envelope
|
|
247
260
|
}
|
|
248
261
|
}
|
|
249
262
|
```
|
|
263
|
+
|
|
264
|
+
Tenant-resolution failures surface as `ApiError` with distinctive codes:
|
|
265
|
+
|
|
266
|
+
- `code: "TENANT_SUSPENDED"` — your tenant is suspended (see `err.message` for the limit reason)
|
|
267
|
+
- `code: "NO_TENANT"` — your account has no provisioned tenant yet
|
package/dist/index.d.ts
CHANGED
|
@@ -4,23 +4,61 @@ import { SubgraphAgentSchema, SubgraphSpecOptions } from "@secondlayer/shared/su
|
|
|
4
4
|
import { InferSubgraphClient } from "@secondlayer/subgraphs";
|
|
5
5
|
type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
6
6
|
interface SecondLayerOptions {
|
|
7
|
-
/** Base URL of the Secondlayer API (trailing slashes are stripped). */
|
|
7
|
+
/** Base URL of the Secondlayer platform API (trailing slashes are stripped). */
|
|
8
8
|
baseUrl: string;
|
|
9
9
|
/** Bearer token for authenticated requests. */
|
|
10
10
|
apiKey?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Explicit tenant API base URL — bypass the auto-resolution that calls
|
|
13
|
+
* `/api/tenants/me` on first tenant-resource request. Use when you already
|
|
14
|
+
* know your tenant URL (OSS, staging, or any custom routing setup).
|
|
15
|
+
*/
|
|
16
|
+
tenantBaseUrl?: string;
|
|
11
17
|
/** Fetch implementation. Tests and edge runtimes can provide their own. */
|
|
12
18
|
fetchImpl?: FetchLike;
|
|
13
19
|
/** Deploy origin label sent as `x-sl-origin` (telemetry). Defaults to `cli`. */
|
|
14
20
|
origin?: "cli" | "mcp" | "session";
|
|
15
21
|
}
|
|
22
|
+
type TenantSession = {
|
|
23
|
+
apiUrl: string
|
|
24
|
+
token: string
|
|
25
|
+
expiresAtMs: number
|
|
26
|
+
};
|
|
16
27
|
declare abstract class BaseClient {
|
|
17
28
|
protected baseUrl: string;
|
|
18
29
|
protected apiKey?: string;
|
|
19
30
|
protected origin: "cli" | "mcp" | "session";
|
|
31
|
+
protected tenantBaseUrlOverride?: string;
|
|
32
|
+
private _tenantSession;
|
|
33
|
+
private _tenantSessionPromise;
|
|
20
34
|
constructor(options?: Partial<SecondLayerOptions>);
|
|
21
35
|
static authHeaders(apiKey?: string): Record<string, string>;
|
|
22
36
|
protected request<T>(method: string, path: string, body?: unknown): Promise<T>;
|
|
37
|
+
protected requestAt<T>(baseUrl: string, method: string, path: string, body?: unknown, authToken?: string): Promise<T>;
|
|
23
38
|
protected requestText(method: string, path: string, body?: unknown): Promise<string>;
|
|
39
|
+
/**
|
|
40
|
+
* Resolve + cache a tenant session for tenant-resource calls (subgraphs,
|
|
41
|
+
* subscriptions). On the platform API, those routes are not mounted —
|
|
42
|
+
* they live on per-tenant containers at `https://<slug>.api.secondlayer.tools`,
|
|
43
|
+
* which expect a short-lived HS256 JWT (not the platform `sk-sl_*` key).
|
|
44
|
+
*
|
|
45
|
+
* `POST /api/tenants/me/keys/mint-ephemeral` returns both the tenant `apiUrl`
|
|
46
|
+
* and a 5-min `serviceKey` JWT in one round-trip. We cache the session and
|
|
47
|
+
* refresh before expiry. Failures are NOT cached, so a flaky platform call
|
|
48
|
+
* doesn't permanently break the SDK.
|
|
49
|
+
*
|
|
50
|
+
* Bypass via `tenantBaseUrl` constructor option for OSS / staging / custom
|
|
51
|
+
* routing where the same `apiKey` works against both surfaces.
|
|
52
|
+
*/
|
|
53
|
+
protected getTenantSession(): Promise<TenantSession>;
|
|
54
|
+
/**
|
|
55
|
+
* Returns just the tenant API base URL. Convenience wrapper around
|
|
56
|
+
* `getTenantSession` for callers that don't need the auth token (e.g. tests).
|
|
57
|
+
*/
|
|
58
|
+
protected getTenantBaseUrl(): Promise<string>;
|
|
59
|
+
private mintTenantSession;
|
|
60
|
+
protected requestAtTenant<T>(method: string, path: string, body?: unknown): Promise<T>;
|
|
61
|
+
protected requestTextAtTenant(method: string, path: string, body?: unknown): Promise<string>;
|
|
24
62
|
private fetchResponse;
|
|
25
63
|
}
|
|
26
64
|
interface SubgraphSource {
|
|
@@ -475,7 +513,9 @@ declare class ApiError extends Error {
|
|
|
475
513
|
status: number;
|
|
476
514
|
/** Raw response body (parsed JSON if possible) — preserved for callers that need error details. */
|
|
477
515
|
body?: unknown;
|
|
478
|
-
|
|
516
|
+
/** Stable machine-readable code from the API's `{error, code}` error envelope. */
|
|
517
|
+
code?: string;
|
|
518
|
+
constructor(status: number, message: string, body?: unknown, code?: string);
|
|
479
519
|
}
|
|
480
520
|
/**
|
|
481
521
|
* Thrown on optimistic-concurrency conflict when a deploy supplies an
|
package/dist/index.js
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
class ApiError extends Error {
|
|
3
3
|
status;
|
|
4
4
|
body;
|
|
5
|
-
|
|
5
|
+
code;
|
|
6
|
+
constructor(status, message, body, code) {
|
|
6
7
|
super(message);
|
|
7
8
|
this.status = status;
|
|
8
9
|
this.body = body;
|
|
10
|
+
this.code = code;
|
|
9
11
|
this.name = "ApiError";
|
|
10
12
|
}
|
|
11
13
|
}
|
|
@@ -23,15 +25,20 @@ class VersionConflictError extends ApiError {
|
|
|
23
25
|
|
|
24
26
|
// src/base.ts
|
|
25
27
|
var DEFAULT_BASE_URL = "https://api.secondlayer.tools";
|
|
28
|
+
var TENANT_JWT_REFRESH_BUFFER_MS = 30000;
|
|
26
29
|
|
|
27
30
|
class BaseClient {
|
|
28
31
|
baseUrl;
|
|
29
32
|
apiKey;
|
|
30
33
|
origin;
|
|
34
|
+
tenantBaseUrlOverride;
|
|
35
|
+
_tenantSession = null;
|
|
36
|
+
_tenantSessionPromise = null;
|
|
31
37
|
constructor(options = {}) {
|
|
32
38
|
this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
33
39
|
this.apiKey = options.apiKey;
|
|
34
40
|
this.origin = options.origin ?? "cli";
|
|
41
|
+
this.tenantBaseUrlOverride = options.tenantBaseUrl?.replace(/\/+$/, "");
|
|
35
42
|
}
|
|
36
43
|
static authHeaders(apiKey) {
|
|
37
44
|
const headers = {
|
|
@@ -43,19 +50,71 @@ class BaseClient {
|
|
|
43
50
|
return headers;
|
|
44
51
|
}
|
|
45
52
|
async request(method, path, body) {
|
|
46
|
-
|
|
53
|
+
return this.requestAt(this.baseUrl, method, path, body);
|
|
54
|
+
}
|
|
55
|
+
async requestAt(baseUrl, method, path, body, authToken) {
|
|
56
|
+
const response = await this.fetchResponse(baseUrl, method, path, body, authToken);
|
|
47
57
|
if (response.status === 204) {
|
|
48
58
|
return;
|
|
49
59
|
}
|
|
50
60
|
return response.json();
|
|
51
61
|
}
|
|
52
62
|
async requestText(method, path, body) {
|
|
53
|
-
const response = await this.fetchResponse(method, path, body);
|
|
63
|
+
const response = await this.fetchResponse(this.baseUrl, method, path, body);
|
|
54
64
|
return response.text();
|
|
55
65
|
}
|
|
56
|
-
async
|
|
57
|
-
|
|
58
|
-
|
|
66
|
+
async getTenantSession() {
|
|
67
|
+
if (this.tenantBaseUrlOverride) {
|
|
68
|
+
return {
|
|
69
|
+
apiUrl: this.tenantBaseUrlOverride,
|
|
70
|
+
token: this.apiKey ?? "",
|
|
71
|
+
expiresAtMs: Number.POSITIVE_INFINITY
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const cached = this._tenantSession;
|
|
75
|
+
if (cached && cached.expiresAtMs - Date.now() > TENANT_JWT_REFRESH_BUFFER_MS) {
|
|
76
|
+
return cached;
|
|
77
|
+
}
|
|
78
|
+
if (!this._tenantSessionPromise) {
|
|
79
|
+
this._tenantSessionPromise = this.mintTenantSession().catch((err) => {
|
|
80
|
+
this._tenantSessionPromise = null;
|
|
81
|
+
throw err;
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return this._tenantSessionPromise;
|
|
85
|
+
}
|
|
86
|
+
async getTenantBaseUrl() {
|
|
87
|
+
return (await this.getTenantSession()).apiUrl;
|
|
88
|
+
}
|
|
89
|
+
async mintTenantSession() {
|
|
90
|
+
const body = await this.request("POST", "/api/tenants/me/keys/mint-ephemeral");
|
|
91
|
+
if (!body.apiUrl) {
|
|
92
|
+
throw new ApiError(404, "No tenant API URL available for this account. Provision a tenant at https://secondlayer.tools/platform.", body, "NO_TENANT");
|
|
93
|
+
}
|
|
94
|
+
if (!body.serviceKey) {
|
|
95
|
+
throw new ApiError(500, "Tenant mint-ephemeral returned no serviceKey.", body, "NO_TENANT_TOKEN");
|
|
96
|
+
}
|
|
97
|
+
const session = {
|
|
98
|
+
apiUrl: body.apiUrl.replace(/\/+$/, ""),
|
|
99
|
+
token: body.serviceKey,
|
|
100
|
+
expiresAtMs: Date.parse(body.expiresAt)
|
|
101
|
+
};
|
|
102
|
+
this._tenantSession = session;
|
|
103
|
+
this._tenantSessionPromise = null;
|
|
104
|
+
return session;
|
|
105
|
+
}
|
|
106
|
+
async requestAtTenant(method, path, body) {
|
|
107
|
+
const session = await this.getTenantSession();
|
|
108
|
+
return this.requestAt(session.apiUrl, method, path, body, session.token);
|
|
109
|
+
}
|
|
110
|
+
async requestTextAtTenant(method, path, body) {
|
|
111
|
+
const session = await this.getTenantSession();
|
|
112
|
+
const response = await this.fetchResponse(session.apiUrl, method, path, body, session.token);
|
|
113
|
+
return response.text();
|
|
114
|
+
}
|
|
115
|
+
async fetchResponse(baseUrl, method, path, body, authToken) {
|
|
116
|
+
const url = `${baseUrl}${path}`;
|
|
117
|
+
const headers = BaseClient.authHeaders(authToken ?? this.apiKey);
|
|
59
118
|
headers["x-sl-origin"] = this.origin;
|
|
60
119
|
let response;
|
|
61
120
|
try {
|
|
@@ -65,7 +124,7 @@ class BaseClient {
|
|
|
65
124
|
body: body ? JSON.stringify(body) : undefined
|
|
66
125
|
});
|
|
67
126
|
} catch {
|
|
68
|
-
throw new ApiError(0, `Cannot reach API at ${
|
|
127
|
+
throw new ApiError(0, `Cannot reach API at ${baseUrl}. Check your connection or try again.`);
|
|
69
128
|
}
|
|
70
129
|
if (!response.ok) {
|
|
71
130
|
if (response.status === 401) {
|
|
@@ -77,11 +136,12 @@ class BaseClient {
|
|
|
77
136
|
throw new ApiError(429, msg);
|
|
78
137
|
}
|
|
79
138
|
if (response.status >= 500) {
|
|
80
|
-
throw new ApiError(response.status, `Server error. Try again or check status at ${
|
|
139
|
+
throw new ApiError(response.status, `Server error. Try again or check status at ${baseUrl}/health`);
|
|
81
140
|
}
|
|
82
141
|
const errorBody = await response.text();
|
|
83
142
|
let message = `HTTP ${response.status}`;
|
|
84
143
|
let parsedBody = errorBody;
|
|
144
|
+
let code;
|
|
85
145
|
try {
|
|
86
146
|
const json = JSON.parse(errorBody);
|
|
87
147
|
parsedBody = json;
|
|
@@ -91,11 +151,14 @@ class BaseClient {
|
|
|
91
151
|
} else if (err && typeof err === "object") {
|
|
92
152
|
message = JSON.stringify(err);
|
|
93
153
|
}
|
|
154
|
+
if (typeof json.code === "string") {
|
|
155
|
+
code = json.code;
|
|
156
|
+
}
|
|
94
157
|
} catch {
|
|
95
158
|
if (errorBody)
|
|
96
159
|
message = errorBody;
|
|
97
160
|
}
|
|
98
|
-
throw new ApiError(response.status, message, parsedBody);
|
|
161
|
+
throw new ApiError(response.status, message, parsedBody, code);
|
|
99
162
|
}
|
|
100
163
|
return response;
|
|
101
164
|
}
|
|
@@ -173,28 +236,28 @@ function buildSpecQueryString(options) {
|
|
|
173
236
|
|
|
174
237
|
class Subgraphs extends BaseClient {
|
|
175
238
|
async list() {
|
|
176
|
-
return this.
|
|
239
|
+
return this.requestAtTenant("GET", "/api/subgraphs");
|
|
177
240
|
}
|
|
178
241
|
async get(name) {
|
|
179
|
-
return this.
|
|
242
|
+
return this.requestAtTenant("GET", `/api/subgraphs/${name}`);
|
|
180
243
|
}
|
|
181
244
|
async openapi(name, options) {
|
|
182
|
-
return this.
|
|
245
|
+
return this.requestAtTenant("GET", `/api/subgraphs/${name}/openapi.json${buildSpecQueryString(options)}`);
|
|
183
246
|
}
|
|
184
247
|
async schema(name, options) {
|
|
185
|
-
return this.
|
|
248
|
+
return this.requestAtTenant("GET", `/api/subgraphs/${name}/schema.json${buildSpecQueryString(options)}`);
|
|
186
249
|
}
|
|
187
250
|
async markdown(name, options) {
|
|
188
|
-
return this.
|
|
251
|
+
return this.requestTextAtTenant("GET", `/api/subgraphs/${name}/docs.md${buildSpecQueryString(options)}`);
|
|
189
252
|
}
|
|
190
253
|
async reindex(name, options) {
|
|
191
|
-
return this.
|
|
254
|
+
return this.requestAtTenant("POST", `/api/subgraphs/${name}/reindex`, options);
|
|
192
255
|
}
|
|
193
256
|
async stop(name) {
|
|
194
|
-
return this.
|
|
257
|
+
return this.requestAtTenant("POST", `/api/subgraphs/${name}/stop`);
|
|
195
258
|
}
|
|
196
259
|
async backfill(name, options) {
|
|
197
|
-
return this.
|
|
260
|
+
return this.requestAtTenant("POST", `/api/subgraphs/${name}/backfill`, options);
|
|
198
261
|
}
|
|
199
262
|
async gaps(name, opts) {
|
|
200
263
|
const qs = new URLSearchParams;
|
|
@@ -205,27 +268,27 @@ class Subgraphs extends BaseClient {
|
|
|
205
268
|
if (opts?.resolved !== undefined)
|
|
206
269
|
qs.set("resolved", String(opts.resolved));
|
|
207
270
|
const query = qs.toString();
|
|
208
|
-
return this.
|
|
271
|
+
return this.requestAtTenant("GET", `/api/subgraphs/${name}/gaps${query ? `?${query}` : ""}`);
|
|
209
272
|
}
|
|
210
273
|
async delete(name, options) {
|
|
211
274
|
const qs = options?.force ? "?force=true" : "";
|
|
212
|
-
return this.
|
|
275
|
+
return this.requestAtTenant("DELETE", `/api/subgraphs/${name}${qs}`);
|
|
213
276
|
}
|
|
214
277
|
async deploy(data) {
|
|
215
|
-
return this.
|
|
278
|
+
return this.requestAtTenant("POST", "/api/subgraphs", data);
|
|
216
279
|
}
|
|
217
280
|
async getSource(name) {
|
|
218
|
-
return this.
|
|
281
|
+
return this.requestAtTenant("GET", `/api/subgraphs/${name}/source`);
|
|
219
282
|
}
|
|
220
283
|
async bundle(data) {
|
|
221
|
-
return this.
|
|
284
|
+
return this.requestAtTenant("POST", "/api/subgraphs/bundle", data);
|
|
222
285
|
}
|
|
223
286
|
async queryTable(name, table, params = {}) {
|
|
224
|
-
const result = await this.
|
|
287
|
+
const result = await this.requestAtTenant("GET", `/api/subgraphs/${name}/${table}${buildSubgraphQueryString(params)}`);
|
|
225
288
|
return Array.isArray(result) ? result : result.data;
|
|
226
289
|
}
|
|
227
290
|
async queryTableCount(name, table, params = {}) {
|
|
228
|
-
return this.
|
|
291
|
+
return this.requestAtTenant("GET", `/api/subgraphs/${name}/${table}/count${buildSubgraphQueryString(params)}`);
|
|
229
292
|
}
|
|
230
293
|
typed(def) {
|
|
231
294
|
const result = {};
|
|
@@ -648,40 +711,40 @@ function createStreamsClient(options) {
|
|
|
648
711
|
// src/subscriptions/client.ts
|
|
649
712
|
class Subscriptions extends BaseClient {
|
|
650
713
|
async list() {
|
|
651
|
-
return this.
|
|
714
|
+
return this.requestAtTenant("GET", "/api/subscriptions");
|
|
652
715
|
}
|
|
653
716
|
async get(id) {
|
|
654
|
-
return this.
|
|
717
|
+
return this.requestAtTenant("GET", `/api/subscriptions/${id}`);
|
|
655
718
|
}
|
|
656
719
|
async create(input) {
|
|
657
|
-
return this.
|
|
720
|
+
return this.requestAtTenant("POST", "/api/subscriptions", input);
|
|
658
721
|
}
|
|
659
722
|
async update(id, patch) {
|
|
660
|
-
return this.
|
|
723
|
+
return this.requestAtTenant("PATCH", `/api/subscriptions/${id}`, patch);
|
|
661
724
|
}
|
|
662
725
|
async pause(id) {
|
|
663
|
-
return this.
|
|
726
|
+
return this.requestAtTenant("POST", `/api/subscriptions/${id}/pause`);
|
|
664
727
|
}
|
|
665
728
|
async resume(id) {
|
|
666
|
-
return this.
|
|
729
|
+
return this.requestAtTenant("POST", `/api/subscriptions/${id}/resume`);
|
|
667
730
|
}
|
|
668
731
|
async delete(id) {
|
|
669
|
-
return this.
|
|
732
|
+
return this.requestAtTenant("DELETE", `/api/subscriptions/${id}`);
|
|
670
733
|
}
|
|
671
734
|
async rotateSecret(id) {
|
|
672
|
-
return this.
|
|
735
|
+
return this.requestAtTenant("POST", `/api/subscriptions/${id}/rotate-secret`);
|
|
673
736
|
}
|
|
674
737
|
async recentDeliveries(id) {
|
|
675
|
-
return this.
|
|
738
|
+
return this.requestAtTenant("GET", `/api/subscriptions/${id}/deliveries`);
|
|
676
739
|
}
|
|
677
740
|
async replay(id, range) {
|
|
678
|
-
return this.
|
|
741
|
+
return this.requestAtTenant("POST", `/api/subscriptions/${id}/replay`, range);
|
|
679
742
|
}
|
|
680
743
|
async dead(id) {
|
|
681
|
-
return this.
|
|
744
|
+
return this.requestAtTenant("GET", `/api/subscriptions/${id}/dead`);
|
|
682
745
|
}
|
|
683
746
|
async requeueDead(id, outboxId) {
|
|
684
|
-
return this.
|
|
747
|
+
return this.requestAtTenant("POST", `/api/subscriptions/${id}/dead/${outboxId}/requeue`);
|
|
685
748
|
}
|
|
686
749
|
}
|
|
687
750
|
|
|
@@ -872,5 +935,5 @@ export {
|
|
|
872
935
|
ApiError
|
|
873
936
|
};
|
|
874
937
|
|
|
875
|
-
//# debugId=
|
|
938
|
+
//# debugId=324E81D5B88A8D7F64756E2164756E21
|
|
876
939
|
//# sourceMappingURL=index.js.map
|