@secondlayer/sdk 6.16.0 → 6.17.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.
@@ -156,6 +156,17 @@ interface CreateApiKeyResponse {
156
156
  tier: string | null;
157
157
  createdAt: string;
158
158
  }
159
+ /** A key as seen by {@link ApiKeys.list} — metadata only, never the plaintext. */
160
+ interface ApiKeySummary {
161
+ id: string;
162
+ prefix: string;
163
+ name: string | null;
164
+ status: "active" | "revoked";
165
+ product: string;
166
+ tier: string | null;
167
+ createdAt: string;
168
+ lastUsedAt: string | null;
169
+ }
159
170
  declare class ApiKeys extends BaseClient {
160
171
  constructor(options?: Partial<SecondLayerOptions>);
161
172
  /**
@@ -164,6 +175,18 @@ declare class ApiKeys extends BaseClient {
164
175
  * only once.
165
176
  */
166
177
  create(params?: CreateApiKeyParams): Promise<CreateApiKeyResponse>;
178
+ /**
179
+ * List the account's keys (metadata only — no plaintext). Requires an
180
+ * account-level (owner) key or a dashboard session.
181
+ */
182
+ list(): Promise<{
183
+ keys: ApiKeySummary[]
184
+ }>;
185
+ /** Revoke a key by id. Existing requests with that key stop working. */
186
+ revoke(id: string): Promise<{
187
+ revoked: true
188
+ id: string
189
+ }>;
167
190
  }
168
191
  /**
169
192
  * Typed client for the contract-discovery API (`GET /v1/contracts`).
@@ -284,13 +307,26 @@ type CursorDataset<P> = {
284
307
  }) => AsyncIterable<DatasetRow>
285
308
  };
286
309
  declare class Datasets extends BaseClient {
310
+ private catalogPromise?;
287
311
  constructor(options?: Partial<SecondLayerOptions>);
288
312
  /** Dataset catalog + freshness (the discovery endpoint). */
289
313
  listDatasets(): Promise<unknown>;
290
314
  /**
315
+ * Generic read by slug for ANY catalog dataset — cursor or bespoke. Resolves
316
+ * the slug against the live `/v1/datasets` catalog (so datasets added
317
+ * server-side work with no SDK change), issues the GET, and normalizes the
318
+ * response into a uniform `{ rows, next_cursor, tip }` envelope. Single-object
319
+ * datasets (bns/resolve, network-health/summary) come back as 0-or-1 rows.
320
+ * Accepts both family (`sbtc-events`) and path (`sbtc/events`) slug forms.
321
+ * Prefer this over {@link query} unless you specifically need cursor-only
322
+ * semantics.
323
+ */
324
+ get(slug: string, params?: Record<string, unknown>): Promise<CursorEnvelope>;
325
+ /**
291
326
  * Generic cursor query by slug — used by the CLI. Params are passed through as
292
327
  * REST query keys (snake_case), so callers can use the documented filter names
293
- * directly. Throws for non-cursor (bespoke) datasets.
328
+ * directly. Throws for non-cursor (bespoke) datasets — use {@link get} for
329
+ * those (and for slugs added to the catalog after this SDK was built).
294
330
  */
295
331
  query(slug: string, params?: Record<string, unknown>): Promise<CursorEnvelope>;
296
332
  readonly stxTransfers: CursorDataset<StxTransfersParams>;
