@bounded-sh/core 0.0.100-performance.1 → 0.0.101

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 (30) hide show
  1. package/dist/client/app-token.d.ts +77 -0
  2. package/dist/client/create-client.d.ts +69 -17
  3. package/dist/client/subscription-v2.d.ts +23 -0
  4. package/dist/{index-B8PpRj6x.js → index-BEGeDFQI.js} +739 -106
  5. package/dist/index-BEGeDFQI.js.map +1 -0
  6. package/dist/{index-DW5yMQwV.mjs → index-CMT0saxy.mjs} +736 -107
  7. package/dist/index-CMT0saxy.mjs.map +1 -0
  8. package/dist/index.d.ts +3 -1
  9. package/dist/index.js +5 -1
  10. package/dist/index.js.map +1 -1
  11. package/dist/index.mjs +1 -1
  12. package/dist/{realtime-store-CeDT7vJF.mjs → realtime-store-DqAAqfo9.mjs} +2 -2
  13. package/dist/{realtime-store-CeDT7vJF.mjs.map → realtime-store-DqAAqfo9.mjs.map} +1 -1
  14. package/dist/{realtime-store-D7mJ7rTj.js → realtime-store-DrtEj-bE.js} +2 -2
  15. package/dist/{realtime-store-D7mJ7rTj.js.map → realtime-store-DrtEj-bE.js.map} +1 -1
  16. package/dist/{solana-message-w2FxOkFA.js → solana-message-CJTnYiJu.js} +2 -2
  17. package/dist/{solana-message-w2FxOkFA.js.map → solana-message-CJTnYiJu.js.map} +1 -1
  18. package/dist/{solana-message-BGkYFU3C.mjs → solana-message-C_717vAx.mjs} +2 -2
  19. package/dist/{solana-message-BGkYFU3C.mjs.map → solana-message-C_717vAx.mjs.map} +1 -1
  20. package/dist/{solana-operations-DJQDYdrk.js → solana-operations-CU0YbiMu.js} +3 -3
  21. package/dist/{solana-operations-DJQDYdrk.js.map → solana-operations-CU0YbiMu.js.map} +1 -1
  22. package/dist/{solana-operations-CHgW7Pue.mjs → solana-operations-DGNA2nGZ.mjs} +4 -4
  23. package/dist/{solana-operations-CHgW7Pue.mjs.map → solana-operations-DGNA2nGZ.mjs.map} +1 -1
  24. package/dist/{solana-v1-operations-BPn-poee.mjs → solana-v1-operations-Bpd4TASG.mjs} +3 -3
  25. package/dist/{solana-v1-operations-BPn-poee.mjs.map → solana-v1-operations-Bpd4TASG.mjs.map} +1 -1
  26. package/dist/{solana-v1-operations-BF2rjG23.js → solana-v1-operations-yQII8r1s.js} +3 -3
  27. package/dist/{solana-v1-operations-BF2rjG23.js.map → solana-v1-operations-yQII8r1s.js.map} +1 -1
  28. package/package.json +2 -2
  29. package/dist/index-B8PpRj6x.js.map +0 -1
  30. package/dist/index-DW5yMQwV.mjs.map +0 -1
