@drakkar.software/starfish-client 1.19.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/append.d.ts +50 -0
- package/dist/bindings/broadcast.d.ts +19 -0
- package/dist/bindings/broadcast.js +65 -0
- package/dist/bindings/react.d.ts +12 -0
- package/dist/bindings/react.js +25 -0
- package/dist/bindings/zustand.js +250 -13
- package/dist/bindings/zustand.js.map +3 -3
- package/dist/client.d.ts +12 -5
- package/dist/config.d.ts +10 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +25 -8
- package/dist/index.js.map +2 -2
- package/dist/mobile-lifecycle.js +2 -2
- package/dist/polling.js +2 -2
- package/package.json +2 -2
- package/dist/hash.d.ts +0 -10
- package/dist/hash.js +0 -34
- package/dist/platform.d.ts +0 -52
- package/dist/platform.js +0 -62
package/dist/append.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { StarfishClient } from "./client.js";
|
|
2
|
+
import type { PushSuccess } from "@drakkar.software/starfish-protocol";
|
|
3
|
+
/**
|
|
4
|
+
* Appends `item` to an append-only collection.
|
|
5
|
+
*
|
|
6
|
+
* Sends `{ data: item, baseHash: null }` — the server ignores `baseHash` for
|
|
7
|
+
* append-only collections (conflict detection is disabled or delegated to
|
|
8
|
+
* `checkLastItem` on the server side).
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* await pushAppend(client, "/push/events", { type: "click", ts: Date.now() })
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export declare function pushAppend(client: StarfishClient, path: string, item: Record<string, unknown>): Promise<PushSuccess>;
|
|
15
|
+
export interface PullAppendListOptions {
|
|
16
|
+
/** Array field name. Defaults to `"items"` (server default). */
|
|
17
|
+
field?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Return only items appended after this timestamp (milliseconds since epoch).
|
|
20
|
+
* Sent as `?checkpoint=<since>`. Omit for a full pull.
|
|
21
|
+
*/
|
|
22
|
+
since?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Return only the last K items. Applied after the `since` filter.
|
|
25
|
+
* Useful for "latest N entries" queries without a tracked checkpoint.
|
|
26
|
+
* Sent as `?last=<K>`.
|
|
27
|
+
*/
|
|
28
|
+
last?: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Pulls the stored item array from an append-only collection.
|
|
32
|
+
*
|
|
33
|
+
* Returns `data[field]` filtered to an array; returns `[]` when the document
|
|
34
|
+
* does not exist yet or the field is absent / not an array.
|
|
35
|
+
*
|
|
36
|
+
* Pass `{ since: ts }` for incremental pulls — only items appended after `ts`
|
|
37
|
+
* are returned (requires per-item timestamps on the server, available from 2.0.0).
|
|
38
|
+
*
|
|
39
|
+
* ```ts
|
|
40
|
+
* // Full pull
|
|
41
|
+
* const events = await pullAppendList(client, "/pull/events")
|
|
42
|
+
*
|
|
43
|
+
* // Incremental pull
|
|
44
|
+
* const newEvents = await pullAppendList(client, "/pull/events", { since: lastSyncTs })
|
|
45
|
+
*
|
|
46
|
+
* // Custom field name
|
|
47
|
+
* const logs = await pullAppendList(client, "/pull/audit", { field: "logs" })
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export declare function pullAppendList<T = unknown>(client: StarfishClient, path: string, options?: PullAppendListOptions): Promise<T[]>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { StoreApi } from "zustand/vanilla";
|
|
2
|
+
import type { StarfishStore } from "./zustand.js";
|
|
3
|
+
/**
|
|
4
|
+
* Syncs a Zustand Starfish store across browser tabs using BroadcastChannel.
|
|
5
|
+
* Returns a cleanup function that closes the channel.
|
|
6
|
+
*/
|
|
7
|
+
export declare function setupBroadcastSync(store: StoreApi<StarfishStore>, name: string): () => void;
|
|
8
|
+
/**
|
|
9
|
+
* Syncs a Zustand Starfish store across browser tabs using storage events.
|
|
10
|
+
* Fallback for environments without BroadcastChannel.
|
|
11
|
+
* Returns a cleanup function.
|
|
12
|
+
*/
|
|
13
|
+
export declare function setupStorageFallback(store: StoreApi<StarfishStore>, name: string): () => void;
|
|
14
|
+
/**
|
|
15
|
+
* Auto-detects the best cross-tab sync mechanism and sets it up.
|
|
16
|
+
* Uses BroadcastChannel when available, falls back to storage events.
|
|
17
|
+
* Returns a cleanup function.
|
|
18
|
+
*/
|
|
19
|
+
export declare function setupCrossTabSync(store: StoreApi<StarfishStore>, name: string): () => void;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Syncs a Zustand Starfish store across browser tabs using BroadcastChannel.
|
|
3
|
+
* Returns a cleanup function that closes the channel.
|
|
4
|
+
*/
|
|
5
|
+
export function setupBroadcastSync(store, name) {
|
|
6
|
+
const channel = new BroadcastChannel(`starfish-${name}`);
|
|
7
|
+
let lastReceivedData = null;
|
|
8
|
+
channel.onmessage = (event) => {
|
|
9
|
+
lastReceivedData = event.data.data;
|
|
10
|
+
store.setState({ data: event.data.data, dirty: event.data.dirty });
|
|
11
|
+
};
|
|
12
|
+
const unsub = store.subscribe((state, prev) => {
|
|
13
|
+
if (state.data === lastReceivedData)
|
|
14
|
+
return;
|
|
15
|
+
if (state.data !== prev.data || state.dirty !== prev.dirty) {
|
|
16
|
+
channel.postMessage({ data: state.data, dirty: state.dirty });
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
return () => {
|
|
20
|
+
unsub();
|
|
21
|
+
channel.close();
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Syncs a Zustand Starfish store across browser tabs using storage events.
|
|
26
|
+
* Fallback for environments without BroadcastChannel.
|
|
27
|
+
* Returns a cleanup function.
|
|
28
|
+
*/
|
|
29
|
+
export function setupStorageFallback(store, name) {
|
|
30
|
+
const storageKey = `starfish-broadcast-${name}`;
|
|
31
|
+
let lastReceivedData = null;
|
|
32
|
+
const onStorage = (e) => {
|
|
33
|
+
if (e.key !== storageKey || !e.newValue)
|
|
34
|
+
return;
|
|
35
|
+
const payload = JSON.parse(e.newValue);
|
|
36
|
+
lastReceivedData = payload.data;
|
|
37
|
+
store.setState({ data: payload.data, dirty: payload.dirty });
|
|
38
|
+
};
|
|
39
|
+
globalThis.addEventListener("storage", onStorage);
|
|
40
|
+
const unsub = store.subscribe((state, prev) => {
|
|
41
|
+
if (state.data === lastReceivedData)
|
|
42
|
+
return;
|
|
43
|
+
if (state.data !== prev.data || state.dirty !== prev.dirty) {
|
|
44
|
+
localStorage.setItem(storageKey, JSON.stringify({ data: state.data, dirty: state.dirty }));
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
return () => {
|
|
48
|
+
unsub();
|
|
49
|
+
globalThis.removeEventListener("storage", onStorage);
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Auto-detects the best cross-tab sync mechanism and sets it up.
|
|
54
|
+
* Uses BroadcastChannel when available, falls back to storage events.
|
|
55
|
+
* Returns a cleanup function.
|
|
56
|
+
*/
|
|
57
|
+
export function setupCrossTabSync(store, name) {
|
|
58
|
+
if (typeof BroadcastChannel !== "undefined") {
|
|
59
|
+
return setupBroadcastSync(store, name);
|
|
60
|
+
}
|
|
61
|
+
if (typeof globalThis.addEventListener === "function" && typeof localStorage !== "undefined") {
|
|
62
|
+
return setupStorageFallback(store, name);
|
|
63
|
+
}
|
|
64
|
+
return () => { };
|
|
65
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { StoreApi } from "zustand/vanilla";
|
|
2
|
+
import type { StarfishStore, StarfishState } from "./zustand.js";
|
|
3
|
+
/** Derived sync status for UI display. */
|
|
4
|
+
export type SyncStatus = "synced" | "syncing" | "pending" | "error" | "offline";
|
|
5
|
+
/** Derive a single sync status from store state. */
|
|
6
|
+
export declare function deriveSyncStatus(state: StarfishState): SyncStatus;
|
|
7
|
+
/** Use the full Starfish store state and actions. */
|
|
8
|
+
export declare function useStarfish(store: StoreApi<StarfishStore>): StarfishStore;
|
|
9
|
+
/** Use only the synced data, with an optional selector for fine-grained subscriptions. */
|
|
10
|
+
export declare function useStarfishData<T = Record<string, unknown>>(store: StoreApi<StarfishStore>, selector?: (data: Record<string, unknown>) => T): T;
|
|
11
|
+
/** Use the derived sync status (synced | syncing | pending | error | offline). */
|
|
12
|
+
export declare function useSyncStatus(store: StoreApi<StarfishStore>): SyncStatus;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useStore } from "zustand";
|
|
2
|
+
/** Derive a single sync status from store state. */
|
|
3
|
+
export function deriveSyncStatus(state) {
|
|
4
|
+
if (!state.online)
|
|
5
|
+
return "offline";
|
|
6
|
+
if (state.error)
|
|
7
|
+
return "error";
|
|
8
|
+
if (state.syncing)
|
|
9
|
+
return "syncing";
|
|
10
|
+
if (state.dirty)
|
|
11
|
+
return "pending";
|
|
12
|
+
return "synced";
|
|
13
|
+
}
|
|
14
|
+
/** Use the full Starfish store state and actions. */
|
|
15
|
+
export function useStarfish(store) {
|
|
16
|
+
return useStore(store);
|
|
17
|
+
}
|
|
18
|
+
/** Use only the synced data, with an optional selector for fine-grained subscriptions. */
|
|
19
|
+
export function useStarfishData(store, selector) {
|
|
20
|
+
return useStore(store, (state) => selector ? selector(state.data) : state.data);
|
|
21
|
+
}
|
|
22
|
+
/** Use the derived sync status (synced | syncing | pending | error | offline). */
|
|
23
|
+
export function useSyncStatus(store) {
|
|
24
|
+
return useStore(store, deriveSyncStatus);
|
|
25
|
+
}
|
package/dist/bindings/zustand.js
CHANGED
|
@@ -1,11 +1,231 @@
|
|
|
1
1
|
// src/bindings/zustand.ts
|
|
2
2
|
import { createStore } from "zustand/vanilla";
|
|
3
3
|
import { useStore } from "zustand";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
|
|
5
|
+
// ../../../node_modules/.pnpm/zustand@5.0.11_@types+react@19.2.14_immer@11.1.4_react@19.2.4_use-sync-external-store@1.6.0_react@19.2.4_/node_modules/zustand/esm/middleware.mjs
|
|
6
|
+
var subscribeWithSelectorImpl = (fn) => (set, get, api) => {
|
|
7
|
+
const origSubscribe = api.subscribe;
|
|
8
|
+
api.subscribe = ((selector, optListener, options) => {
|
|
9
|
+
let listener = selector;
|
|
10
|
+
if (optListener) {
|
|
11
|
+
const equalityFn = (options == null ? void 0 : options.equalityFn) || Object.is;
|
|
12
|
+
let currentSlice = selector(api.getState());
|
|
13
|
+
listener = (state) => {
|
|
14
|
+
const nextSlice = selector(state);
|
|
15
|
+
if (!equalityFn(currentSlice, nextSlice)) {
|
|
16
|
+
const previousSlice = currentSlice;
|
|
17
|
+
optListener(currentSlice = nextSlice, previousSlice);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
if (options == null ? void 0 : options.fireImmediately) {
|
|
21
|
+
optListener(currentSlice, currentSlice);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return origSubscribe(listener);
|
|
25
|
+
});
|
|
26
|
+
const initialState = fn(set, get, api);
|
|
27
|
+
return initialState;
|
|
28
|
+
};
|
|
29
|
+
var subscribeWithSelector = subscribeWithSelectorImpl;
|
|
30
|
+
function createJSONStorage(getStorage, options) {
|
|
31
|
+
let storage;
|
|
32
|
+
try {
|
|
33
|
+
storage = getStorage();
|
|
34
|
+
} catch (e) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const persistStorage = {
|
|
38
|
+
getItem: (name) => {
|
|
39
|
+
var _a;
|
|
40
|
+
const parse = (str2) => {
|
|
41
|
+
if (str2 === null) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
return JSON.parse(str2, options == null ? void 0 : options.reviver);
|
|
45
|
+
};
|
|
46
|
+
const str = (_a = storage.getItem(name)) != null ? _a : null;
|
|
47
|
+
if (str instanceof Promise) {
|
|
48
|
+
return str.then(parse);
|
|
49
|
+
}
|
|
50
|
+
return parse(str);
|
|
51
|
+
},
|
|
52
|
+
setItem: (name, newValue) => storage.setItem(name, JSON.stringify(newValue, options == null ? void 0 : options.replacer)),
|
|
53
|
+
removeItem: (name) => storage.removeItem(name)
|
|
54
|
+
};
|
|
55
|
+
return persistStorage;
|
|
56
|
+
}
|
|
57
|
+
var toThenable = (fn) => (input) => {
|
|
58
|
+
try {
|
|
59
|
+
const result = fn(input);
|
|
60
|
+
if (result instanceof Promise) {
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
then(onFulfilled) {
|
|
65
|
+
return toThenable(onFulfilled)(result);
|
|
66
|
+
},
|
|
67
|
+
catch(_onRejected) {
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
} catch (e) {
|
|
72
|
+
return {
|
|
73
|
+
then(_onFulfilled) {
|
|
74
|
+
return this;
|
|
75
|
+
},
|
|
76
|
+
catch(onRejected) {
|
|
77
|
+
return toThenable(onRejected)(e);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
var persistImpl = (config, baseOptions) => (set, get, api) => {
|
|
83
|
+
let options = {
|
|
84
|
+
storage: createJSONStorage(() => window.localStorage),
|
|
85
|
+
partialize: (state) => state,
|
|
86
|
+
version: 0,
|
|
87
|
+
merge: (persistedState, currentState) => ({
|
|
88
|
+
...currentState,
|
|
89
|
+
...persistedState
|
|
90
|
+
}),
|
|
91
|
+
...baseOptions
|
|
92
|
+
};
|
|
93
|
+
let hasHydrated = false;
|
|
94
|
+
let hydrationVersion = 0;
|
|
95
|
+
const hydrationListeners = /* @__PURE__ */ new Set();
|
|
96
|
+
const finishHydrationListeners = /* @__PURE__ */ new Set();
|
|
97
|
+
let storage = options.storage;
|
|
98
|
+
if (!storage) {
|
|
99
|
+
return config(
|
|
100
|
+
(...args) => {
|
|
101
|
+
console.warn(
|
|
102
|
+
`[zustand persist middleware] Unable to update item '${options.name}', the given storage is currently unavailable.`
|
|
103
|
+
);
|
|
104
|
+
set(...args);
|
|
105
|
+
},
|
|
106
|
+
get,
|
|
107
|
+
api
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
const setItem = () => {
|
|
111
|
+
const state = options.partialize({ ...get() });
|
|
112
|
+
return storage.setItem(options.name, {
|
|
113
|
+
state,
|
|
114
|
+
version: options.version
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
const savedSetState = api.setState;
|
|
118
|
+
api.setState = (state, replace) => {
|
|
119
|
+
savedSetState(state, replace);
|
|
120
|
+
return setItem();
|
|
121
|
+
};
|
|
122
|
+
const configResult = config(
|
|
123
|
+
(...args) => {
|
|
124
|
+
set(...args);
|
|
125
|
+
return setItem();
|
|
126
|
+
},
|
|
127
|
+
get,
|
|
128
|
+
api
|
|
129
|
+
);
|
|
130
|
+
api.getInitialState = () => configResult;
|
|
131
|
+
let stateFromStorage;
|
|
132
|
+
const hydrate = () => {
|
|
133
|
+
var _a, _b;
|
|
134
|
+
if (!storage) return;
|
|
135
|
+
const currentVersion = ++hydrationVersion;
|
|
136
|
+
hasHydrated = false;
|
|
137
|
+
hydrationListeners.forEach((cb) => {
|
|
138
|
+
var _a2;
|
|
139
|
+
return cb((_a2 = get()) != null ? _a2 : configResult);
|
|
140
|
+
});
|
|
141
|
+
const postRehydrationCallback = ((_b = options.onRehydrateStorage) == null ? void 0 : _b.call(options, (_a = get()) != null ? _a : configResult)) || void 0;
|
|
142
|
+
return toThenable(storage.getItem.bind(storage))(options.name).then((deserializedStorageValue) => {
|
|
143
|
+
if (deserializedStorageValue) {
|
|
144
|
+
if (typeof deserializedStorageValue.version === "number" && deserializedStorageValue.version !== options.version) {
|
|
145
|
+
if (options.migrate) {
|
|
146
|
+
const migration = options.migrate(
|
|
147
|
+
deserializedStorageValue.state,
|
|
148
|
+
deserializedStorageValue.version
|
|
149
|
+
);
|
|
150
|
+
if (migration instanceof Promise) {
|
|
151
|
+
return migration.then((result) => [true, result]);
|
|
152
|
+
}
|
|
153
|
+
return [true, migration];
|
|
154
|
+
}
|
|
155
|
+
console.error(
|
|
156
|
+
`State loaded from storage couldn't be migrated since no migrate function was provided`
|
|
157
|
+
);
|
|
158
|
+
} else {
|
|
159
|
+
return [false, deserializedStorageValue.state];
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return [false, void 0];
|
|
163
|
+
}).then((migrationResult) => {
|
|
164
|
+
var _a2;
|
|
165
|
+
if (currentVersion !== hydrationVersion) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const [migrated, migratedState] = migrationResult;
|
|
169
|
+
stateFromStorage = options.merge(
|
|
170
|
+
migratedState,
|
|
171
|
+
(_a2 = get()) != null ? _a2 : configResult
|
|
172
|
+
);
|
|
173
|
+
set(stateFromStorage, true);
|
|
174
|
+
if (migrated) {
|
|
175
|
+
return setItem();
|
|
176
|
+
}
|
|
177
|
+
}).then(() => {
|
|
178
|
+
if (currentVersion !== hydrationVersion) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
postRehydrationCallback == null ? void 0 : postRehydrationCallback(stateFromStorage, void 0);
|
|
182
|
+
stateFromStorage = get();
|
|
183
|
+
hasHydrated = true;
|
|
184
|
+
finishHydrationListeners.forEach((cb) => cb(stateFromStorage));
|
|
185
|
+
}).catch((e) => {
|
|
186
|
+
if (currentVersion !== hydrationVersion) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
postRehydrationCallback == null ? void 0 : postRehydrationCallback(void 0, e);
|
|
190
|
+
});
|
|
191
|
+
};
|
|
192
|
+
api.persist = {
|
|
193
|
+
setOptions: (newOptions) => {
|
|
194
|
+
options = {
|
|
195
|
+
...options,
|
|
196
|
+
...newOptions
|
|
197
|
+
};
|
|
198
|
+
if (newOptions.storage) {
|
|
199
|
+
storage = newOptions.storage;
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
clearStorage: () => {
|
|
203
|
+
storage == null ? void 0 : storage.removeItem(options.name);
|
|
204
|
+
},
|
|
205
|
+
getOptions: () => options,
|
|
206
|
+
rehydrate: () => hydrate(),
|
|
207
|
+
hasHydrated: () => hasHydrated,
|
|
208
|
+
onHydrate: (cb) => {
|
|
209
|
+
hydrationListeners.add(cb);
|
|
210
|
+
return () => {
|
|
211
|
+
hydrationListeners.delete(cb);
|
|
212
|
+
};
|
|
213
|
+
},
|
|
214
|
+
onFinishHydration: (cb) => {
|
|
215
|
+
finishHydrationListeners.add(cb);
|
|
216
|
+
return () => {
|
|
217
|
+
finishHydrationListeners.delete(cb);
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
if (!options.skipHydration) {
|
|
222
|
+
hydrate();
|
|
223
|
+
}
|
|
224
|
+
return stateFromStorage || configResult;
|
|
225
|
+
};
|
|
226
|
+
var persist = persistImpl;
|
|
227
|
+
|
|
228
|
+
// src/bindings/zustand.ts
|
|
9
229
|
import { useEffect, useRef, useState, useCallback } from "react";
|
|
10
230
|
|
|
11
231
|
// src/types.ts
|
|
@@ -25,6 +245,7 @@ var StarfishHttpError = class extends Error {
|
|
|
25
245
|
};
|
|
26
246
|
|
|
27
247
|
// src/client.ts
|
|
248
|
+
var APPEND_DEFAULT_FIELD = "items";
|
|
28
249
|
var StarfishClient = class {
|
|
29
250
|
baseUrl;
|
|
30
251
|
auth;
|
|
@@ -34,13 +255,24 @@ var StarfishClient = class {
|
|
|
34
255
|
this.auth = options.auth;
|
|
35
256
|
this.fetch = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
36
257
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
258
|
+
async pull(path, checkpointOrOptions) {
|
|
259
|
+
let url = `${this.baseUrl}${path}`;
|
|
260
|
+
let appendField;
|
|
261
|
+
if (typeof checkpointOrOptions === "number") {
|
|
262
|
+
if (checkpointOrOptions) url += `?checkpoint=${checkpointOrOptions}`;
|
|
263
|
+
} else if (checkpointOrOptions != null) {
|
|
264
|
+
appendField = checkpointOrOptions.appendField ?? APPEND_DEFAULT_FIELD;
|
|
265
|
+
const params = new URLSearchParams();
|
|
266
|
+
if (checkpointOrOptions.since != null) {
|
|
267
|
+
if (checkpointOrOptions.since < 0) throw new Error("since must be non-negative");
|
|
268
|
+
params.set("checkpoint", String(checkpointOrOptions.since));
|
|
269
|
+
}
|
|
270
|
+
if (checkpointOrOptions.last != null) {
|
|
271
|
+
if (checkpointOrOptions.last < 0) throw new Error("last must be non-negative");
|
|
272
|
+
params.set("last", String(checkpointOrOptions.last));
|
|
273
|
+
}
|
|
274
|
+
if (params.size > 0) url += `?${params.toString()}`;
|
|
275
|
+
}
|
|
44
276
|
const authHeaders = this.auth ? await this.auth({ method: "GET", path, body: null }) : {};
|
|
45
277
|
const res = await this.fetch(url, {
|
|
46
278
|
method: "GET",
|
|
@@ -49,7 +281,12 @@ var StarfishClient = class {
|
|
|
49
281
|
if (!res.ok) {
|
|
50
282
|
throw new StarfishHttpError(res.status, await res.text());
|
|
51
283
|
}
|
|
52
|
-
|
|
284
|
+
const result = await res.json();
|
|
285
|
+
if (appendField !== void 0) {
|
|
286
|
+
const list = result.data?.[appendField];
|
|
287
|
+
return Array.isArray(list) ? list : [];
|
|
288
|
+
}
|
|
289
|
+
return result;
|
|
53
290
|
}
|
|
54
291
|
/**
|
|
55
292
|
* Push synced data to the server.
|