@cosmoops/connect-sdk 0.9.0 → 0.16.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.
Files changed (155) hide show
  1. package/CHANGELOG.md +566 -0
  2. package/README.md +113 -42
  3. package/dist/cjs/cli/doctor.js +3 -3
  4. package/dist/cjs/cli/init.js +3 -3
  5. package/dist/cjs/client/components/access-gate.js +20 -1
  6. package/dist/cjs/client/components/ai-screens.js +73 -0
  7. package/dist/cjs/client/components/ai-widgets.js +37 -0
  8. package/dist/cjs/client/components/app-shell.js +75 -41
  9. package/dist/cjs/client/components/apps-grid.js +3 -2
  10. package/dist/cjs/client/components/ask-ai.js +50 -5
  11. package/dist/cjs/client/components/confirm-dialog.js +40 -0
  12. package/dist/cjs/client/components/cursor-pagination.js +22 -0
  13. package/dist/cjs/client/components/filter-tabs.js +24 -0
  14. package/dist/cjs/client/components/grievance-notice.js +30 -0
  15. package/dist/cjs/client/components/link-button.js +17 -10
  16. package/dist/cjs/client/components/logo.js +4 -3
  17. package/dist/cjs/client/components/nav.js +41 -0
  18. package/dist/cjs/client/components/plan-pricing.js +27 -0
  19. package/dist/cjs/client/components/plans.js +1 -1
  20. package/dist/cjs/client/components/platform-gate.js +48 -0
  21. package/dist/cjs/client/components/shell-context.js +149 -0
  22. package/dist/cjs/client/components/side-nav.js +49 -23
  23. package/dist/cjs/client/components/sidebar.js +127 -0
  24. package/dist/cjs/client/components/subscribe-dialog.js +2 -2
  25. package/dist/cjs/client/components/subscription.js +1 -1
  26. package/dist/cjs/client/components/user-menu.js +96 -37
  27. package/dist/cjs/client/components/workspace-switcher.js +100 -0
  28. package/dist/cjs/client/index.js +72 -2
  29. package/dist/cjs/client/provider.js +57 -2
  30. package/dist/cjs/client/use-checkout.js +9 -5
  31. package/dist/cjs/errors.js +1 -1
  32. package/dist/cjs/index.js +16 -1
  33. package/dist/cjs/mcp/index.js +90 -0
  34. package/dist/cjs/mcp/tools.js +369 -0
  35. package/dist/cjs/next/index.js +2 -2
  36. package/dist/cjs/server/client.js +25 -2
  37. package/dist/cjs/server/handler.js +203 -18
  38. package/dist/cjs/server/http.js +6 -2
  39. package/dist/cjs/server/resources/acting-as.js +16 -0
  40. package/dist/cjs/server/resources/ai.js +22 -2
  41. package/dist/cjs/server/resources/data.js +374 -0
  42. package/dist/cjs/server/resources/identity.js +7 -3
  43. package/dist/cjs/server/resources/kv.js +84 -0
  44. package/dist/cjs/server/resources/platform.js +43 -0
  45. package/dist/cjs/server/resources/sessions.js +1 -1
  46. package/dist/cjs/server/resources/workspaces.js +74 -0
  47. package/dist/cjs/store-schema.js +66 -0
  48. package/dist/cjs/store.js +152 -0
  49. package/dist/cjs/ui/empty-state.js +28 -0
  50. package/dist/cjs/ui/index.js +44 -0
  51. package/dist/cjs/ui/page-header.js +27 -0
  52. package/dist/cjs/ui/page-shell.js +44 -0
  53. package/dist/cjs/ui/section.js +20 -0
  54. package/dist/cjs/ui/setting-row.js +14 -0
  55. package/dist/cjs/ui/status-chip.js +17 -0
  56. package/dist/esm/cli/doctor.js +3 -3
  57. package/dist/esm/cli/init.js +3 -3
  58. package/dist/esm/client/components/access-gate.js +20 -1
  59. package/dist/esm/client/components/ai-screens.js +69 -0
  60. package/dist/esm/client/components/ai-widgets.js +37 -1
  61. package/dist/esm/client/components/app-shell.js +81 -47
  62. package/dist/esm/client/components/apps-grid.js +3 -2
  63. package/dist/esm/client/components/ask-ai.js +51 -6
  64. package/dist/esm/client/components/confirm-dialog.js +37 -0
  65. package/dist/esm/client/components/cursor-pagination.js +19 -0
  66. package/dist/esm/client/components/filter-tabs.js +21 -0
  67. package/dist/esm/client/components/grievance-notice.js +27 -0
  68. package/dist/esm/client/components/link-button.js +17 -10
  69. package/dist/esm/client/components/logo.js +4 -3
  70. package/dist/esm/client/components/nav.js +40 -0
  71. package/dist/esm/client/components/plan-pricing.js +25 -0
  72. package/dist/esm/client/components/plans.js +2 -2
  73. package/dist/esm/client/components/platform-gate.js +45 -0
  74. package/dist/esm/client/components/shell-context.js +141 -0
  75. package/dist/esm/client/components/side-nav.js +48 -21
  76. package/dist/esm/client/components/sidebar.js +120 -0
  77. package/dist/esm/client/components/subscribe-dialog.js +3 -3
  78. package/dist/esm/client/components/subscription.js +2 -2
  79. package/dist/esm/client/components/user-menu.js +95 -38
  80. package/dist/esm/client/components/workspace-switcher.js +97 -0
  81. package/dist/esm/client/index.js +47 -4
  82. package/dist/esm/client/provider.js +53 -2
  83. package/dist/esm/client/use-checkout.js +9 -5
  84. package/dist/esm/errors.js +1 -1
  85. package/dist/esm/index.js +4 -0
  86. package/dist/esm/mcp/index.js +85 -0
  87. package/dist/esm/mcp/tools.js +366 -0
  88. package/dist/esm/next/index.js +2 -2
  89. package/dist/esm/server/client.js +25 -2
  90. package/dist/esm/server/handler.js +203 -18
  91. package/dist/esm/server/http.js +6 -2
  92. package/dist/esm/server/resources/acting-as.js +13 -0
  93. package/dist/esm/server/resources/ai.js +22 -2
  94. package/dist/esm/server/resources/data.js +369 -0
  95. package/dist/esm/server/resources/identity.js +7 -3
  96. package/dist/esm/server/resources/kv.js +80 -0
  97. package/dist/esm/server/resources/platform.js +39 -0
  98. package/dist/esm/server/resources/sessions.js +1 -1
  99. package/dist/esm/server/resources/workspaces.js +70 -0
  100. package/dist/esm/store-schema.js +62 -0
  101. package/dist/esm/store.js +141 -0
  102. package/dist/esm/ui/empty-state.js +25 -0
  103. package/dist/esm/ui/index.js +34 -0
  104. package/dist/esm/ui/page-header.js +24 -0
  105. package/dist/esm/ui/page-shell.js +41 -0
  106. package/dist/esm/ui/section.js +17 -0
  107. package/dist/esm/ui/setting-row.js +11 -0
  108. package/dist/esm/ui/status-chip.js +14 -0
  109. package/dist/types/client/components/ai-screens.d.ts +25 -0
  110. package/dist/types/client/components/ai-widgets.d.ts +34 -1
  111. package/dist/types/client/components/app-shell.d.ts +65 -27
  112. package/dist/types/client/components/apps-grid.d.ts +11 -2
  113. package/dist/types/client/components/ask-ai.d.ts +47 -1
  114. package/dist/types/client/components/confirm-dialog.d.ts +34 -0
  115. package/dist/types/client/components/cursor-pagination.d.ts +27 -0
  116. package/dist/types/client/components/filter-tabs.d.ts +26 -0
  117. package/dist/types/client/components/grievance-notice.d.ts +31 -0
  118. package/dist/types/client/components/link-button.d.ts +32 -9
  119. package/dist/types/client/components/logo.d.ts +18 -2
  120. package/dist/types/client/components/nav.d.ts +24 -0
  121. package/dist/types/client/components/plan-pricing.d.ts +10 -0
  122. package/dist/types/client/components/platform-gate.d.ts +53 -0
  123. package/dist/types/client/components/shell-context.d.ts +116 -0
  124. package/dist/types/client/components/side-nav.d.ts +27 -13
  125. package/dist/types/client/components/sidebar.d.ts +95 -0
  126. package/dist/types/client/components/user-menu.d.ts +68 -22
  127. package/dist/types/client/components/workspace-switcher.d.ts +49 -0
  128. package/dist/types/client/index.d.ts +29 -5
  129. package/dist/types/client/provider.d.ts +109 -6
  130. package/dist/types/client/use-checkout.d.ts +8 -4
  131. package/dist/types/errors.d.ts +1 -1
  132. package/dist/types/index.d.ts +6 -0
  133. package/dist/types/mcp/index.d.ts +74 -0
  134. package/dist/types/mcp/tools.d.ts +107 -0
  135. package/dist/types/next/index.d.ts +1 -1
  136. package/dist/types/server/client.d.ts +25 -2
  137. package/dist/types/server/http.d.ts +8 -2
  138. package/dist/types/server/index.d.ts +4 -0
  139. package/dist/types/server/resources/acting-as.d.ts +11 -0
  140. package/dist/types/server/resources/ai.d.ts +20 -2
  141. package/dist/types/server/resources/data.d.ts +258 -0
  142. package/dist/types/server/resources/kv.d.ts +63 -0
  143. package/dist/types/server/resources/platform.d.ts +32 -0
  144. package/dist/types/server/resources/workspaces.d.ts +55 -0
  145. package/dist/types/store-schema.d.ts +52 -0
  146. package/dist/types/store.d.ts +96 -0
  147. package/dist/types/types.d.ts +369 -16
  148. package/dist/types/ui/empty-state.d.ts +34 -0
  149. package/dist/types/ui/index.d.ts +40 -0
  150. package/dist/types/ui/page-header.d.ts +34 -0
  151. package/dist/types/ui/page-shell.d.ts +39 -0
  152. package/dist/types/ui/section.d.ts +26 -0
  153. package/dist/types/ui/setting-row.d.ts +18 -0
  154. package/dist/types/ui/status-chip.d.ts +36 -0
  155. package/package.json +15 -2
