@spacelr/sdk 0.9.2 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +83 -19
- package/dist/index.d.ts +83 -19
- package/dist/index.js +183 -33
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +183 -33
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -516,6 +516,28 @@ declare class SpacelrEmailVerificationRequiredError extends SpacelrError {
|
|
|
516
516
|
}
|
|
517
517
|
|
|
518
518
|
type ConnectionState = 'connected' | 'reconnecting' | 'disconnected';
|
|
519
|
+
/** A single primitive `where` candidate. */
|
|
520
|
+
type WhereScalar = string | number | boolean;
|
|
521
|
+
/**
|
|
522
|
+
* Multi-value `where` operators (Firestore-style) for realtime subscriptions:
|
|
523
|
+
* - `in`: the document field is a **scalar** equal to any candidate.
|
|
524
|
+
* - `array-contains-any`: the document field is an **array** sharing at least
|
|
525
|
+
* one element with the candidates.
|
|
526
|
+
*
|
|
527
|
+
* The single-value form (`where: { field: scalar }`) stays polymorphic — it
|
|
528
|
+
* matches a scalar field by equality OR an array field by element membership.
|
|
529
|
+
*
|
|
530
|
+
* Mirrors the server definition in `@spacelr-workspace/shared-types`
|
|
531
|
+
* (`realtime-where.ts`); the two must stay structurally identical.
|
|
532
|
+
*/
|
|
533
|
+
type WhereOperator = {
|
|
534
|
+
in: WhereScalar[];
|
|
535
|
+
} | {
|
|
536
|
+
'array-contains-any': WhereScalar[];
|
|
537
|
+
};
|
|
538
|
+
type WhereValue = WhereScalar | WhereOperator;
|
|
539
|
+
/** A realtime subscription `where` filter: field name → scalar or operator. */
|
|
540
|
+
type WhereFilter = Record<string, WhereValue>;
|
|
519
541
|
interface DatabaseChangeEvent {
|
|
520
542
|
type: 'insert' | 'update' | 'delete';
|
|
521
543
|
projectId: string;
|
|
@@ -553,7 +575,7 @@ interface StreamSubscriptionOptions {
|
|
|
553
575
|
projectId: string;
|
|
554
576
|
collectionName: string;
|
|
555
577
|
sinceId?: string;
|
|
556
|
-
where?:
|
|
578
|
+
where?: WhereFilter;
|
|
557
579
|
/**
|
|
558
580
|
* Called for each delivered event. Awaited — the cursor does NOT advance
|
|
559
581
|
* until this resolves. If the callback throws, the cursor is NOT advanced
|
|
@@ -575,6 +597,10 @@ declare class RealtimeClient {
|
|
|
575
597
|
private subscriptions;
|
|
576
598
|
private connecting;
|
|
577
599
|
private roomWhereMap;
|
|
600
|
+
private roomErrorMap;
|
|
601
|
+
private roomGeneration;
|
|
602
|
+
private subEpoch;
|
|
603
|
+
private resubscribingRooms;
|
|
578
604
|
private unsubscribeFromTokenRefreshed;
|
|
579
605
|
private onVisibilityChange;
|
|
580
606
|
private onOnline;
|
|
@@ -584,7 +610,9 @@ declare class RealtimeClient {
|
|
|
584
610
|
private connectionStateListeners;
|
|
585
611
|
private streamSubscriptions;
|
|
586
612
|
constructor(config: RealtimeConfig);
|
|
587
|
-
subscribe(projectId: string, collectionName: string, callback: (event: DatabaseChangeEvent) => void, onError?: (error: Error
|
|
613
|
+
subscribe(projectId: string, collectionName: string, callback: (event: DatabaseChangeEvent) => void, onError?: (error: Error & {
|
|
614
|
+
code?: string;
|
|
615
|
+
}) => void, where?: WhereFilter): Promise<() => void>;
|
|
588
616
|
/**
|
|
589
617
|
* Subscribe to a stream-mode collection using Redis Streams replay +
|
|
590
618
|
* cursor-based delivery. Parallel to `subscribe()` (which targets
|
|
@@ -676,6 +704,31 @@ declare class RealtimeClient {
|
|
|
676
704
|
private ensureWakeListeners;
|
|
677
705
|
private detachWakeListeners;
|
|
678
706
|
private resubscribeAll;
|
|
707
|
+
/**
|
|
708
|
+
* Parse a pubsub room key back into its projectId + collectionName.
|
|
709
|
+
* Room format: `db:{projectId}:{collectionName}` or that base plus `?filter`.
|
|
710
|
+
* Returns null for anything that isn't a well-formed `db:` room.
|
|
711
|
+
*/
|
|
712
|
+
private parseRoom;
|
|
713
|
+
/** Build an Error carrying the gateway's typed `errorCode` (if any) as `.code`. */
|
|
714
|
+
private toSubscribeError;
|
|
715
|
+
/** Call every subscriber's error handler registered for `room` (#661). */
|
|
716
|
+
private notifyRoomError;
|
|
717
|
+
/**
|
|
718
|
+
* Re-run the subscribe handshake for one already-tracked room. Shared by
|
|
719
|
+
* reconnect resubscription (`resubscribeAll`) and the #661
|
|
720
|
+
* `subscription-invalidated` handler.
|
|
721
|
+
*
|
|
722
|
+
* - Deduped per room via `resubscribingRooms` so the duplicate invalidation
|
|
723
|
+
* events the gateway emits (one per evicted room) collapse to one handshake.
|
|
724
|
+
* - Snapshots the room's generation; the ack acts only if it is still current,
|
|
725
|
+
* so a late ack cannot clobber an unsubscribe/re-subscribe that happened in
|
|
726
|
+
* the meantime — and a success that arrives after the room was removed emits
|
|
727
|
+
* a compensating `unsubscribe` so no ghost server room is left behind.
|
|
728
|
+
* - On a genuine denial the subscription is evicted and its stored `onError`
|
|
729
|
+
* is called (with the typed `.code`), instead of silently going quiet.
|
|
730
|
+
*/
|
|
731
|
+
private resubscribeRoom;
|
|
679
732
|
private emitSubscribeEvents;
|
|
680
733
|
private buildStreamPayload;
|
|
681
734
|
private unsubscribeStream;
|
|
@@ -1089,16 +1142,18 @@ interface SubscribeWithSnapshotOptions<T> {
|
|
|
1089
1142
|
* **Snapshot semantics** (full MongoDB query): supports operators like
|
|
1090
1143
|
* `{ status: { $in: [...] } }`, `{ ts: { $gt: ... } }`, etc.
|
|
1091
1144
|
*
|
|
1092
|
-
* **Live-stream semantics** (
|
|
1093
|
-
*
|
|
1094
|
-
*
|
|
1095
|
-
*
|
|
1096
|
-
*
|
|
1097
|
-
*
|
|
1098
|
-
*
|
|
1099
|
-
*
|
|
1100
|
-
*
|
|
1101
|
-
*
|
|
1145
|
+
* **Live-stream semantics** (top-level primitives only): this helper
|
|
1146
|
+
* forwards only top-level primitive values (`string | number | boolean`)
|
|
1147
|
+
* from the Mongo-query `where` to the live filter. MongoDB operators
|
|
1148
|
+
* (`$in`, `$gt`, …) are silently dropped at handshake time and are NOT
|
|
1149
|
+
* translated into the gateway's realtime `in` / `array-contains-any`
|
|
1150
|
+
* operators — the remaining primitive entries still apply, so a typical
|
|
1151
|
+
* chat-style `{ chatId: 'c1' }` filter works identically on both sides. If
|
|
1152
|
+
* you pass a mixed `{ chatId: 'c1', status: { $in: [...] } }`, the snapshot
|
|
1153
|
+
* is fully filtered but the live stream filters by `chatId` only; you may
|
|
1154
|
+
* receive `onChange` events for documents whose `status` is outside your
|
|
1155
|
+
* `$in` set. Re-check in the handler, or use `subscribeEvents()` directly
|
|
1156
|
+
* with a realtime `{ status: { in: [...] } }` operator for live filtering.
|
|
1102
1157
|
*/
|
|
1103
1158
|
where?: Record<string, unknown>;
|
|
1104
1159
|
sort?: Record<string, 1 | -1>;
|
|
@@ -1159,7 +1214,7 @@ interface SubscribeWithSnapshotOptions<T> {
|
|
|
1159
1214
|
interface SubscribeEventsHandlers<T = Record<string, unknown>> {
|
|
1160
1215
|
/** Cursor to resume from. Undefined = fresh subscription, deliver only new events. */
|
|
1161
1216
|
sinceId?: string;
|
|
1162
|
-
where?:
|
|
1217
|
+
where?: WhereFilter;
|
|
1163
1218
|
/**
|
|
1164
1219
|
* Optional resume-cursor store. When set, the SDK loads the previous cursor
|
|
1165
1220
|
* before subscribing and persists the new cursor after each delivered
|
|
@@ -1216,7 +1271,7 @@ interface StreamSubscription {
|
|
|
1216
1271
|
getCursor(): string | undefined;
|
|
1217
1272
|
}
|
|
1218
1273
|
interface SubscribeHandlers<T = Record<string, unknown>> {
|
|
1219
|
-
where?:
|
|
1274
|
+
where?: WhereFilter;
|
|
1220
1275
|
onInsert?: (doc: T & {
|
|
1221
1276
|
_id: string;
|
|
1222
1277
|
}) => void;
|
|
@@ -1546,6 +1601,11 @@ interface FunctionInvokeOptions {
|
|
|
1546
1601
|
*
|
|
1547
1602
|
* If `true` but the user is not signed in, the header is simply omitted —
|
|
1548
1603
|
* safe for `public` invokeMode.
|
|
1604
|
+
*
|
|
1605
|
+
* Note: for `authenticated`/`hybrid` invokeMode, the server additionally
|
|
1606
|
+
* requires the token's user to be a member of the target project — this
|
|
1607
|
+
* flag only controls whether the header is attached, not who the server
|
|
1608
|
+
* accepts.
|
|
1549
1609
|
*/
|
|
1550
1610
|
authenticated?: boolean;
|
|
1551
1611
|
payload?: Record<string, unknown>;
|
|
@@ -1564,10 +1624,14 @@ declare class FunctionsModule {
|
|
|
1564
1624
|
* `config.apiUrl` (which already carries the `/api/v1` prefix).
|
|
1565
1625
|
*
|
|
1566
1626
|
* Auth defaults, based on `invokeMode` semantics:
|
|
1567
|
-
* - webhook:
|
|
1568
|
-
* - authenticated:
|
|
1569
|
-
*
|
|
1570
|
-
* -
|
|
1627
|
+
* - webhook: pass `secret` → Authorization is NOT attached
|
|
1628
|
+
* - authenticated: pass nothing → Authorization IS attached (from token manager);
|
|
1629
|
+
* caller must be a member of the target project
|
|
1630
|
+
* - public: pass nothing → Authorization is attached if logged in, else omitted
|
|
1631
|
+
* - hybrid: pass both `secret` and `authenticated: true`; JWT path
|
|
1632
|
+
* requires project membership like `authenticated`
|
|
1633
|
+
* - platform-authenticated: pass nothing → Authorization IS attached (from token manager);
|
|
1634
|
+
* any signed-in user is accepted, still just attaches the bearer token
|
|
1571
1635
|
*
|
|
1572
1636
|
* To force a specific behaviour, set `authenticated` explicitly — it wins
|
|
1573
1637
|
* over the `secret`-based default.
|
|
@@ -1763,4 +1827,4 @@ interface SpacelrClient {
|
|
|
1763
1827
|
}
|
|
1764
1828
|
declare function createClient(config: SpacelrClientConfig): SpacelrClient;
|
|
1765
1829
|
|
|
1766
|
-
export { type ApiResponse, type AuthLostReason, type AuthState, type AuthStateListener, type AuthorizationUrlParams, BrowserTokenStorage, CodeChallengeMethod, type ConnectionState, CursorInvalidError, type CursorStorage, type DatabaseChangeEvent, type DownloadUrlResponse, type ExchangeCodeParams, type FileInfo, type FileListResponse, FileVisibility, ForbiddenError, type FunctionInvokeOptions, type FunctionInvokeResult, type GapReason, GrantType, type InitMultipartUploadParams, type InitMultipartUploadResponse, type JWK, type JWKSResponse, type ListFilesParams, type LoginParams, type LoginResponse, MemoryTokenStorage, NotFoundError, type OpenIDConfiguration, type PKCEChallenge, type PartEtag, type PasskeyAuthenticationOptionsJSON, type PasskeyAuthenticationResponseJSON, type PasskeyCredential, type PasskeyCredentialDescriptorJSON, type PasskeyLoginResponse, type PasskeyRegistrationOptionsJSON, type PasskeyRegistrationResponseJSON, type PasskeyRegistrationResult, type PushSubscriptionInfo, type QuotaInfo, type RegisterParams, type RegisterResponse, type Schedule, type ScheduleInvokeOptions, type ScheduleListOptions, type ScheduleStatus, type SearchOptions, ServerConfigError, type ShareFileParams, SharePermission, SpacelrAuthError, type SpacelrClient, type SpacelrClientConfig, SpacelrEmailVerificationRequiredError, SpacelrError, SpacelrNetworkError, SpacelrSearchFilterRequiredError, SpacelrTimeoutError, SpacelrTwoFactorRequiredError, type StoredTokens, type StreamGapInfo, type StreamSubscription, type SubscribeEventsHandlers, type SubscribeHandlers, type SubscribeWithSnapshotOptions, type TimelineAndFilter, TimelineError, type TimelineFieldFilter, type TimelineFilter, type TimelineLeafFilter, TimelineModule, type TimelineOrderBy, type TimelineQueryOptions, type TimelineQueryResponse, type TimelineScalar, type TimelineSourceStats, TimeoutError, type TokenResponse, type TokenStorage, type TwoFactorResponse, type TwoFactorVerifyParams, type UnshareFileParams, type UploadFileParams, type UploadLargeFileParams, type UploadProgress, type UserInfo, type UserProfile, ValidationError, type VapidKeyResponse, createClient, generatePKCEChallenge, localStorageCursorStorage, memoryCursorStorage };
|
|
1830
|
+
export { type ApiResponse, type AuthLostReason, type AuthState, type AuthStateListener, type AuthorizationUrlParams, BrowserTokenStorage, CodeChallengeMethod, type ConnectionState, CursorInvalidError, type CursorStorage, type DatabaseChangeEvent, type DownloadUrlResponse, type ExchangeCodeParams, type FileInfo, type FileListResponse, FileVisibility, ForbiddenError, type FunctionInvokeOptions, type FunctionInvokeResult, type GapReason, GrantType, type InitMultipartUploadParams, type InitMultipartUploadResponse, type JWK, type JWKSResponse, type ListFilesParams, type LoginParams, type LoginResponse, MemoryTokenStorage, NotFoundError, type OpenIDConfiguration, type PKCEChallenge, type PartEtag, type PasskeyAuthenticationOptionsJSON, type PasskeyAuthenticationResponseJSON, type PasskeyCredential, type PasskeyCredentialDescriptorJSON, type PasskeyLoginResponse, type PasskeyRegistrationOptionsJSON, type PasskeyRegistrationResponseJSON, type PasskeyRegistrationResult, type PushSubscriptionInfo, type QuotaInfo, type RegisterParams, type RegisterResponse, type Schedule, type ScheduleInvokeOptions, type ScheduleListOptions, type ScheduleStatus, type SearchOptions, ServerConfigError, type ShareFileParams, SharePermission, SpacelrAuthError, type SpacelrClient, type SpacelrClientConfig, SpacelrEmailVerificationRequiredError, SpacelrError, SpacelrNetworkError, SpacelrSearchFilterRequiredError, SpacelrTimeoutError, SpacelrTwoFactorRequiredError, type StoredTokens, type StreamGapInfo, type StreamSubscription, type SubscribeEventsHandlers, type SubscribeHandlers, type SubscribeWithSnapshotOptions, type TimelineAndFilter, TimelineError, type TimelineFieldFilter, type TimelineFilter, type TimelineLeafFilter, TimelineModule, type TimelineOrderBy, type TimelineQueryOptions, type TimelineQueryResponse, type TimelineScalar, type TimelineSourceStats, TimeoutError, type TokenResponse, type TokenStorage, type TwoFactorResponse, type TwoFactorVerifyParams, type UnshareFileParams, type UploadFileParams, type UploadLargeFileParams, type UploadProgress, type UserInfo, type UserProfile, ValidationError, type VapidKeyResponse, type WhereFilter, type WhereOperator, type WhereScalar, type WhereValue, createClient, generatePKCEChallenge, localStorageCursorStorage, memoryCursorStorage };
|
package/dist/index.d.ts
CHANGED
|
@@ -516,6 +516,28 @@ declare class SpacelrEmailVerificationRequiredError extends SpacelrError {
|
|
|
516
516
|
}
|
|
517
517
|
|
|
518
518
|
type ConnectionState = 'connected' | 'reconnecting' | 'disconnected';
|
|
519
|
+
/** A single primitive `where` candidate. */
|
|
520
|
+
type WhereScalar = string | number | boolean;
|
|
521
|
+
/**
|
|
522
|
+
* Multi-value `where` operators (Firestore-style) for realtime subscriptions:
|
|
523
|
+
* - `in`: the document field is a **scalar** equal to any candidate.
|
|
524
|
+
* - `array-contains-any`: the document field is an **array** sharing at least
|
|
525
|
+
* one element with the candidates.
|
|
526
|
+
*
|
|
527
|
+
* The single-value form (`where: { field: scalar }`) stays polymorphic — it
|
|
528
|
+
* matches a scalar field by equality OR an array field by element membership.
|
|
529
|
+
*
|
|
530
|
+
* Mirrors the server definition in `@spacelr-workspace/shared-types`
|
|
531
|
+
* (`realtime-where.ts`); the two must stay structurally identical.
|
|
532
|
+
*/
|
|
533
|
+
type WhereOperator = {
|
|
534
|
+
in: WhereScalar[];
|
|
535
|
+
} | {
|
|
536
|
+
'array-contains-any': WhereScalar[];
|
|
537
|
+
};
|
|
538
|
+
type WhereValue = WhereScalar | WhereOperator;
|
|
539
|
+
/** A realtime subscription `where` filter: field name → scalar or operator. */
|
|
540
|
+
type WhereFilter = Record<string, WhereValue>;
|
|
519
541
|
interface DatabaseChangeEvent {
|
|
520
542
|
type: 'insert' | 'update' | 'delete';
|
|
521
543
|
projectId: string;
|
|
@@ -553,7 +575,7 @@ interface StreamSubscriptionOptions {
|
|
|
553
575
|
projectId: string;
|
|
554
576
|
collectionName: string;
|
|
555
577
|
sinceId?: string;
|
|
556
|
-
where?:
|
|
578
|
+
where?: WhereFilter;
|
|
557
579
|
/**
|
|
558
580
|
* Called for each delivered event. Awaited — the cursor does NOT advance
|
|
559
581
|
* until this resolves. If the callback throws, the cursor is NOT advanced
|
|
@@ -575,6 +597,10 @@ declare class RealtimeClient {
|
|
|
575
597
|
private subscriptions;
|
|
576
598
|
private connecting;
|
|
577
599
|
private roomWhereMap;
|
|
600
|
+
private roomErrorMap;
|
|
601
|
+
private roomGeneration;
|
|
602
|
+
private subEpoch;
|
|
603
|
+
private resubscribingRooms;
|
|
578
604
|
private unsubscribeFromTokenRefreshed;
|
|
579
605
|
private onVisibilityChange;
|
|
580
606
|
private onOnline;
|
|
@@ -584,7 +610,9 @@ declare class RealtimeClient {
|
|
|
584
610
|
private connectionStateListeners;
|
|
585
611
|
private streamSubscriptions;
|
|
586
612
|
constructor(config: RealtimeConfig);
|
|
587
|
-
subscribe(projectId: string, collectionName: string, callback: (event: DatabaseChangeEvent) => void, onError?: (error: Error
|
|
613
|
+
subscribe(projectId: string, collectionName: string, callback: (event: DatabaseChangeEvent) => void, onError?: (error: Error & {
|
|
614
|
+
code?: string;
|
|
615
|
+
}) => void, where?: WhereFilter): Promise<() => void>;
|
|
588
616
|
/**
|
|
589
617
|
* Subscribe to a stream-mode collection using Redis Streams replay +
|
|
590
618
|
* cursor-based delivery. Parallel to `subscribe()` (which targets
|
|
@@ -676,6 +704,31 @@ declare class RealtimeClient {
|
|
|
676
704
|
private ensureWakeListeners;
|
|
677
705
|
private detachWakeListeners;
|
|
678
706
|
private resubscribeAll;
|
|
707
|
+
/**
|
|
708
|
+
* Parse a pubsub room key back into its projectId + collectionName.
|
|
709
|
+
* Room format: `db:{projectId}:{collectionName}` or that base plus `?filter`.
|
|
710
|
+
* Returns null for anything that isn't a well-formed `db:` room.
|
|
711
|
+
*/
|
|
712
|
+
private parseRoom;
|
|
713
|
+
/** Build an Error carrying the gateway's typed `errorCode` (if any) as `.code`. */
|
|
714
|
+
private toSubscribeError;
|
|
715
|
+
/** Call every subscriber's error handler registered for `room` (#661). */
|
|
716
|
+
private notifyRoomError;
|
|
717
|
+
/**
|
|
718
|
+
* Re-run the subscribe handshake for one already-tracked room. Shared by
|
|
719
|
+
* reconnect resubscription (`resubscribeAll`) and the #661
|
|
720
|
+
* `subscription-invalidated` handler.
|
|
721
|
+
*
|
|
722
|
+
* - Deduped per room via `resubscribingRooms` so the duplicate invalidation
|
|
723
|
+
* events the gateway emits (one per evicted room) collapse to one handshake.
|
|
724
|
+
* - Snapshots the room's generation; the ack acts only if it is still current,
|
|
725
|
+
* so a late ack cannot clobber an unsubscribe/re-subscribe that happened in
|
|
726
|
+
* the meantime — and a success that arrives after the room was removed emits
|
|
727
|
+
* a compensating `unsubscribe` so no ghost server room is left behind.
|
|
728
|
+
* - On a genuine denial the subscription is evicted and its stored `onError`
|
|
729
|
+
* is called (with the typed `.code`), instead of silently going quiet.
|
|
730
|
+
*/
|
|
731
|
+
private resubscribeRoom;
|
|
679
732
|
private emitSubscribeEvents;
|
|
680
733
|
private buildStreamPayload;
|
|
681
734
|
private unsubscribeStream;
|
|
@@ -1089,16 +1142,18 @@ interface SubscribeWithSnapshotOptions<T> {
|
|
|
1089
1142
|
* **Snapshot semantics** (full MongoDB query): supports operators like
|
|
1090
1143
|
* `{ status: { $in: [...] } }`, `{ ts: { $gt: ... } }`, etc.
|
|
1091
1144
|
*
|
|
1092
|
-
* **Live-stream semantics** (
|
|
1093
|
-
*
|
|
1094
|
-
*
|
|
1095
|
-
*
|
|
1096
|
-
*
|
|
1097
|
-
*
|
|
1098
|
-
*
|
|
1099
|
-
*
|
|
1100
|
-
*
|
|
1101
|
-
*
|
|
1145
|
+
* **Live-stream semantics** (top-level primitives only): this helper
|
|
1146
|
+
* forwards only top-level primitive values (`string | number | boolean`)
|
|
1147
|
+
* from the Mongo-query `where` to the live filter. MongoDB operators
|
|
1148
|
+
* (`$in`, `$gt`, …) are silently dropped at handshake time and are NOT
|
|
1149
|
+
* translated into the gateway's realtime `in` / `array-contains-any`
|
|
1150
|
+
* operators — the remaining primitive entries still apply, so a typical
|
|
1151
|
+
* chat-style `{ chatId: 'c1' }` filter works identically on both sides. If
|
|
1152
|
+
* you pass a mixed `{ chatId: 'c1', status: { $in: [...] } }`, the snapshot
|
|
1153
|
+
* is fully filtered but the live stream filters by `chatId` only; you may
|
|
1154
|
+
* receive `onChange` events for documents whose `status` is outside your
|
|
1155
|
+
* `$in` set. Re-check in the handler, or use `subscribeEvents()` directly
|
|
1156
|
+
* with a realtime `{ status: { in: [...] } }` operator for live filtering.
|
|
1102
1157
|
*/
|
|
1103
1158
|
where?: Record<string, unknown>;
|
|
1104
1159
|
sort?: Record<string, 1 | -1>;
|
|
@@ -1159,7 +1214,7 @@ interface SubscribeWithSnapshotOptions<T> {
|
|
|
1159
1214
|
interface SubscribeEventsHandlers<T = Record<string, unknown>> {
|
|
1160
1215
|
/** Cursor to resume from. Undefined = fresh subscription, deliver only new events. */
|
|
1161
1216
|
sinceId?: string;
|
|
1162
|
-
where?:
|
|
1217
|
+
where?: WhereFilter;
|
|
1163
1218
|
/**
|
|
1164
1219
|
* Optional resume-cursor store. When set, the SDK loads the previous cursor
|
|
1165
1220
|
* before subscribing and persists the new cursor after each delivered
|
|
@@ -1216,7 +1271,7 @@ interface StreamSubscription {
|
|
|
1216
1271
|
getCursor(): string | undefined;
|
|
1217
1272
|
}
|
|
1218
1273
|
interface SubscribeHandlers<T = Record<string, unknown>> {
|
|
1219
|
-
where?:
|
|
1274
|
+
where?: WhereFilter;
|
|
1220
1275
|
onInsert?: (doc: T & {
|
|
1221
1276
|
_id: string;
|
|
1222
1277
|
}) => void;
|
|
@@ -1546,6 +1601,11 @@ interface FunctionInvokeOptions {
|
|
|
1546
1601
|
*
|
|
1547
1602
|
* If `true` but the user is not signed in, the header is simply omitted —
|
|
1548
1603
|
* safe for `public` invokeMode.
|
|
1604
|
+
*
|
|
1605
|
+
* Note: for `authenticated`/`hybrid` invokeMode, the server additionally
|
|
1606
|
+
* requires the token's user to be a member of the target project — this
|
|
1607
|
+
* flag only controls whether the header is attached, not who the server
|
|
1608
|
+
* accepts.
|
|
1549
1609
|
*/
|
|
1550
1610
|
authenticated?: boolean;
|
|
1551
1611
|
payload?: Record<string, unknown>;
|
|
@@ -1564,10 +1624,14 @@ declare class FunctionsModule {
|
|
|
1564
1624
|
* `config.apiUrl` (which already carries the `/api/v1` prefix).
|
|
1565
1625
|
*
|
|
1566
1626
|
* Auth defaults, based on `invokeMode` semantics:
|
|
1567
|
-
* - webhook:
|
|
1568
|
-
* - authenticated:
|
|
1569
|
-
*
|
|
1570
|
-
* -
|
|
1627
|
+
* - webhook: pass `secret` → Authorization is NOT attached
|
|
1628
|
+
* - authenticated: pass nothing → Authorization IS attached (from token manager);
|
|
1629
|
+
* caller must be a member of the target project
|
|
1630
|
+
* - public: pass nothing → Authorization is attached if logged in, else omitted
|
|
1631
|
+
* - hybrid: pass both `secret` and `authenticated: true`; JWT path
|
|
1632
|
+
* requires project membership like `authenticated`
|
|
1633
|
+
* - platform-authenticated: pass nothing → Authorization IS attached (from token manager);
|
|
1634
|
+
* any signed-in user is accepted, still just attaches the bearer token
|
|
1571
1635
|
*
|
|
1572
1636
|
* To force a specific behaviour, set `authenticated` explicitly — it wins
|
|
1573
1637
|
* over the `secret`-based default.
|
|
@@ -1763,4 +1827,4 @@ interface SpacelrClient {
|
|
|
1763
1827
|
}
|
|
1764
1828
|
declare function createClient(config: SpacelrClientConfig): SpacelrClient;
|
|
1765
1829
|
|
|
1766
|
-
export { type ApiResponse, type AuthLostReason, type AuthState, type AuthStateListener, type AuthorizationUrlParams, BrowserTokenStorage, CodeChallengeMethod, type ConnectionState, CursorInvalidError, type CursorStorage, type DatabaseChangeEvent, type DownloadUrlResponse, type ExchangeCodeParams, type FileInfo, type FileListResponse, FileVisibility, ForbiddenError, type FunctionInvokeOptions, type FunctionInvokeResult, type GapReason, GrantType, type InitMultipartUploadParams, type InitMultipartUploadResponse, type JWK, type JWKSResponse, type ListFilesParams, type LoginParams, type LoginResponse, MemoryTokenStorage, NotFoundError, type OpenIDConfiguration, type PKCEChallenge, type PartEtag, type PasskeyAuthenticationOptionsJSON, type PasskeyAuthenticationResponseJSON, type PasskeyCredential, type PasskeyCredentialDescriptorJSON, type PasskeyLoginResponse, type PasskeyRegistrationOptionsJSON, type PasskeyRegistrationResponseJSON, type PasskeyRegistrationResult, type PushSubscriptionInfo, type QuotaInfo, type RegisterParams, type RegisterResponse, type Schedule, type ScheduleInvokeOptions, type ScheduleListOptions, type ScheduleStatus, type SearchOptions, ServerConfigError, type ShareFileParams, SharePermission, SpacelrAuthError, type SpacelrClient, type SpacelrClientConfig, SpacelrEmailVerificationRequiredError, SpacelrError, SpacelrNetworkError, SpacelrSearchFilterRequiredError, SpacelrTimeoutError, SpacelrTwoFactorRequiredError, type StoredTokens, type StreamGapInfo, type StreamSubscription, type SubscribeEventsHandlers, type SubscribeHandlers, type SubscribeWithSnapshotOptions, type TimelineAndFilter, TimelineError, type TimelineFieldFilter, type TimelineFilter, type TimelineLeafFilter, TimelineModule, type TimelineOrderBy, type TimelineQueryOptions, type TimelineQueryResponse, type TimelineScalar, type TimelineSourceStats, TimeoutError, type TokenResponse, type TokenStorage, type TwoFactorResponse, type TwoFactorVerifyParams, type UnshareFileParams, type UploadFileParams, type UploadLargeFileParams, type UploadProgress, type UserInfo, type UserProfile, ValidationError, type VapidKeyResponse, createClient, generatePKCEChallenge, localStorageCursorStorage, memoryCursorStorage };
|
|
1830
|
+
export { type ApiResponse, type AuthLostReason, type AuthState, type AuthStateListener, type AuthorizationUrlParams, BrowserTokenStorage, CodeChallengeMethod, type ConnectionState, CursorInvalidError, type CursorStorage, type DatabaseChangeEvent, type DownloadUrlResponse, type ExchangeCodeParams, type FileInfo, type FileListResponse, FileVisibility, ForbiddenError, type FunctionInvokeOptions, type FunctionInvokeResult, type GapReason, GrantType, type InitMultipartUploadParams, type InitMultipartUploadResponse, type JWK, type JWKSResponse, type ListFilesParams, type LoginParams, type LoginResponse, MemoryTokenStorage, NotFoundError, type OpenIDConfiguration, type PKCEChallenge, type PartEtag, type PasskeyAuthenticationOptionsJSON, type PasskeyAuthenticationResponseJSON, type PasskeyCredential, type PasskeyCredentialDescriptorJSON, type PasskeyLoginResponse, type PasskeyRegistrationOptionsJSON, type PasskeyRegistrationResponseJSON, type PasskeyRegistrationResult, type PushSubscriptionInfo, type QuotaInfo, type RegisterParams, type RegisterResponse, type Schedule, type ScheduleInvokeOptions, type ScheduleListOptions, type ScheduleStatus, type SearchOptions, ServerConfigError, type ShareFileParams, SharePermission, SpacelrAuthError, type SpacelrClient, type SpacelrClientConfig, SpacelrEmailVerificationRequiredError, SpacelrError, SpacelrNetworkError, SpacelrSearchFilterRequiredError, SpacelrTimeoutError, SpacelrTwoFactorRequiredError, type StoredTokens, type StreamGapInfo, type StreamSubscription, type SubscribeEventsHandlers, type SubscribeHandlers, type SubscribeWithSnapshotOptions, type TimelineAndFilter, TimelineError, type TimelineFieldFilter, type TimelineFilter, type TimelineLeafFilter, TimelineModule, type TimelineOrderBy, type TimelineQueryOptions, type TimelineQueryResponse, type TimelineScalar, type TimelineSourceStats, TimeoutError, type TokenResponse, type TokenStorage, type TwoFactorResponse, type TwoFactorVerifyParams, type UnshareFileParams, type UploadFileParams, type UploadLargeFileParams, type UploadProgress, type UserInfo, type UserProfile, ValidationError, type VapidKeyResponse, type WhereFilter, type WhereOperator, type WhereScalar, type WhereValue, createClient, generatePKCEChallenge, localStorageCursorStorage, memoryCursorStorage };
|
package/dist/index.js
CHANGED
|
@@ -657,6 +657,57 @@ var PERMANENT_STREAM_ACK_ERRORS = /* @__PURE__ */ new Set([
|
|
|
657
657
|
"Not a member of this project",
|
|
658
658
|
"Subscribe denied"
|
|
659
659
|
]);
|
|
660
|
+
function isWhereScalar(value) {
|
|
661
|
+
const t = typeof value;
|
|
662
|
+
return t === "string" || t === "number" || t === "boolean";
|
|
663
|
+
}
|
|
664
|
+
function isWhereOperator(value) {
|
|
665
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
666
|
+
return false;
|
|
667
|
+
}
|
|
668
|
+
const keys = Object.keys(value);
|
|
669
|
+
if (keys.length !== 1) return false;
|
|
670
|
+
const op = keys[0];
|
|
671
|
+
if (op !== "in" && op !== "array-contains-any") return false;
|
|
672
|
+
const candidates = value[op];
|
|
673
|
+
return Array.isArray(candidates) && candidates.every(isWhereScalar);
|
|
674
|
+
}
|
|
675
|
+
var WHERE_PATH_ABSENT = /* @__PURE__ */ Symbol("where-path-absent");
|
|
676
|
+
var WHERE_PATH_UNRESOLVABLE = /* @__PURE__ */ Symbol("where-path-unresolvable");
|
|
677
|
+
var RESERVED_PATH_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
678
|
+
var MAX_WHERE_PATH_SEGMENTS = 16;
|
|
679
|
+
function resolveWherePath(document2, key) {
|
|
680
|
+
const segments = key.split(".");
|
|
681
|
+
if (segments.length > MAX_WHERE_PATH_SEGMENTS) return WHERE_PATH_ABSENT;
|
|
682
|
+
let cur = document2;
|
|
683
|
+
for (const segment of segments) {
|
|
684
|
+
if (RESERVED_PATH_SEGMENTS.has(segment)) return WHERE_PATH_ABSENT;
|
|
685
|
+
if (cur === null || cur === void 0) return WHERE_PATH_ABSENT;
|
|
686
|
+
if (Array.isArray(cur)) return WHERE_PATH_UNRESOLVABLE;
|
|
687
|
+
if (typeof cur !== "object") return WHERE_PATH_ABSENT;
|
|
688
|
+
if (!Object.prototype.hasOwnProperty.call(cur, segment)) {
|
|
689
|
+
return WHERE_PATH_ABSENT;
|
|
690
|
+
}
|
|
691
|
+
cur = cur[segment];
|
|
692
|
+
}
|
|
693
|
+
return cur === void 0 ? WHERE_PATH_ABSENT : cur;
|
|
694
|
+
}
|
|
695
|
+
function whereValueMatches(actual, expected) {
|
|
696
|
+
if (actual === WHERE_PATH_ABSENT || actual === WHERE_PATH_UNRESOLVABLE) {
|
|
697
|
+
return false;
|
|
698
|
+
}
|
|
699
|
+
if (isWhereOperator(expected)) {
|
|
700
|
+
if ("in" in expected) {
|
|
701
|
+
if (Array.isArray(actual)) return false;
|
|
702
|
+
return expected.in.includes(actual);
|
|
703
|
+
}
|
|
704
|
+
if (!Array.isArray(actual)) return false;
|
|
705
|
+
const candidates = expected["array-contains-any"];
|
|
706
|
+
return actual.some((el) => candidates.includes(el));
|
|
707
|
+
}
|
|
708
|
+
if (Array.isArray(actual)) return actual.includes(expected);
|
|
709
|
+
return actual === expected;
|
|
710
|
+
}
|
|
660
711
|
var RealtimeClient = class {
|
|
661
712
|
constructor(config) {
|
|
662
713
|
this.socket = null;
|
|
@@ -664,6 +715,21 @@ var RealtimeClient = class {
|
|
|
664
715
|
this.connecting = null;
|
|
665
716
|
// Store original where objects per room for reconnect resubscription
|
|
666
717
|
this.roomWhereMap = /* @__PURE__ */ new Map();
|
|
718
|
+
// Per-room, per-callback error handlers so EVERY subscriber to a room (not
|
|
719
|
+
// just the first) is notified on a denial, and a departed subscriber's handler
|
|
720
|
+
// is never left dangling for its still-active siblings (#661).
|
|
721
|
+
this.roomErrorMap = /* @__PURE__ */ new Map();
|
|
722
|
+
// Per-room generation stamp, taken from a global monotonic counter each time a
|
|
723
|
+
// room becomes active. A re-subscribe snapshots the room's stamp and acts on
|
|
724
|
+
// the ack only if it is still current — so a late ack cannot notify or clean
|
|
725
|
+
// up after the app has unsubscribed/re-subscribed (#661). Entries are deleted
|
|
726
|
+
// on last-unsubscribe (bounded); the global counter guarantees a re-subscribe
|
|
727
|
+
// always gets a strictly higher stamp, so there is no ABA hole.
|
|
728
|
+
this.roomGeneration = /* @__PURE__ */ new Map();
|
|
729
|
+
this.subEpoch = 0;
|
|
730
|
+
// Rooms with a re-subscribe handshake in flight — dedupes the duplicate
|
|
731
|
+
// `subscription-invalidated` events the gateway emits per evicted room (#661).
|
|
732
|
+
this.resubscribingRooms = /* @__PURE__ */ new Set();
|
|
667
733
|
this.unsubscribeFromTokenRefreshed = null;
|
|
668
734
|
// Wake-up listeners (browser only) — recover from long OS suspends where
|
|
669
735
|
// socket.io's internal reconnect loop may have already given up.
|
|
@@ -692,7 +758,17 @@ var RealtimeClient = class {
|
|
|
692
758
|
}
|
|
693
759
|
const callbacks = this.subscriptions.get(room);
|
|
694
760
|
callbacks?.add(callback);
|
|
761
|
+
if (onError) {
|
|
762
|
+
let handlers = this.roomErrorMap.get(room);
|
|
763
|
+
if (!handlers) {
|
|
764
|
+
handlers = /* @__PURE__ */ new Map();
|
|
765
|
+
this.roomErrorMap.set(room, handlers);
|
|
766
|
+
}
|
|
767
|
+
handlers.set(callback, onError);
|
|
768
|
+
}
|
|
695
769
|
if (callbacks?.size === 1) {
|
|
770
|
+
const generation = ++this.subEpoch;
|
|
771
|
+
this.roomGeneration.set(room, generation);
|
|
696
772
|
if (where && Object.keys(where).length > 0) {
|
|
697
773
|
this.roomWhereMap.set(room, where);
|
|
698
774
|
}
|
|
@@ -704,8 +780,8 @@ var RealtimeClient = class {
|
|
|
704
780
|
"subscribe",
|
|
705
781
|
payload,
|
|
706
782
|
(response) => {
|
|
707
|
-
if (response.error &&
|
|
708
|
-
|
|
783
|
+
if (response.error && this.roomGeneration.get(room) === generation) {
|
|
784
|
+
this.notifyRoomError(room, this.toSubscribeError(response));
|
|
709
785
|
}
|
|
710
786
|
}
|
|
711
787
|
);
|
|
@@ -714,9 +790,12 @@ var RealtimeClient = class {
|
|
|
714
790
|
const callbacks2 = this.subscriptions.get(room);
|
|
715
791
|
if (callbacks2) {
|
|
716
792
|
callbacks2.delete(callback);
|
|
793
|
+
this.roomErrorMap.get(room)?.delete(callback);
|
|
717
794
|
if (callbacks2.size === 0) {
|
|
718
795
|
this.subscriptions.delete(room);
|
|
719
796
|
this.roomWhereMap.delete(room);
|
|
797
|
+
this.roomErrorMap.delete(room);
|
|
798
|
+
this.roomGeneration.delete(room);
|
|
720
799
|
const payload = { projectId, collectionName };
|
|
721
800
|
if (where && Object.keys(where).length > 0) {
|
|
722
801
|
payload["where"] = where;
|
|
@@ -869,6 +948,9 @@ var RealtimeClient = class {
|
|
|
869
948
|
}
|
|
870
949
|
this.subscriptions.clear();
|
|
871
950
|
this.roomWhereMap.clear();
|
|
951
|
+
this.roomErrorMap.clear();
|
|
952
|
+
this.roomGeneration.clear();
|
|
953
|
+
this.resubscribingRooms.clear();
|
|
872
954
|
this.streamSubscriptions.clear();
|
|
873
955
|
this.connecting = null;
|
|
874
956
|
}
|
|
@@ -1018,6 +1100,18 @@ var RealtimeClient = class {
|
|
|
1018
1100
|
}
|
|
1019
1101
|
}
|
|
1020
1102
|
});
|
|
1103
|
+
this.socket.on(
|
|
1104
|
+
"subscription-invalidated",
|
|
1105
|
+
(payload) => {
|
|
1106
|
+
if (!payload?.projectId || !payload?.collectionName) return;
|
|
1107
|
+
const base = `db:${payload.projectId}:${payload.collectionName}`;
|
|
1108
|
+
for (const room of this.subscriptions.keys()) {
|
|
1109
|
+
if (room === base || room.startsWith(`${base}?`)) {
|
|
1110
|
+
this.resubscribeRoom(room);
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
);
|
|
1021
1115
|
this.socket.on("event", (payload) => {
|
|
1022
1116
|
this.dispatchStreamEvent(payload).catch(() => void 0);
|
|
1023
1117
|
});
|
|
@@ -1045,12 +1139,7 @@ var RealtimeClient = class {
|
|
|
1045
1139
|
if (!where) return false;
|
|
1046
1140
|
if (!event.document) return false;
|
|
1047
1141
|
for (const [key, value] of Object.entries(where)) {
|
|
1048
|
-
|
|
1049
|
-
if (Array.isArray(docValue)) {
|
|
1050
|
-
if (!docValue.includes(value)) {
|
|
1051
|
-
return false;
|
|
1052
|
-
}
|
|
1053
|
-
} else if (docValue !== value) {
|
|
1142
|
+
if (!whereValueMatches(resolveWherePath(event.document, key), value)) {
|
|
1054
1143
|
return false;
|
|
1055
1144
|
}
|
|
1056
1145
|
}
|
|
@@ -1124,31 +1213,88 @@ var RealtimeClient = class {
|
|
|
1124
1213
|
this.onOnline = null;
|
|
1125
1214
|
}
|
|
1126
1215
|
resubscribeAll() {
|
|
1216
|
+
this.resubscribingRooms.clear();
|
|
1127
1217
|
for (const [room] of this.subscriptions) {
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1218
|
+
this.resubscribeRoom(room);
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
/**
|
|
1222
|
+
* Parse a pubsub room key back into its projectId + collectionName.
|
|
1223
|
+
* Room format: `db:{projectId}:{collectionName}` or that base plus `?filter`.
|
|
1224
|
+
* Returns null for anything that isn't a well-formed `db:` room.
|
|
1225
|
+
*/
|
|
1226
|
+
parseRoom(room) {
|
|
1227
|
+
const queryIdx = room.indexOf("?");
|
|
1228
|
+
const basePart = queryIdx >= 0 ? room.substring(0, queryIdx) : room;
|
|
1229
|
+
const parts = basePart.split(":");
|
|
1230
|
+
if (parts.length < 3 || parts[0] !== "db") return null;
|
|
1231
|
+
return { projectId: parts[1], collectionName: parts.slice(2).join(":") };
|
|
1232
|
+
}
|
|
1233
|
+
/** Build an Error carrying the gateway's typed `errorCode` (if any) as `.code`. */
|
|
1234
|
+
toSubscribeError(response) {
|
|
1235
|
+
const err = new Error(response.error ?? "Subscribe denied");
|
|
1236
|
+
if (response.errorCode) err.code = response.errorCode;
|
|
1237
|
+
return err;
|
|
1238
|
+
}
|
|
1239
|
+
/** Call every subscriber's error handler registered for `room` (#661). */
|
|
1240
|
+
notifyRoomError(room, error) {
|
|
1241
|
+
const handlers = this.roomErrorMap.get(room);
|
|
1242
|
+
if (!handlers) return;
|
|
1243
|
+
for (const handler of handlers.values()) {
|
|
1244
|
+
try {
|
|
1245
|
+
handler(error);
|
|
1246
|
+
} catch {
|
|
1149
1247
|
}
|
|
1150
1248
|
}
|
|
1151
1249
|
}
|
|
1250
|
+
/**
|
|
1251
|
+
* Re-run the subscribe handshake for one already-tracked room. Shared by
|
|
1252
|
+
* reconnect resubscription (`resubscribeAll`) and the #661
|
|
1253
|
+
* `subscription-invalidated` handler.
|
|
1254
|
+
*
|
|
1255
|
+
* - Deduped per room via `resubscribingRooms` so the duplicate invalidation
|
|
1256
|
+
* events the gateway emits (one per evicted room) collapse to one handshake.
|
|
1257
|
+
* - Snapshots the room's generation; the ack acts only if it is still current,
|
|
1258
|
+
* so a late ack cannot clobber an unsubscribe/re-subscribe that happened in
|
|
1259
|
+
* the meantime — and a success that arrives after the room was removed emits
|
|
1260
|
+
* a compensating `unsubscribe` so no ghost server room is left behind.
|
|
1261
|
+
* - On a genuine denial the subscription is evicted and its stored `onError`
|
|
1262
|
+
* is called (with the typed `.code`), instead of silently going quiet.
|
|
1263
|
+
*/
|
|
1264
|
+
resubscribeRoom(room) {
|
|
1265
|
+
if (!this.socket) return;
|
|
1266
|
+
if (this.resubscribingRooms.has(room)) return;
|
|
1267
|
+
if (!this.subscriptions.has(room)) return;
|
|
1268
|
+
const parsed = this.parseRoom(room);
|
|
1269
|
+
if (!parsed) return;
|
|
1270
|
+
const { projectId, collectionName } = parsed;
|
|
1271
|
+
const where = this.roomWhereMap.get(room);
|
|
1272
|
+
const generation = this.roomGeneration.get(room) ?? 0;
|
|
1273
|
+
const payload = { projectId, collectionName };
|
|
1274
|
+
if (where) payload["where"] = where;
|
|
1275
|
+
this.resubscribingRooms.add(room);
|
|
1276
|
+
this.socket.emit(
|
|
1277
|
+
"subscribe",
|
|
1278
|
+
payload,
|
|
1279
|
+
(response) => {
|
|
1280
|
+
this.resubscribingRooms.delete(room);
|
|
1281
|
+
if ((this.roomGeneration.get(room) ?? 0) !== generation) {
|
|
1282
|
+
if (!this.subscriptions.has(room) && !response?.error) {
|
|
1283
|
+
this.socket?.emit("unsubscribe", payload);
|
|
1284
|
+
}
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
if (response?.error) {
|
|
1288
|
+
const error = this.toSubscribeError(response);
|
|
1289
|
+
this.notifyRoomError(room, error);
|
|
1290
|
+
this.subscriptions.delete(room);
|
|
1291
|
+
this.roomWhereMap.delete(room);
|
|
1292
|
+
this.roomErrorMap.delete(room);
|
|
1293
|
+
this.roomGeneration.delete(room);
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
);
|
|
1297
|
+
}
|
|
1152
1298
|
emitSubscribeEvents(state) {
|
|
1153
1299
|
return new Promise((resolve) => {
|
|
1154
1300
|
if (!this.socket) {
|
|
@@ -3011,10 +3157,14 @@ var FunctionsModule = class {
|
|
|
3011
3157
|
* `config.apiUrl` (which already carries the `/api/v1` prefix).
|
|
3012
3158
|
*
|
|
3013
3159
|
* Auth defaults, based on `invokeMode` semantics:
|
|
3014
|
-
* - webhook:
|
|
3015
|
-
* - authenticated:
|
|
3016
|
-
*
|
|
3017
|
-
* -
|
|
3160
|
+
* - webhook: pass `secret` → Authorization is NOT attached
|
|
3161
|
+
* - authenticated: pass nothing → Authorization IS attached (from token manager);
|
|
3162
|
+
* caller must be a member of the target project
|
|
3163
|
+
* - public: pass nothing → Authorization is attached if logged in, else omitted
|
|
3164
|
+
* - hybrid: pass both `secret` and `authenticated: true`; JWT path
|
|
3165
|
+
* requires project membership like `authenticated`
|
|
3166
|
+
* - platform-authenticated: pass nothing → Authorization IS attached (from token manager);
|
|
3167
|
+
* any signed-in user is accepted, still just attaches the bearer token
|
|
3018
3168
|
*
|
|
3019
3169
|
* To force a specific behaviour, set `authenticated` explicitly — it wins
|
|
3020
3170
|
* over the `secret`-based default.
|