@cosmoops/connect-sdk 0.9.0 → 0.15.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.
- package/CHANGELOG.md +497 -0
- package/README.md +111 -41
- package/dist/cjs/cli/doctor.js +3 -3
- package/dist/cjs/cli/init.js +3 -3
- package/dist/cjs/client/components/access-gate.js +20 -1
- package/dist/cjs/client/components/app-shell.js +75 -41
- package/dist/cjs/client/components/apps-grid.js +3 -2
- package/dist/cjs/client/components/ask-ai.js +20 -1
- package/dist/cjs/client/components/confirm-dialog.js +40 -0
- package/dist/cjs/client/components/cursor-pagination.js +22 -0
- package/dist/cjs/client/components/filter-tabs.js +24 -0
- package/dist/cjs/client/components/grievance-notice.js +30 -0
- package/dist/cjs/client/components/link-button.js +17 -10
- package/dist/cjs/client/components/logo.js +4 -3
- package/dist/cjs/client/components/nav.js +41 -0
- package/dist/cjs/client/components/plan-pricing.js +27 -0
- package/dist/cjs/client/components/plans.js +1 -1
- package/dist/cjs/client/components/platform-gate.js +48 -0
- package/dist/cjs/client/components/shell-context.js +149 -0
- package/dist/cjs/client/components/side-nav.js +49 -23
- package/dist/cjs/client/components/sidebar.js +127 -0
- package/dist/cjs/client/components/subscribe-dialog.js +2 -2
- package/dist/cjs/client/components/subscription.js +1 -1
- package/dist/cjs/client/components/user-menu.js +96 -37
- package/dist/cjs/client/components/workspace-switcher.js +100 -0
- package/dist/cjs/client/index.js +69 -2
- package/dist/cjs/client/provider.js +57 -2
- package/dist/cjs/client/use-checkout.js +9 -5
- package/dist/cjs/errors.js +1 -1
- package/dist/cjs/index.js +11 -1
- package/dist/cjs/next/index.js +2 -2
- package/dist/cjs/server/client.js +25 -2
- package/dist/cjs/server/handler.js +196 -18
- package/dist/cjs/server/http.js +6 -2
- package/dist/cjs/server/resources/acting-as.js +16 -0
- package/dist/cjs/server/resources/ai.js +15 -2
- package/dist/cjs/server/resources/data.js +374 -0
- package/dist/cjs/server/resources/identity.js +7 -3
- package/dist/cjs/server/resources/kv.js +84 -0
- package/dist/cjs/server/resources/platform.js +43 -0
- package/dist/cjs/server/resources/sessions.js +1 -1
- package/dist/cjs/server/resources/workspaces.js +74 -0
- package/dist/cjs/store.js +152 -0
- package/dist/cjs/ui/empty-state.js +28 -0
- package/dist/cjs/ui/index.js +44 -0
- package/dist/cjs/ui/page-header.js +27 -0
- package/dist/cjs/ui/page-shell.js +44 -0
- package/dist/cjs/ui/section.js +20 -0
- package/dist/cjs/ui/setting-row.js +14 -0
- package/dist/cjs/ui/status-chip.js +17 -0
- package/dist/esm/cli/doctor.js +3 -3
- package/dist/esm/cli/init.js +3 -3
- package/dist/esm/client/components/access-gate.js +20 -1
- package/dist/esm/client/components/app-shell.js +81 -47
- package/dist/esm/client/components/apps-grid.js +3 -2
- package/dist/esm/client/components/ask-ai.js +20 -1
- package/dist/esm/client/components/confirm-dialog.js +37 -0
- package/dist/esm/client/components/cursor-pagination.js +19 -0
- package/dist/esm/client/components/filter-tabs.js +21 -0
- package/dist/esm/client/components/grievance-notice.js +27 -0
- package/dist/esm/client/components/link-button.js +17 -10
- package/dist/esm/client/components/logo.js +4 -3
- package/dist/esm/client/components/nav.js +40 -0
- package/dist/esm/client/components/plan-pricing.js +25 -0
- package/dist/esm/client/components/plans.js +2 -2
- package/dist/esm/client/components/platform-gate.js +45 -0
- package/dist/esm/client/components/shell-context.js +141 -0
- package/dist/esm/client/components/side-nav.js +48 -21
- package/dist/esm/client/components/sidebar.js +120 -0
- package/dist/esm/client/components/subscribe-dialog.js +3 -3
- package/dist/esm/client/components/subscription.js +2 -2
- package/dist/esm/client/components/user-menu.js +95 -38
- package/dist/esm/client/components/workspace-switcher.js +97 -0
- package/dist/esm/client/index.js +46 -4
- package/dist/esm/client/provider.js +53 -2
- package/dist/esm/client/use-checkout.js +9 -5
- package/dist/esm/errors.js +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/next/index.js +2 -2
- package/dist/esm/server/client.js +25 -2
- package/dist/esm/server/handler.js +196 -18
- package/dist/esm/server/http.js +6 -2
- package/dist/esm/server/resources/acting-as.js +13 -0
- package/dist/esm/server/resources/ai.js +15 -2
- package/dist/esm/server/resources/data.js +369 -0
- package/dist/esm/server/resources/identity.js +7 -3
- package/dist/esm/server/resources/kv.js +80 -0
- package/dist/esm/server/resources/platform.js +39 -0
- package/dist/esm/server/resources/sessions.js +1 -1
- package/dist/esm/server/resources/workspaces.js +70 -0
- package/dist/esm/store.js +141 -0
- package/dist/esm/ui/empty-state.js +25 -0
- package/dist/esm/ui/index.js +34 -0
- package/dist/esm/ui/page-header.js +24 -0
- package/dist/esm/ui/page-shell.js +41 -0
- package/dist/esm/ui/section.js +17 -0
- package/dist/esm/ui/setting-row.js +11 -0
- package/dist/esm/ui/status-chip.js +14 -0
- package/dist/types/client/components/app-shell.d.ts +65 -27
- package/dist/types/client/components/apps-grid.d.ts +11 -2
- package/dist/types/client/components/confirm-dialog.d.ts +34 -0
- package/dist/types/client/components/cursor-pagination.d.ts +27 -0
- package/dist/types/client/components/filter-tabs.d.ts +26 -0
- package/dist/types/client/components/grievance-notice.d.ts +31 -0
- package/dist/types/client/components/link-button.d.ts +32 -9
- package/dist/types/client/components/logo.d.ts +18 -2
- package/dist/types/client/components/nav.d.ts +24 -0
- package/dist/types/client/components/plan-pricing.d.ts +10 -0
- package/dist/types/client/components/platform-gate.d.ts +53 -0
- package/dist/types/client/components/shell-context.d.ts +116 -0
- package/dist/types/client/components/side-nav.d.ts +27 -13
- package/dist/types/client/components/sidebar.d.ts +95 -0
- package/dist/types/client/components/user-menu.d.ts +68 -22
- package/dist/types/client/components/workspace-switcher.d.ts +49 -0
- package/dist/types/client/index.d.ts +27 -5
- package/dist/types/client/provider.d.ts +109 -6
- package/dist/types/client/use-checkout.d.ts +8 -4
- package/dist/types/errors.d.ts +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/next/index.d.ts +1 -1
- package/dist/types/server/client.d.ts +25 -2
- package/dist/types/server/http.d.ts +8 -2
- package/dist/types/server/index.d.ts +4 -0
- package/dist/types/server/resources/acting-as.d.ts +11 -0
- package/dist/types/server/resources/ai.d.ts +9 -1
- package/dist/types/server/resources/data.d.ts +258 -0
- package/dist/types/server/resources/kv.d.ts +63 -0
- package/dist/types/server/resources/platform.d.ts +32 -0
- package/dist/types/server/resources/workspaces.d.ts +55 -0
- package/dist/types/store.d.ts +96 -0
- package/dist/types/types.d.ts +319 -15
- package/dist/types/ui/empty-state.d.ts +34 -0
- package/dist/types/ui/index.d.ts +40 -0
- package/dist/types/ui/page-header.d.ts +34 -0
- package/dist/types/ui/page-shell.d.ts +39 -0
- package/dist/types/ui/section.d.ts +26 -0
- package/dist/types/ui/setting-row.d.ts +18 -0
- package/dist/types/ui/status-chip.d.ts +36 -0
- package/package.json +6 -2
|
@@ -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,96 @@
|
|
|
1
|
+
import type { StoreDocument, StoreFieldTransform, StoreOrderBy, StorePrecondition, StoreQueryFilter } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Building blocks for `data.commit` - the transform sentinels, and the two
|
|
4
|
+
* ways to say "only if nothing has moved".
|
|
5
|
+
*
|
|
6
|
+
* Isomorphic and dependency-free, so a Server Component and a client component
|
|
7
|
+
* building the same write use the same constructors. They are functions rather
|
|
8
|
+
* than object literals you write by hand because the sentinel shape is an
|
|
9
|
+
* encoding, not an API: `{ __transform: "increment", by: 1 }` is what goes over
|
|
10
|
+
* the wire, and nothing outside this file should have to know that.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Add to a number, atomically.
|
|
14
|
+
*
|
|
15
|
+
* **The one operation you genuinely cannot do yourself.** Increments are
|
|
16
|
+
* commutative, so two concurrent `increment(500)` calls both land - where
|
|
17
|
+
* read-add-write from your own process loses one of them however carefully it
|
|
18
|
+
* retries, because the read and the write are two round trips with a gap in
|
|
19
|
+
* between. Use this for balances, counters and tallies, and reach for a
|
|
20
|
+
* precondition only when the new value depends on the old one in a way
|
|
21
|
+
* addition cannot express.
|
|
22
|
+
*
|
|
23
|
+
* Negative to subtract; there is no separate `decrement`.
|
|
24
|
+
*/
|
|
25
|
+
export declare function increment(by: number): StoreFieldTransform;
|
|
26
|
+
/** Connect's own write time, rather than a clock reading from your process. */
|
|
27
|
+
export declare function serverTimestamp(): StoreFieldTransform;
|
|
28
|
+
/** Adds the values not already in the array. Order is not preserved. */
|
|
29
|
+
export declare function arrayUnion(...values: unknown[]): StoreFieldTransform;
|
|
30
|
+
/** Removes every occurrence of each value. */
|
|
31
|
+
export declare function arrayRemove(...values: unknown[]): StoreFieldTransform;
|
|
32
|
+
/**
|
|
33
|
+
* Removes the field entirely.
|
|
34
|
+
*
|
|
35
|
+
* `merge` only - a `replace` already drops every field it does not list, so
|
|
36
|
+
* asking it to delete one is a contradiction rather than a narrowing.
|
|
37
|
+
*/
|
|
38
|
+
export declare function deleteField(): StoreFieldTransform;
|
|
39
|
+
/**
|
|
40
|
+
* "Only if this document has not changed since I read it."
|
|
41
|
+
*
|
|
42
|
+
* Pass the document a read returned, and the precondition is built from its
|
|
43
|
+
* `_updateTime`. Taking the document rather than the timestamp is deliberate:
|
|
44
|
+
* a caller who has to reach for `doc.data._updateTime` themselves is one typo
|
|
45
|
+
* away from a precondition that silently does nothing.
|
|
46
|
+
*
|
|
47
|
+
* Throws rather than degrading when the document carries no `_updateTime` -
|
|
48
|
+
* that only happens for a document you built yourself rather than read, and a
|
|
49
|
+
* precondition that quietly became "no precondition" is worse than a crash at
|
|
50
|
+
* the call site.
|
|
51
|
+
*/
|
|
52
|
+
export declare function unchangedSince(document: StoreDocument): StorePrecondition;
|
|
53
|
+
/** "Only if this document exists" / "only if it does not." */
|
|
54
|
+
export declare function documentExists(exists?: boolean): StorePrecondition;
|
|
55
|
+
/** How one field sits in a composite index. `array-contains` is a position, not an order. */
|
|
56
|
+
export type StoreIndexDirection = "asc" | "desc" | "array-contains";
|
|
57
|
+
export interface StoreIndexField {
|
|
58
|
+
field: string;
|
|
59
|
+
direction: StoreIndexDirection;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The composite index a query shape needs, or `null` when Firestore's
|
|
63
|
+
* automatic single-field indexes already serve it.
|
|
64
|
+
*
|
|
65
|
+
* Use it to generate your Store tab declarations from the queries your code
|
|
66
|
+
* actually makes, rather than transcribing them by hand and discovering the
|
|
67
|
+
* gap in production:
|
|
68
|
+
*
|
|
69
|
+
* ```ts
|
|
70
|
+
* const needed = MY_QUERY_SHAPES.map(storeIndexFor).filter(Boolean);
|
|
71
|
+
* ```
|
|
72
|
+
*
|
|
73
|
+
* **Field order is Firestore's, not yours.** Equality fields first (sorted, so
|
|
74
|
+
* the order you passed them in cannot produce two different declarations for
|
|
75
|
+
* one query), then the `array-contains` field, then the field the query orders
|
|
76
|
+
* by. Declaring the same shape twice in two orders builds two indexes, one of
|
|
77
|
+
* which is dead weight on every document write.
|
|
78
|
+
*
|
|
79
|
+
* Connect runs the same derivation server-side and is the authority: a query
|
|
80
|
+
* with no matching declared index is refused with the index it needs spelled
|
|
81
|
+
* out, so a disagreement here surfaces on the first call rather than silently.
|
|
82
|
+
*
|
|
83
|
+
* Throws for a query Firestore cannot serve at all - two `array-contains`
|
|
84
|
+
* filters, ranges over two fields, or a range ordered by a different field.
|
|
85
|
+
*/
|
|
86
|
+
export declare function storeIndexFor(query: {
|
|
87
|
+
filters?: readonly StoreQueryFilter[];
|
|
88
|
+
orderBy?: StoreOrderBy | null;
|
|
89
|
+
}): StoreIndexField[] | null;
|
|
90
|
+
/**
|
|
91
|
+
* An index as one comparable string - `"status:asc,createdAt:desc"`.
|
|
92
|
+
*
|
|
93
|
+
* Deduplicate a generated list on this before declaring it: a dozen query
|
|
94
|
+
* shapes across a screen usually collapse to two or three distinct indexes.
|
|
95
|
+
*/
|
|
96
|
+
export declare function storeIndexSignature(fields: readonly StoreIndexField[]): string;
|
package/dist/types/types.d.ts
CHANGED
|
@@ -25,8 +25,21 @@ export interface ConnectUser {
|
|
|
25
25
|
* failing outright - so pair the two rather than branching on `null`.
|
|
26
26
|
*/
|
|
27
27
|
photoUrl: string | null;
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
/**
|
|
29
|
+
* The workspace this session is scoped to - the tenant to key your own data
|
|
30
|
+
* on. **`null` only for a Connect platform staff session**, where `staff`
|
|
31
|
+
* is `true`; see `SessionAccess`.
|
|
32
|
+
*
|
|
33
|
+
* Nullable since 0.11.0, and the breaking change in that release. If you do
|
|
34
|
+
* not turn staff access on for your application (it is off by default and
|
|
35
|
+
* per-application), this is never `null` and the only work is a type
|
|
36
|
+
* narrow. If you do, decide first what your tenanting does with a session
|
|
37
|
+
* that has no tenant - typically: serve your operator console off `staff`,
|
|
38
|
+
* and nothing else.
|
|
39
|
+
*/
|
|
40
|
+
workspaceId: string | null;
|
|
41
|
+
/** `null` on a staff session, for the same reason as `workspaceId`. */
|
|
42
|
+
workspaceName: string | null;
|
|
30
43
|
/**
|
|
31
44
|
* Whether this is a one-seat personal account or a team.
|
|
32
45
|
*
|
|
@@ -40,8 +53,10 @@ export interface ConnectUser {
|
|
|
40
53
|
* Re-read it from `identity.getUser()`; do not cache it past the sign-in.
|
|
41
54
|
* An `"individual"` workspace can convert to `"business"` under a live
|
|
42
55
|
* session (the reverse never happens).
|
|
56
|
+
*
|
|
57
|
+
* `null` on a staff session - there is no workspace to have a type.
|
|
43
58
|
*/
|
|
44
|
-
workspaceType: WorkspaceType;
|
|
59
|
+
workspaceType: WorkspaceType | null;
|
|
45
60
|
}
|
|
46
61
|
export type WorkspaceType = "individual" | "business";
|
|
47
62
|
export type ApplicationAudience = "b2b" | "b2c" | "both";
|
|
@@ -86,15 +101,61 @@ export interface SessionSubscription {
|
|
|
86
101
|
isTrialing: boolean;
|
|
87
102
|
}
|
|
88
103
|
/**
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
104
|
+
* Who the signed-in account is to you, in three flat fields.
|
|
105
|
+
*
|
|
106
|
+
* Replaces the `applicationRole` (`{role, granted}`) and `platformAccess`
|
|
107
|
+
* (`{roles}`) blocks, which were mutually exclusive by construction: a
|
|
108
|
+
* Connect platform account belongs to no workspace, so one of the two was
|
|
109
|
+
* always the empty answer and every integration wrote the same null-check
|
|
110
|
+
* twice.
|
|
111
|
+
*
|
|
112
|
+
* Read `staff` first - it decides what the other two mean.
|
|
113
|
+
*
|
|
114
|
+
* **`staff` is not a client-side security boundary.** `<ConnectPlatformGate>`
|
|
115
|
+
* and the hooks around it decide what to *render*; the browser can ignore all
|
|
116
|
+
* of it. Re-check it server-side with `client.identity.getUser(sessionToken)`
|
|
117
|
+
* before serving anything an operator console exposes.
|
|
92
118
|
*/
|
|
93
|
-
export interface
|
|
119
|
+
export interface SessionAccess {
|
|
120
|
+
/**
|
|
121
|
+
* The one role this session holds, or `null`.
|
|
122
|
+
*
|
|
123
|
+
* For a member (`staff: false`): the role matched on *your* application,
|
|
124
|
+
* resolved for your `client_id` rather than their workspace-wide roles -
|
|
125
|
+
* `null` when their permissions match no single role you defined, including
|
|
126
|
+
* when they have no access to your application at all.
|
|
127
|
+
*
|
|
128
|
+
* For staff (`staff: true`): the highest-authority Connect platform role
|
|
129
|
+
* **your application accepts** - never Connect's full role list for that
|
|
130
|
+
* person. An application accepting only `developer` is told `"developer"`
|
|
131
|
+
* about a `platform_admin` who also holds it.
|
|
132
|
+
*/
|
|
94
133
|
role: string | null;
|
|
95
|
-
|
|
134
|
+
/**
|
|
135
|
+
* What to gate on. Permission ids from *your* own `permissionCatalog` for a
|
|
136
|
+
* member; Connect `platform:*` ids for staff. One vocabulary at a time,
|
|
137
|
+
* decided by `staff` - a member session never carries a `platform:*` id and
|
|
138
|
+
* a staff session never carries one of yours.
|
|
139
|
+
*/
|
|
140
|
+
permissions: string[];
|
|
141
|
+
/**
|
|
142
|
+
* `true` when this is one of Connect's own operators rather than your
|
|
143
|
+
* customer.
|
|
144
|
+
*
|
|
145
|
+
* Only ever `true` when a Connect platform account signs in **and** your
|
|
146
|
+
* application has staff access turned on for that role. It is off by
|
|
147
|
+
* default; a platform admin enables it per application in the Connect
|
|
148
|
+
* Console, so until then this is always `false`.
|
|
149
|
+
*
|
|
150
|
+
* A staff session is shaped differently, and every difference is
|
|
151
|
+
* deliberate: `user.workspaceId`/`workspaceName`/`workspaceType` are `null`
|
|
152
|
+
* (these accounts belong to no workspace, by enforcement rather than by
|
|
153
|
+
* accident) and `subscription` is `null` (nobody is paying for staff to be
|
|
154
|
+
* here). Serve your operator console off this session and nothing else.
|
|
155
|
+
*/
|
|
156
|
+
staff: boolean;
|
|
96
157
|
}
|
|
97
|
-
export interface TokenResponse {
|
|
158
|
+
export interface TokenResponse extends SessionAccess {
|
|
98
159
|
idToken: string;
|
|
99
160
|
tokenType: "Bearer";
|
|
100
161
|
/** Seconds until `idToken` expires - about 5 minutes. */
|
|
@@ -105,15 +166,17 @@ export interface TokenResponse {
|
|
|
105
166
|
sessionTokenExpiresIn: number;
|
|
106
167
|
user: ConnectUser;
|
|
107
168
|
subscription: SessionSubscription | null;
|
|
108
|
-
applicationRole: ApplicationRole;
|
|
109
169
|
/** Whether this account is the kind your application serves - see `SessionAudience`. */
|
|
110
170
|
audience: SessionAudience;
|
|
111
171
|
}
|
|
112
|
-
export interface UserResponse {
|
|
172
|
+
export interface UserResponse extends SessionAccess {
|
|
113
173
|
user: ConnectUser;
|
|
114
174
|
subscription: SessionSubscription | null;
|
|
115
|
-
|
|
116
|
-
|
|
175
|
+
/**
|
|
176
|
+
* Live, like `subscription` and `role`/`permissions`/`staff` - a workspace
|
|
177
|
+
* that converted to a business since sign-in matches here without another
|
|
178
|
+
* sign-in.
|
|
179
|
+
*/
|
|
117
180
|
audience: SessionAudience;
|
|
118
181
|
/** ISO 8601. Refresh proactively off this rather than waiting for a 401. */
|
|
119
182
|
sessionExpiresAt: string;
|
|
@@ -248,7 +311,7 @@ export interface Plan {
|
|
|
248
311
|
/** GST-exclusive, in paise. */
|
|
249
312
|
price: number;
|
|
250
313
|
currency: "INR";
|
|
251
|
-
billingInterval:
|
|
314
|
+
billingInterval: BillingInterval;
|
|
252
315
|
gstRate: number;
|
|
253
316
|
addons: PlanAddon[];
|
|
254
317
|
seats: PlanSeats;
|
|
@@ -257,6 +320,16 @@ export interface Plan {
|
|
|
257
320
|
/** What this plan unlocks, labelled for display - see `PlanFeature`. */
|
|
258
321
|
features: PlanFeature[];
|
|
259
322
|
}
|
|
323
|
+
/**
|
|
324
|
+
* How often a plan bills.
|
|
325
|
+
*
|
|
326
|
+
* `"one-time"` is a plan that is paid for once and does not renew, so it has
|
|
327
|
+
* no cadence to pair with: `planGroup` still groups it, but there is no
|
|
328
|
+
* monthly/annual toggle to render for it. A pricing grid that assumes two
|
|
329
|
+
* cadences will show it under whichever tab happens to be selected - check the
|
|
330
|
+
* interval before offering the switch.
|
|
331
|
+
*/
|
|
332
|
+
export type BillingInterval = "monthly" | "annual" | "one-time";
|
|
260
333
|
export interface PlansResponse {
|
|
261
334
|
plans: Plan[];
|
|
262
335
|
}
|
|
@@ -264,7 +337,7 @@ export interface PlansResponse {
|
|
|
264
337
|
export interface SubscribedPlan {
|
|
265
338
|
id: string;
|
|
266
339
|
name: string;
|
|
267
|
-
billingInterval:
|
|
340
|
+
billingInterval: BillingInterval;
|
|
268
341
|
price: number;
|
|
269
342
|
currency: "INR";
|
|
270
343
|
gstRate: number;
|
|
@@ -483,6 +556,46 @@ export interface WorkspaceApp {
|
|
|
483
556
|
/** Launchable but not healthy (paused, or cancelling at period end) - badge it, do not hide it. */
|
|
484
557
|
needsAttention: boolean;
|
|
485
558
|
}
|
|
559
|
+
/**
|
|
560
|
+
* The platform's DPDP grievance channel - `companyProfile.grievance`.
|
|
561
|
+
*
|
|
562
|
+
* Flat and non-nullable: Connect resolves every field against its published
|
|
563
|
+
* defaults before answering, so there is no "not configured yet" state to
|
|
564
|
+
* branch on here. See `PlatformResource.getCompanyProfile`.
|
|
565
|
+
*/
|
|
566
|
+
export interface GrievanceChannel {
|
|
567
|
+
officerName: string;
|
|
568
|
+
officerEmail: string;
|
|
569
|
+
/** Free text, e.g. `"Monday - Friday, 10:00 AM - 6:00 PM IST"`. */
|
|
570
|
+
hours: string;
|
|
571
|
+
/** The redressal window the published notice commits to, in days. */
|
|
572
|
+
responseDays: number;
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Who operates the platform your application runs on, and how a user
|
|
576
|
+
* complains to them.
|
|
577
|
+
*
|
|
578
|
+
* You are a Data Processor on this notice, not a second Data Fiduciary - so
|
|
579
|
+
* this is the block your privacy page and your consent capture print, rather
|
|
580
|
+
* than one of your own kept in environment variables. That is the whole point
|
|
581
|
+
* of reading it: a statutory contact copied into `.env` goes stale silently,
|
|
582
|
+
* and every application holding a copy publishes a channel that reaches
|
|
583
|
+
* nobody.
|
|
584
|
+
*/
|
|
585
|
+
export interface CompanyProfile {
|
|
586
|
+
legalName: string;
|
|
587
|
+
/** Trading name, for prose the full registered name reads oddly in. */
|
|
588
|
+
brandName: string;
|
|
589
|
+
registeredAddress: string;
|
|
590
|
+
contactEmail: string;
|
|
591
|
+
website: string | null;
|
|
592
|
+
/** GSTIN, `null` if the operating entity is not GST-registered. */
|
|
593
|
+
gstNumber: string | null;
|
|
594
|
+
grievance: GrievanceChannel;
|
|
595
|
+
}
|
|
596
|
+
export interface CompanyProfileResponse {
|
|
597
|
+
companyProfile: CompanyProfile;
|
|
598
|
+
}
|
|
486
599
|
export type SessionDeviceType = "desktop" | "mobile" | "tablet" | "unknown";
|
|
487
600
|
export interface SessionDevice {
|
|
488
601
|
userAgent: string;
|
|
@@ -787,3 +900,194 @@ export interface AiSourcesInput {
|
|
|
787
900
|
}
|
|
788
901
|
/** The tools this client knows how to draw. Anything else in the stream is ignored. */
|
|
789
902
|
export type AiToolName = "showTable" | "showChart" | "showMetrics" | "showTimeline" | "showDetails" | "showSteps" | "showSources";
|
|
903
|
+
/**
|
|
904
|
+
* One document from the `data` store.
|
|
905
|
+
*
|
|
906
|
+
* Connect's own bookkeeping rides *inside* `data` under a `_` prefix -
|
|
907
|
+
* `_createdAt`, `_updatedAt`, `_createdBy`, `_updatedBy` - rather than as
|
|
908
|
+
* sibling fields, so a document round-trips through your code unchanged. You
|
|
909
|
+
* cannot write a `_`-prefixed field; that is what makes `_createdBy`
|
|
910
|
+
* trustworthy. It is `"application"` for a server-to-server write, or the
|
|
911
|
+
* Connect user id when a signed-in user drove it.
|
|
912
|
+
*/
|
|
913
|
+
export interface StoreDocument {
|
|
914
|
+
id: string;
|
|
915
|
+
/**
|
|
916
|
+
* The document's own fields, plus Connect's bookkeeping under `_`:
|
|
917
|
+
* `_createdAt`, `_updatedAt`, `_createdBy`, `_updatedBy`, and - on anything
|
|
918
|
+
* that came from a read - `_updateTime`.
|
|
919
|
+
*
|
|
920
|
+
* `_updateTime` is the value a `{ updateTime }` precondition on `commit` is
|
|
921
|
+
* compared against. It is Firestore's own write timestamp rather than the
|
|
922
|
+
* `_updatedAt` field beside it, because a transform in the same commit can
|
|
923
|
+
* move the field and cannot touch the write timestamp.
|
|
924
|
+
*
|
|
925
|
+
* None of them may be written back - strip every `_`-prefixed key before
|
|
926
|
+
* sending a document you read.
|
|
927
|
+
*/
|
|
928
|
+
data: Record<string, unknown>;
|
|
929
|
+
}
|
|
930
|
+
/** The comparison operators a store query may use - narrower than Firestore's own set. */
|
|
931
|
+
export type StoreQueryOperator = "==" | "!=" | "<" | "<=" | ">" | ">=" | "in" | "not-in" | "array-contains";
|
|
932
|
+
export interface StoreQueryFilter {
|
|
933
|
+
/** Must be one of the collection's declared `queryableFields`. */
|
|
934
|
+
field: string;
|
|
935
|
+
op: StoreQueryOperator;
|
|
936
|
+
value: unknown;
|
|
937
|
+
}
|
|
938
|
+
export interface StoreOrderBy {
|
|
939
|
+
field: string;
|
|
940
|
+
direction: "asc" | "desc";
|
|
941
|
+
}
|
|
942
|
+
export interface StoreQueryPage {
|
|
943
|
+
items: StoreDocument[];
|
|
944
|
+
/** Pass as the next `cursor`. `null` is the end of the collection. */
|
|
945
|
+
nextCursor: string | null;
|
|
946
|
+
}
|
|
947
|
+
export type StoreAggregationOperation = "count" | "sum" | "avg";
|
|
948
|
+
/**
|
|
949
|
+
* One aggregation over a filtered collection.
|
|
950
|
+
*
|
|
951
|
+
* `alias` is what the result is keyed by, so a single request can carry a
|
|
952
|
+
* count and a sum without the response shape depending on argument order.
|
|
953
|
+
*/
|
|
954
|
+
export interface StoreAggregation {
|
|
955
|
+
op: StoreAggregationOperation;
|
|
956
|
+
/** Required for `sum`/`avg`. `count` counts documents and takes none. */
|
|
957
|
+
field?: string | null;
|
|
958
|
+
alias: string;
|
|
959
|
+
}
|
|
960
|
+
export interface StoreAggregatePage {
|
|
961
|
+
/**
|
|
962
|
+
* One number per alias.
|
|
963
|
+
*
|
|
964
|
+
* A `sum` or `avg` over nothing is `null`, not `0` - "nothing matched" and
|
|
965
|
+
* "the total is zero" are different facts. A `count` is always a number.
|
|
966
|
+
*/
|
|
967
|
+
values: Record<string, number | null>;
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* A value Connect computes at write time instead of storing verbatim.
|
|
971
|
+
*
|
|
972
|
+
* Build them with the `increment`/`serverTimestamp`/`arrayUnion`/
|
|
973
|
+
* `arrayRemove`/`deleteField` helpers rather than by hand - the sentinel shape
|
|
974
|
+
* is an encoding, not an API.
|
|
975
|
+
*
|
|
976
|
+
* `increment` is the one that cannot be expressed any other way: it is
|
|
977
|
+
* commutative, so concurrent increments all land with no precondition and no
|
|
978
|
+
* retry, which read-modify-write from your own process can never guarantee.
|
|
979
|
+
*/
|
|
980
|
+
export type StoreFieldTransform = {
|
|
981
|
+
__transform: "increment";
|
|
982
|
+
by: number;
|
|
983
|
+
} | {
|
|
984
|
+
__transform: "serverTimestamp";
|
|
985
|
+
} | {
|
|
986
|
+
__transform: "arrayUnion";
|
|
987
|
+
values: unknown[];
|
|
988
|
+
} | {
|
|
989
|
+
__transform: "arrayRemove";
|
|
990
|
+
values: unknown[];
|
|
991
|
+
} | {
|
|
992
|
+
__transform: "delete";
|
|
993
|
+
};
|
|
994
|
+
/**
|
|
995
|
+
* What must still be true of a document for a commit to land.
|
|
996
|
+
*
|
|
997
|
+
* `{ updateTime }` is the `_updateTime` a read returned - the optimistic half
|
|
998
|
+
* of read, decide, commit. A precondition that no longer holds fails the whole
|
|
999
|
+
* commit with a 409 and writes nothing.
|
|
1000
|
+
*/
|
|
1001
|
+
export type StorePrecondition = {
|
|
1002
|
+
exists: boolean;
|
|
1003
|
+
} | {
|
|
1004
|
+
updateTime: string;
|
|
1005
|
+
};
|
|
1006
|
+
export type StoreCommitMode = "create" | "replace" | "merge" | "delete";
|
|
1007
|
+
/** One write inside a commit. Each names its own collection - a commit may span several. */
|
|
1008
|
+
export interface StoreCommitOperation {
|
|
1009
|
+
op: StoreCommitMode;
|
|
1010
|
+
collection: string;
|
|
1011
|
+
/** Omit only on `create`, to have an id allocated. */
|
|
1012
|
+
id?: string | null;
|
|
1013
|
+
/** Absent on `delete`. Top-level values may be `StoreFieldTransform`s. */
|
|
1014
|
+
data?: Record<string, unknown> | null;
|
|
1015
|
+
/** Not accepted on `create`, which already means "and it must not exist". */
|
|
1016
|
+
precondition?: StorePrecondition | null;
|
|
1017
|
+
}
|
|
1018
|
+
export interface StoreCommitResult {
|
|
1019
|
+
/** The document id each operation wrote, positionally aligned with the operations sent. */
|
|
1020
|
+
ids: string[];
|
|
1021
|
+
}
|
|
1022
|
+
/** One entry from the `kv` store. */
|
|
1023
|
+
export interface KvEntry {
|
|
1024
|
+
key: string;
|
|
1025
|
+
value: unknown;
|
|
1026
|
+
/** ISO-8601, or `null` when the entry never expires. */
|
|
1027
|
+
expiresAt: string | null;
|
|
1028
|
+
updatedAt: string;
|
|
1029
|
+
}
|
|
1030
|
+
export interface KvPage {
|
|
1031
|
+
items: KvEntry[];
|
|
1032
|
+
/**
|
|
1033
|
+
* Pass as the next `cursor`. `null` is the end of the namespace.
|
|
1034
|
+
*
|
|
1035
|
+
* A page can be shorter than `limit` and still have one - expired entries
|
|
1036
|
+
* are filtered after paging - so this, not an empty `items`, is the signal
|
|
1037
|
+
* to stop.
|
|
1038
|
+
*/
|
|
1039
|
+
nextCursor: string | null;
|
|
1040
|
+
}
|
|
1041
|
+
/** One row from a cross-workspace scan - carries the workspace it belongs to. */
|
|
1042
|
+
export interface ScannedStoreDocument extends StoreDocument {
|
|
1043
|
+
workspaceId: string;
|
|
1044
|
+
}
|
|
1045
|
+
export interface StoreScanPage {
|
|
1046
|
+
items: ScannedStoreDocument[];
|
|
1047
|
+
/** `{workspaceId}/{documentId}`. Pass as the next `cursor`; `null` is the end. */
|
|
1048
|
+
nextCursor: string | null;
|
|
1049
|
+
}
|
|
1050
|
+
/** One workspace the signed-in user belongs to - a row in a workspace switcher. */
|
|
1051
|
+
export interface UserWorkspace {
|
|
1052
|
+
workspaceId: string;
|
|
1053
|
+
name: string;
|
|
1054
|
+
type: WorkspaceType;
|
|
1055
|
+
/** The workspace this session is currently pinned to. */
|
|
1056
|
+
isActive: boolean;
|
|
1057
|
+
/**
|
|
1058
|
+
* Whether your application is sold to this kind of workspace. `false` rows
|
|
1059
|
+
* are worth showing disabled with the reason - a workspace that silently
|
|
1060
|
+
* disappears from a switcher reads as data loss to the person who owns it.
|
|
1061
|
+
*/
|
|
1062
|
+
inAudience: boolean;
|
|
1063
|
+
/** Whether it holds an entitling subscription to your application. */
|
|
1064
|
+
subscribed: boolean;
|
|
1065
|
+
}
|
|
1066
|
+
/**
|
|
1067
|
+
* The result of a workspace switch.
|
|
1068
|
+
*
|
|
1069
|
+
* `sessionToken` is a **new** credential - the one you switched from is
|
|
1070
|
+
* revoked. Persist this before navigating, or the user is signed out.
|
|
1071
|
+
*/
|
|
1072
|
+
export interface WorkspaceSwitchResult {
|
|
1073
|
+
sessionToken: string;
|
|
1074
|
+
sessionTokenExpiresIn: number;
|
|
1075
|
+
workspaceId: string;
|
|
1076
|
+
workspaceName: string;
|
|
1077
|
+
workspaceType: WorkspaceType;
|
|
1078
|
+
}
|
|
1079
|
+
/**
|
|
1080
|
+
* One member of the workspace a session is on.
|
|
1081
|
+
*
|
|
1082
|
+
* `granted` is resolved for *your* application - permission ids from your own
|
|
1083
|
+
* catalog, not the member's Connect roles.
|
|
1084
|
+
*/
|
|
1085
|
+
export interface ConnectWorkspaceMember {
|
|
1086
|
+
userId: string;
|
|
1087
|
+
email: string;
|
|
1088
|
+
displayName: string | null;
|
|
1089
|
+
photoUrl: string | null;
|
|
1090
|
+
status: string;
|
|
1091
|
+
granted: string[];
|
|
1092
|
+
role: string | null;
|
|
1093
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
export interface ConnectEmptyStateProps {
|
|
3
|
+
/** Rendered at `size-8`. Any icon element - lucide, an SVG, a glyph. */
|
|
4
|
+
icon?: ReactNode;
|
|
5
|
+
/** What is absent, in the user's words. One line. */
|
|
6
|
+
title: ReactNode;
|
|
7
|
+
description?: ReactNode;
|
|
8
|
+
/**
|
|
9
|
+
* The one control that resolves the emptiness - "New contact" for a list
|
|
10
|
+
* that has never had anything in it, "Clear filter" for one whose contents
|
|
11
|
+
* are merely hidden.
|
|
12
|
+
*/
|
|
13
|
+
action?: ReactNode;
|
|
14
|
+
variant?: "card" | "dashed" | "plain";
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* What a list, grid or table shows when it has nothing to show.
|
|
19
|
+
*
|
|
20
|
+
* **The two empty states are different facts and the copy must say so.** "You
|
|
21
|
+
* have no contacts" and "no contacts match this filter" call for different
|
|
22
|
+
* words and a different action - create the thing, versus undo the filter
|
|
23
|
+
* hiding it. Both render through here; the caller decides which by what it
|
|
24
|
+
* passes as `action`. Getting this wrong costs a user real time: an
|
|
25
|
+
* unfiltered-looking empty state in front of a filtered list reads as data
|
|
26
|
+
* loss.
|
|
27
|
+
*
|
|
28
|
+
* `variant` chooses the container by what the state stands *in*: `card` (the
|
|
29
|
+
* default), `dashed` for a region that will be filled in place - a file grid,
|
|
30
|
+
* a board column - where the outline is what says "things appear here", and
|
|
31
|
+
* `plain` for a slot already inside a card that would otherwise nest two
|
|
32
|
+
* borders.
|
|
33
|
+
*/
|
|
34
|
+
export declare function ConnectEmptyState({ icon, title, description, action, variant, className, }: Readonly<ConnectEmptyStateProps>): import("react").JSX.Element;
|