@persql/sdk 1.1.0 → 1.2.1

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/dist/index.d.cts CHANGED
@@ -1,9 +1,11 @@
1
+ /** Wire-shape query result — columns plus positional rows, before the SDK reshapes rows into objects. */
1
2
  interface RawQueryResult {
2
3
  columns: string[];
3
4
  rows: unknown[][];
4
5
  rowsRead: number;
5
6
  rowsWritten: number;
6
7
  }
8
+ /** In-process driver backing `new PerSQL({ local })` — runs SQL through `better-sqlite3` instead of the HTTP API. */
7
9
  declare class LocalDriver {
8
10
  private readonly path;
9
11
  private db;
@@ -46,7 +48,14 @@ declare class LocalDriver {
46
48
  * );
47
49
  */
48
50
 
51
+ /** Machine-readable category of a SQL failure, parsed from the engine's error message. */
49
52
  type SqlErrorKind = "unique_violation" | "not_null_violation" | "fk_violation" | "check_violation" | "unknown_table" | "unknown_column" | "syntax_error" | "type_mismatch" | "database_locked" | "too_many_params" | "readonly" | "unknown";
53
+ /**
54
+ * Structured SQL error envelope — the failure kind plus the table,
55
+ * column, constraint, and hint parsed from the engine message.
56
+ * Available as `PerSQLError.detail` so callers can branch on
57
+ * `detail.kind` instead of string-matching messages.
58
+ */
50
59
  interface SqlErrorDetail {
51
60
  kind: SqlErrorKind;
52
61
  message: string;
@@ -60,6 +69,7 @@ interface SqlErrorDetail {
60
69
  near?: string;
61
70
  hint?: string;
62
71
  }
72
+ /** Constructor options for `PerSQL`. */
63
73
  interface PerSQLOptions {
64
74
  /** Bearer token. Get one at https://console.persql.com → Tokens. */
65
75
  token?: string;
@@ -82,6 +92,10 @@ interface PerSQLOptions {
82
92
  */
83
93
  local?: string;
84
94
  }
95
+ /**
96
+ * Result of a SQL statement — column names, positional rows, and the
97
+ * same rows reshaped as objects (`data`).
98
+ */
85
99
  interface QueryResult<T = Record<string, unknown>> {
86
100
  /** Column names in result-set order. */
87
101
  columns: string[];
@@ -91,7 +105,32 @@ interface QueryResult<T = Record<string, unknown>> {
91
105
  data: T[];
92
106
  rowsRead: number;
93
107
  rowsWritten: number;
108
+ /**
109
+ * Per-call usage and cost the server echoes on `query()`. Undefined in
110
+ * local mode (no server) and on the per-statement results of `batch()`
111
+ * (a batch reports one aggregate meta, not one per statement).
112
+ */
113
+ meta?: QueryMeta;
94
114
  }
115
+ /**
116
+ * Usage and cost the server attaches to a query response. `costUsd` is
117
+ * computed from the public rate card — the same numbers the spend report
118
+ * uses. `snapshot` is set when the auto-snapshot pipeline took a labeled
119
+ * bookmark before a destructive call.
120
+ */
121
+ interface QueryMeta {
122
+ rowsRead: number;
123
+ rowsWritten: number;
124
+ durationMs: number;
125
+ statementCount: number;
126
+ costUsd: number;
127
+ snapshot: {
128
+ id: string;
129
+ label: string;
130
+ } | null;
131
+ queryLogId: string;
132
+ }
133
+ /** Options for `db.batch()` — transaction wrapping and idempotency / plan-replay keys. */
95
134
  interface BatchOptions {
96
135
  /** Wrap all statements in BEGIN/COMMIT. Rolls back on first error. */
97
136
  transaction?: boolean;
@@ -188,6 +227,7 @@ interface SchemaDoctorReport {
188
227
  tablesScanned: number;
189
228
  generatedAt: string;
190
229
  }
230
+ /** Per-call options for `db.query()` — idempotency and plan-replay keys. */
191
231
  interface QueryOptions {
192
232
  idempotencyKey?: string;
193
233
  /** See `BatchOptions.planKey`. */
@@ -195,6 +235,7 @@ interface QueryOptions {
195
235
  /** See `BatchOptions.planStep`. */
196
236
  planStep?: string;
197
237
  }
238
+ /** One user table and its row count, as returned by `db.tables()`. */
198
239
  interface TableInfo {
199
240
  name: string;
200
241
  rowCount: number;
@@ -222,6 +263,7 @@ interface BatchExpect {
222
263
  rowsWrittenAtLeast?: number;
223
264
  rowsWrittenAtMost?: number;
224
265
  }
266
+ /** One entry of a `db.batch()` call — SQL, positional params, and an optional server-side assertion. */
225
267
  interface Statement {
226
268
  sql: string;
227
269
  params?: unknown[];
@@ -266,15 +308,22 @@ type ValidateResult = {
266
308
  error: string;
267
309
  errorDetail?: SqlErrorDetail;
268
310
  };
311
+ /**
312
+ * Thrown for any failed API call. `status` carries the HTTP status;
313
+ * `detail` (set on /query and /batch failures) is the parsed SQL
314
+ * error envelope.
315
+ */
269
316
  declare class PerSQLError extends Error {
270
317
  status: number;
271
318
  detail?: SqlErrorDetail;
272
319
  constructor(status: number, message: string, detail?: SqlErrorDetail);
273
320
  }
321
+ /** Thrown on HTTP 429. `retryAfterSeconds` echoes the server's Retry-After header. */
274
322
  declare class RateLimitError extends PerSQLError {
275
323
  retryAfterSeconds: number;
276
324
  constructor(retryAfterSeconds: number, message?: string);
277
325
  }
326
+ /** One approval rule a blocked write matched — which glob hit which table, and whether it requires approval or denies outright. */
278
327
  interface ApprovalRuleHit {
279
328
  ruleId: string;
280
329
  tableGlob: string;
@@ -301,6 +350,16 @@ declare class ApprovalRequiredError extends PerSQLError {
301
350
  expiresAt: string;
302
351
  });
303
352
  }
353
+ /**
354
+ * Entry point of the SDK. Construct with a bearer token — or with
355
+ * `{ local: ":memory:" }` for an in-process SQLite test database —
356
+ * then call `database()` for a handle to query.
357
+ *
358
+ * ```ts
359
+ * const persql = new PerSQL({ token: process.env.PERSQL_TOKEN! });
360
+ * const db = persql.database("acme/orders");
361
+ * ```
362
+ */
304
363
  declare class PerSQL {
305
364
  /** @internal — exposed read-only so PerSQLDatabase can build URLs. */
306
365
  readonly token: string;
@@ -344,8 +403,22 @@ declare class PerSQL {
344
403
  */
345
404
  database(path: string): PerSQLDatabase;
346
405
  database(namespace: string, slug: string): PerSQLDatabase;
406
+ /**
407
+ * Provision and list databases in the token's namespace. `create`
408
+ * needs an unscoped admin token; `database()` then gives you a handle
409
+ * to query the result.
410
+ */
411
+ get databases(): PerSQLDatabases;
347
412
  /** @internal */
348
413
  request<T>(method: "GET" | "POST" | "PUT" | "DELETE", path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
414
+ /**
415
+ * @internal — like `request`, but also surfaces the response `meta`
416
+ * envelope (usage + `costUsd`) that the data-only `request` drops.
417
+ */
418
+ requestWithMeta<T>(method: "GET" | "POST" | "PUT" | "DELETE", path: string, body?: unknown, headers?: Record<string, string>): Promise<{
419
+ data: T;
420
+ meta?: QueryMeta;
421
+ }>;
349
422
  /** @internal — raw fetch returning the underlying Response. */
350
423
  fetchRaw(method: "GET", path: string): Promise<Response>;
351
424
  /**
@@ -368,6 +441,7 @@ declare class PerSQL {
368
441
  */
369
442
  get support(): SupportClient;
370
443
  }
444
+ /** Input for `persql.support.createTicket()`. */
371
445
  interface CreateSupportTicketOptions {
372
446
  subject: string;
373
447
  body: string;
@@ -376,6 +450,7 @@ interface CreateSupportTicketOptions {
376
450
  /** Caller-supplied extras merged into the ticket's diagnostic context. */
377
451
  errorContext?: Record<string, unknown>;
378
452
  }
453
+ /** Files support tickets from SDK code. Reached via `persql.support`. */
379
454
  declare class SupportClient {
380
455
  private readonly client;
381
456
  constructor(client: PerSQL);
@@ -383,6 +458,58 @@ declare class SupportClient {
383
458
  id: string;
384
459
  }>;
385
460
  }
461
+ /** Database metadata as returned by `persql.databases.create()` and `list()`. */
462
+ interface DatabaseInfo {
463
+ id: string;
464
+ namespaceId: string;
465
+ slug: string;
466
+ name: string;
467
+ status: string;
468
+ region: string;
469
+ forkedFromId: string | null;
470
+ forkedAt: string | null;
471
+ expiresAt: string | null;
472
+ branchRef: string | null;
473
+ createdAt: string;
474
+ updatedAt: string;
475
+ }
476
+ /** Input for `persql.databases.create()`. */
477
+ interface CreateDatabaseOptions {
478
+ /** Display name, 1-50 chars. */
479
+ name: string;
480
+ /** URL slug; derived from `name` when omitted. */
481
+ slug?: string;
482
+ /** Region hint: auto, wnam, enam, weur, eeur, apac. Default: auto. */
483
+ region?: string;
484
+ /** Auto-delete after N days (1..30). Null/undefined = keep until deleted. */
485
+ ttlDays?: number | null;
486
+ }
487
+ /**
488
+ * Namespace-level database management. Reached via `persql.databases`.
489
+ * Both calls require a server-mode (non-local) client.
490
+ */
491
+ declare class PerSQLDatabases {
492
+ private readonly client;
493
+ constructor(client: PerSQL);
494
+ /**
495
+ * Provision a new isolated database and return its metadata. Requires
496
+ * an unscoped admin token (scoped tokens can't create databases).
497
+ */
498
+ create(opts: CreateDatabaseOptions): Promise<DatabaseInfo>;
499
+ /** List databases in the token's namespace, newest first (paginated). */
500
+ list(opts?: {
501
+ cursor?: string;
502
+ pageSize?: number;
503
+ }): Promise<{
504
+ data: DatabaseInfo[];
505
+ nextCursor: string | null;
506
+ }>;
507
+ }
508
+ /**
509
+ * Handle to one database, addressed as `<namespace>/<slug>`. Obtained
510
+ * via `persql.database(...)`; all query, schema, branch, approval, and
511
+ * tool helpers live here.
512
+ */
386
513
  declare class PerSQLDatabase {
387
514
  private readonly client;
388
515
  readonly namespace: string;
@@ -647,7 +774,7 @@ declare class PerSQLDatabase {
647
774
  runTool(input: {
648
775
  sql: string;
649
776
  params?: unknown[];
650
- }): Promise<QueryResult<Record<string, unknown>>>;
777
+ }): Promise<QueryResult>;
651
778
  /**
652
779
  * Generate a *bundle* of typed tools — one per table — instead of one
653
780
  * generic `query` tool. Agents perform dramatically better with narrow
@@ -679,11 +806,13 @@ declare class PerSQLDatabase {
679
806
  */
680
807
  asTools(): Promise<DatabaseToolBundle>;
681
808
  }
809
+ /** Tool definition in the Anthropic Messages API shape — one `tools` entry for `messages.create`. */
682
810
  interface AnthropicTool {
683
811
  name: string;
684
812
  description: string;
685
813
  input_schema: Record<string, unknown>;
686
814
  }
815
+ /** Tool definition in the OpenAI Chat Completions shape — one `tools` entry for `chat.completions.create`. */
687
816
  interface OpenAiTool {
688
817
  type: "function";
689
818
  function: {
@@ -692,6 +821,7 @@ interface OpenAiTool {
692
821
  parameters: Record<string, unknown>;
693
822
  };
694
823
  }
824
+ /** Output of `db.asTool()` — one SQL-query tool rendered in every supported framework shape. */
695
825
  interface SingleToolBundle {
696
826
  /** Anthropic shape — pass as one entry of `tools` to messages.create. */
697
827
  anthropic: AnthropicTool;
@@ -706,6 +836,7 @@ interface SingleToolBundle {
706
836
  /** OpenAI Agents SDK shape — hand to `Agent({ tools: [...] })`. */
707
837
  openaiAgents: () => OpenAiAgentsTool[];
708
838
  }
839
+ /** Output of `db.asTools()` — typed per-table tools plus the `run` dispatcher, in every supported framework shape. */
709
840
  interface DatabaseToolBundle {
710
841
  /** Tool definitions in Anthropic shape — pass as `tools` to messages.create. */
711
842
  anthropic: AnthropicTool[];
@@ -739,11 +870,13 @@ interface DatabaseToolBundle {
739
870
  */
740
871
  openaiAgents: () => OpenAiAgentsTool[];
741
872
  }
873
+ /** Tool shape for the Vercel AI SDK — spread into `streamText({ tools })`. */
742
874
  interface AiSdkTool {
743
875
  description: string;
744
876
  inputSchema: Record<string, unknown>;
745
877
  execute: (input: Record<string, unknown>) => Promise<unknown>;
746
878
  }
879
+ /** Tool shape for Mastra — wrap with `createTool()` if you need the Mastra envelope. */
747
880
  interface MastraTool {
748
881
  id: string;
749
882
  description: string;
@@ -775,11 +908,14 @@ interface OpenAiAgentsTool {
775
908
  parameters: Record<string, unknown>;
776
909
  invoke: (input: Record<string, unknown>) => Promise<unknown>;
777
910
  }
911
+ /** Kind of row change reported by `db.subscribe()` and `db.changes()`. */
778
912
  type SubscribeChangeKind = "update" | "insert" | "delete" | "schema" | "unknown";
913
+ /** One row-change event delivered to `SubscribeOptions.onChange`. */
779
914
  interface SubscribeChange {
780
915
  table: string;
781
916
  kind: SubscribeChangeKind;
782
917
  }
918
+ /** Options for `db.subscribe()` — table filter and event callbacks. */
783
919
  interface SubscribeOptions {
784
920
  /** Tables to listen to. Omit / empty / `["*"]` = all tables. */
785
921
  tables?: string[];
@@ -812,7 +948,12 @@ interface BranchInfo {
812
948
  name: string;
813
949
  };
814
950
  }
951
+ /**
952
+ * How a branch is applied back to its parent: `schema` applies the DDL
953
+ * delta; `promote` replaces the parent's contents wholesale.
954
+ */
815
955
  type BranchMergeMode = "schema" | "promote";
956
+ /** One DDL operation in a branch-merge plan: create, drop, or replace. */
816
957
  type BranchMergePlanStep = {
817
958
  op: "create";
818
959
  type: string;
@@ -829,6 +970,7 @@ type BranchMergePlanStep = {
829
970
  beforeSql: string;
830
971
  afterSql: string;
831
972
  };
973
+ /** Outcome of `db.branches.merge()` — what was applied, the pre-merge snapshot, and the plan when previewing. */
832
974
  interface BranchMergeResult {
833
975
  mode: BranchMergeMode;
834
976
  preview: boolean;
@@ -842,6 +984,7 @@ interface BranchMergeResult {
842
984
  };
843
985
  note?: string;
844
986
  }
987
+ /** Options for `db.branches.upsert()`. */
845
988
  interface BranchUpsertOptions {
846
989
  /** Human-readable name. Defaults to `<parent name> (<ref>)`. */
847
990
  name?: string;
@@ -850,6 +993,7 @@ interface BranchUpsertOptions {
850
993
  /** Auto-delete after N days (1..30). Null/undefined = keep until deleted. */
851
994
  ttlDays?: number | null;
852
995
  }
996
+ /** Options for `db.branches.merge()`. */
853
997
  interface BranchMergeOptions {
854
998
  /** Default `schema`. `promote` replaces parent contents wholesale. */
855
999
  mode?: BranchMergeMode;
@@ -860,6 +1004,7 @@ interface BranchMergeOptions {
860
1004
  /** Default true — captures `pre-merge:<ref>` snapshot of the parent. */
861
1005
  snapshot?: boolean;
862
1006
  }
1007
+ /** Output of `db.proposals.propose()` — the EXPLAIN plan, estimated affected rows, and the single-use execution token. */
863
1008
  interface ProposalPlan {
864
1009
  sql: string;
865
1010
  plan: unknown[][];
@@ -868,10 +1013,12 @@ interface ProposalPlan {
868
1013
  expiresAt: string;
869
1014
  action: "read" | "write" | "admin";
870
1015
  }
1016
+ /** Options for `db.proposals.propose()`. */
871
1017
  interface ProposeOptions {
872
1018
  params?: unknown[];
873
1019
  ttlSec?: number;
874
1020
  }
1021
+ /** Lifecycle state of an approval token, as returned by `db.approvals.get()` and `poll()`. */
875
1022
  interface ApprovalStatus {
876
1023
  status: "pending" | "approved" | "denied";
877
1024
  hits: ApprovalRuleHit[];
@@ -879,6 +1026,7 @@ interface ApprovalStatus {
879
1026
  expiresAt: string;
880
1027
  decidedAt: string | null;
881
1028
  }
1029
+ /** Pacing and cancellation options for `db.approvals.poll()`. */
882
1030
  interface PollApprovalOptions {
883
1031
  /** Default 2000ms. */
884
1032
  intervalMs?: number;
@@ -887,6 +1035,7 @@ interface PollApprovalOptions {
887
1035
  /** Cancellation signal. */
888
1036
  signal?: AbortSignal;
889
1037
  }
1038
+ /** Callbacks and pacing for `db.approvals.subscribe()`. */
890
1039
  interface SubscribeApprovalOptions {
891
1040
  onApprovalRequired?: (event: ApprovalEvent) => void;
892
1041
  onApprovalResolved?: (event: ApprovalEvent) => void;
@@ -897,11 +1046,17 @@ interface SubscribeApprovalOptions {
897
1046
  /** Logged-by-default error sink. */
898
1047
  onError?: (err: Error) => void;
899
1048
  }
1049
+ /** One approval state change delivered by `db.approvals.subscribe()`. */
900
1050
  interface ApprovalEvent {
901
1051
  approvalToken: string;
902
1052
  status: "pending" | "approved" | "denied";
903
1053
  hits: ApprovalRuleHit[];
904
1054
  }
1055
+ /**
1056
+ * Approval-token lifecycle for writes blocked by a require_approval
1057
+ * rule — look up, poll, subscribe, and redeem. Reached via
1058
+ * `db.approvals`.
1059
+ */
905
1060
  declare class PerSQLApprovals {
906
1061
  private readonly client;
907
1062
  private readonly namespace;
@@ -940,6 +1095,7 @@ declare class PerSQLApprovals {
940
1095
  */
941
1096
  redeem<T = Record<string, unknown>>(approvalToken: string): Promise<QueryResult<T> | QueryResult<T>[]>;
942
1097
  }
1098
+ /** A require_approval / deny rule on a database, as returned by `db.approvalRules.list()`. */
943
1099
  interface ApprovalRule {
944
1100
  id: string;
945
1101
  databaseId: string;
@@ -949,11 +1105,13 @@ interface ApprovalRule {
949
1105
  createdById: string;
950
1106
  createdAt: string;
951
1107
  }
1108
+ /** Input for `db.approvalRules.create()`. */
952
1109
  interface CreateApprovalRuleInput {
953
1110
  tableGlob: string;
954
1111
  action: "require_approval" | "deny";
955
1112
  note?: string;
956
1113
  }
1114
+ /** Manage the require_approval / deny rules of a database. Reached via `db.approvalRules`. */
957
1115
  declare class PerSQLApprovalRules {
958
1116
  private readonly client;
959
1117
  private readonly namespace;
@@ -967,6 +1125,11 @@ declare class PerSQLApprovalRules {
967
1125
  deleted: true;
968
1126
  }>;
969
1127
  }
1128
+ /**
1129
+ * Two-phase writes: `propose()` pre-flights the SQL and mints a
1130
+ * single-use execution token; `apply()` redeems it. Reached via
1131
+ * `db.proposals`.
1132
+ */
970
1133
  declare class PerSQLProposals {
971
1134
  private readonly client;
972
1135
  private readonly namespace;
@@ -992,6 +1155,7 @@ declare class PerSQLProposals {
992
1155
  */
993
1156
  apply<T = Record<string, unknown>>(executionToken: string): Promise<QueryResult<T>>;
994
1157
  }
1158
+ /** Output of `db.branches.claim()` — the fresh branch plus the scoped token minted for it. */
995
1159
  interface ClaimedBranch {
996
1160
  branchRef: string;
997
1161
  databaseId: string;
@@ -1004,6 +1168,7 @@ interface ClaimedBranch {
1004
1168
  expiresAt: string;
1005
1169
  outcome: "created" | "reset";
1006
1170
  }
1171
+ /** Options for `db.branches.claim()`. */
1007
1172
  interface ClaimBranchOptions {
1008
1173
  /** Explicit ref. Omit to auto-generate from `purpose`. */
1009
1174
  ref?: string;
@@ -1015,6 +1180,11 @@ interface ClaimBranchOptions {
1015
1180
  role?: "read" | "write" | "admin";
1016
1181
  region?: string;
1017
1182
  }
1183
+ /**
1184
+ * Branch management for one database — list, create-or-reset, delete,
1185
+ * merge, pin (handoff), claim (lease), and migration rendering.
1186
+ * Reached via `db.branches`.
1187
+ */
1018
1188
  declare class PerSQLBranches {
1019
1189
  private readonly client;
1020
1190
  private readonly namespace;
@@ -1113,4 +1283,4 @@ type SqliteProxyDriver = (sql: string, params: unknown[], method: "all" | "run"
1113
1283
  rows: unknown;
1114
1284
  }>;
1115
1285
 
1116
- export { type AiSdkTool, type AnthropicTool, type ApprovalEvent, ApprovalRequiredError, type ApprovalRule, type ApprovalRuleHit, type ApprovalStatus, type BatchExpect, type BatchOptions, type BranchInfo, type BranchMergeMode, type BranchMergeOptions, type BranchMergePlanStep, type BranchMergeResult, type BranchUpsertOptions, type ClaimBranchOptions, type ClaimedBranch, type CreateApprovalRuleInput, type CreateSupportTicketOptions, type DatabaseSchema, type DatabaseToolBundle, type DescribeBundle, type HandoffClaim, type LangChainTool, type MastraTool, type OpenAiAgentsTool, type OpenAiTool, PerSQL, PerSQLApprovalRules, PerSQLApprovals, PerSQLBranches, PerSQLDatabase, PerSQLError, type PerSQLOptions, PerSQLProposals, type PollApprovalOptions, type ProposalPlan, type ProposeOptions, type QueryLogEntry, type QueryOptions, type QueryResult, RateLimitError, type RawQueryResult, type SchemaDoctorReport, type SchemaSearchResponse, type SingleToolBundle, type SqlErrorDetail, type SqlErrorKind, type SqliteProxyDriver, type Statement, type SubscribeApprovalOptions, type SubscribeChange, type SubscribeChangeKind, type SubscribeOptions, SupportClient, type TableInfo, type TableSample, type ValidateResult, type WhoamiInfo };
1286
+ export { type AiSdkTool, type AnthropicTool, type ApprovalEvent, ApprovalRequiredError, type ApprovalRule, type ApprovalRuleHit, type ApprovalStatus, type BatchExpect, type BatchOptions, type BranchInfo, type BranchMergeMode, type BranchMergeOptions, type BranchMergePlanStep, type BranchMergeResult, type BranchUpsertOptions, type ClaimBranchOptions, type ClaimedBranch, type CreateApprovalRuleInput, type CreateDatabaseOptions, type CreateSupportTicketOptions, type DatabaseInfo, type DatabaseSchema, type DatabaseToolBundle, type DescribeBundle, type HandoffClaim, type LangChainTool, type MastraTool, type OpenAiAgentsTool, type OpenAiTool, PerSQL, PerSQLApprovalRules, PerSQLApprovals, PerSQLBranches, PerSQLDatabase, PerSQLDatabases, PerSQLError, type PerSQLOptions, PerSQLProposals, type PollApprovalOptions, type ProposalPlan, type ProposeOptions, type QueryLogEntry, type QueryMeta, type QueryOptions, type QueryResult, RateLimitError, type RawQueryResult, type SchemaDoctorReport, type SchemaSearchResponse, type SingleToolBundle, type SqlErrorDetail, type SqlErrorKind, type SqliteProxyDriver, type Statement, type SubscribeApprovalOptions, type SubscribeChange, type SubscribeChangeKind, type SubscribeOptions, SupportClient, type TableInfo, type TableSample, type ValidateResult, type WhoamiInfo };