lazypock 0.8.4 → 0.9.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/README.md +60 -10
- package/dist/index.cjs +87 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +65 -8
- package/dist/index.d.ts +65 -8
- package/dist/index.global.js +87 -29
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +87 -29
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/collection.ts +77 -9
- package/src/index.ts +1 -1
- package/src/lazypock.ts +10 -0
- package/src/realtime.ts +79 -11
package/README.md
CHANGED
|
@@ -329,10 +329,13 @@ PocketBase-style service for the collections themselves (admin):
|
|
|
329
329
|
|
|
330
330
|
- `realtime.connect(opts)` — Connect to WebSocket
|
|
331
331
|
- `realtime.disconnect()` — Disconnect
|
|
332
|
-
- `realtime.
|
|
332
|
+
- `realtime.refresh()` — Reconnect with the current auth token (auto-called on auth change)
|
|
333
|
+
- `realtime.setTokenProvider(fn)` — Register a token provider consulted at every connect
|
|
334
|
+
- `realtime.subscribe(topic, callback, joinPayload?)` — Low-level subscribe (any topic, e.g. `collection:posts` or custom `chat:room1`)
|
|
333
335
|
- `realtime.unsubscribe(topic, callback?)` — Low-level unsubscribe
|
|
334
|
-
- `
|
|
335
|
-
- `collection(name).
|
|
336
|
+
- `realtime.unsubscribeByPrefix(prefix)` — Remove all subscriptions under a topic prefix
|
|
337
|
+
- `collection(name).subscribe(topicOrCallback?, callback?, options?)` — PocketBase-style record subscription; callback receives `{ action, record }` (full record); returns unsubscribe fn
|
|
338
|
+
- `collection(name).unsubscribe(topic?)` — Unsubscribe `'*'`, a record id, or all subscriptions
|
|
336
339
|
|
|
337
340
|
### CollectionService
|
|
338
341
|
|
|
@@ -346,8 +349,8 @@ Returned by `client.collection(name)`.
|
|
|
346
349
|
- `create(data, options?)` — Create record
|
|
347
350
|
- `update(id, data, options?)` — Update record
|
|
348
351
|
- `delete(id, options?)` — Delete record
|
|
349
|
-
- `subscribe(callback
|
|
350
|
-
- `unsubscribe(
|
|
352
|
+
- `subscribe(topicOrCallback?, callback?, options?)` — PocketBase-style: `subscribe(cb)`, `subscribe('*', cb)`, `subscribe('id', cb)`, or with `{ expand }` options (legacy `subscribe(cb, recordId)` still works)
|
|
353
|
+
- `unsubscribe(topic?)` — `unsubscribe('*')` / `unsubscribe('id')` / `unsubscribe()` (all)
|
|
351
354
|
- `authWithPassword(identity, password, options?)` — Login to this auth collection
|
|
352
355
|
- `authRefresh(options?)` — Refresh token for this auth collection
|
|
353
356
|
- `authMethods(options?)` — Get available auth methods
|
|
@@ -533,23 +536,56 @@ The SDK automatically refreshes expired auth tokens. When a token expires, the n
|
|
|
533
536
|
|
|
534
537
|
## Real-time Subscriptions
|
|
535
538
|
|
|
539
|
+
### PocketBase-style `subscribe` / `unsubscribe`
|
|
540
|
+
|
|
541
|
+
Collection subscriptions use the same argument order as PocketBase. The
|
|
542
|
+
callback always receives the **full record** (all fields) — `select()`
|
|
543
|
+
projections only affect `getList`/`getOne`, never subscriptions:
|
|
544
|
+
|
|
536
545
|
```typescript
|
|
537
|
-
// Subscribe to all
|
|
546
|
+
// Subscribe to all records (three equivalent forms)
|
|
538
547
|
const off = client.collection('posts').subscribe((event) => {
|
|
539
548
|
console.log(event.action); // 'create' | 'update' | 'delete'
|
|
540
|
-
console.log(event.record);
|
|
549
|
+
console.log(event.record); // full record — all fields
|
|
541
550
|
});
|
|
551
|
+
client.collection('posts').subscribe('*', (event) => { ... });
|
|
542
552
|
|
|
543
|
-
// Subscribe to a
|
|
544
|
-
client.collection('posts').subscribe((event) => { ... }
|
|
553
|
+
// Subscribe to a single record
|
|
554
|
+
client.collection('posts').subscribe('RECORD_ID', (event) => { ... });
|
|
555
|
+
|
|
556
|
+
// With options — forwarded to the server channel join payload
|
|
557
|
+
// (available to onRealtimeSubscribeRequest hooks)
|
|
558
|
+
client.collection('posts').subscribe('*', (event) => { ... }, {
|
|
559
|
+
expand: 'author',
|
|
560
|
+
customKey: 'any extra key is forwarded',
|
|
561
|
+
});
|
|
545
562
|
|
|
546
563
|
// Unsubscribe
|
|
547
|
-
client.collection('posts').unsubscribe();
|
|
564
|
+
client.collection('posts').unsubscribe('*'); // wildcard only
|
|
565
|
+
client.collection('posts').unsubscribe('RECORD_ID'); // one record
|
|
566
|
+
client.collection('posts').unsubscribe(); // everything in this collection
|
|
548
567
|
|
|
549
568
|
// ...or call the returned unsubscribe function for one-shot listeners:
|
|
550
569
|
off();
|
|
551
570
|
```
|
|
552
571
|
|
|
572
|
+
The legacy callback-first form (`subscribe(cb, recordId)`) still works.
|
|
573
|
+
`headers` in the options object is accepted for PocketBase signature
|
|
574
|
+
compatibility but is not sent over the WebSocket.
|
|
575
|
+
|
|
576
|
+
### Auth tokens are attached automatically
|
|
577
|
+
|
|
578
|
+
The WebSocket automatically uses the current auth token (`authStore.token`)
|
|
579
|
+
and **reconnects when auth changes** (login / logout / token refresh) — so
|
|
580
|
+
subscriptions to rule-protected collections and admin channels work without
|
|
581
|
+
any manual socket management:
|
|
582
|
+
|
|
583
|
+
```typescript
|
|
584
|
+
await client.login('admin@example.com', 'secret');
|
|
585
|
+
// The socket reconnects with the new token; existing subscriptions re-join.
|
|
586
|
+
client.collection('private_feed').subscribe('*', (e) => { ... });
|
|
587
|
+
```
|
|
588
|
+
|
|
553
589
|
### Anonymous / rule-based realtime
|
|
554
590
|
|
|
555
591
|
Realtime subscriptions honor your API **and list rules** — matching PocketBase
|
|
@@ -565,6 +601,20 @@ const off = client.collection('public_feed').subscribe((e) => {
|
|
|
565
601
|
});
|
|
566
602
|
```
|
|
567
603
|
|
|
604
|
+
### Custom channels
|
|
605
|
+
|
|
606
|
+
Any topic string can be subscribed to via the low-level realtime service
|
|
607
|
+
(PocketBase behavior — anonymous joins allowed, broadcasts come from the
|
|
608
|
+
server side):
|
|
609
|
+
|
|
610
|
+
```typescript
|
|
611
|
+
// Subscribe to an arbitrary topic
|
|
612
|
+
const off = client.realtime.subscribe('chat:room1', (event) => {
|
|
613
|
+
console.log(event.event, event.payload);
|
|
614
|
+
});
|
|
615
|
+
off(); // or client.realtime.unsubscribe('chat:room1');
|
|
616
|
+
```
|
|
617
|
+
|
|
568
618
|
|
|
569
619
|
## Releasing (automatic)
|
|
570
620
|
|
package/dist/index.cjs
CHANGED
|
@@ -817,25 +817,24 @@ var CollectionService = class _CollectionService {
|
|
|
817
817
|
derived.fieldsPreset = this.fieldsPreset;
|
|
818
818
|
return derived;
|
|
819
819
|
}
|
|
820
|
-
|
|
821
|
-
/**
|
|
822
|
-
* Subscribe to realtime changes for this collection.
|
|
823
|
-
* The event's `action` is one of `"create" | "update" | "delete"`.
|
|
824
|
-
*
|
|
825
|
-
* Access is governed by the collection's `listRule` (PocketBase semantics):
|
|
826
|
-
* public collections allow anonymous subscriptions; other collections
|
|
827
|
-
* require a matching logged-in user or superuser.
|
|
828
|
-
*
|
|
829
|
-
* @param callback Received on every record change.
|
|
830
|
-
* @param recordId Optional — subscribe to a single record instead of `*`.
|
|
831
|
-
* @returns A function that unsubscribes this callback.
|
|
832
|
-
*/
|
|
833
|
-
subscribe(callback, recordId) {
|
|
820
|
+
subscribe(topicOrCallback, maybeCallback, options) {
|
|
834
821
|
if (!this.realtime) {
|
|
835
822
|
console.warn("[lazypock] No realtime service configured.");
|
|
836
823
|
return () => {
|
|
837
824
|
};
|
|
838
825
|
}
|
|
826
|
+
let recordId;
|
|
827
|
+
let callback;
|
|
828
|
+
let joinPayload;
|
|
829
|
+
if (typeof topicOrCallback === "function") {
|
|
830
|
+
callback = topicOrCallback;
|
|
831
|
+
if (typeof maybeCallback === "string") recordId = maybeCallback;
|
|
832
|
+
} else {
|
|
833
|
+
callback = maybeCallback;
|
|
834
|
+
recordId = topicOrCallback === "*" ? void 0 : topicOrCallback;
|
|
835
|
+
const { headers: _ignored, ...rest } = options ?? {};
|
|
836
|
+
if (Object.keys(rest).length > 0) joinPayload = rest;
|
|
837
|
+
}
|
|
839
838
|
const topic = "collection:" + this.collectionName + (recordId ? ":" + recordId : "");
|
|
840
839
|
const handler = (raw) => {
|
|
841
840
|
const record = raw.payload?.["record"] ?? {};
|
|
@@ -846,15 +845,23 @@ var CollectionService = class _CollectionService {
|
|
|
846
845
|
});
|
|
847
846
|
};
|
|
848
847
|
this.realtime.ensureConnected();
|
|
849
|
-
this.realtime.subscribe(topic, handler);
|
|
848
|
+
this.realtime.subscribe(topic, handler, joinPayload);
|
|
850
849
|
return () => this.realtime?.unsubscribe(topic, handler);
|
|
851
850
|
}
|
|
852
851
|
/**
|
|
853
|
-
* Unsubscribe
|
|
854
|
-
*
|
|
852
|
+
* Unsubscribe from realtime changes (PocketBase-compatible):
|
|
853
|
+
* - `unsubscribe()` — remove **all** subscriptions of this collection
|
|
854
|
+
* - `unsubscribe('*')` — remove wildcard subscriptions
|
|
855
|
+
* - `unsubscribe('RECORD_ID')` — remove that record's subscriptions
|
|
855
856
|
*/
|
|
856
857
|
unsubscribe(recordId) {
|
|
857
|
-
|
|
858
|
+
if (recordId === void 0) {
|
|
859
|
+
this.realtime?.unsubscribeByPrefix(
|
|
860
|
+
"collection:" + this.collectionName
|
|
861
|
+
);
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
const topic = "collection:" + this.collectionName + (recordId === "*" ? "" : ":" + recordId);
|
|
858
865
|
this.realtime?.unsubscribe(topic);
|
|
859
866
|
}
|
|
860
867
|
// ── Auth Collection Methods ──
|
|
@@ -921,6 +928,7 @@ var RealtimeService = class {
|
|
|
921
928
|
this.reconnectAttempt = 0;
|
|
922
929
|
this.maxReconnectDelay = 5e3;
|
|
923
930
|
this.url = "";
|
|
931
|
+
this.tokenProvider = null;
|
|
924
932
|
// ── Heartbeat ──
|
|
925
933
|
this.heartbeatInterval = null;
|
|
926
934
|
}
|
|
@@ -946,6 +954,32 @@ var RealtimeService = class {
|
|
|
946
954
|
setUrl(url) {
|
|
947
955
|
this.url = url;
|
|
948
956
|
}
|
|
957
|
+
/**
|
|
958
|
+
* Register a token provider consulted at every (re)connect.
|
|
959
|
+
* When set, it takes precedence over the token passed to {@link connect}.
|
|
960
|
+
*/
|
|
961
|
+
setTokenProvider(provider) {
|
|
962
|
+
this.tokenProvider = provider;
|
|
963
|
+
}
|
|
964
|
+
/**
|
|
965
|
+
* Reconnect the socket immediately with the current token.
|
|
966
|
+
* Called by the SDK when auth changes (login/logout/token refresh) so
|
|
967
|
+
* private-channel joins are authorized with the new credentials. No-op
|
|
968
|
+
* when the socket has never been opened and nothing is subscribed.
|
|
969
|
+
*/
|
|
970
|
+
refresh() {
|
|
971
|
+
if (typeof WebSocket === "undefined") return;
|
|
972
|
+
if (!this.ws && this.subscriptions.size === 0) return;
|
|
973
|
+
this.clearReconnectTimer();
|
|
974
|
+
const ws = this.ws;
|
|
975
|
+
this.ws = null;
|
|
976
|
+
if (ws) {
|
|
977
|
+
ws.onclose = null;
|
|
978
|
+
ws.close();
|
|
979
|
+
}
|
|
980
|
+
this.reconnectAttempt = 0;
|
|
981
|
+
this.doConnect();
|
|
982
|
+
}
|
|
949
983
|
/*
|
|
950
984
|
* Ensure the socket is connected, then subscribe.
|
|
951
985
|
* Used by collection-level convenience wrappers so a connection is opened
|
|
@@ -969,15 +1003,30 @@ var RealtimeService = class {
|
|
|
969
1003
|
this.ws = null;
|
|
970
1004
|
}
|
|
971
1005
|
/**
|
|
972
|
-
* Subscribe to a topic (e.g. "collection:posts" or "
|
|
1006
|
+
* Subscribe to a topic (e.g. "collection:posts" or "custom:chat-room").
|
|
973
1007
|
* The backend Channel authorizes via listRule on join.
|
|
1008
|
+
*
|
|
1009
|
+
* @param joinPayload Optional payload forwarded with the channel join
|
|
1010
|
+
* (available to the server's join callback / hooks, e.g. `expand`).
|
|
974
1011
|
*/
|
|
975
|
-
subscribe(topic, callback) {
|
|
1012
|
+
subscribe(topic, callback, joinPayload) {
|
|
976
1013
|
const subs = this.subscriptions.get(topic) || [];
|
|
977
|
-
subs.push({ topic, callback });
|
|
1014
|
+
subs.push({ topic, callback, joinPayload });
|
|
978
1015
|
this.subscriptions.set(topic, subs);
|
|
979
1016
|
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
980
|
-
this.joinTopic(topic);
|
|
1017
|
+
this.joinTopic(topic, joinPayload);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
/**
|
|
1021
|
+
* Remove all subscriptions for topics under `prefix` (the topic itself or
|
|
1022
|
+
* any topic starting with `prefix + ":"`). Used by collection-level
|
|
1023
|
+
* `unsubscribe()` to drop every subscription of a collection.
|
|
1024
|
+
*/
|
|
1025
|
+
unsubscribeByPrefix(prefix) {
|
|
1026
|
+
for (const topic of [...this.subscriptions.keys()]) {
|
|
1027
|
+
if (topic === prefix || topic.startsWith(prefix + ":")) {
|
|
1028
|
+
this.subscriptions.delete(topic);
|
|
1029
|
+
}
|
|
981
1030
|
}
|
|
982
1031
|
}
|
|
983
1032
|
/**
|
|
@@ -996,9 +1045,14 @@ var RealtimeService = class {
|
|
|
996
1045
|
}
|
|
997
1046
|
}
|
|
998
1047
|
resubscribeAll() {
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1048
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1049
|
+
for (const entries of this.subscriptions.values()) {
|
|
1050
|
+
for (const entry of entries) {
|
|
1051
|
+
if (seen.has(entry.topic)) continue;
|
|
1052
|
+
seen.add(entry.topic);
|
|
1053
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
1054
|
+
this.joinTopic(entry.topic, entry.joinPayload);
|
|
1055
|
+
}
|
|
1002
1056
|
}
|
|
1003
1057
|
}
|
|
1004
1058
|
}
|
|
@@ -1010,8 +1064,9 @@ var RealtimeService = class {
|
|
|
1010
1064
|
return;
|
|
1011
1065
|
}
|
|
1012
1066
|
let url = this.url;
|
|
1013
|
-
|
|
1014
|
-
|
|
1067
|
+
const token = this.tokenProvider ? this.tokenProvider() : this.token;
|
|
1068
|
+
if (token) {
|
|
1069
|
+
url += (url.includes("?") ? "&" : "?") + "token=" + encodeURIComponent(token);
|
|
1015
1070
|
}
|
|
1016
1071
|
this.ws = new WebSocket(url);
|
|
1017
1072
|
this.ws.onopen = () => {
|
|
@@ -1058,12 +1113,12 @@ var RealtimeService = class {
|
|
|
1058
1113
|
}
|
|
1059
1114
|
}
|
|
1060
1115
|
}
|
|
1061
|
-
joinTopic(topic) {
|
|
1116
|
+
joinTopic(topic, joinPayload) {
|
|
1062
1117
|
const ref = this.nextRef();
|
|
1063
1118
|
const msg = JSON.stringify({
|
|
1064
1119
|
topic,
|
|
1065
1120
|
event: "phx_join",
|
|
1066
|
-
payload: {},
|
|
1121
|
+
payload: joinPayload ?? {},
|
|
1067
1122
|
ref
|
|
1068
1123
|
});
|
|
1069
1124
|
this.ws?.send(msg);
|
|
@@ -1635,6 +1690,9 @@ var LazypockClient = class {
|
|
|
1635
1690
|
if (!options.realtime) {
|
|
1636
1691
|
this.realtime.setUrl(wsUrlFromBaseUrl(baseUrl));
|
|
1637
1692
|
}
|
|
1693
|
+
this.realtime.setTokenProvider(() => this.authStore.token);
|
|
1694
|
+
this.authStore.onChange(() => this.realtime.refresh());
|
|
1695
|
+
this.authReady.then(() => this.realtime.refresh());
|
|
1638
1696
|
this.files = new FilesService(this.http);
|
|
1639
1697
|
this.collections = new CollectionsService(this.http, this.realtime);
|
|
1640
1698
|
if (options.types?.schemas) {
|