@@ -0,0 +1,77 @@
1
+ /** A minted child-app credential and the moment it stops being usable. */
2
+ export interface AppToken {
3
+ /** The bare id token to send as `Authorization: Bearer <token>`. */
4
+ token: string;
5
+ /** Epoch milliseconds at which the token expires. */
6
+ expiresAtMs: number;
7
+ /** The child app the token is pinned to. */
8
+ appId: string;
9
+ }
10
+ /** Why an exchange stopped trying. Recorded, never retried past this point. */
11
+ export interface AppTokenGiveUp {
12
+ appId: string;
13
+ /** HTTP status of the last attempt, or null for a transport failure. */
14
+ status: number | null;
15
+ /** Machine-readable reason: the issuer's `error` field where it sent one. */
16
+ reason: string;
17
+ attempts: number;
18
+ }
19
+ export interface ExchangeAppTokenOptions {
20
+ /**
21
+ * Origin of the Bounded Auth issuer that serves `/app-token/exchange` - the
22
+ * resolved `humanAuthApiUrl` for your network. A path suffix is tolerated.
23
+ */
24
+ authApiUrl: string;
25
+ /** The child app to mint a token for. */
26
+ appId: string;
27
+ /** The signed-in PARENT app's id token (e.g. the venue session's). */
28
+ parentToken: string;
29
+ /** HTTP attempts, including the first. Default 3, clamped to 1..5. */
30
+ maxAttempts?: number;
31
+ /** Called once when an exchange stops trying. Defaults to a console warning. */
32
+ onGiveUp?: (info: AppTokenGiveUp) => void;
33
+ /** Test seam; defaults to the global fetch. */
34
+ fetchImpl?: typeof fetch;
35
+ }
36
+ /** Thrown when an exchange stops trying. `giveUp` carries the recorded reason. */
37
+ export declare class AppTokenExchangeError extends Error {
38
+ readonly giveUp: AppTokenGiveUp;
39
+ constructor(giveUp: AppTokenGiveUp);
40
+ }
41
+ /**
42
+ * Get a child-app id token for the human the parent token belongs to, from cache
43
+ * when one is still comfortably fresh and otherwise by exchanging once.
44
+ *
45
+ * Concurrent callers for the same (parent credential, appId) share one exchange.
46
+ * On give-up this rejects with {@link AppTokenExchangeError} and caches nothing.
47
+ */
48
+ export declare function exchangeAppToken(options: ExchangeAppTokenOptions): Promise<AppToken>;
49
+ /**
50
+ * Drop cached child-app tokens. Call this on sign-out; an identity change needs
51
+ * no call, because the cache key is the exact parent credential.
52
+ *
53
+ * With no argument every entry goes. `appId` narrows to one child app, and
54
+ * `parentToken` narrows to one session's entries.
55
+ */
56
+ export declare function clearAppTokenCache(scope?: {
57
+ appId?: string;
58
+ parentToken?: string;
59
+ }): void;
60
+ export interface AppTokenAuthHeadersOptions extends Omit<ExchangeAppTokenOptions, 'parentToken'> {
61
+ /**
62
+ * The CURRENT parent-app credential, read on every call so a refreshed session
63
+ * is picked up and a signed-out one degrades the instance to anonymous.
64
+ * Return the bare id token, or null/undefined when signed out.
65
+ */
66
+ parentToken: () => string | null | undefined | Promise<string | null | undefined>;
67
+ }
68
+ /**
69
+ * Build the `getAuthHeaders` a scoped `createClient({ appId, getAuthHeaders })`
70
+ * instance authenticates with, backed by {@link exchangeAppToken}.
71
+ *
72
+ * Signed out (`parentToken()` yields nothing) returns NO headers, which leaves
73
+ * the scoped instance anonymous - the public read lane - instead of failing. A
74
+ * give-up on the exchange itself rejects, so a broken exchange surfaces as an
75
+ * error rather than as silently unauthenticated reads.
76
+ */
77
+ export declare function createAppTokenAuthHeaders(options: AppTokenAuthHeadersOptions): () => Promise<Record<string, string>>;
@@ -14,24 +14,58 @@
14
14
  * per-subscription override channel, plus the websocket layer already keying
15
15
  * connections by appId.
16
16
  *
17
- * ## v1 scope: read-only, unauthenticated
17
+ * ## Anonymous by default, authenticated with `getAuthHeaders`
18
18
  *