@@ -2,6 +2,9 @@ import type { FetchLike } from "./http.js";
2
2
  import { AccessResource } from "./resources/access.js";
3
3
  import { AppsResource } from "./resources/apps.js";
4
4
  import { AiResource } from "./resources/ai.js";
5
+ import { DataResource } from "./resources/data.js";
6
+ import { WorkspacesResource } from "./resources/workspaces.js";
7
+ import { KvResource } from "./resources/kv.js";
5
8
  import { BillingResource } from "./resources/billing.js";
6
9
  import { DriveResource } from "./resources/drive.js";
7
10
  import { EntitlementsResource } from "./resources/entitlements.js";
@@ -11,10 +14,11 @@ import { LogsResource } from "./resources/logs.js";
11
14
  import { AnalyticsResource } from "./resources/analytics.js";
12
15
  import { OtpResource } from "./resources/otp.js";
13
16
  import { SessionsResource } from "./resources/sessions.js";
17
+ import { PlatformResource } from "./resources/platform.js";
14
18
  export declare const DEFAULT_BASE_URL = "https://connect.cosmoops.com";
15
19
  export interface ConnectClientOptions {
16
20
  /**
17
- * The API key plaintext from this application's Integration tab, shown once at
21
+ * The API key plaintext from this application's Connect client section, shown once at
18
22
  * creation.
19
23
  *
20
24
  * One credential does three jobs: it is the `Authorization: Bearer` value
@@ -73,6 +77,10 @@ export declare class ConnectClient {
73
77
  readonly drive: DriveResource;
74
78
  /** RAG chat and structured extraction. */
75
79
  readonly ai: AiResource;
80
+ /** Your application's own document store - see `DataResource`. */
81
+ readonly data: DataResource;
82
+ /** Your application's key/value store - see `KvResource`. */
83
+ readonly kv: KvResource;
76
84
  /** WhatsApp/SMS phone verification. */
77
85
  readonly otp: OtpResource;
78
86
  /** Feature events into the Console's audit log, filterable to your application. */
@@ -83,7 +91,22 @@ export declare class ConnectClient {
83
91
  readonly sessions: SessionsResource;
84
92
  /** Composio-hosted third-party connections (Google Calendar, Slack, ...) a workspace has connected. */
85
93
  readonly integrations: IntegrationsResource;
94
+ /** Which workspaces the signed-in user belongs to, switching between them, and the team - see `WorkspacesResource`. */
95
+ readonly workspaces: WorkspacesResource;
96
+ /** The platform's own legal identity and DPDP grievance channel - see `PlatformResource`. */
97
+ readonly platform: PlatformResource;
98
+ /**
99
+ * Relays one `/data/*` or `/kv/*` request verbatim - `createConnectHandler`'s
100
+ * store route and nothing else. See `storePassthrough`.
101
+ * @internal
102
+ */
103
+ readonly storeRelay: (spec: {
104
+ method: "GET" | "POST" | "PUT" | "DELETE";
105
+ path: string;
106
+ json?: unknown;
107
+ headers?: Record<string, string>;
108
+ }) => Promise<Response>;
86
109
  constructor(options: ConnectClientOptions);
87
110
  }
88
111
  /** Kept in step with `package.json` by `sdk.test.ts`. */
89
- export declare const SDK_VERSION = "0.9.0";
112
+ export declare const SDK_VERSION = "0.16.0";
@@ -1,6 +1,11 @@
1
1
  export type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;
2
2
  export interface RequestSpec {
3
- method: "GET" | "POST";
3
+ /**
4
+ * `PUT` and `DELETE` were added for the store resources. Both are idempotent
5
+ * by HTTP's own definition, so both are retried like `GET` - see the retry
6
+ * note on `request`.
7
+ */
8
+ method: "GET" | "POST" | "PUT" | "DELETE";
4
9
  /** Relative to the API base, e.g. `/plans`. */
5
10
  path: string;
6
11
  query?: Record<string, string | number | undefined>;
@@ -49,7 +54,8 @@ export declare function pathSegment(value: string, name: string): string;
49
54
  * `ConnectError`.
50
55
  *
51
56
  * **On retries.** Only idempotent requests are retried without a caller
52
- * opt-in: every `GET`, plus any `POST` carrying an `Idempotency-Key` (the
57
+ * opt-in: every `GET`, `PUT` and `DELETE`, plus any `POST` carrying an
58
+ * `Idempotency-Key` (the
53
59
  * resources that mutate - `billing.consumeAddon`, `otp.send` - always set
54
60
  * one). A bare `POST` is attempted exactly once, because a 429 or a 504 says
55
61
  * nothing about whether the server already did the work.
@@ -18,11 +18,15 @@ export type { AppsResource } from "./resources/apps.js";
18
18
  export type { EntitlementsResource } from "./resources/entitlements.js";
19
19
  export type { BillingResource } from "./resources/billing.js";
20
20
  export type { AiResource } from "./resources/ai.js";
21
+ export type { DataResource } from "./resources/data.js";
22
+ export type { KvResource } from "./resources/kv.js";
23
+ export type { WorkspacesResource } from "./resources/workspaces.js";
21
24
  export type { OtpResource } from "./resources/otp.js";
22
25
  export type { LogsResource } from "./resources/logs.js";
23
26
  export type { AnalyticsResource } from "./resources/analytics.js";
24
27
  export type { SessionsResource } from "./resources/sessions.js";
25
28
  export type { ExecuteIntegrationActionInput, IntegrationsResource } from "./resources/integrations.js";
29
+ export type { PlatformResource } from "./resources/platform.js";
26
30
  /**
27
31
  * Webhook verification and a ready-made POST route. Server-only for the
28
32
  * obvious reason: the signing secret must never reach a browser bundle.
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The header that turns a server-to-server store call into one made *on behalf
3
+ * of* a signed-in user.
4
+ *
5
+ * Connect resolves the token to a user and a workspace itself and checks the
6
+ * collection's declared permission against that member's live roles - so a
7
+ * caller supplying this is asking for a *narrower* check than an application
8
+ * call, never a wider one. An unresolvable token is refused rather than
9
+ * quietly downgraded, precisely because the downgrade would be an escalation.
10
+ */
11
+ export declare function actingAs(sessionToken: string | undefined): Record<string, string>;
@@ -1,6 +1,13 @@
1
1
  import { type TransportOptions } from "../http.js";
2
- import type { ChatMessage } from "../../types.js";
3
- /** RAG chat and structured extraction against a workspace's data. */
2
+ import type { ChatMessage, ConnectScreen } from "../../types.js";
3
+ /**
4
+ * RAG chat and structured extraction against a workspace's data.
5
+ *
6
+ * `asUser` on `chat` behaves as it does on the store resources: pass a
7
+ * signed-in user's `sessionToken` and Connect resolves the workspace from it,
8
+ * ignoring `workspaceId`. Without it you are the application, naming the
9
+ * workspace yourself.
10
+ */
4
11
  export declare class AiResource {
5
12
  private readonly transport;
6
13
  constructor(transport: TransportOptions);
@@ -13,6 +20,17 @@ export declare class AiResource {
13
20
  chat(input: {
14
21
  workspaceId: string;
15
22
  messages: ChatMessage[];
23
+ asUser?: string;
24
+ /**
25
+ * Your application's screens, so the assistant answers in your product's
26
+ * words and can link into it - see `ConnectScreen`. Prose and links only:
27
+ * Connect never uses these to decide what anyone may do.
28
+ */
29
+ screens?: readonly ConnectScreen[] | unknown;
30
+ /** Where the user is, as `{ pathname }`. Matched against `screens`. */
31
+ screenContext?: {
32
+ pathname?: string;
33
+ } | unknown;
16
34
  }, options?: {
17
35
  signal?: AbortSignal;
18
36
  }): Promise<Response>;
@@ -0,0 +1,258 @@
1
+ import { type TransportOptions } from "../http.js";
2
+ import type { StoreAggregatePage, StoreAggregation, StoreCommitOperation, StoreCommitResult, StoreDocument, StoreQueryFilter, StoreQueryPage, StoreOrderBy, StoreScanPage } from "../../types.js";
3
+ /**
4
+ * Your application's own document store - a dedicated Firestore database that
5
+ * only Connect can reach.
6
+ *
7
+ * You hold no Firebase credential and write no security rules. Collections are
8
+ * declared in the Console; an undeclared name is refused rather than created,
9
+ * and `workspaceId` is a path segment rather than a filter, so a query cannot
10
+ * cross tenants.
11
+ *
12
+ * **A query needs an index for its shape, and there are two ways to have one.**
13
+ * Firestore maintains a single-field index automatically, which covers one
14
+ * filter, or an `orderBy` on that same field. Anything wider - two filters, or
15
+ * a filter ordered by a different field - needs a composite index **declared
16
+ * on your application's Store tab**, which Connect then builds in your
17
+ * database. A query with no matching index is refused up front, naming the
18
+ * index to declare, rather than failing later with a Firestore index error
19
+ * pointing at a console you cannot open.
20
+ *
21
+ * **Dates are ISO-8601 strings.** The store writes what you send it, and JSON
22
+ * has no date type, so a `Date` must be sent as `toISOString()`. That is not a
23
+ * compromise: UTC ISO strings sort lexicographically in chronological order,
24
+ * so ordering and range filters on them behave exactly as they would on a
25
+ * timestamp - and Connect's own `_createdAt`/`_updatedAt` come back in the
26
+ * same form.
27
+ *
28
+ * **`workspaceId` is omitted for master data.** A collection declared
29
+ * application-scoped in the Console belongs to no workspace - one copy, owned
30
+ * by you, read by every tenant. Those are written with your API key only,
31
+ * never on behalf of a user: a write permission is something a *workspace*
32
+ * grants, so honouring one on shared data would give one tenant's admin reach
33
+ * over every other tenant's reference data.
34
+ *
35
+ * **`asUser` is how you act on behalf of a signed-in user.** Pass the opaque
36
+ * `sessionToken` you were given at sign-in and Connect resolves the user and
37
+ * their workspace from it - checking the collection's declared permission
38
+ * against what that member actually holds, and **ignoring `workspaceId`
39
+ * entirely**. Without it you are the application itself: your API key's scopes
40
+ * are the only gate, and the per-user permissions do not apply. The bridge
41
+ * (`createConnectHandler`) always passes it, which is what makes the browser
42
+ * path safe.
43
+ */
44
+ export declare class DataResource {
45
+ private readonly transport;
46
+ constructor(transport: TransportOptions);
47
+ /** Reads one document. Throws `ConnectError` with status 404 when it does not exist. */
48
+ get(input: {
49
+ workspaceId?: string;
50
+ collection: string;
51
+ id: string;
52
+ asUser?: string;
53
+ }): Promise<StoreDocument>;
54
+ /**
55
+ * Lists a collection, one page at a time.
56
+ *
57
+ * `filters` are ANDed. More than one, or one combined with an `orderBy` on a
58
+ * different field, needs a composite index declared on the Store tab - see
59
+ * the class note above.
60
+ *
61
+ * `limit` defaults to 50 and caps at 200. Page by passing the previous
62
+ * page's `nextCursor`; a `null` `nextCursor` is the end of the collection.
63
+ */
64
+ query(input: {
65
+ workspaceId?: string;
66
+ collection: string;
67
+ filters?: StoreQueryFilter[];
68
+ orderBy?: StoreOrderBy;
69
+ limit?: number;
70
+ cursor?: string | null;
71
+ asUser?: string;
72
+ }): Promise<StoreQueryPage>;
73
+ /**
74
+ * Counts, sums and averages a filtered collection - **in Firestore**, not by
75
+ * reading the rows back and adding them up.
76
+ *
77
+ * That distinction is the whole point. An aggregation is billed by the index
78
+ * entries it scans rather than by the documents it would have returned, so a
79
+ * dashboard tile over ten thousand rows costs a handful of reads and stays
80
+ * flat as the collection grows. Paging the rows into your own process to sum
81
+ * them is an unbounded read, and it is what every integration does when
82
+ * there is no aggregate call to reach for.
83
+ *
84
+ * ```ts
85
+ * const { values } = await connect.data.aggregate({
86
+ * workspaceId,
87
+ * collection: "deals",
88
+ * filters: [{ field: "outcome", op: "==", value: "won" }],
89
+ * aggregations: [
90
+ * { op: "count", alias: "count" },
91
+ * { op: "sum", field: "value", alias: "total" },
92
+ * ],
93
+ * });
94
+ * ```
95
+ *
96
+ * A `sum` or `avg` over nothing answers `null` rather than `0`, because
97
+ * "nothing matched" and "the total is zero" are different facts.
98
+ *
99
+ * The index it needs is the one its **filters** need plus the aggregated
100
+ * field - Firestore reads a sum out of the index rather than out of the
101
+ * documents.
102
+ *
103
+ * There is no `orderBy`, `limit` or `cursor`: the aggregate of an ordered
104
+ * page is the aggregate of the whole filtered set.
105
+ */
106
+ aggregate(input: {
107
+ workspaceId?: string;
108
+ collection: string;
109
+ aggregations: StoreAggregation[];
110
+ filters?: StoreQueryFilter[];
111
+ asUser?: string;
112
+ }): Promise<StoreAggregatePage>;
113
+ /**
114
+ * Reads one collection across **every workspace at once** - your own
115
+ * back-office view of your whole customer base. Each row carries the
116
+ * `workspaceId` it came from.
117
+ *
118
+ * Needs the `data:read:all` scope, and the collection must be marked
119
+ * cross-workspace in the Console. There is deliberately **no `asUser`**: a
120
+ * scan is never available on behalf of a signed-in user, because a workspace
121
+ * admin is an admin of their own workspace and not of your other customers.
122
+ *
123
+ * Filtering and ordering follow the same index rules as `query`, and
124
+ * additionally need collection-group scope. Connect creates those from the
125
+ * declaration - the single-field ones when you mark a field queryable, and a
126
+ * group-scoped copy of every composite index the collection declares. A scan
127
+ * issued in the minutes after that declaration may fail while the index
128
+ * builds.
129
+ */
130
+ scan(input: {
131
+ collection: string;
132
+ filters?: StoreQueryFilter[];
133
+ orderBy?: StoreOrderBy;
134
+ limit?: number;
135
+ cursor?: string | null;
136
+ }): Promise<StoreScanPage>;
137
+ /**
138
+ * Creates a document. Pass `id` to choose it, or omit it and one is
139
+ * allocated. An `id` that already exists throws with status 409 - use
140
+ * `replace` to overwrite deliberately.
141
+ */
142
+ create(input: {
143
+ workspaceId?: string;
144
+ collection: string;
145
+ data: Record<string, unknown>;
146
+ id?: string;
147
+ asUser?: string;
148
+ }): Promise<StoreDocument>;
149
+ /** Replaces every field, creating the document if it does not exist. `_createdAt`/`_createdBy` survive. */
150
+ replace(input: {
151
+ workspaceId?: string;
152
+ collection: string;
153
+ id: string;
154
+ data: Record<string, unknown>;
155
+ asUser?: string;
156
+ }): Promise<StoreDocument>;
157
+ /** Merges top-level fields into an existing document. Throws with status 404 if it does not exist. */
158
+ update(input: {
159
+ workspaceId?: string;
160
+ collection: string;
161
+ id: string;
162
+ data: Record<string, unknown>;
163
+ asUser?: string;
164
+ }): Promise<StoreDocument>;
165
+ /** Idempotent - deleting a document that is not there succeeds. */
166
+ delete(input: {
167
+ workspaceId?: string;
168
+ collection: string;
169
+ id: string;
170
+ asUser?: string;
171
+ }): Promise<void>;
172
+ /**
173
+ * Applies a set of writes atomically, across as many of your collections as
174
+ * they name. All of them land, or none.
175
+ *
176
+ * This is the store's answer to a database transaction, and the shape is
177
+ * deliberate: **you never hold one**. A transaction handle passed across an
178
+ * HTTP boundary is a server-side lock whose duration your network controls,
179
+ * with Firestore's sixty-second ceiling and lock contention as the failure
180
+ * mode. Instead you read, decide, and commit *conditional on nothing having
181
+ * moved* - Connect runs the whole set as one short transaction on its side.
182
+ *
183
+ * ```ts
184
+ * await connect.data.commit({
185
+ * workspaceId,
186
+ * operations: [
187
+ * { op: "create", collection: "ledger", id, data: entry },
188
+ * {
189
+ * op: "merge",
190
+ * collection: "funds",
191
+ * id: entry.fundId,
192
+ * data: { balancePaise: increment(entry.amountPaise) },
193
+ * },
194
+ * ],
195
+ * });
196
+ * ```
197
+ *
198
+ * A precondition that no longer holds throws `ConnectError` with status 409
199
+ * and **writes nothing** - see `transaction`, which re-reads and retries for
200
+ * you. Note that the example above needs no precondition at all: an
201
+ * `increment` is commutative, so concurrent commits both land correctly.
202
+ *
203
+ * Returns the id each operation wrote, in order - which is how you learn the
204
+ * id a `create` without one was given.
205
+ */
206
+ commit(input: {
207
+ workspaceId?: string;
208
+ operations: StoreCommitOperation[];
209
+ asUser?: string;
210
+ }): Promise<StoreCommitResult>;
211
+ /**
212
+ * Read, decide, commit - retried when the world moved underneath you.
213
+ *
214
+ * The optimistic-concurrency loop, written once. `build` reads whatever it
215
+ * needs and returns the operations to apply; if any precondition among them
216
+ * has gone stale by the time Connect looks, the commit is rejected whole and
217
+ * `build` runs again against fresh reads.
218
+ *
219
+ * ```ts
220
+ * await connect.data.transaction(async () => {
221
+ * const fund = await connect.data.get({ workspaceId, collection: "funds", id });
222
+ * if (!fund.data.active) throw new Error("That fund is closed.");
223
+ * return [
224
+ * {
225
+ * op: "merge",
226
+ * collection: "funds",
227
+ * id,
228
+ * data: { balancePaise: increment(amount) },
229
+ * precondition: unchangedSince(fund),
230
+ * },
231
+ * ];
232
+ * });
233
+ * ```
234
+ *
235
+ * **Only a 409 is retried.** A 403 or a validation error means the operation
236
+ * was wrong rather than late, and retrying it would turn one clear failure
237
+ * into `attempts` identical ones. An error thrown by `build` itself - the
238
+ * closed-fund check above - is not retried either: it is a decision, not a
239
+ * collision.
240
+ *
241
+ * `build` must be free of side effects outside the returned operations,
242
+ * because it can run more than once. That is the price of not holding a
243
+ * lock, and it is the same contract Firestore's own `runTransaction` states
244
+ * for the same reason.
245
+ */
246
+ transaction(build: () => Promise<StoreCommitOperation[]>, options?: {
247
+ workspaceId?: string;
248
+ asUser?: string;
249
+ attempts?: number;
250
+ }): Promise<StoreCommitResult>;
251
+ private write;
252
+ }
253
+ export declare function storePassthrough(transport: TransportOptions, spec: {
254
+ method: "GET" | "POST" | "PUT" | "DELETE";
255
+ path: string;
256
+ json?: unknown;
257
+ headers?: Record<string, string>;
258
+ }): Promise<Response>;
@@ -0,0 +1,63 @@
1
+ import { type TransportOptions } from "../http.js";
2
+ import type { KvEntry, KvPage } from "../../types.js";
3
+ /**
4
+ * Your application's key/value store - small values addressed by namespace and
5
+ * key, per workspace.
6
+ *
7
+ * Use it for what a document store is bad at: a feature flag, a cached
8
+ * computation, a counter, a draft nobody needs to query. Anything you want to
9
+ * *find* rather than *fetch* belongs in `data`.
10
+ *
11
+ * Namespaces are declared in the Console exactly like collections. Keys are
12
+ * `[A-Za-z0-9_-]` up to 64 characters, and values are JSON up to 128 KB.
13
+ *
14
+ * `asUser` behaves exactly as it does on `DataResource` - pass a signed-in
15
+ * user's `sessionToken` and Connect derives the user and workspace from it,
16
+ * checking the namespace's declared permission against their live roles.
17
+ */
18
+ export declare class KvResource {
19
+ private readonly transport;
20
+ constructor(transport: TransportOptions);
21
+ /**
22
+ * Reads one key. Throws `ConnectError` with status 404 when the key is
23
+ * absent **or expired** - expiry is evaluated on read, so there is no window
24
+ * where a stale value is still served.
25
+ */
26
+ get(input: {
27
+ workspaceId?: string;
28
+ namespace: string;
29
+ key: string;
30
+ asUser?: string;
31
+ }): Promise<KvEntry>;
32
+ /**
33
+ * Pages a namespace in key order.
34
+ *
35
+ * Expired entries are filtered out after paging, so a page can come back
36
+ * shorter than `limit` while still having a `nextCursor` - treat
37
+ * `nextCursor` as the only signal that more keys exist, never an empty page.
38
+ */
39
+ list(input: {
40
+ workspaceId?: string;
41
+ namespace: string;
42
+ limit?: number;
43
+ cursor?: string | null;
44
+ asUser?: string;
45
+ }): Promise<KvPage>;
46
+ /** Sets a value, replacing whatever was there. `ttlSeconds` expires it; omit for no expiry. */
47
+ set(input: {
48
+ workspaceId?: string;
49
+ namespace: string;
50
+ key: string;
51
+ value: unknown;
52
+ ttlSeconds?: number;
53
+ asUser?: string;
54
+ }): Promise<KvEntry>;
55
+ /** Idempotent - deleting an absent key succeeds. */
56
+ delete(input: {
57
+ workspaceId?: string;
58
+ namespace: string;
59
+ key: string;
60
+ asUser?: string;
61
+ }): Promise<void>;
62
+ private path;
63
+ }
@@ -0,0 +1,32 @@
1
+ import { type TransportOptions } from "../http.js";
2
+ import type { CompanyProfile } from "../../types.js";
3
+ /**
4
+ * Facts about the platform itself, rather than about a workspace or a user.
5
+ *
6
+ * One method today, and the resource exists anyway rather than hanging
7
+ * `getCompanyProfile` off `identity` or `workspaces`: neither of those is
8
+ * what it is about. This answers "who operates the thing my application runs
9
+ * on", which is a different subject from every other resource here, and a
10
+ * misfiled method is harder to find than an obvious one-method namespace.
11
+ */
12
+ export declare class PlatformResource {
13
+ private readonly transport;
14
+ constructor(transport: TransportOptions);
15
+ /**
16
+ * The operating entity's legal identity and its DPDP grievance channel.
17
+ *
18
+ * Takes no `workspaceId` and takes no `asUser` - the answer is the same for
19
+ * every tenant and every caller, because it is a fact about the platform.
20
+ *
21
+ * Returns the inner `companyProfile` rather than the envelope, matching
22
+ * `apps.list()` and `billing.listPlans()`: the wrapper is a wire detail, and
23
+ * every caller would destructure it on the next line.
24
+ *
25
+ * **Do not copy the result into your own configuration.** Cache it if you
26
+ * render it on a hot path - Connect already sends
27
+ * `s-maxage=3600, stale-while-revalidate=86400` - but the reason this is an
28
+ * endpoint at all is that a grievance officer copied into `.env` goes stale
29
+ * without anyone finding out.
30
+ */
31
+ getCompanyProfile(): Promise<CompanyProfile>;
32
+ }
@@ -0,0 +1,55 @@
1
+ import { type TransportOptions } from "../http.js";
2
+ import type { ConnectWorkspaceMember, UserWorkspace, WorkspaceSwitchResult } from "../../types.js";
3
+ /**
4
+ * Which workspaces the signed-in user belongs to, moving between them, and who
5
+ * else is on the team.
6
+ *
7
+ * **Every call here needs `asUser`** - the signed-in user's opaque
8
+ * `sessionToken`. There is no application-wide form of any of these: they take
9
+ * no `userId` and no `workspaceId`, and resolve both from the token. That is
10
+ * what makes the whole module safe to reach from a browser through
11
+ * `createConnectHandler`, and it is why a partner cannot enumerate another
12
+ * tenant's team with their own API key.
13
+ */
14
+ export declare class WorkspacesResource {
15
+ private readonly transport;
16
+ constructor(transport: TransportOptions);
17
+ /**
18
+ * The workspaces this user could switch to.
19
+ *
20
+ * Rows carry `inAudience` and `subscribed` so a switcher can show a
21
+ * workspace it cannot select, with the reason. Hiding it instead reads as
22
+ * data loss to the person who owns it.
23
+ */
24
+ list(options: {
25
+ asUser: string;
26
+ }): Promise<UserWorkspace[]>;
27
+ /**
28
+ * Moves this session to another workspace, returning a **new session
29
+ * token**. The one you passed is revoked.
30
+ *
31
+ * Persist the result before navigating - dropping it signs the user out,
32
+ * because the old token no longer works.
33
+ *
34
+ * Throws with status 404 when the workspace does not exist *or* the user is
35
+ * not a member (deliberately indistinguishable), and 403 when it is outside
36
+ * your audience, requires an MFA factor they have not enrolled, or is the
37
+ * workspace they are already on.
38
+ */
39
+ switch(input: {
40
+ workspaceId: string;
41
+ }, options: {
42
+ asUser: string;
43
+ }): Promise<WorkspaceSwitchResult>;
44
+ /**
45
+ * The team of the workspace this session is on.
46
+ *
47
+ * Members carry permissions already resolved for *your* application, not raw
48
+ * Connect roles - so you never reimplement Connect's role resolution, and
49
+ * you are told nothing about the customer's Connect account beyond what your
50
+ * application needs.
51
+ */
52
+ members(options: {
53
+ asUser: string;
54
+ }): Promise<ConnectWorkspaceMember[]>;
55
+ }
@@ -0,0 +1,52 @@
1
+ import type { StoreQueryFilter, StoreOrderBy } from "./types.js";
2
+ import { type StoreIndexField } from "./store.js";
3
+ /**
4
+ * One collection as an application declares it, and the helpers that read a
5
+ * declaration back.
6
+ *
7
+ * The declaration is the same shape Connect stores on `Application.storeSchema`
8
+ * and the same one `scripts/seed-*-application.ts` writes there, so an app that
9
+ * builds its schema from its own query matrix can hand the result straight to
10
+ * anything here without a second type of its own. Connect remains the
11
+ * authority - this is a local copy of what was declared, useful for deriving
12
+ * things *from* the declaration, never for deciding access.
13
+ *
14
+ * Isomorphic and dependency-free, like the rest of the root entry.
15
+ */
16
+ export interface StoreCollectionDeclaration {
17
+ name: string;
18
+ kind: "data" | "kv";
19
+ scope: "workspace" | "application";
20
+ /** Prose. Connect shows it in the Console; `deriveStoreTools` gives it to a model. */
21
+ description: string;
22
+ /** The per-user permission Connect checks when a call carries `asUser`. `null` means any member. */
23
+ readPermission: string | null;
24
+ writePermission: string | null;
25
+ /** Whether `data.scan` may read this collection across every workspace. */
26
+ crossWorkspace: boolean;
27
+ /** Create-only. Connect refuses a replace, a merge or a delete for every caller. */
28
+ appendOnly: boolean;
29
+ /** Nothing may be filtered, ordered or aggregated on a field not listed. */
30
+ queryableFields: string[];
31
+ indexes: {
32
+ fields: StoreIndexField[];
33
+ }[];
34
+ }
35
+ /** Whether one of `declared` can serve a query needing exactly `required`. */
36
+ export declare function storeIndexCovers(declared: readonly {
37
+ fields: StoreIndexField[];
38
+ }[], required: readonly StoreIndexField[]): boolean;
39
+ /**
40
+ * Why a collection cannot serve a query, in words a caller can act on - or
41
+ * `null` when it can.
42
+ *
43
+ * Runs the same two rules Connect runs, in the same order: every field must be
44
+ * declared queryable, and the shape must be inside an automatic single-field
45
+ * index or covered by a declared composite one. Checking here rather than
46
+ * letting the call fail is not about saving a round trip - it is so the refusal
47
+ * can name the index to declare while the caller still has the query in hand.
48
+ */
49
+ export declare function describeQueryRejection(collection: StoreCollectionDeclaration, query: {
50
+ filters?: readonly StoreQueryFilter[];
51
+ orderBy?: StoreOrderBy | null;
52
+ }): string | null;