naro-js 0.5.0 → 0.6.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 +248 -20
- package/dist/auth/storage.d.ts +16 -6
- package/dist/client.d.ts +24 -0
- package/dist/errors.d.ts +70 -0
- package/dist/http.d.ts +2 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2384 -42
- package/dist/realtime/channel.d.ts +64 -0
- package/dist/realtime/client.d.ts +18 -0
- package/dist/realtime/close.d.ts +44 -0
- package/dist/realtime/identity.d.ts +68 -0
- package/dist/realtime/presence.d.ts +27 -0
- package/dist/realtime/protocol.d.ts +78 -0
- package/dist/realtime/socket.d.ts +96 -0
- package/dist/realtime/types.d.ts +89 -0
- package/dist/storage/client.d.ts +26 -2
- package/dist/storage/types.d.ts +19 -0
- package/package.json +2 -2
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { DatabaseShape, RowOf, TableName, TablesOf, UntypedDatabase } from "../types.js";
|
|
2
|
+
import type { PostgresChangesFilter, RealtimeBroadcastMessage, RealtimeChannelOptions, RealtimeChannelState, RealtimePostgresChangesPayload, RealtimePresenceJoinPayload, RealtimePresenceLeavePayload, RealtimePresenceState, RealtimeResult, RealtimeSendResult, RealtimeStatusCallback, RealtimeSystemMessage } from "./types.js";
|
|
3
|
+
/** The row type a change of `Table` carries: the generated Row, or any for `*` and untyped clients. */
|
|
4
|
+
type ChangeRow<Database, Table> = Table extends TableName<Database> ? RowOf<TablesOf<Database>[Table]> : Record<string, unknown>;
|
|
5
|
+
export declare function warnInDevelopment(message: string): void;
|
|
6
|
+
/**
|
|
7
|
+
* One subscription as the join carries it (original spec §7.3): keys this
|
|
8
|
+
* server does not take go too, so the hub names them (결정 15); an empty
|
|
9
|
+
* filter is none.
|
|
10
|
+
*/
|
|
11
|
+
export declare function changeSpec(filter: PostgresChangesFilter): Record<string, unknown>;
|
|
12
|
+
/** The wire's `data` (Supabase's `type`, `record`, `old_record`) as supabase-js hands it over. */
|
|
13
|
+
export declare function changePayload(data: Record<string, unknown>): RealtimePostgresChangesPayload;
|
|
14
|
+
export declare class RealtimeChannel<Database extends DatabaseShape<Database> = UntypedDatabase> {
|
|
15
|
+
#private;
|
|
16
|
+
readonly name: string;
|
|
17
|
+
readonly topic: string;
|
|
18
|
+
readonly isPrivate: boolean;
|
|
19
|
+
readonly options: RealtimeChannelOptions;
|
|
20
|
+
get state(): RealtimeChannelState;
|
|
21
|
+
/**
|
|
22
|
+
* A listener (original spec §15), as supabase-js names them. Row changes
|
|
23
|
+
* and presence count from the next join; broadcast and system at once.
|
|
24
|
+
*/
|
|
25
|
+
on<Table extends TableName<Database> | "*" = TableName<Database> | "*">(type: "postgres_changes", filter: PostgresChangesFilter<Table>, callback: (payload: RealtimePostgresChangesPayload<ChangeRow<Database, Table>>) => void): this;
|
|
26
|
+
on(type: "broadcast", filter: {
|
|
27
|
+
event: string;
|
|
28
|
+
}, callback: (message: RealtimeBroadcastMessage) => void): this;
|
|
29
|
+
on(type: "presence", filter: {
|
|
30
|
+
event: "sync";
|
|
31
|
+
}, callback: () => void): this;
|
|
32
|
+
on(type: "presence", filter: {
|
|
33
|
+
event: "join";
|
|
34
|
+
}, callback: (payload: RealtimePresenceJoinPayload) => void): this;
|
|
35
|
+
on(type: "presence", filter: {
|
|
36
|
+
event: "leave";
|
|
37
|
+
}, callback: (payload: RealtimePresenceLeavePayload) => void): this;
|
|
38
|
+
on(type: "system", filter: Record<string, never>, callback: (message: RealtimeSystemMessage) => void): this;
|
|
39
|
+
subscribe(callback?: RealtimeStatusCallback): this;
|
|
40
|
+
unsubscribe(): Promise<RealtimeResult>;
|
|
41
|
+
/**
|
|
42
|
+
* Broadcasts `message` on the join, or over HTTP before it: the server's
|
|
43
|
+
* answer, `lost` when the connection went before it (RealtimeSendResult).
|
|
44
|
+
*/
|
|
45
|
+
send(message: RealtimeBroadcastMessage): Promise<RealtimeSendResult>;
|
|
46
|
+
/**
|
|
47
|
+
* Tracks `state` as this connection's presence here (original spec
|
|
48
|
+
* §11.1): the hub's answer. Only on a join — before it, NOT_JOINED, and
|
|
49
|
+
* nothing is kept for later (결정 18). It goes as its JSON reads back, and
|
|
50
|
+
* that copy, not the app's object, is what a later join sends again.
|
|
51
|
+
* While who the connection is is not settled (a new token in flight, a
|
|
52
|
+
* retry), it waits until it is (정정 (k)): the hub may yet refuse the new
|
|
53
|
+
* token for now and keep the last user, whose presence it would be.
|
|
54
|
+
*/
|
|
55
|
+
track(state: Record<string, unknown>): Promise<RealtimeSendResult>;
|
|
56
|
+
/**
|
|
57
|
+
* Takes this connection's presence off the channel: the hub's answer, or
|
|
58
|
+
* none when not joined. After the app's tracks that wait, if any.
|
|
59
|
+
*/
|
|
60
|
+
untrack(): Promise<RealtimeSendResult>;
|
|
61
|
+
/** The presences of the channel, by key (결정 16): a copy. */
|
|
62
|
+
presenceState(): RealtimePresenceState;
|
|
63
|
+
}
|
|
64
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AuthClient } from "../auth/client.js";
|
|
2
|
+
import type { HttpConfig } from "../http.js";
|
|
3
|
+
import type { DatabaseShape, UntypedDatabase } from "../types.js";
|
|
4
|
+
import { RealtimeChannel } from "./channel.js";
|
|
5
|
+
import type { RealtimeChannelOptions, RealtimeOptions, RealtimeResult } from "./types.js";
|
|
6
|
+
export declare class RealtimeClient<Database extends DatabaseShape<Database> = UntypedDatabase> {
|
|
7
|
+
#private;
|
|
8
|
+
constructor(input: {
|
|
9
|
+
http: HttpConfig;
|
|
10
|
+
auth: AuthClient;
|
|
11
|
+
service: boolean;
|
|
12
|
+
options: RealtimeOptions;
|
|
13
|
+
});
|
|
14
|
+
channel(name: string, options?: RealtimeChannelOptions): RealtimeChannel<Database>;
|
|
15
|
+
getChannels(): RealtimeChannel<Database>[];
|
|
16
|
+
removeChannel(channel: RealtimeChannel<Database>): Promise<RealtimeResult>;
|
|
17
|
+
removeAllChannels(): Promise<RealtimeResult>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** 1, 2, 5, then 10 seconds, each times 0.5–1.5 (original spec §15). */
|
|
2
|
+
export declare const BACKOFF_STEPS_MS: readonly [1000, 2000, 5000, 10000];
|
|
3
|
+
/** 4029's floor: a refused client must not knock faster (design §8.5). */
|
|
4
|
+
export declare const RATE_LIMITED_MIN_MS = 5000;
|
|
5
|
+
/** The dispatcher's per-address window is a fixed minute (3-2c-2 결정 9). */
|
|
6
|
+
export declare const IP_LIMIT_WAIT_MS = 60000;
|
|
7
|
+
/**
|
|
8
|
+
* A restart closes everyone at once, so every 4503 return waits 0–10 s (design
|
|
9
|
+
* D6). The wait does not grow with the attempt count or the unopened tries:
|
|
10
|
+
* during a lasting BACKEND_UNAVAILABLE a client comes back about every 5 s on
|
|
11
|
+
* average, without slowing down.
|
|
12
|
+
*/
|
|
13
|
+
export declare const RESTART_JITTER_MS = 10000;
|
|
14
|
+
/** Tries in a row that never opened before the client slows down (known-issues #271). */
|
|
15
|
+
export declare const UNOPENED_SLOWDOWN_AFTER = 6;
|
|
16
|
+
export declare const UNOPENED_DELAY_MS = 60000;
|
|
17
|
+
export type CloseAction = {
|
|
18
|
+
kind: "stop";
|
|
19
|
+
} | {
|
|
20
|
+
kind: "retry";
|
|
21
|
+
minMs: number;
|
|
22
|
+
} | {
|
|
23
|
+
kind: "session";
|
|
24
|
+
} | {
|
|
25
|
+
kind: "restart";
|
|
26
|
+
} | {
|
|
27
|
+
kind: "wait";
|
|
28
|
+
ms: number;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* `reason` is the close's reason, or the code of the `system` frame sent
|
|
32
|
+
* just before it — the two are the same string (data-core `closeFrame`, the
|
|
33
|
+
* dispatcher's `realtime-refuse.ts`). A code this table does not know is
|
|
34
|
+
* tried again like 4500: a server newer than this SDK can always refuse again.
|
|
35
|
+
*/
|
|
36
|
+
export declare function closeAction(code: number, reason: string): CloseAction;
|
|
37
|
+
export declare function backoffMs(attempt: number, random: () => number): number;
|
|
38
|
+
/** How long to wait before the next try after `action`. */
|
|
39
|
+
export declare function delayAfter(action: Exclude<CloseAction, {
|
|
40
|
+
kind: "stop";
|
|
41
|
+
}>, state: {
|
|
42
|
+
attempt: number;
|
|
43
|
+
unopened: number;
|
|
44
|
+
}, random: () => number): number;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { TokenStore } from "../auth/storage.js";
|
|
2
|
+
import { type RealtimeSocket } from "./socket.js";
|
|
3
|
+
export type IdentityConfig = {
|
|
4
|
+
tokens: TokenStore;
|
|
5
|
+
accessJwt: TokenStore | undefined;
|
|
6
|
+
service: boolean;
|
|
7
|
+
socket: () => RealtimeSocket;
|
|
8
|
+
/** Private channels the connection holds: each is judged again on a change. */
|
|
9
|
+
privateChannels: () => number;
|
|
10
|
+
/**
|
|
11
|
+
* The connection is live (`isLive()`) again: the joins that waited may go.
|
|
12
|
+
* Called whenever it becomes live — on every connection, and after every
|
|
13
|
+
* store read that ended in a settled identity — whether or not anything
|
|
14
|
+
* changed (a write of the same value, a retry with nothing to tell) and
|
|
15
|
+
* whether or not the hub took it (a refusal not tried again settles it
|
|
16
|
+
* too). The receiver must be idempotent.
|
|
17
|
+
*/
|
|
18
|
+
onLive: () => void;
|
|
19
|
+
/**
|
|
20
|
+
* `auth.ready()`: resolves once a sign-in the page URL carried has been
|
|
21
|
+
* traded for its session (or failed). Each read of the store waits for it.
|
|
22
|
+
*/
|
|
23
|
+
ready: () => Promise<unknown>;
|
|
24
|
+
};
|
|
25
|
+
/** A connection may change its token as fast as its private channels are judged again: 100 at once, then 2 a second (3-2c-1). */
|
|
26
|
+
export declare function rejudgeWaitMs(privateChannels: number): number;
|
|
27
|
+
export declare class Identity {
|
|
28
|
+
#private;
|
|
29
|
+
constructor(config: IdentityConfig);
|
|
30
|
+
/** The token the hub took on this connection; null for anon or before its answer. */
|
|
31
|
+
confirmed(): string | null;
|
|
32
|
+
/**
|
|
33
|
+
* The token the hub will hold once what was sent is handled — the last
|
|
34
|
+
* frame in flight, or else the one it took: the hub handles the
|
|
35
|
+
* connection's frames in order, so a frame sent now is judged for it.
|
|
36
|
+
* A frame in flight may yet be refused; the hub then keeps `confirmed()`.
|
|
37
|
+
*/
|
|
38
|
+
heldToken(): string | null;
|
|
39
|
+
/**
|
|
40
|
+
* Nothing about who the connection is is pending — no read, no frame in
|
|
41
|
+
* flight, no retry, no refusal: a join sent now is judged for the identity
|
|
42
|
+
* the hub settled on, the stored one unless it refused that for good on
|
|
43
|
+
* this connection (결정 24).
|
|
44
|
+
*/
|
|
45
|
+
isLive(): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* A new connection: anon until told. Resolves once the stored token is
|
|
48
|
+
* sent, or found to need no frame; `onLive` says when the hub took it.
|
|
49
|
+
*/
|
|
50
|
+
opened(): Promise<void>;
|
|
51
|
+
/** The stored token may have changed: tell the open connection what is new. */
|
|
52
|
+
changed(): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* A 4401: forget the stored session only if the hub answered
|
|
55
|
+
* INVALID_SESSION to it by name, and it is still stored — a sign-in since
|
|
56
|
+
* then keeps its new token (결정 3). A 4401 with no such answer clears
|
|
57
|
+
* nothing (plan 2026-10-28 정정): the hub keeps the expiry it read when it
|
|
58
|
+
* took the token (known-issues #183) while the backend's sign-in slides
|
|
59
|
+
* the session's under the same token, so a tab open for days is closed
|
|
60
|
+
* with a session that is still good. The next connection tells the token
|
|
61
|
+
* again, and hears INVALID_SESSION — and clears it — if it really is
|
|
62
|
+
* dead. The access JWT goes with the session it was minted for, so only
|
|
63
|
+
* when that session went.
|
|
64
|
+
*/
|
|
65
|
+
refused(): Promise<void>;
|
|
66
|
+
/** The connection is gone: what it was told is forgotten. */
|
|
67
|
+
closed(): void;
|
|
68
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { RealtimePresenceEntry, RealtimePresenceState } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* What join and leave say, as supabase-js's RealtimePresence does: a join's
|
|
4
|
+
* `currentPresences` are what the key held before it (none for a key's
|
|
5
|
+
* first), a leave's what it holds after it (plan 정정 (j)).
|
|
6
|
+
*/
|
|
7
|
+
export type PresenceEmit = {
|
|
8
|
+
join(key: string, newPresences: RealtimePresenceEntry[], currentPresences: RealtimePresenceEntry[]): void;
|
|
9
|
+
leave(key: string, leftPresences: RealtimePresenceEntry[], currentPresences: RealtimePresenceEntry[]): void;
|
|
10
|
+
sync(): void;
|
|
11
|
+
};
|
|
12
|
+
export declare class Presence {
|
|
13
|
+
#private;
|
|
14
|
+
state(): RealtimePresenceState;
|
|
15
|
+
/** A new join, or the connection dropped: what is here stays until its presence_state. */
|
|
16
|
+
unsync(): void;
|
|
17
|
+
clear(): void;
|
|
18
|
+
/**
|
|
19
|
+
* No presence_state will say what is here any more (a join that may not
|
|
20
|
+
* read presence, one refused, the server's close, a stop — plan 정정
|
|
21
|
+
* (k)): each key leaves, then sync — said only when someone was here. A
|
|
22
|
+
* diff waits for the next presence_state.
|
|
23
|
+
*/
|
|
24
|
+
leaveAll(emit: PresenceEmit): void;
|
|
25
|
+
applyState(payload: unknown, emit: PresenceEmit): void;
|
|
26
|
+
applyDiff(payload: unknown, emit: PresenceEmit): void;
|
|
27
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export declare const REALTIME_PROTOCOL_VERSION = "1.0.0";
|
|
2
|
+
export declare const PHOENIX_TOPIC = "phoenix";
|
|
3
|
+
export declare const REALTIME_TOPIC_PREFIX = "realtime:";
|
|
4
|
+
/**
|
|
5
|
+
* The heartbeat byte for byte: the runtime answers exactly this for the hub
|
|
6
|
+
* (`setWebSocketAutoResponse`), so it never wakes a hibernating hub. Any other
|
|
7
|
+
* spelling is an ordinary frame that wakes it every 25 seconds.
|
|
8
|
+
*/
|
|
9
|
+
export declare const HEARTBEAT_REQUEST = "{\"topic\":\"phoenix\",\"event\":\"heartbeat\",\"payload\":{},\"ref\":\"hb\"}";
|
|
10
|
+
export declare const HEARTBEAT_REF = "hb";
|
|
11
|
+
export declare const MAX_CHANNEL_NAME_LENGTH = 128;
|
|
12
|
+
/**
|
|
13
|
+
* One connection's frame bucket on the hub (data-core realtime-limits.ts):
|
|
14
|
+
* 10 a second, 120 deep, full at the open. A frame over it closes the
|
|
15
|
+
* connection with 4008 (CLIENT_FLOODING); the heartbeat does not count —
|
|
16
|
+
* the runtime answers it. socket.ts paces its frames to it.
|
|
17
|
+
*/
|
|
18
|
+
export declare const CONNECTION_FRAMES_PER_SECOND = 10;
|
|
19
|
+
export declare const CONNECTION_FRAME_BURST = 120;
|
|
20
|
+
/**
|
|
21
|
+
* The refusals the hub warns a connection for beside the refusal — the
|
|
22
|
+
* project's joins, message and presence budgets (its `warn`: `tooManyJoins`,
|
|
23
|
+
* also for its queue of ordered frames full, and the broadcast and presence
|
|
24
|
+
* handlers) — and `FLOOD_WARNINGS` of them within `FLOOD_WINDOW_MS` close the
|
|
25
|
+
* connection with 4008 (data-core realtime-limits.ts). wire.test.ts reads
|
|
26
|
+
* the hub's source for every warning it gives.
|
|
27
|
+
*/
|
|
28
|
+
export declare const WARNED_CODES: ReadonlySet<string>;
|
|
29
|
+
export declare const FLOOD_WARNINGS = 10;
|
|
30
|
+
export declare const FLOOD_WINDOW_MS = 60000;
|
|
31
|
+
/** The most channels of one connection on one table's changes, `*` on every table (3-2c-1). */
|
|
32
|
+
export declare const MAX_CHANGE_CHANNELS_PER_TABLE = 4;
|
|
33
|
+
/**
|
|
34
|
+
* The hub's INVALID_CHANNEL_CONFIG reason at `config.postgres_changes[<i>].table`
|
|
35
|
+
* for a join over `MAX_CHANGE_CHANNELS_PER_TABLE` (data-core
|
|
36
|
+
* `JOIN_CONFIG_REASONS.changeChannels`): a cap a leave still owed may hold a
|
|
37
|
+
* place under (channel.ts).
|
|
38
|
+
*/
|
|
39
|
+
export declare const CHANGE_CHANNELS_REASON: string;
|
|
40
|
+
/** One frame of the server, its fields checked; anything else is null. */
|
|
41
|
+
export type ServerFrame = {
|
|
42
|
+
topic: string;
|
|
43
|
+
event: string;
|
|
44
|
+
payload: Record<string, unknown>;
|
|
45
|
+
ref: string | null;
|
|
46
|
+
/** The join a channel frame is about (3-2a P11); null on the phoenix topic. */
|
|
47
|
+
joinRef: string | null;
|
|
48
|
+
};
|
|
49
|
+
export declare function readServerFrame(data: unknown): ServerFrame | null;
|
|
50
|
+
/**
|
|
51
|
+
* The limit a size refusal names (data-core `tooLargeReason`: "is larger
|
|
52
|
+
* than N bytes"), or null for any other reason. The hub measures a frame
|
|
53
|
+
* before it reads it, so this refusal comes on the phoenix topic with no
|
|
54
|
+
* `ref` (`parseClientFrame`); the SDK matches it to its frames by size.
|
|
55
|
+
*/
|
|
56
|
+
export declare function frameLimitOf(reason: unknown): number | null;
|
|
57
|
+
/**
|
|
58
|
+
* A text's length in UTF-8 bytes, as the hub measures a frame: a copy of
|
|
59
|
+
* data-core's `utf8ByteLength` (values.ts) — a lone surrogate is 3 bytes.
|
|
60
|
+
*/
|
|
61
|
+
export declare function utf8Length(text: string): number;
|
|
62
|
+
/** One client frame as text; channel frames carry their join's `join_ref`. */
|
|
63
|
+
export declare function clientFrame(topic: string, event: string, payload: Record<string, unknown>, ref: string | null, joinRef?: string): string;
|
|
64
|
+
/**
|
|
65
|
+
* Why a channel name is not valid, as `INVALID_CHANNEL`'s reason: it
|
|
66
|
+
* finishes "The channel name is not valid: it …". The name is measured in
|
|
67
|
+
* code points and kept as it is — no trimming, no Unicode normalisation —
|
|
68
|
+
* but the characters that make a name look like another (control and
|
|
69
|
+
* bidirectional marks) are refused. A control character is reported before
|
|
70
|
+
* a bidirectional one wherever they stand.
|
|
71
|
+
*/
|
|
72
|
+
export declare function channelNameProblem(name: string): string | undefined;
|
|
73
|
+
/**
|
|
74
|
+
* The connection's URL (original spec §6.3): a browser's WebSocket cannot
|
|
75
|
+
* send headers, so the key rides in the query, encoded. Nothing logs it;
|
|
76
|
+
* an error never quotes it (socket.ts).
|
|
77
|
+
*/
|
|
78
|
+
export declare function socketUrl(baseUrl: string, apiKey: string): string;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { type NaroError } from "../errors.js";
|
|
2
|
+
import { type ServerFrame } from "./protocol.js";
|
|
3
|
+
import type { RealtimeOptions } from "./types.js";
|
|
4
|
+
export type Reply = {
|
|
5
|
+
ok: true;
|
|
6
|
+
response: Record<string, unknown>;
|
|
7
|
+
} | {
|
|
8
|
+
ok: false;
|
|
9
|
+
error: NaroError;
|
|
10
|
+
lost: boolean;
|
|
11
|
+
};
|
|
12
|
+
export type SocketEvents = {
|
|
13
|
+
/** Open: client.ts sends the identity first, then the joins. */
|
|
14
|
+
opened(): void;
|
|
15
|
+
/** A frame no request waits for (channel frames). */
|
|
16
|
+
frame(frame: ServerFrame): void;
|
|
17
|
+
/** Closed, and coming back after a wait. */
|
|
18
|
+
dropped(error: NaroError, status: "CHANNEL_ERROR" | "TIMED_OUT"): void;
|
|
19
|
+
/** Closed for good, until a channel subscribes again. */
|
|
20
|
+
stopped(error: NaroError): void;
|
|
21
|
+
/** A 4401: the conditional delete; the wait begins when it is done, or after `timeoutMs`. */
|
|
22
|
+
sessionRefused(): Promise<void>;
|
|
23
|
+
};
|
|
24
|
+
export type SocketConfig = {
|
|
25
|
+
baseUrl: string;
|
|
26
|
+
apiKey: string;
|
|
27
|
+
service: boolean;
|
|
28
|
+
options: RealtimeOptions;
|
|
29
|
+
events: SocketEvents;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Whose frame a request is, which sets its turn (`request()`): the app's
|
|
33
|
+
* call (the default), behind the frames waiting and refused when it would
|
|
34
|
+
* not go within `timeoutMs`; a frame the SDK sends of its own accord, before
|
|
35
|
+
* the app's; the identity's `access_token`, the same, but after the app's
|
|
36
|
+
* presence frames still to go.
|
|
37
|
+
*/
|
|
38
|
+
export type Turn = "app" | "sdk" | "identity";
|
|
39
|
+
/**
|
|
40
|
+
* Runs a handler: what it throws is thrown again on its own (결정 22), and
|
|
41
|
+
* the caller goes on — a socket mid-close, a timer, a promise's handler.
|
|
42
|
+
*/
|
|
43
|
+
export declare function safely(callback: () => void): void;
|
|
44
|
+
export declare class RealtimeSocket {
|
|
45
|
+
#private;
|
|
46
|
+
constructor(config: SocketConfig);
|
|
47
|
+
get generation(): number;
|
|
48
|
+
get timeoutMs(): number;
|
|
49
|
+
isOpen(): boolean;
|
|
50
|
+
nextRef(): string;
|
|
51
|
+
/**
|
|
52
|
+
* Opens unless a connection is open, opening or due — once the wait the
|
|
53
|
+
* last close or stop set is over. After the app's own close the counts
|
|
54
|
+
* start over; after a stop they go on, as after a drop.
|
|
55
|
+
*/
|
|
56
|
+
connect(): void;
|
|
57
|
+
/** The last channel went: close for good, with no event. */
|
|
58
|
+
disconnect(): void;
|
|
59
|
+
/**
|
|
60
|
+
* Lets go of the open connection and comes back as after a drop (1006),
|
|
61
|
+
* the channels told: for a connection that holds an identity the app no
|
|
62
|
+
* longer means and cannot change (identity.ts). Nothing unless open.
|
|
63
|
+
*/
|
|
64
|
+
reconnect(): void;
|
|
65
|
+
/**
|
|
66
|
+
* The hub answered this connection: the next drop waits from the first
|
|
67
|
+
* step — unless the last close was 4008 and no heartbeat was answered
|
|
68
|
+
* since (`#flooded`).
|
|
69
|
+
*/
|
|
70
|
+
served(): void;
|
|
71
|
+
/**
|
|
72
|
+
* A frame, in its `turn` (the app's unless said), and the `phx_reply`
|
|
73
|
+
* with its `ref` within `timeoutMs` of when it went — or, for one over the
|
|
74
|
+
* size limit the hub named on this connection, that refusal at once and
|
|
75
|
+
* nothing sent; for an app's frame but a leave that would not go within
|
|
76
|
+
* `timeoutMs`, CLIENT_FLOODING at once and nothing sent.
|
|
77
|
+
*/
|
|
78
|
+
request(topic: string, event: string, payload: Record<string, unknown>, options?: {
|
|
79
|
+
joinRef?: string;
|
|
80
|
+
ref?: string;
|
|
81
|
+
turn?: Turn;
|
|
82
|
+
}): Promise<Reply>;
|
|
83
|
+
/**
|
|
84
|
+
* Runs `go` once a frame the SDK sends of its own accord may go: at once
|
|
85
|
+
* when nothing waits before it and there is room — the warnings of the
|
|
86
|
+
* last minute and the requests in flight fewer than `SDK_WARNING_ROOM`
|
|
87
|
+
* (the app's frames still waiting their turn are not in flight yet, and
|
|
88
|
+
* go after the SDK's), or the warnings fewer and none of the SDK's own
|
|
89
|
+
* unanswered — or else when there is, in the order asked. The
|
|
90
|
+
* frame itself is sent with `turn: "sdk"`. `key` names the sender:
|
|
91
|
+
* asked again before it ran, it keeps its place and runs the newer `go`,
|
|
92
|
+
* which reads its own state again then. Nothing while the connection is
|
|
93
|
+
* not open, and forgotten with it: the sender tries again when it is live.
|
|
94
|
+
*/
|
|
95
|
+
whenRoom(key: object, go: () => void): void;
|
|
96
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { NaroError } from "../errors.js";
|
|
2
|
+
/** What `subscribe`'s callback hears (original spec §15). */
|
|
3
|
+
export type RealtimeStatus = "SUBSCRIBED" | "CHANNEL_ERROR" | "TIMED_OUT" | "CLOSED";
|
|
4
|
+
export type RealtimeChannelState = "closed" | "joining" | "joined" | "leaving" | "errored";
|
|
5
|
+
export type RealtimeStatusCallback = (status: RealtimeStatus, error?: NaroError) => void;
|
|
6
|
+
export type RealtimeResult = {
|
|
7
|
+
data: null;
|
|
8
|
+
error: NaroError | null;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* What `send()`, `track()` and `untrack()` answer. `lost` is true when the
|
|
12
|
+
* connection closed before the server answered: the frame may have been
|
|
13
|
+
* delivered, or not. So `CLIENT_FLOODING` with `lost: false` was refused
|
|
14
|
+
* here and not sent — send it again later — and with `lost: true` the
|
|
15
|
+
* server closed the connection (4008) and this one may have gone through.
|
|
16
|
+
*/
|
|
17
|
+
export type RealtimeSendResult = RealtimeResult & {
|
|
18
|
+
lost: boolean;
|
|
19
|
+
};
|
|
20
|
+
export type RealtimeChannelOptions = {
|
|
21
|
+
config?: {
|
|
22
|
+
private?: boolean;
|
|
23
|
+
broadcast?: {
|
|
24
|
+
self?: boolean;
|
|
25
|
+
ack?: boolean;
|
|
26
|
+
};
|
|
27
|
+
presence?: {
|
|
28
|
+
key?: string;
|
|
29
|
+
enabled?: boolean;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
export type PostgresChangesEvent = "*" | "INSERT" | "UPDATE" | "DELETE";
|
|
34
|
+
export type PostgresChangesFilter<Table extends string = string> = {
|
|
35
|
+
event: PostgresChangesEvent;
|
|
36
|
+
schema?: "public";
|
|
37
|
+
table?: Table | "*";
|
|
38
|
+
filter?: string;
|
|
39
|
+
};
|
|
40
|
+
export type RealtimePostgresChangesPayload<Row extends object = Record<string, unknown>> = {
|
|
41
|
+
schema: "public";
|
|
42
|
+
table: string;
|
|
43
|
+
commit_timestamp: string;
|
|
44
|
+
eventType: "INSERT" | "UPDATE" | "DELETE";
|
|
45
|
+
new: Row | Record<string, never>;
|
|
46
|
+
old: Partial<Row>;
|
|
47
|
+
errors: string[] | null;
|
|
48
|
+
};
|
|
49
|
+
export type RealtimeBroadcastMessage = {
|
|
50
|
+
type: "broadcast";
|
|
51
|
+
event: string;
|
|
52
|
+
payload: Record<string, unknown>;
|
|
53
|
+
};
|
|
54
|
+
/** BULK_CHANGE, EVENTS_DROPPED and the reason a channel closed (original spec §7.4). */
|
|
55
|
+
export type RealtimeSystemMessage = {
|
|
56
|
+
status: "ok" | "error";
|
|
57
|
+
extension: string;
|
|
58
|
+
code: string;
|
|
59
|
+
message: string;
|
|
60
|
+
channel: string | null;
|
|
61
|
+
};
|
|
62
|
+
export type RealtimePresenceEntry = Record<string, unknown> & {
|
|
63
|
+
presence_ref: string;
|
|
64
|
+
/** The signed-in user who tracked it, or null: the one value the server sets. */
|
|
65
|
+
naro_user_id: string | null;
|
|
66
|
+
};
|
|
67
|
+
export type RealtimePresenceState = Record<string, RealtimePresenceEntry[]>;
|
|
68
|
+
/** A presence join as supabase-js's listeners hear it. */
|
|
69
|
+
export type RealtimePresenceJoinPayload = {
|
|
70
|
+
event: "join";
|
|
71
|
+
key: string;
|
|
72
|
+
newPresences: RealtimePresenceEntry[];
|
|
73
|
+
/** What the key held before this join, as supabase-js says it; [] for its first. */
|
|
74
|
+
currentPresences: RealtimePresenceEntry[];
|
|
75
|
+
};
|
|
76
|
+
export type RealtimePresenceLeavePayload = {
|
|
77
|
+
event: "leave";
|
|
78
|
+
key: string;
|
|
79
|
+
leftPresences: RealtimePresenceEntry[];
|
|
80
|
+
/** What the key holds after this leave; [] when it was the last. */
|
|
81
|
+
currentPresences: RealtimePresenceEntry[];
|
|
82
|
+
};
|
|
83
|
+
/** The global WebSocket, or `ws`'s: any class built from a URL. */
|
|
84
|
+
export type WebSocketConstructor = new (url: string) => unknown;
|
|
85
|
+
export type RealtimeOptions = {
|
|
86
|
+
WebSocket?: WebSocketConstructor;
|
|
87
|
+
heartbeatIntervalMs?: number;
|
|
88
|
+
timeoutMs?: number;
|
|
89
|
+
};
|
package/dist/storage/client.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AuthClient } from "../auth/client.js";
|
|
2
2
|
import { type HttpConfig } from "../http.js";
|
|
3
3
|
import type { NaroResponse } from "../types.js";
|
|
4
|
-
import type { CreateSignedUploadUrlOptions, DownloadOption, FileObject, FolderEntry, ListOptions, SignedUploadResult, SignedUploadUrl, SignedUrlEntry, UpdateOptions, UploadBody, UploadOptions, UploadResult } from "./types.js";
|
|
4
|
+
import type { CopyOptions, CreateSignedUploadUrlOptions, DownloadOption, FileObject, FolderEntry, ListOptions, MoveOptions, MoveResult, SignedUploadResult, SignedUploadUrl, SignedUrlEntry, UpdateOptions, UploadBody, UploadOptions, UploadResult } from "./types.js";
|
|
5
5
|
/** One bucket's objects: supabase-js's StorageFileApi. */
|
|
6
6
|
export declare class StorageFileApi {
|
|
7
7
|
#private;
|
|
@@ -40,9 +40,33 @@ export declare class StorageFileApi {
|
|
|
40
40
|
list(prefix?: string, options?: ListOptions): Promise<NaroResponse<(FileObject | FolderEntry)[]>>;
|
|
41
41
|
/** Removes the objects named — 1 to 1,000 — and answers the ones it removed. */
|
|
42
42
|
remove(paths: string[]): Promise<NaroResponse<FileObject[]>>;
|
|
43
|
+
/**
|
|
44
|
+
* Renames an object inside its bucket (Storage 3-1i): the same object — its
|
|
45
|
+
* id and owner — under `toPath`, its old name gone. It never replaces an
|
|
46
|
+
* object that is there (`OBJECT_EXISTS`), takes files up to 5 GiB − 5 MiB
|
|
47
|
+
* (`OBJECT_TOO_LARGE`), and needs room in the project for a second copy
|
|
48
|
+
* until it is done (`STORAGE_QUOTA_EXCEEDED`). Its signed links stop
|
|
49
|
+
* working at the old name: a link is bound to the name, the object's id
|
|
50
|
+
* and its bytes, so moving it back can open them again. A backend whose
|
|
51
|
+
* Storage was turned on before this method came answers `NOT_FOUND`:
|
|
52
|
+
* update it with `naro backend enable`.
|
|
53
|
+
*/
|
|
54
|
+
move(fromPath: string, toPath: string, options?: MoveOptions): Promise<NaroResponse<MoveResult>>;
|
|
55
|
+
/**
|
|
56
|
+
* Copies an object inside its bucket (Storage 3-1i): the source's bytes,
|
|
57
|
+
* type and cache-control under `toPath` — a new object of the caller's,
|
|
58
|
+
* or with `upsert: true` the one that is there, which keeps its owner.
|
|
59
|
+
* Files up to 5 GiB − 5 MiB, within the bucket's limits as they are now.
|
|
60
|
+
* It answers `{ id, path, fullPath }` — `path` is the name inside the
|
|
61
|
+
* bucket; supabase-js's copy puts the bucket in front of it. A backend
|
|
62
|
+
* whose Storage was turned on before this method came answers
|
|
63
|
+
* `NOT_FOUND`: update it with `naro backend enable`.
|
|
64
|
+
*/
|
|
65
|
+
copy(fromPath: string, toPath: string, options?: CopyOptions): Promise<NaroResponse<UploadResult>>;
|
|
43
66
|
/**
|
|
44
67
|
* A link to the object for `expiresIn` seconds (1–604,800). It stops
|
|
45
|
-
* working once the object is replaced or removed
|
|
68
|
+
* working once the object is replaced or removed, and while the object is
|
|
69
|
+
* moved away from `path` (moved back, it can open it again).
|
|
46
70
|
*/
|
|
47
71
|
createSignedUrl(path: string, expiresIn: number, options?: DownloadOption): Promise<NaroResponse<{
|
|
48
72
|
signedUrl: string;
|
package/dist/storage/types.d.ts
CHANGED
|
@@ -115,3 +115,22 @@ export type SignedUrlEntry = {
|
|
|
115
115
|
signedUrl: string | null;
|
|
116
116
|
error: string | null;
|
|
117
117
|
};
|
|
118
|
+
/** `move`'s options (Storage 3-1i): supabase-js's. */
|
|
119
|
+
export type MoveOptions = {
|
|
120
|
+
/**
|
|
121
|
+
* Taken as supabase-js sends it. Objects move within one bucket: another
|
|
122
|
+
* bucket answers `INVALID_REQUEST` (`details.path` "destinationBucket").
|
|
123
|
+
*/
|
|
124
|
+
destinationBucket?: string;
|
|
125
|
+
};
|
|
126
|
+
/** `copy`'s options (Storage 3-1i). */
|
|
127
|
+
export type CopyOptions = {
|
|
128
|
+
/** As `MoveOptions`'s: one bucket only. */
|
|
129
|
+
destinationBucket?: string;
|
|
130
|
+
/** Replace an object at the destination. Default false: `OBJECT_EXISTS`. */
|
|
131
|
+
upsert?: boolean;
|
|
132
|
+
};
|
|
133
|
+
/** What `move` answers: supabase-js's words. */
|
|
134
|
+
export type MoveResult = {
|
|
135
|
+
message: string;
|
|
136
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "naro-js",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Auth, data and files for your Naro project, from the browser or the server.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"tsup": "8.5.0",
|
|
24
24
|
"typescript": "7.0.2",
|
|
25
25
|
"vitest": "3.2.7",
|
|
26
|
-
"@next-deploy/config": "0.1.0",
|
|
27
26
|
"@next-deploy/backend-worker": "0.1.0",
|
|
27
|
+
"@next-deploy/config": "0.1.0",
|
|
28
28
|
"@next-deploy/data-core": "0.1.0",
|
|
29
29
|
"@next-deploy/sdk": "0.1.0"
|
|
30
30
|
},
|