@spacelr/sdk 0.1.10 → 0.2.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 +253 -10
- package/dist/index.d.ts +253 -10
- package/dist/index.js +391 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +388 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -378,7 +378,9 @@ var TokenManager = class {
|
|
|
378
378
|
return tokens.accessToken;
|
|
379
379
|
}
|
|
380
380
|
async setTokens(tokens) {
|
|
381
|
-
|
|
381
|
+
const needsDefault = tokens.expiresAt === void 0 && !!tokens.refreshToken;
|
|
382
|
+
const normalised = needsDefault ? { ...tokens, expiresAt: Math.floor(Date.now() / 1e3) } : tokens;
|
|
383
|
+
await this.storage.setTokens(normalised);
|
|
382
384
|
this.authLostEmitted = false;
|
|
383
385
|
}
|
|
384
386
|
async clearTokens() {
|
|
@@ -543,6 +545,13 @@ async function generatePKCEChallenge() {
|
|
|
543
545
|
// libs/sdk/src/core/realtime.ts
|
|
544
546
|
import { io } from "socket.io-client";
|
|
545
547
|
var REBUILD_RETRY_DELAY_MS = 5e3;
|
|
548
|
+
var PERMANENT_STREAM_ACK_ERRORS = /* @__PURE__ */ new Set([
|
|
549
|
+
"not-stream-collection",
|
|
550
|
+
"Collection not found",
|
|
551
|
+
"Invalid sinceId format",
|
|
552
|
+
"Not a member of this project",
|
|
553
|
+
"Subscribe denied"
|
|
554
|
+
]);
|
|
546
555
|
var RealtimeClient = class {
|
|
547
556
|
constructor(config) {
|
|
548
557
|
this.socket = null;
|
|
@@ -563,6 +572,7 @@ var RealtimeClient = class {
|
|
|
563
572
|
this.rebuildRetryTimer = null;
|
|
564
573
|
this.connectionState = "disconnected";
|
|
565
574
|
this.connectionStateListeners = /* @__PURE__ */ new Set();
|
|
575
|
+
this.streamSubscriptions = /* @__PURE__ */ new Map();
|
|
566
576
|
this.config = config;
|
|
567
577
|
}
|
|
568
578
|
async subscribe(projectId, collectionName, callback, onError, where) {
|
|
@@ -611,6 +621,53 @@ var RealtimeClient = class {
|
|
|
611
621
|
}
|
|
612
622
|
};
|
|
613
623
|
}
|
|
624
|
+
/**
|
|
625
|
+
* Subscribe to a stream-mode collection using Redis Streams replay +
|
|
626
|
+
* cursor-based delivery. Parallel to `subscribe()` (which targets
|
|
627
|
+
* pubsub-mode collections). The server validates that the collection is
|
|
628
|
+
* configured in stream mode and rejects the handshake otherwise.
|
|
629
|
+
*
|
|
630
|
+
* The per-subscription cursor (`lastDeliveredId`) advances after each
|
|
631
|
+
* `onEvent` promise resolves, so reconnects resume from the last delivered
|
|
632
|
+
* event rather than the originally-subscribed `sinceId`.
|
|
633
|
+
*/
|
|
634
|
+
async subscribeWithCursor(options) {
|
|
635
|
+
this.ensureWakeListeners();
|
|
636
|
+
if (this.connectionState === "disconnected") {
|
|
637
|
+
this.setConnectionState("reconnecting");
|
|
638
|
+
}
|
|
639
|
+
await this.ensureConnected();
|
|
640
|
+
const streamKey = `stream:${options.projectId}:${options.collectionName}`;
|
|
641
|
+
const state = {
|
|
642
|
+
options,
|
|
643
|
+
lastDeliveredId: options.sinceId ?? null,
|
|
644
|
+
streamKey,
|
|
645
|
+
dispatchQueue: Promise.resolve()
|
|
646
|
+
};
|
|
647
|
+
const existing = this.streamSubscriptions.get(streamKey);
|
|
648
|
+
if (existing && existing.size > 0) {
|
|
649
|
+
existing.add(state);
|
|
650
|
+
return () => this.unsubscribeStream(state);
|
|
651
|
+
}
|
|
652
|
+
const set = /* @__PURE__ */ new Set();
|
|
653
|
+
set.add(state);
|
|
654
|
+
this.streamSubscriptions.set(streamKey, set);
|
|
655
|
+
try {
|
|
656
|
+
const ack = await this.emitSubscribeEvents(state);
|
|
657
|
+
if (ack.error) {
|
|
658
|
+
const message = ack.message ?? ack.error;
|
|
659
|
+
const err = new Error(message);
|
|
660
|
+
this.evictStreamKey(streamKey, err, state);
|
|
661
|
+
options.onError?.(err);
|
|
662
|
+
throw err;
|
|
663
|
+
}
|
|
664
|
+
return () => this.unsubscribeStream(state);
|
|
665
|
+
} catch (err) {
|
|
666
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
667
|
+
this.evictStreamKey(streamKey, error, state);
|
|
668
|
+
throw error;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
614
671
|
/**
|
|
615
672
|
* Tear down the realtime client permanently. After this call the instance
|
|
616
673
|
* is disposed — subsequent `subscribe()` calls will not re-establish a
|
|
@@ -635,6 +692,7 @@ var RealtimeClient = class {
|
|
|
635
692
|
}
|
|
636
693
|
this.subscriptions.clear();
|
|
637
694
|
this.roomWhereMap.clear();
|
|
695
|
+
this.streamSubscriptions.clear();
|
|
638
696
|
this.connecting = null;
|
|
639
697
|
}
|
|
640
698
|
getConnectionState() {
|
|
@@ -716,6 +774,7 @@ var RealtimeClient = class {
|
|
|
716
774
|
resolve();
|
|
717
775
|
} else {
|
|
718
776
|
this.resubscribeAll();
|
|
777
|
+
void this.resubscribeAllStreams();
|
|
719
778
|
}
|
|
720
779
|
});
|
|
721
780
|
this.socket.on("connect_error", (err) => {
|
|
@@ -748,6 +807,17 @@ var RealtimeClient = class {
|
|
|
748
807
|
}
|
|
749
808
|
}
|
|
750
809
|
});
|
|
810
|
+
this.socket.on("event", (payload) => {
|
|
811
|
+
this.dispatchStreamEvent(payload).catch(() => void 0);
|
|
812
|
+
});
|
|
813
|
+
this.socket.on("event-gap", (info) => {
|
|
814
|
+
const streamKey = `stream:${info.projectId}:${info.collectionName}`;
|
|
815
|
+
const set = this.streamSubscriptions.get(streamKey);
|
|
816
|
+
if (!set) return;
|
|
817
|
+
for (const state of set) {
|
|
818
|
+
state.options.onGap?.(info);
|
|
819
|
+
}
|
|
820
|
+
});
|
|
751
821
|
this.socket.on("disconnect", () => {
|
|
752
822
|
if (!this.disposed) {
|
|
753
823
|
this.setConnectionState("reconnecting");
|
|
@@ -805,6 +875,9 @@ var RealtimeClient = class {
|
|
|
805
875
|
if (this.subscriptions.size > 0) {
|
|
806
876
|
this.resubscribeAll();
|
|
807
877
|
}
|
|
878
|
+
if (this.streamSubscriptions.size > 0) {
|
|
879
|
+
void this.resubscribeAllStreams();
|
|
880
|
+
}
|
|
808
881
|
}
|
|
809
882
|
scheduleRebuildRetry() {
|
|
810
883
|
if (this.disposed || this.rebuildRetryTimer) return;
|
|
@@ -864,8 +937,186 @@ var RealtimeClient = class {
|
|
|
864
937
|
}
|
|
865
938
|
}
|
|
866
939
|
}
|
|
940
|
+
emitSubscribeEvents(state) {
|
|
941
|
+
return new Promise((resolve) => {
|
|
942
|
+
if (!this.socket) {
|
|
943
|
+
resolve({ error: "disconnected" });
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
const socket = this.socket;
|
|
947
|
+
const onDisconnect = () => {
|
|
948
|
+
socket.off("disconnect", onDisconnect);
|
|
949
|
+
resolve({ error: "disconnected" });
|
|
950
|
+
};
|
|
951
|
+
socket.once("disconnect", onDisconnect);
|
|
952
|
+
socket.emit(
|
|
953
|
+
"subscribe-events",
|
|
954
|
+
this.buildStreamPayload(state),
|
|
955
|
+
(ack) => {
|
|
956
|
+
socket.off("disconnect", onDisconnect);
|
|
957
|
+
resolve(ack);
|
|
958
|
+
}
|
|
959
|
+
);
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
buildStreamPayload(state) {
|
|
963
|
+
const { projectId, collectionName, where } = state.options;
|
|
964
|
+
const payload = { projectId, collectionName };
|
|
965
|
+
if (state.lastDeliveredId) payload["sinceId"] = state.lastDeliveredId;
|
|
966
|
+
if (where && Object.keys(where).length > 0) payload["where"] = where;
|
|
967
|
+
return payload;
|
|
968
|
+
}
|
|
969
|
+
unsubscribeStream(state) {
|
|
970
|
+
const set = this.streamSubscriptions.get(state.streamKey);
|
|
971
|
+
if (!set) return;
|
|
972
|
+
set.delete(state);
|
|
973
|
+
if (set.size === 0) {
|
|
974
|
+
this.streamSubscriptions.delete(state.streamKey);
|
|
975
|
+
this.socket?.emit("unsubscribe-events", {
|
|
976
|
+
projectId: state.options.projectId,
|
|
977
|
+
collectionName: state.options.collectionName
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
dispatchStreamEvent(payload) {
|
|
982
|
+
if (!payload.eventId) return Promise.resolve();
|
|
983
|
+
const streamKey = `stream:${payload.projectId}:${payload.collectionName}`;
|
|
984
|
+
const set = this.streamSubscriptions.get(streamKey);
|
|
985
|
+
if (!set) return Promise.resolve();
|
|
986
|
+
const entryId = payload.eventId;
|
|
987
|
+
for (const state of set) {
|
|
988
|
+
state.dispatchQueue = state.dispatchQueue.then(async () => {
|
|
989
|
+
const liveSet = this.streamSubscriptions.get(state.streamKey);
|
|
990
|
+
if (!liveSet?.has(state)) return;
|
|
991
|
+
try {
|
|
992
|
+
await state.options.onEvent({ eventId: entryId, event: payload });
|
|
993
|
+
state.lastDeliveredId = entryId;
|
|
994
|
+
} catch (err) {
|
|
995
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
996
|
+
try {
|
|
997
|
+
state.options.onError?.(error);
|
|
998
|
+
} catch {
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
return Promise.resolve();
|
|
1004
|
+
}
|
|
1005
|
+
async resubscribeAllStreams() {
|
|
1006
|
+
const work = [];
|
|
1007
|
+
for (const set of this.streamSubscriptions.values()) {
|
|
1008
|
+
const states = Array.from(set);
|
|
1009
|
+
if (states.length === 0) continue;
|
|
1010
|
+
const primary = this.pickEarliestCursorState(states);
|
|
1011
|
+
work.push(this.resubscribeOne(primary, set));
|
|
1012
|
+
}
|
|
1013
|
+
try {
|
|
1014
|
+
await Promise.all(work);
|
|
1015
|
+
} catch {
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
async resubscribeOne(primary, set) {
|
|
1019
|
+
try {
|
|
1020
|
+
const ack = await this.emitSubscribeEvents(primary);
|
|
1021
|
+
if (ack.error) {
|
|
1022
|
+
const err = new Error(ack.message ?? ack.error);
|
|
1023
|
+
for (const state of [...set]) {
|
|
1024
|
+
state.options.onError?.(err);
|
|
1025
|
+
}
|
|
1026
|
+
if (PERMANENT_STREAM_ACK_ERRORS.has(ack.error)) {
|
|
1027
|
+
this.streamSubscriptions.delete(primary.streamKey);
|
|
1028
|
+
}
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
if (!this.streamSubscriptions.has(primary.streamKey)) {
|
|
1032
|
+
this.socket?.emit("unsubscribe-events", {
|
|
1033
|
+
projectId: primary.options.projectId,
|
|
1034
|
+
collectionName: primary.options.collectionName
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
} catch (err) {
|
|
1038
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
1039
|
+
for (const state of [...set]) {
|
|
1040
|
+
state.options.onError?.(error);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
pickEarliestCursorState(states) {
|
|
1045
|
+
const parse = (id) => {
|
|
1046
|
+
if (!id) return [Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER];
|
|
1047
|
+
const [ms, seq = "0"] = id.split("-");
|
|
1048
|
+
return [Number(ms), Number(seq)];
|
|
1049
|
+
};
|
|
1050
|
+
let earliest = states[0];
|
|
1051
|
+
let [eMs, eSeq] = parse(earliest.lastDeliveredId);
|
|
1052
|
+
for (let i = 1; i < states.length; i++) {
|
|
1053
|
+
const [ms, seq] = parse(states[i].lastDeliveredId);
|
|
1054
|
+
if (ms < eMs || ms === eMs && seq < eSeq) {
|
|
1055
|
+
earliest = states[i];
|
|
1056
|
+
eMs = ms;
|
|
1057
|
+
eSeq = seq;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
return earliest;
|
|
1061
|
+
}
|
|
1062
|
+
/**
|
|
1063
|
+
* Evict the primary subscriber AND every sibling that joined the same
|
|
1064
|
+
* streamKey via the dedup path. Fires `onError` on all siblings, then
|
|
1065
|
+
* removes the entire streamKey entry from `streamSubscriptions`.
|
|
1066
|
+
*/
|
|
1067
|
+
evictStreamKey(streamKey, err, primary) {
|
|
1068
|
+
const set = this.streamSubscriptions.get(streamKey);
|
|
1069
|
+
if (!set) return;
|
|
1070
|
+
for (const sibling of [...set]) {
|
|
1071
|
+
if (sibling !== primary) {
|
|
1072
|
+
sibling.options.onError?.(err);
|
|
1073
|
+
}
|
|
1074
|
+
set.delete(sibling);
|
|
1075
|
+
}
|
|
1076
|
+
this.streamSubscriptions.delete(streamKey);
|
|
1077
|
+
}
|
|
867
1078
|
};
|
|
868
1079
|
|
|
1080
|
+
// libs/sdk/src/core/cursor-storage.ts
|
|
1081
|
+
function memoryCursorStorage() {
|
|
1082
|
+
const store = /* @__PURE__ */ new Map();
|
|
1083
|
+
return {
|
|
1084
|
+
load(key) {
|
|
1085
|
+
return store.get(key) ?? null;
|
|
1086
|
+
},
|
|
1087
|
+
save(key, cursor) {
|
|
1088
|
+
store.set(key, cursor);
|
|
1089
|
+
}
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
function localStorageCursorStorage(prefix = "spacelr:cursor:") {
|
|
1093
|
+
const storage = (() => {
|
|
1094
|
+
try {
|
|
1095
|
+
const candidate = globalThis.localStorage;
|
|
1096
|
+
return typeof candidate === "undefined" ? null : candidate;
|
|
1097
|
+
} catch {
|
|
1098
|
+
return null;
|
|
1099
|
+
}
|
|
1100
|
+
})();
|
|
1101
|
+
return {
|
|
1102
|
+
load(key) {
|
|
1103
|
+
if (!storage) return null;
|
|
1104
|
+
try {
|
|
1105
|
+
return storage.getItem(prefix + key);
|
|
1106
|
+
} catch {
|
|
1107
|
+
return null;
|
|
1108
|
+
}
|
|
1109
|
+
},
|
|
1110
|
+
save(key, cursor) {
|
|
1111
|
+
if (!storage) return;
|
|
1112
|
+
try {
|
|
1113
|
+
storage.setItem(prefix + key, cursor);
|
|
1114
|
+
} catch {
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
|
|
869
1120
|
// libs/sdk/src/modules/auth.module.ts
|
|
870
1121
|
var AuthModule = class {
|
|
871
1122
|
constructor(http, tokenManager, config) {
|
|
@@ -1410,6 +1661,46 @@ var QueryBuilder = class {
|
|
|
1410
1661
|
this._offset = offset;
|
|
1411
1662
|
return this;
|
|
1412
1663
|
}
|
|
1664
|
+
/**
|
|
1665
|
+
* **Constraint:** the cursor value must be a 24-hex ObjectId string.
|
|
1666
|
+
* Collections using custom non-ObjectId `_id` strings will not work
|
|
1667
|
+
* correctly with cursor pagination — the server's `$lt`/`$gt` comparison
|
|
1668
|
+
* uses BSON type ordering, mixing string `_id`s with ObjectId comparison
|
|
1669
|
+
* produces undefined behaviour. Documented limitation; future work may
|
|
1670
|
+
* add opaque cursor tokens that abstract over `_id` types.
|
|
1671
|
+
*
|
|
1672
|
+
* Switch to cursor-pagination mode: return documents with `_id < id`
|
|
1673
|
+
* (in the sort-defined order). The cursor refers to the cursor *value*,
|
|
1674
|
+
* not visual UI direction. Requires `.sort()` to be `{ _id: 1 }` or
|
|
1675
|
+
* `{ _id: -1 }` (or omitted — server defaults to `{ _id: 1 }`).
|
|
1676
|
+
*
|
|
1677
|
+
* Narrows the builder's mode parameter so subsequent `.execute()` returns
|
|
1678
|
+
* a `CursorResult<T>` instead of `OffsetResult<T>`.
|
|
1679
|
+
*
|
|
1680
|
+
* **For paginating further:** the `nextCursor` field returned by
|
|
1681
|
+
* `execute()` is the `_id` of the last document on the page. To load the
|
|
1682
|
+
* next older page, pass it again to `.before()`. (Do NOT pass it to
|
|
1683
|
+
* `.after()` — that would request docs newer than this page.)
|
|
1684
|
+
*
|
|
1685
|
+
* **Cannot be combined with `.offset()`.** Type system allows the chain
|
|
1686
|
+
* for ergonomics, but the server rejects it with HTTP 400.
|
|
1687
|
+
*/
|
|
1688
|
+
before(id) {
|
|
1689
|
+
this._before = id;
|
|
1690
|
+
return this;
|
|
1691
|
+
}
|
|
1692
|
+
/**
|
|
1693
|
+
* Switch to cursor-pagination mode: return documents with `_id > id`.
|
|
1694
|
+
* See `.before()` for full semantics — including the ObjectId-only cursor
|
|
1695
|
+
* constraint and the `.offset()` incompatibility (server-enforced 400).
|
|
1696
|
+
*
|
|
1697
|
+
* **For paginating further:** pass the returned `nextCursor` to
|
|
1698
|
+
* `.after()` again to load the next newer page.
|
|
1699
|
+
*/
|
|
1700
|
+
after(id) {
|
|
1701
|
+
this._after = id;
|
|
1702
|
+
return this;
|
|
1703
|
+
}
|
|
1413
1704
|
select(fields) {
|
|
1414
1705
|
this._fields = fields;
|
|
1415
1706
|
return this;
|
|
@@ -1424,6 +1715,8 @@ var QueryBuilder = class {
|
|
|
1424
1715
|
if (this._sort) query["sort"] = JSON.stringify(this._sort);
|
|
1425
1716
|
if (this._limit !== void 0) query["limit"] = this._limit;
|
|
1426
1717
|
if (this._offset !== void 0) query["offset"] = this._offset;
|
|
1718
|
+
if (this._before !== void 0) query["before"] = this._before;
|
|
1719
|
+
if (this._after !== void 0) query["after"] = this._after;
|
|
1427
1720
|
if (this._fields) query["fields"] = this._fields.join(",");
|
|
1428
1721
|
if (this._populate.length) {
|
|
1429
1722
|
query["populate"] = this._populate.map(
|
|
@@ -1566,6 +1859,97 @@ var CollectionRef = class {
|
|
|
1566
1859
|
}
|
|
1567
1860
|
};
|
|
1568
1861
|
}
|
|
1862
|
+
subscribeEvents(handlers) {
|
|
1863
|
+
if (!this.realtime) {
|
|
1864
|
+
throw new Error("Realtime not available: no RealtimeClient configured");
|
|
1865
|
+
}
|
|
1866
|
+
let lastCursor;
|
|
1867
|
+
let unsub = null;
|
|
1868
|
+
let unsubscribed = false;
|
|
1869
|
+
const cursorStorage = handlers.cursorStorage;
|
|
1870
|
+
const cursorKey = handlers.cursorKey ?? `${this.projectId}:${this.collectionName}`;
|
|
1871
|
+
if (cursorStorage && handlers.where && Object.keys(handlers.where).length > 0 && handlers.cursorKey === void 0) {
|
|
1872
|
+
console.warn(
|
|
1873
|
+
`[spacelr] subscribeEvents on ${this.projectId}:${this.collectionName} uses a 'where' filter with cursorStorage but no explicit cursorKey \u2014 filtered subscriptions sharing the default key will silently skip events across reconnects. Pass a unique cursorKey (e.g. include a hash of the filter).`
|
|
1874
|
+
);
|
|
1875
|
+
}
|
|
1876
|
+
const realtime = this.realtime;
|
|
1877
|
+
let lastSavePromise = Promise.resolve();
|
|
1878
|
+
const onEvent = async ({
|
|
1879
|
+
eventId,
|
|
1880
|
+
event
|
|
1881
|
+
}) => {
|
|
1882
|
+
try {
|
|
1883
|
+
if (event.type === "insert" && handlers.onInsert && event.document) {
|
|
1884
|
+
await handlers.onInsert({
|
|
1885
|
+
...event.document,
|
|
1886
|
+
_eventId: eventId
|
|
1887
|
+
});
|
|
1888
|
+
} else if (event.type === "update" && handlers.onUpdate && event.document) {
|
|
1889
|
+
await handlers.onUpdate({
|
|
1890
|
+
...event.document,
|
|
1891
|
+
_eventId: eventId
|
|
1892
|
+
});
|
|
1893
|
+
} else if (event.type === "delete" && handlers.onDelete) {
|
|
1894
|
+
await handlers.onDelete(event.documentId, eventId);
|
|
1895
|
+
}
|
|
1896
|
+
lastCursor = eventId;
|
|
1897
|
+
if (cursorStorage) {
|
|
1898
|
+
lastSavePromise = lastSavePromise.then(
|
|
1899
|
+
() => cursorStorage.save(cursorKey, eventId),
|
|
1900
|
+
() => cursorStorage.save(cursorKey, eventId)
|
|
1901
|
+
).catch((err) => {
|
|
1902
|
+
handlers.onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
1903
|
+
});
|
|
1904
|
+
}
|
|
1905
|
+
} catch (err) {
|
|
1906
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
1907
|
+
}
|
|
1908
|
+
};
|
|
1909
|
+
const callSubscribe = (sinceId) => realtime.subscribeWithCursor({
|
|
1910
|
+
projectId: this.projectId,
|
|
1911
|
+
collectionName: this.collectionName,
|
|
1912
|
+
sinceId,
|
|
1913
|
+
where: handlers.where,
|
|
1914
|
+
onEvent,
|
|
1915
|
+
onGap: handlers.onGap,
|
|
1916
|
+
onError: handlers.onError
|
|
1917
|
+
});
|
|
1918
|
+
let promise;
|
|
1919
|
+
if (handlers.sinceId !== void 0 || !cursorStorage) {
|
|
1920
|
+
promise = callSubscribe(handlers.sinceId);
|
|
1921
|
+
} else {
|
|
1922
|
+
promise = (async () => {
|
|
1923
|
+
let resumeFrom;
|
|
1924
|
+
try {
|
|
1925
|
+
const loaded = await Promise.resolve(cursorStorage.load(cursorKey));
|
|
1926
|
+
if (loaded) resumeFrom = loaded;
|
|
1927
|
+
} catch (err) {
|
|
1928
|
+
handlers.onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
1929
|
+
}
|
|
1930
|
+
return callSubscribe(resumeFrom);
|
|
1931
|
+
})();
|
|
1932
|
+
}
|
|
1933
|
+
promise.then((u) => {
|
|
1934
|
+
if (unsubscribed) {
|
|
1935
|
+
u();
|
|
1936
|
+
} else {
|
|
1937
|
+
unsub = u;
|
|
1938
|
+
}
|
|
1939
|
+
}).catch(() => void 0);
|
|
1940
|
+
return {
|
|
1941
|
+
unsubscribe() {
|
|
1942
|
+
unsubscribed = true;
|
|
1943
|
+
if (unsub) {
|
|
1944
|
+
unsub();
|
|
1945
|
+
unsub = null;
|
|
1946
|
+
}
|
|
1947
|
+
},
|
|
1948
|
+
getCursor() {
|
|
1949
|
+
return lastCursor;
|
|
1950
|
+
}
|
|
1951
|
+
};
|
|
1952
|
+
}
|
|
1569
1953
|
};
|
|
1570
1954
|
var DatabaseModule = class {
|
|
1571
1955
|
constructor(http, projectId, realtime) {
|
|
@@ -1831,6 +2215,8 @@ export {
|
|
|
1831
2215
|
SpacelrTimeoutError,
|
|
1832
2216
|
SpacelrTwoFactorRequiredError,
|
|
1833
2217
|
createClient,
|
|
1834
|
-
generatePKCEChallenge
|
|
2218
|
+
generatePKCEChallenge,
|
|
2219
|
+
localStorageCursorStorage,
|
|
2220
|
+
memoryCursorStorage
|
|
1835
2221
|
};
|
|
1836
2222
|
//# sourceMappingURL=index.mjs.map
|