19
- * A scoped instance is UNAUTHENTICATED. It sends no auth, never reads or
20
- * refreshes the global session, and connects to its app's realtime endpoint
21
- * anonymously. It is therefore only useful against collections a Bounded app
22
- * exposes with a public read rule (`read: true`) - private/per-user reads will
23
- * be denied by the target app's policy, exactly as an anonymous caller would be.
19
+ * With no `getAuthHeaders`, a scoped instance is UNAUTHENTICATED. It sends no
20
+ * auth, never reads or refreshes the global session, and connects to its app's
21
+ * realtime endpoint anonymously. It is therefore only useful against collections
22
+ * a Bounded app exposes with a public read rule (`read: true`) - private/per-user
23
+ * reads will be denied by the target app's policy, exactly as an anonymous caller
24
+ * would be.
25
+ *
26
+ * Supply `getAuthHeaders` and the instance authenticates AS THAT CREDENTIAL, on
27
+ * both transports, with nothing shared with any other principal:
28
+ * - HTTP sends exactly the headers the provider returns, alongside this
29
+ * instance's `X-App-Id`. The provider is the SOLE auth source: the ambient
30
+ * global session is neither read nor refreshed nor cleared, and the read
31
+ * cache is keyed by the provider's own opaque credential.
32
+ * - The websocket gets its OWN connection, keyed by that credential
33
+ * (`principalKey`/`principalToken`), so it never shares a socket, an auth
34
+ * frame or cached data with the global client or with an anonymous scoped
35
+ * instance for the same appId.
36
+ * - No per-instance session is ever persisted. The global session store (which
37
+ * deletes a stored session whose appId differs from the global config's) is
38
+ * only reachable through the global session manager, and a per-instance
39
+ * provider never calls into it.
40
+ * - A provider that returns no credential leaves the instance ANONYMOUS for
41
+ * that call rather than failing it, so a signed-out page degrades to the
42
+ * public read lane. An already-authenticated socket is never silently
43
+ * downgraded: it keeps failing closed on a credential that stops resolving.
24
44
  *
25
45
  * Writes and any auth-requiring surface are NOT supported on a scoped instance
26
- * and throw a clear error. Per-instance authentication is a planned follow-up.
46
+ * and throw a clear error, with or without a credential.
27
47
  */
28
48
  import { type GetOptions, type SearchOptions, type CountOptions, type AggregateOptions, type QueryAggregateOptions, type RunQueryOptions, type AggregateOperation, type AggregateResult, type AggregateRow, type AggregateSpec, type GetManyResult, type RequestOverrides } from './operations';
29
49
  import { type ClientConfig } from './config';
30
50
  import type { SubscriptionOptions } from '../types';
51
+ /**
52
+ * Resolve the auth headers a scoped instance authenticates with. Called per
53
+ * request and per subscribe, so a rotated credential is picked up without
54
+ * recreating the client.
55
+ *
56
+ * Return `{ Authorization: 'Bearer <idToken>' }` for the app this instance is
57
+ * bound to - typically from `createAppTokenAuthHeaders`, which exchanges the
58
+ * signed-in parent app's session for a child-app token. Return `{}` (or null)
59
+ * when there is no credential: the instance stays anonymous for that call.
60
+ */
61
+ export type ScopedAuthHeadersProvider = () => Record<string, string> | null | undefined | Promise<Record<string, string> | null | undefined>;
31
62
  /** Config accepted by {@link createClient}. Only `appId` is required; `network`
32
- * (or explicit endpoint fields) selects the target Bounded backend. */
63
+ * (or explicit endpoint fields) selects the target Bounded backend. Supply
64
+ * `getAuthHeaders` to make the instance authenticated instead of anonymous. */
33
65
  export type CreateClientConfig = Partial<ClientConfig> & {
34
66
  appId: string;
67
+ /** Per-instance credential source. See {@link ScopedAuthHeadersProvider}. */
68
+ getAuthHeaders?: ScopedAuthHeadersProvider;
35
69
  };
36
70
  /**
37
71
  * A read-only client scoped to a single Bounded app, isolated from the global
@@ -40,6 +74,8 @@ export type CreateClientConfig = Partial<ClientConfig> & {
40
74
  export interface BoundedClient {
41
75
  /** The app id this instance is bound to. */
42
76
  readonly appId: string;
77
+ /** True when this instance was given a `getAuthHeaders` credential source. */
78
+ readonly authenticated: boolean;
43
79
  /** A snapshot of this instance's fully-resolved config (endpoints + appId). */
44
80
  getConfig(): ClientConfig;
45
81
  /** Read a document or collection. See the global `get`. */
@@ -68,36 +104,52 @@ export interface BoundedClient {
68
104
  }[], opts?: RunQueryOptions): Promise<any[]>;
69
105
  /**
70
106
  * Subscribe to realtime updates for a path on THIS app's realtime endpoint.
71
- * Anonymous (read-only): the collection must be publicly readable. Returns an
72
- * unsubscribe function. Subscriptions on this instance are fully independent
73
- * of the global client and of any other scoped instance.
107
+ * Authenticated when the instance has a credential (its own socket, keyed by
108
+ * that credential), anonymous otherwise - in which case the collection must be
109
+ * publicly readable. Returns an unsubscribe function. Subscriptions on this
110
+ * instance are fully independent of the global client and of any other scoped
111
+ * instance.
74
112
  */
75
113
  subscribe(path: string, options: SubscriptionOptions | ((data: any) => void)): Promise<() => Promise<void>>;
