@bounded-sh/core 0.0.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/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # @tarobase/core
2
+
3
+ Core functionality for Tarobase SDKs. This package provides the shared functionality used by both the web and server SDKs.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @tarobase/core
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ This package is typically not used directly, but is instead used as a dependency by `@tarobase/web` and `@tarobase/server`. If you're building a browser application, use `@tarobase/web`. If you're building a server application, use `@tarobase/server`.
14
+
15
+ ## API Reference
16
+
17
+ ### Core Types
18
+
19
+ ```typescript
20
+ export interface ClientConfig {
21
+ appId: string;
22
+ apiUrl: string;
23
+ authMethod: string;
24
+ privyConfig?: any;
25
+ chain?: string;
26
+ useSessionStorage?: boolean;
27
+ }
28
+
29
+ export interface AuthProvider {
30
+ login(): Promise<User | null>;
31
+ runTransaction(evmTransactionData?: EVMTransaction, solTransactionData?: SolTransaction, options?: SetOptions): Promise<TransactionResult>;
32
+ signMessage(message: string): Promise<string>;
33
+ restoreSession(): Promise<User | null>;
34
+ logout(): Promise<void>;
35
+ getNativeMethods(): Promise<any>;
36
+ }
37
+
38
+ export interface User {
39
+ address: string;
40
+ provider: AuthProvider;
41
+ }
42
+
43
+ export interface GetManyResult {
44
+ path: string;
45
+ data: any | null;
46
+ error?: {
47
+ code: 'NOT_FOUND' | 'UNAUTHORIZED' | 'INVALID_PATH';
48
+ message: string;
49
+ };
50
+ }
51
+ ```
52
+
53
+ ### Core Operations
54
+
55
+ ```typescript
56
+ // Initialize the SDK
57
+ function configInit(newConfig: Partial<ClientConfig>, options?: SessionOptions): Promise<void>;
58
+
59
+ // Get the current configuration
60
+ function getConfig(): Promise<ClientConfig>;
61
+
62
+ // Data operations
63
+ function get(path: string): Promise<any>;
64
+ function getMany(paths: string[], options?: { bypassCache?: boolean }): Promise<GetManyResult[]>;
65
+ function set(path: string, data: any, options?: SetOptions): Promise<any>;
66
+ function setMany(paths: { [key: string]: any }, options?: SetOptions): Promise<any>;
67
+ function setFile(path: string, file: File, metadata?: any): Promise<any>;
68
+ function getFiles(path: string): Promise<any>;
69
+ function runQuery(queryString: string, variables?: any): Promise<any>;
70
+ function runQueryMany(queryString: string, variables?: any): Promise<any>;
71
+
72
+ // Subscription
73
+ function subscribe(path: string, options?: SubscriptionOptions): Promise<() => void>;
74
+ ```
75
+
76
+ ## Contributing
77
+
78
+ Please see the main repository for contribution guidelines.
@@ -0,0 +1,105 @@
1
+ import { AuthProvider } from '../types';
2
+ export interface ClientConfig {
3
+ name: string;
4
+ logoUrl: string;
5
+ apiKey: string;
6
+ /** Auth method. 'email' = Bounded Better Auth human login (email OTP, inline) —
7
+ * the default for most apps. 'phantom' = connect a Solana wallet (Phantom), the
8
+ * recommended wallet option (yields a real @user.address). 'guest' = zero-config
9
+ * anonymous (device keypair). All coexist; an app can offer email login AND Phantom
10
+ * AND `signInAnonymously()` side by side. */
11
+ authMethod: 'none' | 'email' | 'guest' | 'wallet' | 'rainbowkit' | 'coinbase-smart-wallet' | 'onboard' | 'phantom' | 'mobile-wallet-adapter' | 'privy' | 'privy-expo';
12
+ wsApiUrl: string;
13
+ apiUrl: string;
14
+ /**
15
+ * Base URL of the Bounded Functions dispatcher (the imperative escape hatch).
16
+ * Optional and ADDITIVE — when unset, bounded.functions.invoke() throws a
17
+ * clear "functions not configured" error. Defaults to the staging dispatcher.
18
+ */
19
+ functionsUrl?: string;
20
+ appId: string;
21
+ /** Wallet/SIWS issuer (wallet + guest providers sign challenges against this). */
22
+ authApiUrl: string;
23
+ /** Human-login issuer (Bounded Better Auth — email OTP). The 'email' provider
24
+ * calls {humanAuthApiUrl}/email + /verify. Defaults per network. */
25
+ humanAuthApiUrl?: string;
26
+ /**
27
+ * Selects a Bounded backend preset. When set, the endpoint defaults
28
+ * (apiUrl/wsApiUrl/authApiUrl/functionsUrl) resolve to that network's Bounded
29
+ * services instead of the legacy defaults — so a Bounded app needs only
30
+ * `{ appId, network }`. Anything you pass explicitly still wins. Unset =
31
+ * legacy behavior (unchanged for existing apps).
32
+ */
33
+ network?: 'bounded' | 'bounded-staging' | 'bounded-production';
34
+ chain: string;
35
+ rpcUrl: string;
36
+ skipBackendInit: boolean;
37
+ authProvider: AuthProvider | null;
38
+ isServer: boolean;
39
+ phantomConfig?: {
40
+ appId?: string;
41
+ providers?: Array<'injected' | 'google' | 'apple' | 'phantom' | 'deeplink'>;
42
+ redirectUrl?: string;
43
+ autoConnect?: boolean;
44
+ /** Theme for the login modal: 'light' or 'dark'. Defaults to 'dark'. */
45
+ theme?: 'light' | 'dark';
46
+ appName?: string;
47
+ appIcon?: string;
48
+ /** Custom title shown at the top of the login modal. */
49
+ modalTitle?: string;
50
+ /** Custom subtitle shown below the title in the login modal. */
51
+ modalSubtitle?: string;
52
+ /** When true, the Phantom login modal offers a Privy (email/social) fallback
53
+ * route, hot-swapping to the Privy provider on demand. Web only. */
54
+ enablePrivyFallback?: boolean;
55
+ };
56
+ /** Privy (web) configuration — the @privy-io/react-auth app id + its config
57
+ * object. Used when authMethod is 'privy' (a co-equal login route alongside
58
+ * email/phantom/guest). The Privy embedded/external Solana wallet signs the
59
+ * standard SIWS challenge, so the session is minted via the same wallet path
60
+ * as Phantom — no Privy-specific backend verification required. */
61
+ privyConfig?: {
62
+ appId: string;
63
+ config: any;
64
+ };
65
+ /** React Native (Expo) Privy provider instance, bridged from the host app's
66
+ * <PrivyProvider> tree via setPrivyMethods(). Required when authMethod is
67
+ * 'privy-expo' because @privy-io/expo is hook-based and must be rendered by
68
+ * the RN component tree (see PrivyExpoProvider). */
69
+ privyExpoProvider?: AuthProvider;
70
+ mobileWalletConfig?: {
71
+ appIdentity?: {
72
+ name?: string;
73
+ uri?: string;
74
+ icon?: string;
75
+ };
76
+ cluster?: string;
77
+ theme?: 'light' | 'dark';
78
+ };
79
+ mockAuth?: boolean;
80
+ }
81
+ export declare let clientConfig: ClientConfig;
82
+ type BoundedEndpoints = Pick<ClientConfig, 'wsApiUrl' | 'apiUrl' | 'authApiUrl' | 'humanAuthApiUrl' | 'functionsUrl'>;
83
+ export declare const BOUNDED_NETWORKS: Record<string, BoundedEndpoints>;
84
+ /**
85
+ * The hosted `/.well-known/bounded-webhook-keys.json` URL for the configured
86
+ * Bounded network, or `undefined` when no Bounded network is set. Used by
87
+ * `verifyWebhook` so a staging receiver that did `init({ network:
88
+ * 'bounded-staging' })` verifies against the STAGING signing keys instead of
89
+ * the production default. Synchronous and non-blocking on purpose: a pure
90
+ * webhook receiver may never call `init()`, so we read the already-applied
91
+ * config rather than awaiting `getConfig()` (which blocks on initialization).
92
+ * Returns `undefined` when the network is unknown so the caller keeps its
93
+ * fail-closed production default (audit SDK LOW-7).
94
+ */
95
+ export declare function getWebhookKeysUrl(): string | undefined;
96
+ /**
97
+ * True when init() configured a Bounded network (the Cloudflare-native stack).
98
+ * Synchronous + non-blocking. Used to route reads/writes that behave differently
99
+ * on Bounded vs the legacy Tarobase backend (e.g. `count`/`aggregate`, which on
100
+ * Bounded must use the deterministic server aggregation, not the legacy AI query).
101
+ */
102
+ export declare function isBoundedNetwork(): boolean;
103
+ export declare function init(newConfig: Partial<ClientConfig>): Promise<void>;
104
+ export declare function getConfig(): Promise<ClientConfig>;
105
+ export {};
@@ -0,0 +1,24 @@
1
+ /** A server-resolved field operation. Pass as a field value in `set`/`setMany`. */
2
+ export type FieldOperation = {
3
+ operation: 'increment';
4
+ value: number;
5
+ } | {
6
+ operation: 'time';
7
+ value: 'now';
8
+ };
9
+ /**
10
+ * Atomically add `value` to a numeric field when the write commits, server-side.
11
+ * Use a negative `value` to decrement. The field starts from 0 if the document
12
+ * (or field) does not exist yet. Concurrent increments are race-free.
13
+ *
14
+ * await set("counters/likes", { n: increment(1) });
15
+ * await set("scores/p1", { points: increment(-5) });
16
+ */
17
+ export declare function increment(value: number): FieldOperation;
18
+ /**
19
+ * Stamp a field with the server's clock (Unix seconds) when the write commits —
20
+ * the trustworthy "when did this happen" you can't get from a client clock.
21
+ *
22
+ * await set("posts/p1", { createdAt: serverTimestamp() });
23
+ */
24
+ export declare function serverTimestamp(): FieldOperation;
@@ -0,0 +1,34 @@
1
+ export type InvokeOptions = {
2
+ /** Per-call timeout (ms) for the HTTP request to the dispatcher. */
3
+ timeoutMs?: number;
4
+ /** Extra headers (advanced/testing). */
5
+ headers?: Record<string, string>;
6
+ /**
7
+ * @internal Auth override. The server `WalletClient.invoke` sets this so the
8
+ * call authenticates as that wallet's session instead of the ambient
9
+ * `BOUNDED_PRIVATE_KEY` keypair (which would otherwise throw when unset). App
10
+ * code never sets this.
11
+ */
12
+ _overrides?: {
13
+ _getAuthHeaders?: () => Promise<Record<string, string>>;
14
+ };
15
+ };
16
+ export declare class FunctionInvokeError extends Error {
17
+ statusCode?: number | undefined;
18
+ details?: any | undefined;
19
+ constructor(message: string, statusCode?: number | undefined, details?: any | undefined);
20
+ }
21
+ /**
22
+ * Invoke a deployed Bounded Function by name. Returns the function's JSON.
23
+ *
24
+ * const res = await bounded.functions.invoke('syncStripe', { customerId });
25
+ *
26
+ * Throws FunctionInvokeError on 401 (not logged in / bad token), 403 (the
27
+ * function's auth policy rule denied this caller), 404 (unknown function),
28
+ * 503 (Workers for Platforms not configured), or any non-2xx / transport error.
29
+ */
30
+ export declare function invoke<T = any>(name: string, args?: Record<string, any>, opts?: InvokeOptions): Promise<T>;
31
+ /** The `bounded.functions` namespace surface. */
32
+ export declare const functions: {
33
+ invoke: typeof invoke;
34
+ };
@@ -0,0 +1,120 @@
1
+ /**
2
+ * An effect a tick may emit. Structured-clonable JSON ONLY — no functions/closures.
3
+ *
4
+ * `id` is your stable correlation/idempotency key (e.g. `"npc:7:turn-42"`). It is
5
+ * MANDATORY for charged kinds (ai/function/agent/onchain) so an in-sim retry of the
6
+ * SAME logical action reuses the SAME id and never double-charges. For the other
7
+ * kinds the platform assigns a deterministic `${tick}:${seq}` fallback.
8
+ *
9
+ * `onBehalfOf` runs the effect AS that player (their auth/rules downstream). It must
10
+ * be a player whose intent you processed in the SAME tick (no privilege escalation);
11
+ * omit it to run as the game's own service principal.
12
+ */
13
+ export type Effect = {
14
+ kind: "ai";
15
+ id: string;
16
+ model: string;
17
+ input: unknown;
18
+ onBehalfOf?: string;
19
+ } | {
20
+ kind: "function";
21
+ id: string;
22
+ name: string;
23
+ args?: Record<string, unknown>;
24
+ onBehalfOf?: string;
25
+ } | {
26
+ kind: "agent";
27
+ id: string;
28
+ agent: string;
29
+ message: unknown;
30
+ onBehalfOf?: string;
31
+ } | {
32
+ kind: "http";
33
+ id?: string;
34
+ url: string;
35
+ method?: string;
36
+ headers?: Record<string, string>;
37
+ body?: unknown;
38
+ /** Secret NAME(s) only — the value is injected host-side on egress, never in your code. */
39
+ secret?: string | string[];
40
+ onBehalfOf?: string;
41
+ } | {
42
+ kind: "onchain";
43
+ id: string;
44
+ collection: string;
45
+ op: "set" | "delete";
46
+ data?: Record<string, unknown>;
47
+ onBehalfOf?: string;
48
+ /** Require the player to co-sign the transaction. */
49
+ cosign?: boolean;
50
+ } | {
51
+ kind: "data";
52
+ id?: string;
53
+ op: "set" | "delete";
54
+ path: string;
55
+ document?: Record<string, unknown>;
56
+ onBehalfOf?: string;
57
+ } | {
58
+ kind: "schedule";
59
+ id?: string;
60
+ name: string;
61
+ atMs?: number;
62
+ everyMs?: number;
63
+ };
64
+ export type EffectKind = Effect["kind"];
65
+ /** Reserved intent address an effect result arrives on. */
66
+ export declare const EFFECT_INTENT_ADDRESS: "@effect";
67
+ /**
68
+ * The result of an effect, re-entered into your tick as an intent on the reserved
69
+ * `@effect` address. Match `effectId` to the `id` you emitted; read `result` iff
70
+ * `ok`, else `error` (an enumerated code).
71
+ */
72
+ export interface EffectResult<T = unknown> {
73
+ __effect: true;
74
+ effectId: string;
75
+ ok: boolean;
76
+ result?: T;
77
+ error?: string;
78
+ }
79
+ /** A per-tick input: a player intent, or an `@effect` result the platform re-injected. */
80
+ export interface LiveIntent {
81
+ address: string;
82
+ intent: unknown;
83
+ }
84
+ /**
85
+ * The tick may return the bare next state, OR `{ state, effects }` to also emit
86
+ * effects this tick. Use {@link withEffects} to build the wrapper unambiguously.
87
+ */
88
+ export type LiveTickResult<S> = S | {
89
+ state: S;
90
+ effects?: Effect[];
91
+ };
92
+ /** A Bounded live module (game/realtime-room logic) that can emit effects. */
93
+ export interface LiveModule<S = unknown> {
94
+ init(seed: unknown): S;
95
+ tick(state: S, intents: LiveIntent[], dt: number): LiveTickResult<S>;
96
+ views?(state: S): Record<string, unknown>;
97
+ }
98
+ /**
99
+ * Build the `{ state, effects }` tick return. Always include this (even with an
100
+ * empty list) when you intend the wrapper, so the runtime never has to guess
101
+ * whether your state object happens to look like the wrapper.
102
+ *
103
+ * return withEffects(next, [{ kind: "ai", id: `npc:${id}:${next.turn}`, model, input }]);
104
+ */
105
+ export declare function withEffects<S>(state: S, effects?: Effect[]): {
106
+ state: S;
107
+ effects: Effect[];
108
+ };
109
+ /**
110
+ * Narrow an incoming intent to an effect result inside your tick:
111
+ *
112
+ * for (const i of intents) {
113
+ * if (i.address === EFFECT_INTENT_ADDRESS && isEffectResult(i.intent)) {
114
+ * applyResult(state, i.intent); // i.intent.effectId / .ok / .result / .error
115
+ * } else { applyPlayerIntent(state, i); }
116
+ * }
117
+ */
118
+ export declare function isEffectResult<T = unknown>(intent: unknown): intent is EffectResult<T>;
119
+ /** Identity helper purely for type inference when authoring a module. */
120
+ export declare function defineLiveModule<S>(mod: LiveModule<S>): LiveModule<S>;
@@ -0,0 +1,61 @@
1
+ export type LiveIntentOptions = {
2
+ /** Per-call timeout (ms) for the HTTP request to the realtime worker. */
3
+ timeoutMs?: number;
4
+ /** Extra headers (advanced/testing). */
5
+ headers?: Record<string, string>;
6
+ /**
7
+ * Fire-and-forget over the room socket without awaiting a server ack — lowest
8
+ * latency, but a rejection (auth/policy deny, room gone) is NOT surfaced. Use
9
+ * for high-frequency, idempotent input (movement/aim). Default (false): the
10
+ * intent rides the socket but AWAITS the ack, so a denial throws — important
11
+ * for join/ready/leave and any intent whose failure the player must know about.
12
+ */
13
+ fireAndForget?: boolean;
14
+ };
15
+ export declare class LiveIntentError extends Error {
16
+ statusCode?: number | undefined;
17
+ details?: any | undefined;
18
+ constructor(message: string, statusCode?: number | undefined, details?: any | undefined);
19
+ }
20
+ /**
21
+ * Send a player intent to a running live room. Returns `{ ok: true }`.
22
+ *
23
+ * await bounded.live.intent('rooms/abc', { type: 'move', dir: 'up' });
24
+ *
25
+ * `roomPath` is the document path of the room (e.g. `rooms/abc`). The worker
26
+ * derives the room from this path and reads `intent` as the opaque per-tick
27
+ * input. The caller's address is taken from the attached session token.
28
+ *
29
+ * Throws LiveIntentError on 401 (not logged in / bad token), 404 (room has no
30
+ * live module), 503 (live runtime/module unavailable), or any non-2xx /
31
+ * transport error / timeout.
32
+ */
33
+ export declare function intent(roomPath: string, intent: unknown, opts?: LiveIntentOptions): Promise<{
34
+ ok: true;
35
+ }>;
36
+ export type SubscribeViewOptions = {
37
+ /** Address whose view to read. Defaults to the logged-in user's address. */
38
+ address?: string;
39
+ /** Called with the latest per-player view document. */
40
+ onData: (view: any) => void;
41
+ /** Called on subscription error. */
42
+ onError?: (error: any) => void;
43
+ };
44
+ /**
45
+ * Subscribe to YOUR per-player view of a room. Thin sugar over:
46
+ *
47
+ * subscribe('<roomPath>/view/<myAddress>', { onData, onError })
48
+ *
49
+ * The address defaults to the logged-in user's address (from the session
50
+ * token's claims); pass `opts.address` to override. Returns the unsubscribe
51
+ * function (a Promise<() => Promise<void>>, same as `subscribe`).
52
+ *
53
+ * Note: this is a browser-first helper (the WS subscription manager is
54
+ * browser-oriented). Server consumers should use `live.intent`.
55
+ */
56
+ export declare function subscribeView(roomPath: string, opts: SubscribeViewOptions): Promise<() => Promise<void>>;
57
+ /** The `bounded.live` namespace surface. */
58
+ export declare const live: {
59
+ intent: typeof intent;
60
+ subscribeView: typeof subscribeView;
61
+ };
@@ -0,0 +1,264 @@
1
+ import { AuthProvider } from '../types';
2
+ import { PublicKey, Transaction, VersionedTransaction } from '@solana/web3.js';
3
+ /** Internal overrides for per-request auth and headers. */
4
+ export type RequestOverrides = {
5
+ headers?: Record<string, string>;
6
+ authProvider?: AuthProvider;
7
+ _getAuthHeaders?: () => Promise<Record<string, string>>;
8
+ _clearAuth?: () => Promise<void>;
9
+ _walletAddress?: string;
10
+ timeout?: number;
11
+ };
12
+ export type SetOptions = {
13
+ shouldSubmitTx?: boolean;
14
+ _overrides?: RequestOverrides;
15
+ };
16
+ /**
17
+ * Thrown when a user's wallet doesn't have enough SOL to cover the transaction.
18
+ * Apps can catch this to trigger an onramp/funding flow.
19
+ */
20
+ export declare class InsufficientBalanceError extends Error {
21
+ address: string;
22
+ balanceLamports: number;
23
+ estimatedCostLamports: number;
24
+ deficitLamports: number;
25
+ deficitSol: number;
26
+ constructor(address: string, balanceLamports: number, estimatedCostLamports: number, deficitLamports: number, deficitSol: number);
27
+ }
28
+ /**
29
+ * Options for the get function.
30
+ */
31
+ export type GetOptions = {
32
+ /**
33
+ * Structured MongoDB-style filter for collection reads, e.g.
34
+ * `{ status: "open", amount: { $gt: 10 } }`. Deterministic (no AI). Supported
35
+ * operators: $gt $gte $lt $lte $ne $in $nin $exists $regex ($options) $and $or
36
+ * $nor; a bare value means equality. Read rules still apply on top.
37
+ */
38
+ filter?: Record<string, any>;
39
+ /** Sort spec for collection reads, e.g. `{ createdAt: -1 }` (1 = asc, -1 = desc). */
40
+ sort?: Record<string, number>;
41
+ /** Natural language prompt for AI-powered queries (collections only) */
42
+ prompt?: string | undefined;
43
+ /** Bypass the local cache and fetch fresh data */
44
+ bypassCache?: boolean;
45
+ /** Include documents from sub-paths (nested collections) */
46
+ includeSubPaths?: boolean;
47
+ /** Shape object for relationship resolution - specifies which related documents to include */
48
+ shape?: Record<string, any>;
49
+ /** Maximum number of items to return (opt-in pagination) */
50
+ limit?: number;
51
+ /** Opaque cursor for cursor-based pagination (used with limit) */
52
+ cursor?: string;
53
+ _overrides?: RequestOverrides;
54
+ };
55
+ export type RunQueryOptions = {
56
+ _overrides?: RequestOverrides;
57
+ };
58
+ /**
59
+ * Supported aggregate operations for count/aggregate queries.
60
+ */
61
+ export type AggregateOperation = 'count' | 'uniqueCount' | 'sum' | 'avg' | 'min' | 'max';
62
+ /**
63
+ * Result of a count or aggregate query — always a single numeric value.
64
+ */
65
+ export type AggregateResult = {
66
+ value: number;
67
+ };
68
+ /**
69
+ * Options for the count function.
70
+ */
71
+ export type CountOptions = {
72
+ /** Natural language filter prompt (e.g., "posts created in the last 7 days"). Legacy backend only. */
73
+ prompt?: string;
74
+ /** Structured filter (same shape as `get`/`queryAggregate`). Preferred on Bounded. */
75
+ filter?: Record<string, any>;
76
+ _overrides?: RequestOverrides;
77
+ };
78
+ /**
79
+ * Options for the aggregate function.
80
+ */
81
+ export type AggregateOptions = {
82
+ /** Natural language filter prompt. Legacy backend only. */
83
+ prompt?: string;
84
+ /** Structured filter (same shape as `get`/`queryAggregate`). Preferred on Bounded. */
85
+ filter?: Record<string, any>;
86
+ /** Field name to aggregate on (required for sum, avg, min, max) */
87
+ field?: string;
88
+ _overrides?: RequestOverrides;
89
+ };
90
+ /**
91
+ * Count items in a collection path. Returns a numeric result.
92
+ *
93
+ * This uses the AI query engine with a count-specific prompt prefix,
94
+ * so TaroBase will generate a $count aggregation pipeline and return
95
+ * just the count rather than full documents.
96
+ *
97
+ * IMPORTANT: This only works for collections where the read policy is "true".
98
+ * If the read policy requires per-document checks, the server will return
99
+ * an error because aggregate counts cannot be performed without pulling all
100
+ * documents for access control evaluation.
101
+ *
102
+ * @param path - Collection path (e.g., "posts", "users/abc/comments")
103
+ * @param opts - Optional filter prompt and overrides
104
+ * @returns AggregateResult with the count value
105
+ */
106
+ export declare function count(path: string, opts?: CountOptions): Promise<AggregateResult>;
107
+ /**
108
+ * Run an aggregate operation on a collection path. Returns a numeric result.
109
+ *
110
+ * Supported operations:
111
+ * - count: Total number of documents
112
+ * - uniqueCount: Number of distinct values for a field
113
+ * - sum: Sum of a numeric field
114
+ * - avg: Average of a numeric field
115
+ * - min: Minimum value of a numeric field
116
+ * - max: Maximum value of a numeric field
117
+ *
118
+ * IMPORTANT: This only works for collections where the read policy is "true".
119
+ * If the read policy requires per-document checks, the server will return
120
+ * an error because aggregate operations cannot be performed without pulling
121
+ * all documents for access control evaluation.
122
+ *
123
+ * @param path - Collection path (e.g., "posts", "users/abc/comments")
124
+ * @param operation - The aggregate operation to perform
125
+ * @param opts - Options including optional filter prompt and field name
126
+ * @returns AggregateResult with the computed numeric value
127
+ */
128
+ export declare function aggregate(path: string, operation: AggregateOperation, opts?: AggregateOptions): Promise<AggregateResult>;
129
+ /**
130
+ * Structured aggregation spec — group rows and compute count/sum/avg/min/max.
131
+ * Unlike `aggregate` (a single scalar via AI prompt), this runs deterministically
132
+ * server-side and can return MULTIPLE grouped rows.
133
+ */
134
+ export type AggregateSpec = {
135
+ /** Group rows by these field values (omit for a single overall row). */
136
+ groupBy?: string[];
137
+ /** Include the document count per group. */
138
+ count?: boolean;
139
+ /** Sum these numeric fields per group. */
140
+ sum?: string[];
141
+ /** Average these numeric fields per group. */
142
+ avg?: string[];
143
+ /** Minimum of these fields per group. */
144
+ min?: string[];
145
+ /** Maximum of these fields per group. */
146
+ max?: string[];
147
+ };
148
+ /** One row of a `queryAggregate` result. */
149
+ export type AggregateRow = {
150
+ group?: Record<string, any>;
151
+ count?: number;
152
+ sum?: Record<string, number>;
153
+ avg?: Record<string, number | null>;
154
+ min?: Record<string, any>;
155
+ max?: Record<string, any>;
156
+ };
157
+ /** Options for `queryAggregate`. */
158
+ export type QueryAggregateOptions = {
159
+ /** Structured MongoDB-style filter applied before grouping (same shape as GetOptions.filter). */
160
+ filter?: Record<string, any>;
161
+ _overrides?: RequestOverrides;
162
+ };
163
+ /**
164
+ * Structured, grouped aggregation over a collection. Returns one row per group
165
+ * (or a single row when `spec.groupBy` is omitted). Read rules are enforced — the
166
+ * aggregation only sees documents the caller can read.
167
+ *
168
+ * ```ts
169
+ * const byCat = await queryAggregate("spend", { groupBy: ["category"], count: true, sum: ["amount"] });
170
+ * // [{ group: { category: "food" }, count: 2, sum: { amount: 70 } }, ...]
171
+ * ```
172
+ */
173
+ export declare function queryAggregate(path: string, spec: AggregateSpec, opts?: QueryAggregateOptions): Promise<AggregateRow[]>;
174
+ /**
175
+ * Options for the full-text `search` function.
176
+ */
177
+ export type SearchOptions = {
178
+ /** Restrict the match to these declared search fields (default: all indexed fields). */
179
+ fields?: string[];
180
+ /** Maximum number of matches to return. */
181
+ limit?: number;
182
+ /** Opaque pagination cursor from a prior page. */
183
+ cursor?: string;
184
+ _overrides?: RequestOverrides;
185
+ };
186
+ /**
187
+ * Full-text search a collection declared with `search: { fields: [...] }`.
188
+ *
189
+ * The match runs over the collection's indexed fields (or the subset passed in
190
+ * `opts.fields`) and respects each document's `read` rule — results the caller
191
+ * cannot read are omitted. Returns the matching documents (optionally paged via
192
+ * `opts.limit`/`opts.cursor`).
193
+ *
194
+ * @param path Collection path, e.g. "orgs/o1/docs"
195
+ * @param query Free-text query string (non-empty)
196
+ */
197
+ export declare function search(path: string, query: string, opts?: SearchOptions): Promise<any>;
198
+ export declare function get(path: string, opts?: GetOptions): Promise<any>;
199
+ export type GetManyResult = {
200
+ path: string;
201
+ data: any | null;
202
+ error?: {
203
+ code: 'NOT_FOUND' | 'UNAUTHORIZED' | 'INVALID_PATH';
204
+ message: string;
205
+ };
206
+ };
207
+ export declare function getMany(paths: string[], opts?: {
208
+ bypassCache?: boolean;
209
+ _overrides?: {
210
+ headers?: Record<string, string>;
211
+ };
212
+ }): Promise<GetManyResult[]>;
213
+ export type RunExpressionOptions = {
214
+ returnType?: 'Bool' | 'String' | 'Int' | 'UInt';
215
+ _overrides?: RequestOverrides;
216
+ };
217
+ export type RunExpressionResult = {
218
+ result: any;
219
+ trace?: {
220
+ variable: string;
221
+ resolvedValue: any;
222
+ operation?: string;
223
+ result?: any;
224
+ }[];
225
+ };
226
+ export declare function runQuery(absolutePath: string, queryName: string, queryArgs: any, opts?: RunQueryOptions): Promise<any>;
227
+ export declare function runQueryMany(many: {
228
+ absolutePath: string;
229
+ queryName: string;
230
+ queryArgs: any;
231
+ }[], opts?: RunQueryOptions): Promise<any>;
232
+ export declare function runExpression(expression: string, queryArgs: any, options?: RunExpressionOptions): Promise<RunExpressionResult>;
233
+ export declare function runExpressionMany(many: {
234
+ expression: string;
235
+ queryArgs: any;
236
+ returnType?: 'Bool' | 'String' | 'Int' | 'UInt';
237
+ _overrides?: RequestOverrides;
238
+ }[]): Promise<RunExpressionResult[]>;
239
+ export declare function set(path: string, document: any, options?: SetOptions): Promise<any>;
240
+ export declare function setMany(many: {
241
+ path: string;
242
+ document: any;
243
+ }[], options?: SetOptions): Promise<any>;
244
+ export declare function clearCache(path?: string, opts?: {
245
+ prompt?: string;
246
+ }): void;
247
+ /**
248
+ * SECURITY (H1): Wipe ALL HTTP read caches + in-flight reads. Call this whenever
249
+ * the logged-in identity changes (login / logout / switch identity) so a freshly
250
+ * authenticated principal can never observe data cached for the previous one.
251
+ * This is invoked from the WS auth-change path (reconnectWithNewAuthV2).
252
+ */
253
+ export declare function clearReadCacheForAuthChange(): void;
254
+ export declare function getFiles(path: string, options?: {
255
+ _overrides?: RequestOverrides;
256
+ }): Promise<any>;
257
+ export declare function setFile(path: string, file: File | null, options?: {
258
+ _overrides?: RequestOverrides;
259
+ metadata?: Record<string, any>;
260
+ }): Promise<boolean>;
261
+ export declare function signMessage(message: string): Promise<string>;
262
+ export declare function signTransaction(transaction: Transaction | VersionedTransaction): Promise<Transaction | VersionedTransaction>;
263
+ export declare function signAndSubmitTransaction(transaction: Transaction | VersionedTransaction, feePayer?: PublicKey): Promise<string>;
264
+ export declare function syncItems(paths: string[], options?: SetOptions): Promise<any>;