@secondlayer/sdk 3.5.1 → 3.5.3

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
@@ -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 SDK transparently resolves your tenant URL on the first subgraph or subscription call by hitting `/api/tenants/me` with your API key, then caches the result. You don't need to know the URL — 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.message); // "Contract not found"
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,10 +4,16 @@ 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`. */
@@ -17,10 +23,28 @@ declare abstract class BaseClient {
17
23
  protected baseUrl: string;
18
24
  protected apiKey?: string;
19
25
  protected origin: "cli" | "mcp" | "session";
26
+ protected tenantBaseUrlOverride?: string;
27
+ private _tenantBaseUrlPromise;
20
28
  constructor(options?: Partial<SecondLayerOptions>);
21
29
  static authHeaders(apiKey?: string): Record<string, string>;
22
30
  protected request<T>(method: string, path: string, body?: unknown): Promise<T>;
31
+ protected requestAt<T>(baseUrl: string, method: string, path: string, body?: unknown): Promise<T>;
23
32
  protected requestText(method: string, path: string, body?: unknown): Promise<string>;
33
+ /**
34
+ * Resolve and cache the tenant API base URL for tenant-resource calls
35
+ * (subgraphs, subscriptions). On the platform API, those routes are not
36
+ * mounted — they live on per-tenant containers at
37
+ * `https://<slug>.api.secondlayer.tools`. This method asks
38
+ * `/api/tenants/me` (against the platform baseUrl) for the apiUrl that
39
+ * belongs to the authenticated account.
40
+ *
41
+ * The result is cached on the client instance. Failures are NOT cached, so
42
+ * a flaky platform call doesn't permanently break the SDK.
43
+ */
44
+ protected getTenantBaseUrl(): Promise<string>;
45
+ private resolveTenantBaseUrl;
46
+ protected requestAtTenant<T>(method: string, path: string, body?: unknown): Promise<T>;
47
+ protected requestTextAtTenant(method: string, path: string, body?: unknown): Promise<string>;
24
48
  private fetchResponse;
25
49
  }