@@ -323,7 +359,15 @@ declare class Datasets extends BaseClient {
323
359
  networkHealth(): Promise<{
324
360
  summary: DatasetRow
325
361
  }>;
326
- private get;
362
+ private requestPath;
363
+ /** Resolve a slug → relative path + row key. Known cursor slugs take a
364
+ * network-free fast path; anything else is matched against the live catalog
365
+ * by family name or path tail. */
366
+ private resolveDataset;
367
+ /** Fetch + cache the catalog families. Caches the in-flight promise so
368
+ * concurrent first-calls share one request; no TTL — agent sessions are
369
+ * short-lived, and a new client picks up newly added datasets. */
370
+ private loadCatalog;
327
371
  /** Map camelCase filter fields to snake_case query keys (dropping pagination
328
372
  * controls) and build the canonical query suffix. */
329
373
  private paramsToQuery;
@@ -898,6 +942,76 @@ declare class Index extends BaseClient {
898
942
  private getMempoolTx;
899
943
  private walkMempool;
900
944
  }
945
+ /**
946
+ * Typed client for project management (`/api/projects/*`).
947
+ *
948
+ * Projects are the account-scoped containers for work. Every method requires an
949
+ * account-level (owner) API key or a dashboard session — scoped read keys are
950
+ * rejected. Team mutations (invite/remove/role) are intentionally not exposed
951
+ * here; only the read view ({@link Projects.team}) is.
952
+ */
953
+ interface Project {
954
+ id: string;
955
+ name: string;
956
+ slug: string;
957
+ network: string;
958
+ nodeRpc: string | null;
959
+ settings: Record<string, unknown> | null;
960
+ createdAt: string;
961
+ updatedAt: string;
962
+ }
963
+ interface ProjectTeamMember {
964
+ id: string;
965
+ role: string;
966
+ email: string;
967
+ displayName: string | null;
968
+ avatarUrl: string | null;
969
+ createdAt: string;
970
+ }
971
+ interface ProjectInvitation {
972
+ id: string;
973
+ email: string;
974
+ role: string;
975
+ expiresAt: string;
976
+ createdAt: string;
977
+ }
978
+ interface ProjectTeam {
979
+ members: ProjectTeamMember[];
980
+ invitations: ProjectInvitation[];
981
+ }
982
+ interface CreateProjectParams {
983
+ name: string;
984
+ slug?: string;
985
+ network?: string;
986
+ nodeRpc?: string;
987
+ }
988
+ interface UpdateProjectParams {
989
+ name?: string;
990
+ /** Rename the project's URL slug. */
991
+ slug?: string;
992
+ network?: string;
993
+ nodeRpc?: string;
994
+ settings?: Record<string, unknown>;
995
+ }
996
+ declare class Projects extends BaseClient {
997
+ constructor(options?: Partial<SecondLayerOptions>);
998
+ /** All projects owned by the account, newest-relevant first. */
999
+ list(): Promise<{
1000
+ projects: Project[]
1001
+ }>;
1002
+ /** A single project by slug. */
1003
+ get(slug: string): Promise<Project>;
1004
+ /** Create a project. The creator is added as the project owner. */
1005
+ create(params: CreateProjectParams): Promise<Project>;
1006
+ /** Update a project's name, slug (rename), network, RPC, or settings. */
1007
+ update(slug: string, patch: UpdateProjectParams): Promise<Project>;
1008
+ /** Delete a project. The account's last remaining project cannot be deleted. */
1009
+ delete(slug: string): Promise<{
1010
+ ok: true
1011
+ }>;
1012
+ /** Team members and pending invitations for a project. */
1013
+ team(slug: string): Promise<ProjectTeam>;
1014
+ }
901
1015
  import { StreamsEventType } from "@secondlayer/shared";
902
1016
  /** A Clarity value as Streams serves it: the canonical hex string, a typed
903
1017
  * object carrying that hex (`{ hex }`), or a decoded Clarity-JSON object.
@@ -1309,6 +1423,19 @@ interface ContextAccount {
1309
1423
  email: string;
1310
1424
  plan: string;
1311
1425
  }
1426
+ /** Compact project view for {@link ContextSnapshot}. */
1427
+ interface ContextProject {
1428
+ name: string;
1429
+ slug: string;
1430
+ network: string;
1431
+ }
1432
+ /** Compact API-key view for {@link ContextSnapshot} — never the plaintext. */
1433
+ interface ContextApiKey {
1434
+ prefix: string;
1435
+ name: string | null;
1436
+ status: string;
1437
+ product: string;
1438
+ }
1312
1439
  interface ActiveSubgraphOperation {
1313
1440
  subgraph: string;
1314
1441
  operationId: string;
@@ -1330,6 +1457,10 @@ interface ContextSnapshot {
1330
1457
  count: number
1331
1458
  byStatus: Record<string, number>
1332
1459
  } | null;
1460
+ /** The account's projects (null when unreadable — e.g. a scoped key). */
1461
+ projects: ContextProject[] | null;
1462
+ /** The account's API keys, metadata only (null when unreadable). */
1463
+ apiKeys: ContextApiKey[] | null;
1333
1464
  /** In-flight reindex operations (bounded to subgraphs reporting `reindexing`). */
1334
1465
  activeOperations: ActiveSubgraphOperation[] | null;
1335
1466
  }
@@ -1341,6 +1472,7 @@ declare class SecondLayer extends BaseClient {
1341
1472
  readonly subgraphs: Subgraphs;
1342
1473
  readonly subscriptions: Subscriptions;
1343
1474
  readonly apiKeys: ApiKeys;
1475
+ readonly projects: Projects;
1344
1476
  constructor(options?: Partial<SecondLayerOptions>);
1345
1477
  /**
1346
1478
  * Assemble a {@link ContextSnapshot} — the same orientation an MCP agent reads
@@ -395,6 +395,12 @@ class ApiKeys extends BaseClient {
395
395
  name: params.name
396
396
  });
397
397
  }
398
+ list() {
399
+ return this.request("GET", "/api/keys");
400
+ }
401
+ revoke(id) {
402
+ return this.request("DELETE", `/api/keys/${id}`);
403
+ }
398
404
  }
399
405
 
400
406
  // src/contracts/client.ts
@@ -441,20 +447,35 @@ var CURSOR_SLUGS = {
441
447
  rowKey: "events"
442
448
  }
443
449
  };
450
+ function catalogPathTail(path) {
451
+ return path.replace(/^\/?v1\/datasets\//, "").replace(/^\/+/, "");
452
+ }
444
453
 
445
454
  class Datasets extends BaseClient {
455
+ catalogPromise;
446
456
  constructor(options = {}) {
447
457
  super(options);
448
458
  }
449
459
  listDatasets() {
450
460
  return this.request("GET", "/v1/datasets");
451
461
  }
462
+ async get(slug, params = {}) {
463
+ const { path, rowKey } = await this.resolveDataset(slug);
464
+ const env = await this.requestPath(path, this.paramsToQuery(params));
465
+ const value = env[rowKey];
466
+ const rows = Array.isArray(value) ? value : value == null ? [] : [value];
467
+ return {
468
+ rows,
469
+ next_cursor: env.next_cursor ?? null,
470
+ tip: env.tip
471
+ };
472
+ }
452
473
  async query(slug, params = {}) {
453
474
  const d = CURSOR_SLUGS[slug];
454
475
  if (!d) {
455
476
  throw new Error(`unknown cursor dataset "${slug}" (use one of: ${Object.keys(CURSOR_SLUGS).join(", ")})`);
456
477
  }
457
- const env = await this.get(d.path, this.paramsToQuery(params));
478
+ const env = await this.requestPath(d.path, this.paramsToQuery(params));
458
479
  return {
459
480
  rows: env[d.rowKey] ?? [],
460
481
  next_cursor: env.next_cursor ?? null,
@@ -471,7 +492,7 @@ class Datasets extends BaseClient {
471
492
  bnsNamespaceEvents = this.cursorDataset("bns/namespace-events", "events");
472
493
  bnsMarketplaceEvents = this.cursorDataset("bns/marketplace-events", "events");
473
494
  bnsNames(params = {}) {
474
- return this.get("bns/names", buildQuery({
495
+ return this.requestPath("bns/names", buildQuery({
475
496
  namespace: params.namespace,
476
497
  owner: params.owner,
477
498
  limit: params.limit,
@@ -479,17 +500,40 @@ class Datasets extends BaseClient {
479
500
  }));
480
501
  }
481
502
  bnsNamespaces() {
482
- return this.get("bns/namespaces", "");
503
+ return this.requestPath("bns/namespaces", "");
483
504
  }
484
505
  bnsResolve(fqn) {
485
- return this.get("bns/resolve", buildQuery({ fqn }));
506
+ return this.requestPath("bns/resolve", buildQuery({ fqn }));
486
507
  }
487
508
  networkHealth() {
488
- return this.get("network-health/summary", "");
509
+ return this.requestPath("network-health/summary", "");
489
510
  }
490
- get(path, query) {
511
+ requestPath(path, query) {
491
512
  return this.request("GET", `/v1/datasets/${path}${query}`);
492
513
  }
514
+ async resolveDataset(slug) {
515
+ const cursor = CURSOR_SLUGS[slug];
516
+ if (cursor)
517
+ return { path: cursor.path, rowKey: cursor.rowKey };
518
+ const families = await this.loadCatalog();
519
+ const tail = catalogPathTail(slug);
520
+ const match = families.find((f) => f.family === slug || catalogPathTail(f.path) === tail);
521
+ if (!match) {
522
+ throw new Error(`unknown dataset "${slug}" (available: ${families.map((f) => f.family).join(", ")})`);
523
+ }
524
+ return { path: catalogPathTail(match.path), rowKey: match.row_key };
525
+ }
526
+ loadCatalog() {
527
+ this.catalogPromise ??= (async () => {
528
+ const raw = await this.listDatasets();
529
+ const families = raw.families;
530
+ if (!Array.isArray(families)) {
531
+ throw new Error("dataset catalog response missing families[]");
532
+ }
533
+ return families;
534
+ })();
535
+ return this.catalogPromise;
536
+ }
493
537
  paramsToQuery(params) {
494
538
  const mapped = {};
495
539
  for (const [k, v] of Object.entries(params)) {
@@ -501,7 +545,7 @@ class Datasets extends BaseClient {
501
545
  }
502
546
  cursorDataset(path, rowKey) {
503
547
  const list = async (params = {}) => {
504
- const envelope = await this.get(path, this.paramsToQuery(params));
548
+ const envelope = await this.requestPath(path, this.paramsToQuery(params));
505
549
  return {
506
550
  rows: envelope[rowKey] ?? [],
507
551
  next_cursor: envelope.next_cursor ?? null,
@@ -944,6 +988,31 @@ class Index extends BaseClient {
944
988
  }
945
989
  }
946
990
 
991
+ // src/projects/client.ts
992
+ class Projects extends BaseClient {
993
+ constructor(options = {}) {
994
+ super(options);
995
+ }
996
+ list() {
997
+ return this.request("GET", "/api/projects");
998
+ }
999
+ get(slug) {
1000
+ return this.request("GET", `/api/projects/${slug}`);
1001
+ }
1002
+ create(params) {
1003
+ return this.request("POST", "/api/projects", params);
1004
+ }
1005
+ update(slug, patch) {
1006
+ return this.request("PATCH", `/api/projects/${slug}`, patch);
1007
+ }
1008
+ delete(slug) {
1009
+ return this.request("DELETE", `/api/projects/${slug}`);
1010
+ }
1011
+ team(slug) {
1012
+ return this.request("GET", `/api/projects/${slug}/team`);
1013
+ }
1014
+ }
1015
+
947
1016
  // src/streams/client.ts
948
1017
  import { ed25519 as ed255192 } from "@secondlayer/shared";
949
1018
 
@@ -1622,6 +1691,7 @@ class SecondLayer extends BaseClient {
1622
1691
  subgraphs;
1623
1692
  subscriptions;
1624
1693
  apiKeys;
1694
+ projects;
1625
1695
  constructor(options = {}) {
1626
1696
  super(options);
1627
1697
  this.streams = createStreamsClient({
@@ -1635,15 +1705,26 @@ class SecondLayer extends BaseClient {
1635
1705
  this.subgraphs = new Subgraphs(options);
1636
1706
  this.subscriptions = new Subscriptions(options);
1637
1707
  this.apiKeys = new ApiKeys(options);
1708
+ this.projects = new Projects(options);
1638
1709
  }
1639
1710
  async context() {
1640
1711
  const safe = (p) => p.then((v) => v).catch(() => null);
1641
- const [account, streamsTip, indexEnv, subgraphsRes, subscriptionsRes] = await Promise.all([
1712
+ const [
1713
+ account,
1714
+ streamsTip,
1715
+ indexEnv,
1716
+ subgraphsRes,
1717
+ subscriptionsRes,
1718
+ projectsRes,
1719
+ apiKeysRes
1720
+ ] = await Promise.all([
1642
1721
  safe(this.request("GET", "/api/accounts/me")),
1643
1722
  safe(this.streams.tip()),
1644
1723
  safe(this.index.canonical.list({ limit: 1 })),
1645
1724
  safe(this.subgraphs.list()),
1646
- safe(this.subscriptions.list())
1725
+ safe(this.subscriptions.list()),
1726
+ safe(this.projects.list()),
1727
+ safe(this.apiKeys.list())
1647
1728
  ]);
1648
1729
  const subgraphs = subgraphsRes?.data ?? null;
1649
1730
  let subscriptions = null;
@@ -1669,12 +1750,25 @@ class SecondLayer extends BaseClient {
1669
1750
  }));
1670
1751
  activeOperations = probed.filter((o) => o !== null);
1671
1752
  }
1753
+ const projects = projectsRes ? projectsRes.projects.map((p) => ({
1754
+ name: p.name,
1755
+ slug: p.slug,
1756
+ network: p.network
1757
+ })) : null;
1758
+ const apiKeys = apiKeysRes ? apiKeysRes.keys.map((k) => ({
1759
+ prefix: k.prefix,
1760
+ name: k.name,
1761
+ status: k.status,
1762
+ product: k.product
1763
+ })) : null;
1672
1764
  return {
1673
1765
  account,
1674
1766
  streamsTip,
1675
1767
  indexTip: indexEnv?.tip ?? null,
1676
1768
  subgraphs,
1677
1769
  subscriptions,
1770
+ projects,
1771
+ apiKeys,
1678
1772
  activeOperations
1679
1773
  };
1680
1774
  }
@@ -1695,5 +1789,5 @@ export {
1695
1789
  Subgraphs
1696
1790
  };
1697
1791
 
1698
- //# debugId=4CC7A5C19CBAC4F064756E2164756E21
1792
+ //# debugId=9A541BEB19FD0E7964756E2164756E21
1699
1793
  //# sourceMappingURL=index.js.map