@lark-sh/client 0.1.2 → 0.1.4
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 +40 -1
- package/dist/index.d.ts +40 -1
- package/dist/index.js +353 -62
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +351 -61
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -254,6 +254,9 @@ interface QueryParams {
|
|
|
254
254
|
|
|
255
255
|
/**
|
|
256
256
|
* SubscriptionManager - tracks active subscriptions and routes events to callbacks.
|
|
257
|
+
*
|
|
258
|
+
* Also manages a local data cache that is populated by subscription events.
|
|
259
|
+
* The cache only contains "live" data from active subscriptions.
|
|
257
260
|
*/
|
|
258
261
|
|
|
259
262
|
type SnapshotCallback = (snapshot: DataSnapshot, previousChildKey?: string | null) => void;
|
|
@@ -280,6 +283,7 @@ declare class LarkDatabase {
|
|
|
280
283
|
private _auth;
|
|
281
284
|
private _databaseId;
|
|
282
285
|
private _coordinatorUrl;
|
|
286
|
+
private _volatilePaths;
|
|
283
287
|
private ws;
|
|
284
288
|
private messageQueue;
|
|
285
289
|
private subscriptionManager;
|
|
@@ -299,6 +303,12 @@ declare class LarkDatabase {
|
|
|
299
303
|
* @internal Get the base URL for reference toString().
|
|
300
304
|
*/
|
|
301
305
|
_getBaseUrl(): string;
|
|
306
|
+
/**
|
|
307
|
+
* Get the volatile path patterns from the server.
|
|
308
|
+
* These patterns indicate which paths should use unreliable transport.
|
|
309
|
+
* WebSocket always uses reliable transport, but this is stored for future UDP support.
|
|
310
|
+
*/
|
|
311
|
+
get volatilePaths(): string[];
|
|
302
312
|
/**
|
|
303
313
|
* Connect to a database.
|
|
304
314
|
*
|
|
@@ -356,6 +366,15 @@ declare class LarkDatabase {
|
|
|
356
366
|
_sendPush(path: string, value: unknown): Promise<string>;
|
|
357
367
|
/**
|
|
358
368
|
* @internal Send a once (read) operation.
|
|
369
|
+
*
|
|
370
|
+
* This method first checks if the data is available in the local cache
|
|
371
|
+
* (from an active subscription). If so, it returns the cached value
|
|
372
|
+
* immediately without a server round-trip.
|
|
373
|
+
*
|
|
374
|
+
* Cache is only used when:
|
|
375
|
+
* - No query parameters are specified (queries may filter/order differently)
|
|
376
|
+
* - The path is covered by an active 'value' subscription
|
|
377
|
+
* - We have cached data available
|
|
359
378
|
*/
|
|
360
379
|
_sendOnce(path: string, query?: QueryParams): Promise<DataSnapshot>;
|
|
361
380
|
/**
|
|
@@ -412,4 +431,24 @@ declare class LarkError extends Error {
|
|
|
412
431
|
*/
|
|
413
432
|
declare function generatePushId(): string;
|
|
414
433
|
|
|
415
|
-
|
|
434
|
+
/**
|
|
435
|
+
* Volatile path matching utilities.
|
|
436
|
+
*
|
|
437
|
+
* Volatile paths are patterns where a wildcard matches exactly one path segment.
|
|
438
|
+
* These paths use unreliable transport for better performance (UDP when available).
|
|
439
|
+
*/
|
|
440
|
+
/**
|
|
441
|
+
* Check if a path matches any of the volatile path patterns.
|
|
442
|
+
*
|
|
443
|
+
* Pattern matching rules:
|
|
444
|
+
* - Wildcard matches exactly one path segment (not zero, not multiple)
|
|
445
|
+
* - Patterns and paths are compared segment by segment
|
|
446
|
+
* - Path must have the same number of segments as the pattern
|
|
447
|
+
*
|
|
448
|
+
* @param path - The path to check (e.g., "/players/abc/position")
|
|
449
|
+
* @param patterns - Array of volatile patterns with wildcards
|
|
450
|
+
* @returns true if the path matches any pattern
|
|
451
|
+
*/
|
|
452
|
+
declare function isVolatilePath(path: string, patterns: string[] | null | undefined): boolean;
|
|
453
|
+
|
|
454
|
+
export { type AuthInfo, type ConnectOptions, DataSnapshot, DatabaseReference, type EventType, LarkDatabase, LarkError, OnDisconnect, type QueryState, type SnapshotCallback$1 as SnapshotCallback, generatePushId, isVolatilePath };
|
package/dist/index.d.ts
CHANGED
|
@@ -254,6 +254,9 @@ interface QueryParams {
|
|
|
254
254
|
|
|
255
255
|
/**
|
|
256
256
|
* SubscriptionManager - tracks active subscriptions and routes events to callbacks.
|
|
257
|
+
*
|
|
258
|
+
* Also manages a local data cache that is populated by subscription events.
|
|
259
|
+
* The cache only contains "live" data from active subscriptions.
|
|
257
260
|
*/
|
|
258
261
|
|
|
259
262
|
type SnapshotCallback = (snapshot: DataSnapshot, previousChildKey?: string | null) => void;
|
|
@@ -280,6 +283,7 @@ declare class LarkDatabase {
|
|
|
280
283
|
private _auth;
|
|
281
284
|
private _databaseId;
|
|
282
285
|
private _coordinatorUrl;
|
|
286
|
+
private _volatilePaths;
|
|
283
287
|
private ws;
|
|
284
288
|
private messageQueue;
|
|
285
289
|
private subscriptionManager;
|
|
@@ -299,6 +303,12 @@ declare class LarkDatabase {
|
|
|
299
303
|
* @internal Get the base URL for reference toString().
|
|
300
304
|
*/
|
|
301
305
|
_getBaseUrl(): string;
|
|
306
|
+
/**
|
|
307
|
+
* Get the volatile path patterns from the server.
|
|
308
|
+
* These patterns indicate which paths should use unreliable transport.
|
|
309
|
+
* WebSocket always uses reliable transport, but this is stored for future UDP support.
|
|
310
|
+
*/
|
|
311
|
+
get volatilePaths(): string[];
|
|
302
312
|
/**
|
|
303
313
|
* Connect to a database.
|
|
304
314
|
*
|
|
@@ -356,6 +366,15 @@ declare class LarkDatabase {
|
|
|
356
366
|
_sendPush(path: string, value: unknown): Promise<string>;
|
|
357
367
|
/**
|
|
358
368
|
* @internal Send a once (read) operation.
|
|
369
|
+
*
|
|
370
|
+
* This method first checks if the data is available in the local cache
|
|
371
|
+
* (from an active subscription). If so, it returns the cached value
|
|
372
|
+
* immediately without a server round-trip.
|
|
373
|
+
*
|
|
374
|
+
* Cache is only used when:
|
|
375
|
+
* - No query parameters are specified (queries may filter/order differently)
|
|
376
|
+
* - The path is covered by an active 'value' subscription
|
|
377
|
+
* - We have cached data available
|
|
359
378
|
*/
|
|
360
379
|
_sendOnce(path: string, query?: QueryParams): Promise<DataSnapshot>;
|
|
361
380
|
/**
|
|
@@ -412,4 +431,24 @@ declare class LarkError extends Error {
|
|
|
412
431
|
*/
|
|
413
432
|
declare function generatePushId(): string;
|
|
414
433
|
|
|
415
|
-
|
|
434
|
+
/**
|
|
435
|
+
* Volatile path matching utilities.
|
|
436
|
+
*
|
|
437
|
+
* Volatile paths are patterns where a wildcard matches exactly one path segment.
|
|
438
|
+
* These paths use unreliable transport for better performance (UDP when available).
|
|
439
|
+
*/
|
|
440
|
+
/**
|
|
441
|
+
* Check if a path matches any of the volatile path patterns.
|
|
442
|
+
*
|
|
443
|
+
* Pattern matching rules:
|
|
444
|
+
* - Wildcard matches exactly one path segment (not zero, not multiple)
|
|
445
|
+
* - Patterns and paths are compared segment by segment
|
|
446
|
+
* - Path must have the same number of segments as the pattern
|
|
447
|
+
*
|
|
448
|
+
* @param path - The path to check (e.g., "/players/abc/position")
|
|
449
|
+
* @param patterns - Array of volatile patterns with wildcards
|
|
450
|
+
* @returns true if the path matches any pattern
|
|
451
|
+
*/
|
|
452
|
+
declare function isVolatilePath(path: string, patterns: string[] | null | undefined): boolean;
|
|
453
|
+
|
|
454
|
+
export { type AuthInfo, type ConnectOptions, DataSnapshot, DatabaseReference, type EventType, LarkDatabase, LarkError, OnDisconnect, type QueryState, type SnapshotCallback$1 as SnapshotCallback, generatePushId, isVolatilePath };
|
package/dist/index.js
CHANGED
|
@@ -35,7 +35,8 @@ __export(index_exports, {
|
|
|
35
35
|
LarkDatabase: () => LarkDatabase,
|
|
36
36
|
LarkError: () => LarkError,
|
|
37
37
|
OnDisconnect: () => OnDisconnect,
|
|
38
|
-
generatePushId: () => generatePushId
|
|
38
|
+
generatePushId: () => generatePushId,
|
|
39
|
+
isVolatilePath: () => isVolatilePath
|
|
39
40
|
});
|
|
40
41
|
module.exports = __toCommonJS(index_exports);
|
|
41
42
|
|
|
@@ -119,10 +120,10 @@ var LarkError = class _LarkError extends Error {
|
|
|
119
120
|
|
|
120
121
|
// src/protocol/messages.ts
|
|
121
122
|
function isAckMessage(msg) {
|
|
122
|
-
return "a" in msg
|
|
123
|
+
return "a" in msg;
|
|
123
124
|
}
|
|
124
|
-
function
|
|
125
|
-
return "
|
|
125
|
+
function isJoinCompleteMessage(msg) {
|
|
126
|
+
return "jc" in msg;
|
|
126
127
|
}
|
|
127
128
|
function isNackMessage(msg) {
|
|
128
129
|
return "n" in msg;
|
|
@@ -178,16 +179,12 @@ var MessageQueue = class {
|
|
|
178
179
|
* @returns true if the message was handled (was a response), false otherwise
|
|
179
180
|
*/
|
|
180
181
|
handleMessage(message) {
|
|
181
|
-
if (
|
|
182
|
-
const pending = this.pending.get(message.
|
|
182
|
+
if (isJoinCompleteMessage(message)) {
|
|
183
|
+
const pending = this.pending.get(message.jc);
|
|
183
184
|
if (pending) {
|
|
184
185
|
clearTimeout(pending.timeout);
|
|
185
|
-
this.pending.delete(message.
|
|
186
|
-
pending.resolve(
|
|
187
|
-
uid: message.uid,
|
|
188
|
-
provider: message.provider,
|
|
189
|
-
token: message.token
|
|
190
|
-
});
|
|
186
|
+
this.pending.delete(message.jc);
|
|
187
|
+
pending.resolve(message.vp || []);
|
|
191
188
|
return true;
|
|
192
189
|
}
|
|
193
190
|
}
|
|
@@ -247,6 +244,234 @@ var MessageQueue = class {
|
|
|
247
244
|
}
|
|
248
245
|
};
|
|
249
246
|
|
|
247
|
+
// src/utils/path.ts
|
|
248
|
+
function normalizePath(path) {
|
|
249
|
+
if (!path || path === "/") return "/";
|
|
250
|
+
const segments = path.split("/").filter((segment) => segment.length > 0);
|
|
251
|
+
if (segments.length === 0) return "/";
|
|
252
|
+
return "/" + segments.join("/");
|
|
253
|
+
}
|
|
254
|
+
function joinPath(...segments) {
|
|
255
|
+
const allParts = [];
|
|
256
|
+
for (const segment of segments) {
|
|
257
|
+
if (!segment || segment === "/") continue;
|
|
258
|
+
const parts = segment.split("/").filter((p) => p.length > 0);
|
|
259
|
+
allParts.push(...parts);
|
|
260
|
+
}
|
|
261
|
+
if (allParts.length === 0) return "/";
|
|
262
|
+
return "/" + allParts.join("/");
|
|
263
|
+
}
|
|
264
|
+
function getParentPath(path) {
|
|
265
|
+
const normalized = normalizePath(path);
|
|
266
|
+
if (normalized === "/") return "/";
|
|
267
|
+
const lastSlash = normalized.lastIndexOf("/");
|
|
268
|
+
if (lastSlash <= 0) return "/";
|
|
269
|
+
return normalized.substring(0, lastSlash);
|
|
270
|
+
}
|
|
271
|
+
function getKey(path) {
|
|
272
|
+
const normalized = normalizePath(path);
|
|
273
|
+
if (normalized === "/") return null;
|
|
274
|
+
const lastSlash = normalized.lastIndexOf("/");
|
|
275
|
+
return normalized.substring(lastSlash + 1);
|
|
276
|
+
}
|
|
277
|
+
function getValueAtPath(obj, path) {
|
|
278
|
+
const normalized = normalizePath(path);
|
|
279
|
+
if (normalized === "/") return obj;
|
|
280
|
+
const segments = normalized.split("/").filter((s) => s.length > 0);
|
|
281
|
+
let current = obj;
|
|
282
|
+
for (const segment of segments) {
|
|
283
|
+
if (current === null || current === void 0) {
|
|
284
|
+
return void 0;
|
|
285
|
+
}
|
|
286
|
+
if (typeof current !== "object") {
|
|
287
|
+
return void 0;
|
|
288
|
+
}
|
|
289
|
+
current = current[segment];
|
|
290
|
+
}
|
|
291
|
+
return current;
|
|
292
|
+
}
|
|
293
|
+
function setValueAtPath(obj, path, value) {
|
|
294
|
+
const normalized = normalizePath(path);
|
|
295
|
+
if (normalized === "/") {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const segments = normalized.split("/").filter((s) => s.length > 0);
|
|
299
|
+
let current = obj;
|
|
300
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
301
|
+
const segment = segments[i];
|
|
302
|
+
if (!(segment in current) || typeof current[segment] !== "object" || current[segment] === null) {
|
|
303
|
+
current[segment] = {};
|
|
304
|
+
}
|
|
305
|
+
current = current[segment];
|
|
306
|
+
}
|
|
307
|
+
const lastSegment = segments[segments.length - 1];
|
|
308
|
+
current[lastSegment] = value;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// src/cache/DataCache.ts
|
|
312
|
+
var DataCache = class {
|
|
313
|
+
constructor() {
|
|
314
|
+
// path -> cached value
|
|
315
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Store a value at a path.
|
|
319
|
+
* Called when we receive data from a subscription event.
|
|
320
|
+
*/
|
|
321
|
+
set(path, value) {
|
|
322
|
+
const normalized = normalizePath(path);
|
|
323
|
+
this.cache.set(normalized, value);
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Get the cached value at a path.
|
|
327
|
+
* Returns undefined if not in cache.
|
|
328
|
+
*
|
|
329
|
+
* This method handles nested lookups:
|
|
330
|
+
* - If /boxes is cached with {0: true, 1: false}
|
|
331
|
+
* - get('/boxes/0') returns true (extracted from parent)
|
|
332
|
+
*/
|
|
333
|
+
get(path) {
|
|
334
|
+
const normalized = normalizePath(path);
|
|
335
|
+
if (this.cache.has(normalized)) {
|
|
336
|
+
return { value: this.cache.get(normalized), found: true };
|
|
337
|
+
}
|
|
338
|
+
const ancestorResult = this.getFromAncestor(normalized);
|
|
339
|
+
if (ancestorResult.found) {
|
|
340
|
+
return ancestorResult;
|
|
341
|
+
}
|
|
342
|
+
return { value: void 0, found: false };
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Check if we have cached data that covers the given path.
|
|
346
|
+
* This includes exact matches and ancestor paths.
|
|
347
|
+
*/
|
|
348
|
+
has(path) {
|
|
349
|
+
return this.get(path).found;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Try to get data from an ancestor path.
|
|
353
|
+
* E.g., if /boxes is cached and we want /boxes/5, extract it.
|
|
354
|
+
*/
|
|
355
|
+
getFromAncestor(path) {
|
|
356
|
+
const segments = path.split("/").filter((s) => s.length > 0);
|
|
357
|
+
for (let i = segments.length - 1; i >= 0; i--) {
|
|
358
|
+
const ancestorPath = i === 0 ? "/" : "/" + segments.slice(0, i).join("/");
|
|
359
|
+
if (this.cache.has(ancestorPath)) {
|
|
360
|
+
const ancestorValue = this.cache.get(ancestorPath);
|
|
361
|
+
const relativePath = "/" + segments.slice(i).join("/");
|
|
362
|
+
const extractedValue = getValueAtPath(ancestorValue, relativePath);
|
|
363
|
+
return { value: extractedValue, found: true };
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
if (this.cache.has("/")) {
|
|
367
|
+
const rootValue = this.cache.get("/");
|
|
368
|
+
const extractedValue = getValueAtPath(rootValue, path);
|
|
369
|
+
return { value: extractedValue, found: true };
|
|
370
|
+
}
|
|
371
|
+
return { value: void 0, found: false };
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Remove cached data at a path.
|
|
375
|
+
* Called when unsubscribing from a path that's no longer covered.
|
|
376
|
+
*/
|
|
377
|
+
delete(path) {
|
|
378
|
+
const normalized = normalizePath(path);
|
|
379
|
+
this.cache.delete(normalized);
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Remove cached data at a path and all descendant paths.
|
|
383
|
+
* Called when a subscription is removed and no other subscription covers it.
|
|
384
|
+
*/
|
|
385
|
+
deleteTree(path) {
|
|
386
|
+
const normalized = normalizePath(path);
|
|
387
|
+
this.cache.delete(normalized);
|
|
388
|
+
const prefix = normalized === "/" ? "/" : normalized + "/";
|
|
389
|
+
for (const cachedPath of this.cache.keys()) {
|
|
390
|
+
if (cachedPath.startsWith(prefix) || normalized === "/" && cachedPath !== "/") {
|
|
391
|
+
this.cache.delete(cachedPath);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Update cache when a child value changes.
|
|
397
|
+
* This updates both the child path and any cached ancestor that contains it.
|
|
398
|
+
*
|
|
399
|
+
* E.g., if /boxes is cached and /boxes/5 changes:
|
|
400
|
+
* - Update the /boxes cache to reflect the new /boxes/5 value
|
|
401
|
+
*/
|
|
402
|
+
updateChild(path, value) {
|
|
403
|
+
const normalized = normalizePath(path);
|
|
404
|
+
this.cache.set(normalized, value);
|
|
405
|
+
const segments = normalized.split("/").filter((s) => s.length > 0);
|
|
406
|
+
for (let i = segments.length - 1; i >= 1; i--) {
|
|
407
|
+
const ancestorPath = "/" + segments.slice(0, i).join("/");
|
|
408
|
+
if (this.cache.has(ancestorPath)) {
|
|
409
|
+
const ancestorValue = this.cache.get(ancestorPath);
|
|
410
|
+
if (ancestorValue !== null && typeof ancestorValue === "object") {
|
|
411
|
+
const childKey = segments[i];
|
|
412
|
+
const remainingPath = "/" + segments.slice(i).join("/");
|
|
413
|
+
const updatedAncestor = this.deepClone(ancestorValue);
|
|
414
|
+
setValueAtPath(updatedAncestor, remainingPath, value);
|
|
415
|
+
this.cache.set(ancestorPath, updatedAncestor);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
if (this.cache.has("/") && normalized !== "/") {
|
|
420
|
+
const rootValue = this.cache.get("/");
|
|
421
|
+
if (rootValue !== null && typeof rootValue === "object") {
|
|
422
|
+
const updatedRoot = this.deepClone(rootValue);
|
|
423
|
+
setValueAtPath(updatedRoot, normalized, value);
|
|
424
|
+
this.cache.set("/", updatedRoot);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Remove a child from the cache (e.g., on child_removed event).
|
|
430
|
+
*/
|
|
431
|
+
removeChild(path) {
|
|
432
|
+
const normalized = normalizePath(path);
|
|
433
|
+
this.deleteTree(normalized);
|
|
434
|
+
const segments = normalized.split("/").filter((s) => s.length > 0);
|
|
435
|
+
for (let i = segments.length - 1; i >= 1; i--) {
|
|
436
|
+
const ancestorPath = "/" + segments.slice(0, i).join("/");
|
|
437
|
+
if (this.cache.has(ancestorPath)) {
|
|
438
|
+
const ancestorValue = this.cache.get(ancestorPath);
|
|
439
|
+
if (ancestorValue !== null && typeof ancestorValue === "object") {
|
|
440
|
+
const updatedAncestor = this.deepClone(ancestorValue);
|
|
441
|
+
const parentPath = "/" + segments.slice(i, -1).join("/");
|
|
442
|
+
const childKey = segments[segments.length - 1];
|
|
443
|
+
const parent = parentPath === "/" ? updatedAncestor : getValueAtPath(updatedAncestor, parentPath);
|
|
444
|
+
if (parent && typeof parent === "object") {
|
|
445
|
+
delete parent[childKey];
|
|
446
|
+
}
|
|
447
|
+
this.cache.set(ancestorPath, updatedAncestor);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Clear all cached data.
|
|
454
|
+
*/
|
|
455
|
+
clear() {
|
|
456
|
+
this.cache.clear();
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Get the number of cached paths (for testing/debugging).
|
|
460
|
+
*/
|
|
461
|
+
get size() {
|
|
462
|
+
return this.cache.size;
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Deep clone a value to avoid mutation issues.
|
|
466
|
+
*/
|
|
467
|
+
deepClone(value) {
|
|
468
|
+
if (value === null || typeof value !== "object") {
|
|
469
|
+
return value;
|
|
470
|
+
}
|
|
471
|
+
return JSON.parse(JSON.stringify(value));
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
|
|
250
475
|
// src/connection/SubscriptionManager.ts
|
|
251
476
|
var SubscriptionManager = class {
|
|
252
477
|
constructor() {
|
|
@@ -258,6 +483,7 @@ var SubscriptionManager = class {
|
|
|
258
483
|
this.sendUnsubscribe = null;
|
|
259
484
|
// Callback to create DataSnapshot from event data
|
|
260
485
|
this.createSnapshot = null;
|
|
486
|
+
this.cache = new DataCache();
|
|
261
487
|
}
|
|
262
488
|
/**
|
|
263
489
|
* Initialize the manager with server communication callbacks.
|
|
@@ -370,6 +596,15 @@ var SubscriptionManager = class {
|
|
|
370
596
|
if (eventType === "value") {
|
|
371
597
|
snapshotPath = path;
|
|
372
598
|
snapshotValue = message.v;
|
|
599
|
+
this.cache.set(path, snapshotValue);
|
|
600
|
+
} else if (eventType === "child_added" || eventType === "child_changed") {
|
|
601
|
+
snapshotPath = path === "/" ? `/${message.k}` : `${path}/${message.k}`;
|
|
602
|
+
snapshotValue = message.v;
|
|
603
|
+
this.cache.updateChild(snapshotPath, snapshotValue);
|
|
604
|
+
} else if (eventType === "child_removed") {
|
|
605
|
+
snapshotPath = path === "/" ? `/${message.k}` : `${path}/${message.k}`;
|
|
606
|
+
snapshotValue = message.v;
|
|
607
|
+
this.cache.removeChild(snapshotPath);
|
|
373
608
|
} else {
|
|
374
609
|
snapshotPath = path === "/" ? `/${message.k}` : `${path}/${message.k}`;
|
|
375
610
|
snapshotValue = message.v;
|
|
@@ -397,6 +632,7 @@ var SubscriptionManager = class {
|
|
|
397
632
|
*/
|
|
398
633
|
clear() {
|
|
399
634
|
this.subscriptions.clear();
|
|
635
|
+
this.cache.clear();
|
|
400
636
|
}
|
|
401
637
|
/**
|
|
402
638
|
* Check if there are any subscriptions at a path.
|
|
@@ -404,6 +640,65 @@ var SubscriptionManager = class {
|
|
|
404
640
|
hasSubscriptions(path) {
|
|
405
641
|
return this.subscriptions.has(path);
|
|
406
642
|
}
|
|
643
|
+
/**
|
|
644
|
+
* Check if a path is "covered" by an active subscription.
|
|
645
|
+
*
|
|
646
|
+
* A path is covered if:
|
|
647
|
+
* - There's an active 'value' subscription at that exact path, OR
|
|
648
|
+
* - There's an active 'value' subscription at an ancestor path
|
|
649
|
+
*
|
|
650
|
+
* Child event subscriptions (child_added, etc.) don't provide full coverage
|
|
651
|
+
* because they only notify of changes, not the complete value.
|
|
652
|
+
*/
|
|
653
|
+
isPathCovered(path) {
|
|
654
|
+
const normalized = normalizePath(path);
|
|
655
|
+
if (this.hasValueSubscription(normalized)) {
|
|
656
|
+
return true;
|
|
657
|
+
}
|
|
658
|
+
const segments = normalized.split("/").filter((s) => s.length > 0);
|
|
659
|
+
for (let i = segments.length - 1; i >= 0; i--) {
|
|
660
|
+
const ancestorPath = i === 0 ? "/" : "/" + segments.slice(0, i).join("/");
|
|
661
|
+
if (this.hasValueSubscription(ancestorPath)) {
|
|
662
|
+
return true;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
if (normalized !== "/" && this.hasValueSubscription("/")) {
|
|
666
|
+
return true;
|
|
667
|
+
}
|
|
668
|
+
return false;
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Check if there's a 'value' subscription at a path.
|
|
672
|
+
*/
|
|
673
|
+
hasValueSubscription(path) {
|
|
674
|
+
const pathSubs = this.subscriptions.get(path);
|
|
675
|
+
if (!pathSubs) return false;
|
|
676
|
+
const valueSubs = pathSubs.get("value");
|
|
677
|
+
return valueSubs !== void 0 && valueSubs.length > 0;
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Get a cached value if the path is covered by an active subscription.
|
|
681
|
+
*
|
|
682
|
+
* Returns { value, found: true } if we have cached data for this path
|
|
683
|
+
* (either exact match or extractable from a cached ancestor).
|
|
684
|
+
*
|
|
685
|
+
* Returns { value: undefined, found: false } if:
|
|
686
|
+
* - The path is not covered by any subscription, OR
|
|
687
|
+
* - We don't have cached data yet
|
|
688
|
+
*/
|
|
689
|
+
getCachedValue(path) {
|
|
690
|
+
const normalized = normalizePath(path);
|
|
691
|
+
if (!this.isPathCovered(normalized)) {
|
|
692
|
+
return { value: void 0, found: false };
|
|
693
|
+
}
|
|
694
|
+
return this.cache.get(normalized);
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Get the cache size (for testing/debugging).
|
|
698
|
+
*/
|
|
699
|
+
get cacheSize() {
|
|
700
|
+
return this.cache.size;
|
|
701
|
+
}
|
|
407
702
|
};
|
|
408
703
|
|
|
409
704
|
// src/connection/WebSocketClient.ts
|
|
@@ -544,53 +839,6 @@ var OnDisconnect = class {
|
|
|
544
839
|
}
|
|
545
840
|
};
|
|
546
841
|
|
|
547
|
-
// src/utils/path.ts
|
|
548
|
-
function normalizePath(path) {
|
|
549
|
-
if (!path || path === "/") return "/";
|
|
550
|
-
const segments = path.split("/").filter((segment) => segment.length > 0);
|
|
551
|
-
if (segments.length === 0) return "/";
|
|
552
|
-
return "/" + segments.join("/");
|
|
553
|
-
}
|
|
554
|
-
function joinPath(...segments) {
|
|
555
|
-
const allParts = [];
|
|
556
|
-
for (const segment of segments) {
|
|
557
|
-
if (!segment || segment === "/") continue;
|
|
558
|
-
const parts = segment.split("/").filter((p) => p.length > 0);
|
|
559
|
-
allParts.push(...parts);
|
|
560
|
-
}
|
|
561
|
-
if (allParts.length === 0) return "/";
|
|
562
|
-
return "/" + allParts.join("/");
|
|
563
|
-
}
|
|
564
|
-
function getParentPath(path) {
|
|
565
|
-
const normalized = normalizePath(path);
|
|
566
|
-
if (normalized === "/") return "/";
|
|
567
|
-
const lastSlash = normalized.lastIndexOf("/");
|
|
568
|
-
if (lastSlash <= 0) return "/";
|
|
569
|
-
return normalized.substring(0, lastSlash);
|
|
570
|
-
}
|
|
571
|
-
function getKey(path) {
|
|
572
|
-
const normalized = normalizePath(path);
|
|
573
|
-
if (normalized === "/") return null;
|
|
574
|
-
const lastSlash = normalized.lastIndexOf("/");
|
|
575
|
-
return normalized.substring(lastSlash + 1);
|
|
576
|
-
}
|
|
577
|
-
function getValueAtPath(obj, path) {
|
|
578
|
-
const normalized = normalizePath(path);
|
|
579
|
-
if (normalized === "/") return obj;
|
|
580
|
-
const segments = normalized.split("/").filter((s) => s.length > 0);
|
|
581
|
-
let current = obj;
|
|
582
|
-
for (const segment of segments) {
|
|
583
|
-
if (current === null || current === void 0) {
|
|
584
|
-
return void 0;
|
|
585
|
-
}
|
|
586
|
-
if (typeof current !== "object") {
|
|
587
|
-
return void 0;
|
|
588
|
-
}
|
|
589
|
-
current = current[segment];
|
|
590
|
-
}
|
|
591
|
-
return current;
|
|
592
|
-
}
|
|
593
|
-
|
|
594
842
|
// src/utils/pushid.ts
|
|
595
843
|
var PUSH_CHARS = "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz";
|
|
596
844
|
var lastPushTime = 0;
|
|
@@ -1028,6 +1276,7 @@ var LarkDatabase = class {
|
|
|
1028
1276
|
this._auth = null;
|
|
1029
1277
|
this._databaseId = null;
|
|
1030
1278
|
this._coordinatorUrl = null;
|
|
1279
|
+
this._volatilePaths = [];
|
|
1031
1280
|
this.ws = null;
|
|
1032
1281
|
// Event callbacks
|
|
1033
1282
|
this.connectCallbacks = /* @__PURE__ */ new Set();
|
|
@@ -1060,6 +1309,14 @@ var LarkDatabase = class {
|
|
|
1060
1309
|
}
|
|
1061
1310
|
return "lark://";
|
|
1062
1311
|
}
|
|
1312
|
+
/**
|
|
1313
|
+
* Get the volatile path patterns from the server.
|
|
1314
|
+
* These patterns indicate which paths should use unreliable transport.
|
|
1315
|
+
* WebSocket always uses reliable transport, but this is stored for future UDP support.
|
|
1316
|
+
*/
|
|
1317
|
+
get volatilePaths() {
|
|
1318
|
+
return this._volatilePaths;
|
|
1319
|
+
}
|
|
1063
1320
|
// ============================================
|
|
1064
1321
|
// Connection Management
|
|
1065
1322
|
// ============================================
|
|
@@ -1100,7 +1357,8 @@ var LarkDatabase = class {
|
|
|
1100
1357
|
r: requestId
|
|
1101
1358
|
};
|
|
1102
1359
|
this.send(joinMessage);
|
|
1103
|
-
await this.messageQueue.registerRequest(requestId);
|
|
1360
|
+
const volatilePaths = await this.messageQueue.registerRequest(requestId);
|
|
1361
|
+
this._volatilePaths = volatilePaths || [];
|
|
1104
1362
|
const jwtPayload = decodeJwtPayload(connectResponse.token);
|
|
1105
1363
|
this._auth = {
|
|
1106
1364
|
uid: jwtPayload.sub,
|
|
@@ -1153,6 +1411,7 @@ var LarkDatabase = class {
|
|
|
1153
1411
|
this._state = "disconnected";
|
|
1154
1412
|
this._auth = null;
|
|
1155
1413
|
this._databaseId = null;
|
|
1414
|
+
this._volatilePaths = [];
|
|
1156
1415
|
this._coordinatorUrl = null;
|
|
1157
1416
|
this.subscriptionManager.clear();
|
|
1158
1417
|
this.messageQueue.rejectAll(new Error("Connection closed"));
|
|
@@ -1296,12 +1555,28 @@ var LarkDatabase = class {
|
|
|
1296
1555
|
}
|
|
1297
1556
|
/**
|
|
1298
1557
|
* @internal Send a once (read) operation.
|
|
1558
|
+
*
|
|
1559
|
+
* This method first checks if the data is available in the local cache
|
|
1560
|
+
* (from an active subscription). If so, it returns the cached value
|
|
1561
|
+
* immediately without a server round-trip.
|
|
1562
|
+
*
|
|
1563
|
+
* Cache is only used when:
|
|
1564
|
+
* - No query parameters are specified (queries may filter/order differently)
|
|
1565
|
+
* - The path is covered by an active 'value' subscription
|
|
1566
|
+
* - We have cached data available
|
|
1299
1567
|
*/
|
|
1300
1568
|
async _sendOnce(path, query) {
|
|
1569
|
+
const normalizedPath = normalizePath(path) || "/";
|
|
1570
|
+
if (!query) {
|
|
1571
|
+
const cached = this.subscriptionManager.getCachedValue(normalizedPath);
|
|
1572
|
+
if (cached.found) {
|
|
1573
|
+
return new DataSnapshot(cached.value, path, this);
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1301
1576
|
const requestId = this.messageQueue.nextRequestId();
|
|
1302
1577
|
const message = {
|
|
1303
1578
|
o: "o",
|
|
1304
|
-
p:
|
|
1579
|
+
p: normalizedPath,
|
|
1305
1580
|
r: requestId
|
|
1306
1581
|
};
|
|
1307
1582
|
if (query) {
|
|
@@ -1386,6 +1661,21 @@ var LarkDatabase = class {
|
|
|
1386
1661
|
this.subscriptionManager.unsubscribeAll(path);
|
|
1387
1662
|
}
|
|
1388
1663
|
};
|
|
1664
|
+
|
|
1665
|
+
// src/utils/volatile.ts
|
|
1666
|
+
function isVolatilePath(path, patterns) {
|
|
1667
|
+
if (!patterns || patterns.length === 0) {
|
|
1668
|
+
return false;
|
|
1669
|
+
}
|
|
1670
|
+
const segments = path.replace(/^\//, "").split("/");
|
|
1671
|
+
return patterns.some((pattern) => {
|
|
1672
|
+
const patternSegments = pattern.split("/");
|
|
1673
|
+
if (segments.length !== patternSegments.length) {
|
|
1674
|
+
return false;
|
|
1675
|
+
}
|
|
1676
|
+
return patternSegments.every((p, i) => p === "*" || p === segments[i]);
|
|
1677
|
+
});
|
|
1678
|
+
}
|
|
1389
1679
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1390
1680
|
0 && (module.exports = {
|
|
1391
1681
|
DataSnapshot,
|
|
@@ -1393,6 +1683,7 @@ var LarkDatabase = class {
|
|
|
1393
1683
|
LarkDatabase,
|
|
1394
1684
|
LarkError,
|
|
1395
1685
|
OnDisconnect,
|
|
1396
|
-
generatePushId
|
|
1686
|
+
generatePushId,
|
|
1687
|
+
isVolatilePath
|
|
1397
1688
|
});
|
|
1398
1689
|
//# sourceMappingURL=index.js.map
|