26
50
  interface SubgraphSource {
@@ -475,7 +499,9 @@ declare class ApiError extends Error {
475
499
  status: number;
476
500
  /** Raw response body (parsed JSON if possible) — preserved for callers that need error details. */
477
501
  body?: unknown;
478
- constructor(status: number, message: string, body?: unknown);
502
+ /** Stable machine-readable code from the API's `{error, code}` error envelope. */
503
+ code?: string;
504
+ constructor(status: number, message: string, body?: unknown, code?: string);
479
505
  }
480
506
  /**
481
507
  * 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
- constructor(status, message, body) {
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
  }
@@ -28,10 +30,13 @@ class BaseClient {
28
30
  baseUrl;
29
31
  apiKey;
30
32
  origin;
33
+ tenantBaseUrlOverride;
34
+ _tenantBaseUrlPromise = null;
31
35
  constructor(options = {}) {
32
36
  this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
33
37
  this.apiKey = options.apiKey;
34
38
  this.origin = options.origin ?? "cli";
39
+ this.tenantBaseUrlOverride = options.tenantBaseUrl?.replace(/\/+$/, "");
35
40
  }
36
41
  static authHeaders(apiKey) {
37
42
  const headers = {
@@ -43,18 +48,53 @@ class BaseClient {
43
48
  return headers;
44
49
  }
45
50
  async request(method, path, body) {
46
- const response = await this.fetchResponse(method, path, body);
51
+ return this.requestAt(this.baseUrl, method, path, body);
52
+ }
53
+ async requestAt(baseUrl, method, path, body) {
54
+ const response = await this.fetchResponse(baseUrl, method, path, body);
47
55
  if (response.status === 204) {
48
56
  return;
49
57
  }
50
58
  return response.json();
51
59
  }
52
60
  async requestText(method, path, body) {
53
- const response = await this.fetchResponse(method, path, body);
61
+ const response = await this.fetchResponse(this.baseUrl, method, path, body);
62
+ return response.text();
63
+ }
64
+ getTenantBaseUrl() {
65
+ if (this.tenantBaseUrlOverride) {
66
+ return Promise.resolve(this.tenantBaseUrlOverride);
67
+ }
68
+ if (!this._tenantBaseUrlPromise) {
69
+ this._tenantBaseUrlPromise = this.resolveTenantBaseUrl().catch((err) => {
70
+ this._tenantBaseUrlPromise = null;
71
+ throw err;
72
+ });
73
+ }
74
+ return this._tenantBaseUrlPromise;
75
+ }
76
+ async resolveTenantBaseUrl() {
77
+ const body = await this.request("GET", "/api/tenants/me");
78
+ const tenant = body.tenant;
79
+ if (tenant.suspendedAt) {
80
+ throw new ApiError(403, `Tenant ${tenant.slug} is suspended${tenant.limitReason ? `: ${tenant.limitReason}` : ""}.`, body, "TENANT_SUSPENDED");
81
+ }
82
+ if (!tenant.apiUrl) {
83
+ throw new ApiError(404, "No tenant API URL available for this account. Provision a tenant at https://secondlayer.tools/platform.", body, "NO_TENANT");
84
+ }
85
+ return tenant.apiUrl.replace(/\/+$/, "");
86
+ }
87
+ async requestAtTenant(method, path, body) {
88
+ const tenantUrl = await this.getTenantBaseUrl();
89
+ return this.requestAt(tenantUrl, method, path, body);
90
+ }
91
+ async requestTextAtTenant(method, path, body) {
92
+ const tenantUrl = await this.getTenantBaseUrl();
93
+ const response = await this.fetchResponse(tenantUrl, method, path, body);
54
94
  return response.text();
55
95
  }
56
- async fetchResponse(method, path, body) {
57
- const url = `${this.baseUrl}${path}`;
96
+ async fetchResponse(baseUrl, method, path, body) {
97
+ const url = `${baseUrl}${path}`;
58
98
  const headers = BaseClient.authHeaders(this.apiKey);
59
99
  headers["x-sl-origin"] = this.origin;
60
100
  let response;
@@ -65,7 +105,7 @@ class BaseClient {
65
105
  body: body ? JSON.stringify(body) : undefined
66
106
  });
67
107
  } catch {
68
- throw new ApiError(0, `Cannot reach API at ${this.baseUrl}. Check your connection or try again.`);
108
+ throw new ApiError(0, `Cannot reach API at ${baseUrl}. Check your connection or try again.`);
69
109
  }
70
110
  if (!response.ok) {
71
111
  if (response.status === 401) {
@@ -77,11 +117,12 @@ class BaseClient {
77
117
  throw new ApiError(429, msg);
78
118
  }
79
119
  if (response.status >= 500) {
80
- throw new ApiError(response.status, `Server error. Try again or check status at ${this.baseUrl}/health`);
120
+ throw new ApiError(response.status, `Server error. Try again or check status at ${baseUrl}/health`);
81
121
  }
82
122
  const errorBody = await response.text();
83
123
  let message = `HTTP ${response.status}`;
84
124
  let parsedBody = errorBody;
125
+ let code;
85
126
  try {
86
127
  const json = JSON.parse(errorBody);
87
128
  parsedBody = json;
@@ -91,11 +132,14 @@ class BaseClient {
91
132
  } else if (err && typeof err === "object") {
92
133
  message = JSON.stringify(err);
93
134
  }
135
+ if (typeof json.code === "string") {
136
+ code = json.code;
137
+ }
94
138
  } catch {
95
139
  if (errorBody)
96
140
  message = errorBody;
97
141
  }
98
- throw new ApiError(response.status, message, parsedBody);
142
+ throw new ApiError(response.status, message, parsedBody, code);
99
143
  }
100
144
  return response;
101
145
  }
@@ -173,28 +217,28 @@ function buildSpecQueryString(options) {
173
217
 
174
218
  class Subgraphs extends BaseClient {
175
219
  async list() {
176
- return this.request("GET", "/api/subgraphs");
220
+ return this.requestAtTenant("GET", "/api/subgraphs");
177
221
  }
178
222
  async get(name) {
179
- return this.request("GET", `/api/subgraphs/${name}`);
223
+ return this.requestAtTenant("GET", `/api/subgraphs/${name}`);
180
224
  }
181
225
  async openapi(name, options) {
182
- return this.request("GET", `/api/subgraphs/${name}/openapi.json${buildSpecQueryString(options)}`);
226
+ return this.requestAtTenant("GET", `/api/subgraphs/${name}/openapi.json${buildSpecQueryString(options)}`);
183
227
  }
184
228
  async schema(name, options) {
185
- return this.request("GET", `/api/subgraphs/${name}/schema.json${buildSpecQueryString(options)}`);
229
+ return this.requestAtTenant("GET", `/api/subgraphs/${name}/schema.json${buildSpecQueryString(options)}`);
186
230
  }
187
231
  async markdown(name, options) {
188
- return this.requestText("GET", `/api/subgraphs/${name}/docs.md${buildSpecQueryString(options)}`);
232
+ return this.requestTextAtTenant("GET", `/api/subgraphs/${name}/docs.md${buildSpecQueryString(options)}`);
189
233
  }
190
234
  async reindex(name, options) {
191
- return this.request("POST", `/api/subgraphs/${name}/reindex`, options);
235
+ return this.requestAtTenant("POST", `/api/subgraphs/${name}/reindex`, options);
192
236
  }
193
237
  async stop(name) {
194
- return this.request("POST", `/api/subgraphs/${name}/stop`);
238
+ return this.requestAtTenant("POST", `/api/subgraphs/${name}/stop`);
195
239
  }
196
240
  async backfill(name, options) {
197
- return this.request("POST", `/api/subgraphs/${name}/backfill`, options);
241
+ return this.requestAtTenant("POST", `/api/subgraphs/${name}/backfill`, options);
198
242
  }
199
243
  async gaps(name, opts) {
200
244
  const qs = new URLSearchParams;
@@ -205,27 +249,27 @@ class Subgraphs extends BaseClient {
205
249
  if (opts?.resolved !== undefined)
206
250
  qs.set("resolved", String(opts.resolved));
207
251
  const query = qs.toString();
208
- return this.request("GET", `/api/subgraphs/${name}/gaps${query ? `?${query}` : ""}`);
252
+ return this.requestAtTenant("GET", `/api/subgraphs/${name}/gaps${query ? `?${query}` : ""}`);
209
253
  }
210
254
  async delete(name, options) {
211
255
  const qs = options?.force ? "?force=true" : "";
212
- return this.request("DELETE", `/api/subgraphs/${name}${qs}`);
256
+ return this.requestAtTenant("DELETE", `/api/subgraphs/${name}${qs}`);
213
257
  }
214
258
  async deploy(data) {
215
- return this.request("POST", "/api/subgraphs", data);
259
+ return this.requestAtTenant("POST", "/api/subgraphs", data);
216
260
  }
217
261
  async getSource(name) {
218
- return this.request("GET", `/api/subgraphs/${name}/source`);
262
+ return this.requestAtTenant("GET", `/api/subgraphs/${name}/source`);
219
263
  }
220
264
  async bundle(data) {
221
- return this.request("POST", "/api/subgraphs/bundle", data);
265
+ return this.requestAtTenant("POST", "/api/subgraphs/bundle", data);
222
266
  }
223
267
  async queryTable(name, table, params = {}) {
224
- const result = await this.request("GET", `/api/subgraphs/${name}/${table}${buildSubgraphQueryString(params)}`);
268
+ const result = await this.requestAtTenant("GET", `/api/subgraphs/${name}/${table}${buildSubgraphQueryString(params)}`);
225
269
  return Array.isArray(result) ? result : result.data;
226
270
  }
227
271
  async queryTableCount(name, table, params = {}) {
228
- return this.request("GET", `/api/subgraphs/${name}/${table}/count${buildSubgraphQueryString(params)}`);
272
+ return this.requestAtTenant("GET", `/api/subgraphs/${name}/${table}/count${buildSubgraphQueryString(params)}`);
229
273
  }
230
274
  typed(def) {
231
275
  const result = {};
@@ -648,40 +692,40 @@ function createStreamsClient(options) {
648
692
  // src/subscriptions/client.ts
649
693
  class Subscriptions extends BaseClient {
650
694
  async list() {
651
- return this.request("GET", "/api/subscriptions");
695
+ return this.requestAtTenant("GET", "/api/subscriptions");
652
696
  }
653
697
  async get(id) {
654
- return this.request("GET", `/api/subscriptions/${id}`);
698
+ return this.requestAtTenant("GET", `/api/subscriptions/${id}`);
655
699
  }
656
700
  async create(input) {
657
- return this.request("POST", "/api/subscriptions", input);
701
+ return this.requestAtTenant("POST", "/api/subscriptions", input);
658
702
  }
659
703
  async update(id, patch) {
660
- return this.request("PATCH", `/api/subscriptions/${id}`, patch);
704
+ return this.requestAtTenant("PATCH", `/api/subscriptions/${id}`, patch);
661
705
  }
662
706
  async pause(id) {
663
- return this.request("POST", `/api/subscriptions/${id}/pause`);
707
+ return this.requestAtTenant("POST", `/api/subscriptions/${id}/pause`);
664
708
  }
665
709
  async resume(id) {
666
- return this.request("POST", `/api/subscriptions/${id}/resume`);
710
+ return this.requestAtTenant("POST", `/api/subscriptions/${id}/resume`);
667
711
  }
668
712
  async delete(id) {
669
- return this.request("DELETE", `/api/subscriptions/${id}`);
713
+ return this.requestAtTenant("DELETE", `/api/subscriptions/${id}`);
670
714
  }
671
715
  async rotateSecret(id) {
672
- return this.request("POST", `/api/subscriptions/${id}/rotate-secret`);
716
+ return this.requestAtTenant("POST", `/api/subscriptions/${id}/rotate-secret`);
673
717
  }
674
718
  async recentDeliveries(id) {
675
- return this.request("GET", `/api/subscriptions/${id}/deliveries`);
719
+ return this.requestAtTenant("GET", `/api/subscriptions/${id}/deliveries`);
676
720
  }
677
721
  async replay(id, range) {
678
- return this.request("POST", `/api/subscriptions/${id}/replay`, range);
722
+ return this.requestAtTenant("POST", `/api/subscriptions/${id}/replay`, range);
679
723
  }
680
724
  async dead(id) {
681
- return this.request("GET", `/api/subscriptions/${id}/dead`);
725
+ return this.requestAtTenant("GET", `/api/subscriptions/${id}/dead`);
682
726
  }
683
727
  async requeueDead(id, outboxId) {
684
- return this.request("POST", `/api/subscriptions/${id}/dead/${outboxId}/requeue`);
728
+ return this.requestAtTenant("POST", `/api/subscriptions/${id}/dead/${outboxId}/requeue`);
685
729
  }
686
730
  }
687
731
 
@@ -775,6 +819,13 @@ function requireString2(payload, field) {
775
819
  return value;
776
820
  }
777
821
  function requireHexValue(payload) {
822
+ const rawValue = payload.raw_value;
823
+ if (typeof rawValue === "string") {
824
+ if (!/^0x[0-9a-fA-F]*$/.test(rawValue)) {
825
+ throw new Error("nft_transfer payload has malformed value");
826
+ }
827
+ return rawValue;
828
+ }
778
829
  const value = payload.value;
779
830
  const hex = typeof value === "string" ? value : value && typeof value === "object" && typeof value.hex === "string" ? value.hex : null;
780
831
  if (!hex) {
@@ -865,5 +916,5 @@ export {
865
916
  ApiError
866
917
  };
867
918
 
868
- //# debugId=B2B1ED63B1AB801664756E2164756E21
919
+ //# debugId=AA41F0EF16B7740B64756E2164756E21
869
920
  //# sourceMappingURL=index.js.map