@pawabase/client 0.1.4 → 0.3.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 +22 -7
- package/dist/cjs/agents.d.ts +87 -0
- package/dist/cjs/agents.d.ts.map +1 -0
- package/dist/cjs/agents.js +153 -0
- package/dist/cjs/agents.js.map +1 -0
- package/dist/cjs/ai.d.ts +5 -0
- package/dist/cjs/ai.d.ts.map +1 -1
- package/dist/cjs/ai.js +7 -0
- package/dist/cjs/ai.js.map +1 -1
- package/dist/cjs/index.d.ts +4 -2
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +5 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/manage/access.d.ts +1 -59
- package/dist/cjs/manage/access.d.ts.map +1 -1
- package/dist/cjs/manage/access.js +1 -82
- package/dist/cjs/manage/access.js.map +1 -1
- package/dist/cjs/manage/agents.d.ts +76 -0
- package/dist/cjs/manage/agents.d.ts.map +1 -0
- package/dist/cjs/manage/agents.js +89 -0
- package/dist/cjs/manage/agents.js.map +1 -0
- package/dist/cjs/manage/base.d.ts +6 -16
- package/dist/cjs/manage/base.d.ts.map +1 -1
- package/dist/cjs/manage/base.js +11 -11
- package/dist/cjs/manage/base.js.map +1 -1
- package/dist/cjs/manage/data.d.ts +13 -15
- package/dist/cjs/manage/data.d.ts.map +1 -1
- package/dist/cjs/manage/data.js +14 -21
- package/dist/cjs/manage/data.js.map +1 -1
- package/dist/cjs/manage/files.d.ts +5 -0
- package/dist/cjs/manage/files.d.ts.map +1 -1
- package/dist/cjs/manage/files.js +10 -1
- package/dist/cjs/manage/files.js.map +1 -1
- package/dist/cjs/manage/index.d.ts +9 -7
- package/dist/cjs/manage/index.d.ts.map +1 -1
- package/dist/cjs/manage/index.js +11 -8
- package/dist/cjs/manage/index.js.map +1 -1
- package/dist/cjs/manage/lifecycle.d.ts +15 -38
- package/dist/cjs/manage/lifecycle.d.ts.map +1 -1
- package/dist/cjs/manage/lifecycle.js +21 -52
- package/dist/cjs/manage/lifecycle.js.map +1 -1
- package/dist/cjs/manage/mail.d.ts +11 -1
- package/dist/cjs/manage/mail.d.ts.map +1 -1
- package/dist/cjs/manage/mail.js +11 -0
- package/dist/cjs/manage/mail.js.map +1 -1
- package/dist/cjs/manage/observe.d.ts +6 -0
- package/dist/cjs/manage/observe.d.ts.map +1 -1
- package/dist/cjs/manage/observe.js +8 -2
- package/dist/cjs/manage/observe.js.map +1 -1
- package/dist/cjs/realtime.d.ts +68 -0
- package/dist/cjs/realtime.d.ts.map +1 -1
- package/dist/cjs/realtime.js +153 -1
- package/dist/cjs/realtime.js.map +1 -1
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/agents.d.ts +87 -0
- package/dist/esm/agents.d.ts.map +1 -0
- package/dist/esm/agents.js +148 -0
- package/dist/esm/agents.js.map +1 -0
- package/dist/esm/ai.d.ts +5 -0
- package/dist/esm/ai.d.ts.map +1 -1
- package/dist/esm/ai.js +7 -0
- package/dist/esm/ai.js.map +1 -1
- package/dist/esm/index.d.ts +4 -2
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +2 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/manage/access.d.ts +1 -59
- package/dist/esm/manage/access.d.ts.map +1 -1
- package/dist/esm/manage/access.js +0 -80
- package/dist/esm/manage/access.js.map +1 -1
- package/dist/esm/manage/agents.d.ts +76 -0
- package/dist/esm/manage/agents.d.ts.map +1 -0
- package/dist/esm/manage/agents.js +82 -0
- package/dist/esm/manage/agents.js.map +1 -0
- package/dist/esm/manage/base.d.ts +6 -16
- package/dist/esm/manage/base.d.ts.map +1 -1
- package/dist/esm/manage/base.js +11 -11
- package/dist/esm/manage/base.js.map +1 -1
- package/dist/esm/manage/data.d.ts +13 -15
- package/dist/esm/manage/data.d.ts.map +1 -1
- package/dist/esm/manage/data.js +13 -18
- package/dist/esm/manage/data.js.map +1 -1
- package/dist/esm/manage/files.d.ts +5 -0
- package/dist/esm/manage/files.d.ts.map +1 -1
- package/dist/esm/manage/files.js +10 -1
- package/dist/esm/manage/files.js.map +1 -1
- package/dist/esm/manage/index.d.ts +9 -7
- package/dist/esm/manage/index.d.ts.map +1 -1
- package/dist/esm/manage/index.js +14 -11
- package/dist/esm/manage/index.js.map +1 -1
- package/dist/esm/manage/lifecycle.d.ts +15 -38
- package/dist/esm/manage/lifecycle.d.ts.map +1 -1
- package/dist/esm/manage/lifecycle.js +19 -50
- package/dist/esm/manage/lifecycle.js.map +1 -1
- package/dist/esm/manage/mail.d.ts +11 -1
- package/dist/esm/manage/mail.d.ts.map +1 -1
- package/dist/esm/manage/mail.js +12 -1
- package/dist/esm/manage/mail.js.map +1 -1
- package/dist/esm/manage/observe.d.ts +6 -0
- package/dist/esm/manage/observe.d.ts.map +1 -1
- package/dist/esm/manage/observe.js +8 -2
- package/dist/esm/manage/observe.js.map +1 -1
- package/dist/esm/realtime.d.ts +68 -0
- package/dist/esm/realtime.d.ts.map +1 -1
- package/dist/esm/realtime.js +151 -0
- package/dist/esm/realtime.js.map +1 -1
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/package.json +2 -2
- package/src/agents.ts +184 -0
- package/src/ai.ts +10 -1
- package/src/index.ts +5 -1
- package/src/manage/access.ts +0 -93
- package/src/manage/agents.ts +98 -0
- package/src/manage/base.ts +11 -11
- package/src/manage/data.ts +14 -19
- package/src/manage/files.ts +12 -1
- package/src/manage/index.ts +14 -11
- package/src/manage/lifecycle.ts +20 -64
- package/src/manage/mail.ts +15 -1
- package/src/manage/observe.ts +9 -2
- package/src/realtime.ts +173 -0
- package/src/version.ts +1 -1
package/src/realtime.ts
CHANGED
|
@@ -76,6 +76,8 @@ export class RealtimeClient {
|
|
|
76
76
|
private socket: WebSocketLike | null = null;
|
|
77
77
|
private currentStatus: RealtimeStatus = "idle";
|
|
78
78
|
private readonly channels = new Map<string, RealtimeChannel<any, any>>();
|
|
79
|
+
private readonly queries = new Map<string, LiveQuery<any>>();
|
|
80
|
+
private queryIds = 0;
|
|
79
81
|
private readonly pending = new Map<string, Pending>();
|
|
80
82
|
private readonly statusListeners = new Set<Handler<RealtimeStatus>>();
|
|
81
83
|
private refs = 0;
|
|
@@ -139,6 +141,33 @@ export class RealtimeClient {
|
|
|
139
141
|
this.channels.delete(name);
|
|
140
142
|
}
|
|
141
143
|
|
|
144
|
+
// ── live queries ──────────────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* A live query: the result of `GET /rest/v1/<resource>` with these options, sent again whenever it changes. The same filters, sort,
|
|
148
|
+
* search and list policy apply, so you only ever get rows you could read. The table needs **Broadcast changes** on.
|
|
149
|
+
*
|
|
150
|
+
* ```ts
|
|
151
|
+
* const open = pawabase.realtime.live<Task>("tasks", { filter: { done: "eq.false" }, sort: "-created_at", limit: 20 });
|
|
152
|
+
* open.onChange(({ data }) => render(data));
|
|
153
|
+
* // later: open.stop();
|
|
154
|
+
* ```
|
|
155
|
+
* It reopens by itself after a reconnect. `await open.ready()` resolves with the first result.
|
|
156
|
+
*/
|
|
157
|
+
live<T = Record<string, unknown>>(resource: string, query: LiveQueryOptions = {}, onChange?: Handler<LiveResult<T>>): LiveQuery<T> {
|
|
158
|
+
const id = `lq${++this.queryIds}`;
|
|
159
|
+
const live = new LiveQuery<T>(this, id, resource, query);
|
|
160
|
+
this.queries.set(id, live);
|
|
161
|
+
if (onChange) live.onChange(onChange);
|
|
162
|
+
void live.start();
|
|
163
|
+
return live;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** @internal */
|
|
167
|
+
dropQuery(id: string): void {
|
|
168
|
+
this.queries.delete(id);
|
|
169
|
+
}
|
|
170
|
+
|
|
142
171
|
// ── connection ────────────────────────────────────────────────────────
|
|
143
172
|
|
|
144
173
|
private url(token: string | null): string {
|
|
@@ -343,6 +372,9 @@ export class RealtimeClient {
|
|
|
343
372
|
for (const channel of [...this.channels.values()]) {
|
|
344
373
|
if (channel.wantsJoin) await channel.rejoin().catch(() => undefined);
|
|
345
374
|
}
|
|
375
|
+
for (const live of [...this.queries.values()]) {
|
|
376
|
+
await live.reopen().catch((error: unknown) => live.fail(error instanceof RealtimeError ? error : new RealtimeError("query_failed", String(error))));
|
|
377
|
+
}
|
|
346
378
|
}
|
|
347
379
|
|
|
348
380
|
// ── messages ──────────────────────────────────────────────────────────
|
|
@@ -396,6 +428,14 @@ export class RealtimeClient {
|
|
|
396
428
|
?.applyPresence((frame["joins"] as PresenceMember[]) ?? [], (frame["leaves"] as PresenceMember[]) ?? []);
|
|
397
429
|
return;
|
|
398
430
|
}
|
|
431
|
+
case "query_result": {
|
|
432
|
+
this.queries.get(String(frame["id"]))?.deliver(frame);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
case "query_closed": {
|
|
436
|
+
this.queries.get(String(frame["id"]))?.fail(new RealtimeError("query_closed", String(frame["reason"] ?? "The live query was closed.")));
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
399
439
|
case "pong":
|
|
400
440
|
case "welcome":
|
|
401
441
|
return;
|
|
@@ -549,6 +589,139 @@ export class RealtimeClient {
|
|
|
549
589
|
}
|
|
550
590
|
}
|
|
551
591
|
|
|
592
|
+
/** What a live query reads: the same options as listing a resource over REST. */
|
|
593
|
+
export interface LiveQueryOptions {
|
|
594
|
+
/** `{ status: "eq.paid", total: "gte.1000" }`; a bare value means `eq`. */
|
|
595
|
+
filter?: Record<string, string | number | boolean>;
|
|
596
|
+
/** `"-created_at,title"`: a leading `-` sorts descending. */
|
|
597
|
+
sort?: string;
|
|
598
|
+
/** `"id,title,done"`: the fields to return. */
|
|
599
|
+
select?: string;
|
|
600
|
+
/** Full-text search over the resource's search fields. */
|
|
601
|
+
q?: string;
|
|
602
|
+
/** Rows per result, 1–100. Default 20. */
|
|
603
|
+
limit?: number;
|
|
604
|
+
offset?: number;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/** One result of a live query. `total` is `null` when a per-row policy makes the count unreliable. */
|
|
608
|
+
export interface LiveResult<T> {
|
|
609
|
+
data: T[];
|
|
610
|
+
total: number | null;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/** An open live query. Read `data` at any time; `onChange` hears every new result; `stop()` ends it. */
|
|
614
|
+
export class LiveQuery<T = Record<string, unknown>> {
|
|
615
|
+
private readonly changeHandlers = new Set<Handler<LiveResult<T>>>();
|
|
616
|
+
private readonly errorHandlers = new Set<Handler<RealtimeError>>();
|
|
617
|
+
private current: LiveResult<T> | null = null;
|
|
618
|
+
private stopped = false;
|
|
619
|
+
private firstResolve: ((value: LiveResult<T>) => void) | null = null;
|
|
620
|
+
private firstReject: ((error: unknown) => void) | null = null;
|
|
621
|
+
private readonly first: Promise<LiveResult<T>>;
|
|
622
|
+
|
|
623
|
+
constructor(
|
|
624
|
+
private readonly client: RealtimeClient,
|
|
625
|
+
readonly id: string,
|
|
626
|
+
readonly resource: string,
|
|
627
|
+
readonly query: LiveQueryOptions,
|
|
628
|
+
) {
|
|
629
|
+
this.first = new Promise((resolve, reject) => {
|
|
630
|
+
this.firstResolve = resolve;
|
|
631
|
+
this.firstReject = reject;
|
|
632
|
+
});
|
|
633
|
+
this.first.catch(() => undefined); // a caller that never awaits ready() must not see an unhandled rejection
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/** The latest rows (empty until the first result). */
|
|
637
|
+
get data(): T[] {
|
|
638
|
+
return this.current?.data ?? [];
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
get total(): number | null {
|
|
642
|
+
return this.current?.total ?? null;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/** Resolves with the first result, or rejects if the query is refused. */
|
|
646
|
+
ready(): Promise<LiveResult<T>> {
|
|
647
|
+
return this.first;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
onChange(handler: Handler<LiveResult<T>>): () => void {
|
|
651
|
+
this.changeHandlers.add(handler);
|
|
652
|
+
if (this.current) handler(this.current);
|
|
653
|
+
return () => void this.changeHandlers.delete(handler);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/** Called when the query is refused or closed by the server (for example, the caller lost access). */
|
|
657
|
+
onError(handler: Handler<RealtimeError>): () => void {
|
|
658
|
+
this.errorHandlers.add(handler);
|
|
659
|
+
return () => void this.errorHandlers.delete(handler);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/** @internal */
|
|
663
|
+
async start(): Promise<void> {
|
|
664
|
+
try {
|
|
665
|
+
// Already open: ask now. Not yet: the client asks for every live query as soon as it is welcomed.
|
|
666
|
+
if (this.client.status === "open") await this.reopen();
|
|
667
|
+
else await this.client.whenOpen();
|
|
668
|
+
} catch (error) {
|
|
669
|
+
this.fail(error instanceof RealtimeError ? error : new RealtimeError("query_failed", String(error)));
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/** @internal Ask the server for the query (again, after a reconnect). */
|
|
674
|
+
async reopen(): Promise<void> {
|
|
675
|
+
if (this.stopped) return;
|
|
676
|
+
await this.client.request({ type: "query", id: this.id, resource: this.resource, query: this.query });
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/** @internal */
|
|
680
|
+
deliver(frame: Record<string, unknown>): void {
|
|
681
|
+
if (this.stopped) return;
|
|
682
|
+
const result: LiveResult<T> = {
|
|
683
|
+
data: Array.isArray(frame["data"]) ? (frame["data"] as T[]) : [],
|
|
684
|
+
total: typeof frame["total"] === "number" ? frame["total"] : null,
|
|
685
|
+
};
|
|
686
|
+
this.current = result;
|
|
687
|
+
if (this.firstResolve) {
|
|
688
|
+
this.firstResolve(result);
|
|
689
|
+
this.firstResolve = this.firstReject = null;
|
|
690
|
+
}
|
|
691
|
+
for (const handler of [...this.changeHandlers]) {
|
|
692
|
+
try {
|
|
693
|
+
handler(result);
|
|
694
|
+
} catch {
|
|
695
|
+
/* a listener must not stop the others */
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/** @internal */
|
|
701
|
+
fail(error: RealtimeError): void {
|
|
702
|
+
if (this.firstReject) {
|
|
703
|
+
this.firstReject(error);
|
|
704
|
+
this.firstResolve = this.firstReject = null;
|
|
705
|
+
}
|
|
706
|
+
for (const handler of [...this.errorHandlers]) {
|
|
707
|
+
try {
|
|
708
|
+
handler(error);
|
|
709
|
+
} catch {
|
|
710
|
+
/* ignore */
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
if (error.code === "query_closed") this.stop();
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/** End the live query. */
|
|
717
|
+
stop(): void {
|
|
718
|
+
if (this.stopped) return;
|
|
719
|
+
this.stopped = true;
|
|
720
|
+
this.client.dropQuery(this.id);
|
|
721
|
+
void this.client.request({ type: "unquery", id: this.id }).catch(() => undefined);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
552
725
|
/** One named channel: receive messages and presence, publish, and see who is here. */
|
|
553
726
|
export class RealtimeChannel<T = unknown, M = Record<string, unknown>> {
|
|
554
727
|
private readonly handlers = new Map<string, Set<Handler<any>>>();
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = "0.
|
|
1
|
+
export const VERSION = "0.2.0";
|