76
- /** Not supported on a read-only scoped instance - always throws {@link ScopedClientReadOnlyError}. */
114
+ /** Not supported on a scoped instance - always throws {@link ScopedClientReadOnlyError}. */
77
115
  set(path: string, document: any): Promise<never>;
78
- /** Not supported on a read-only scoped instance - always throws {@link ScopedClientReadOnlyError}. */
116
+ /** Not supported on a scoped instance - always throws {@link ScopedClientReadOnlyError}. */
79
117
  setMany(many: {
80
118
  path: string;
81
119
  document: any;
82
120
  }[]): Promise<never>;
83
121
  }
84
- /** Error thrown when an unsupported (write / auth-requiring) call is made on a
85
- * read-only scoped client. */
122
+ /** Error thrown when an unsupported (write) call is made on a scoped client.
123
+ * Reads may be authenticated per instance; writes still are not supported. */
86
124
  export declare class ScopedClientReadOnlyError extends Error {
87
125
  constructor(operation: string);
88
126
  }
89
127
  /**
90
- * Create a read-only client scoped to a single Bounded app.
128
+ * Create a read-only client scoped to a single Bounded app, anonymous by default
129
+ * and authenticated when given `getAuthHeaders`.
91
130
  *
92
131
  * @example
93
132
  * ```ts
94
133
  * // Global client stays the venue's own authenticated app:
95
134
  * await init({ appId: VENUE_APP, network: 'bounded-production', authMethod: 'email' });
96
135
  *
97
- * // A second, read-only client for the steward runtime app:
136
+ * // A second, anonymous client for the steward runtime app's public collections:
98
137
  * const steward = createClient({ appId: STEWARD_APP, network: 'bounded-production' });
99
138
  * const state = await steward.get('runtime/current');
100
139
  * const stop = await steward.subscribe('runtime/current', (d) => render(d));
140
+ *
141
+ * // The same second app, read AS THE SIGNED-IN HUMAN: the venue session is
142
+ * // exchanged for a token pinned to the child app (see createAppTokenAuthHeaders).
143
+ * const mine = createClient({
144
+ * appId: ROOT_APP,
145
+ * network: 'bounded-production',
146
+ * getAuthHeaders: createAppTokenAuthHeaders({
147
+ * authApiUrl: HUMAN_AUTH_URL,
148
+ * appId: ROOT_APP,
149
+ * parentToken: () => getIdToken(),
150
+ * }),
151
+ * });
152
+ * const stopChat = await mine.subscribe(`conversations/${id}/m`, (d) => render(d));
101
153
  * ```
102
154
  */
103
155
  export declare function createClient(config: CreateClientConfig): BoundedClient;
@@ -5,6 +5,29 @@
5
5
  * It maintains the same external API as v1 but uses the new v2 protocol internally.
6
6
  */
7
7
  import { SubscriptionOptions } from '../types';
8
+ /**
9
+ * Live subscriptions the SDK puts on ONE socket before opening another.
10
+ *
11
+ * The realtime worker refuses the 51st distinct subscribe on a connection with
12
+ * `{ code: 'subscription_limit', status: 429 }` - see
13
+ * `packages/cdk/cloudflare/realtime-worker/src/subscriptions.ts`
14
+ * (`attachment.subscriptions.length >= 50`). The cap is structural there: every
15
+ * subscription spec has to fit the 16KB Durable Object hibernation attachment,
16
+ * and each write fans out across the whole set of one socket.
17
+ *
18
+ * 45 leaves deliberate headroom under that 50: two concurrent subscribeV2 calls
19
+ * can both see room and overshoot by a frame or two, and another tab may already
20
+ * hold subscriptions on the same DO. A refusal that happens anyway is treated as
21
+ * backpressure, not a failure (see rehomeRefusedSubscription).
22
+ */
23
+ export declare const SHARD_CAP = 45;
24
+ /**
25
+ * Sockets one routing target (app, room, or wallet identity) may hold. Past this
26
+ * a subscribe fails loudly with `subscription_limit` instead of opening
27
+ * connections without end - 8 * 45 = 360 concurrent live subscriptions per
28
+ * target, far beyond any legitimate UI.
29
+ */
30
+ export declare const MAX_SHARDS_PER_KEY = 8;
8
31
  /**
9
32
  * Subscribe to data at a path using WebSocket v2.
10
33
  * Returns an unsubscribe function.