naro-js 0.6.1 → 0.7.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 +133 -0
- package/dist/client.d.ts +21 -2
- package/dist/errors.d.ts +6 -0
- package/dist/functions-caller.d.ts +13 -0
- package/dist/functions.d.ts +65 -0
- package/dist/functions.js +74 -0
- package/dist/http.d.ts +73 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +596 -51
- package/dist/invoke/body.d.ts +19 -0
- package/dist/invoke/client.d.ts +52 -0
- package/dist/invoke/errors.d.ts +38 -0
- package/package.json +15 -3
package/dist/index.js
CHANGED
|
@@ -127,6 +127,17 @@ function serviceKeyOffServer() {
|
|
|
127
127
|
message: SERVICE_KEY_OFF_SERVER_MESSAGE
|
|
128
128
|
};
|
|
129
129
|
}
|
|
130
|
+
function accessTokenUnavailable(cause) {
|
|
131
|
+
return {
|
|
132
|
+
code: "ACCESS_TOKEN_UNAVAILABLE",
|
|
133
|
+
message: "createClient's accessToken function failed or returned something that is not a string or null, so the request was not sent.",
|
|
134
|
+
details: { cause: describe(cause) }
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function accessTokenAuthMessage(name) {
|
|
138
|
+
return `naro.auth.${name} is not available on a client made with accessToken: its token comes from your accessToken function. Use a client made without accessToken to sign in.`;
|
|
139
|
+
}
|
|
140
|
+
var ACCESS_TOKEN_CHANNEL_MESSAGE = "naro.channel() is not available on a client made with accessToken: Realtime signs in with a session, not an access token. Broadcast from a function with naroAdmin.";
|
|
130
141
|
function notJoined(channel) {
|
|
131
142
|
return {
|
|
132
143
|
code: "NOT_JOINED",
|
|
@@ -195,6 +206,16 @@ function storeFor(config, path) {
|
|
|
195
206
|
return SESSION_PREFIXES.some((prefix) => path.startsWith(prefix)) ? config.tokens : config.accessJwt ?? null;
|
|
196
207
|
}
|
|
197
208
|
var ACCESS_JWT_MARGIN_MS = 6e4;
|
|
209
|
+
function deadlineOf(timeoutMs) {
|
|
210
|
+
return timeoutMs === void 0 ? null : { at: Date.now() + timeoutMs, ms: timeoutMs };
|
|
211
|
+
}
|
|
212
|
+
function remainingMs(deadline) {
|
|
213
|
+
return Math.max(0, deadline.at - Date.now());
|
|
214
|
+
}
|
|
215
|
+
var EXPIRED = Symbol("expired");
|
|
216
|
+
function timedOut(deadline) {
|
|
217
|
+
return requestTimeout(deadline?.ms ?? 0);
|
|
218
|
+
}
|
|
198
219
|
function timesOf(jwt) {
|
|
199
220
|
const payload = jwt.split(".")[1];
|
|
200
221
|
if (payload === void 0 || payload === "") return null;
|
|
@@ -290,6 +311,13 @@ async function accessJwtFor(config, jwtStore) {
|
|
|
290
311
|
async function bearerFor(config, path, withToken) {
|
|
291
312
|
if (!withToken) return { token: null, session: null };
|
|
292
313
|
const store = storeFor(config, path);
|
|
314
|
+
if (config.accessToken !== void 0 && store !== config.tokens) {
|
|
315
|
+
try {
|
|
316
|
+
return { token: await config.accessToken.get(), session: null };
|
|
317
|
+
} catch (cause) {
|
|
318
|
+
return { error: accessTokenUnavailable(cause) };
|
|
319
|
+
}
|
|
320
|
+
}
|
|
293
321
|
if (store === null) return { token: null, session: null };
|
|
294
322
|
if (store === config.tokens) {
|
|
295
323
|
const token = await store.get();
|
|
@@ -304,9 +332,12 @@ async function retryBearer(config, path, error, first) {
|
|
|
304
332
|
if ("error" in renewed) return renewed;
|
|
305
333
|
return renewed.token === null || renewed.token === first.token || renewed.session !== first.session ? null : renewed;
|
|
306
334
|
}
|
|
307
|
-
async function receive(config, options, init) {
|
|
308
|
-
|
|
335
|
+
async function receive(config, options, init, deadline) {
|
|
336
|
+
if (deadline !== null && remainingMs(deadline) === 0)
|
|
337
|
+
return { error: timedOut(deadline) };
|
|
338
|
+
const controller = deadline !== null && typeof AbortController === "function" ? new AbortController() : null;
|
|
309
339
|
if (controller) init.signal = controller.signal;
|
|
340
|
+
let gaveUp = false;
|
|
310
341
|
const request = (async () => {
|
|
311
342
|
try {
|
|
312
343
|
const fetchImpl = config.fetch ?? globalThis.fetch;
|
|
@@ -314,6 +345,10 @@ async function receive(config, options, init) {
|
|
|
314
345
|
`${config.baseUrl}/_naro/v1${options.path}`,
|
|
315
346
|
init
|
|
316
347
|
);
|
|
348
|
+
if (gaveUp) {
|
|
349
|
+
discard(response);
|
|
350
|
+
return { error: timedOut(deadline) };
|
|
351
|
+
}
|
|
317
352
|
return {
|
|
318
353
|
status: response.status,
|
|
319
354
|
headers: response.headers,
|
|
@@ -323,14 +358,14 @@ async function receive(config, options, init) {
|
|
|
323
358
|
return { error: networkError(error) };
|
|
324
359
|
}
|
|
325
360
|
})();
|
|
326
|
-
|
|
327
|
-
if (timeoutMs === void 0) return request;
|
|
361
|
+
if (deadline === null) return request;
|
|
328
362
|
let timer;
|
|
329
363
|
const expired = new Promise((resolve) => {
|
|
330
364
|
timer = setTimeout(() => {
|
|
365
|
+
gaveUp = true;
|
|
331
366
|
controller?.abort();
|
|
332
|
-
resolve({ error:
|
|
333
|
-
},
|
|
367
|
+
resolve({ error: timedOut(deadline) });
|
|
368
|
+
}, remainingMs(deadline));
|
|
334
369
|
});
|
|
335
370
|
try {
|
|
336
371
|
return await Promise.race([request, expired]);
|
|
@@ -347,15 +382,34 @@ async function call(config, options) {
|
|
|
347
382
|
return { error: invalidQuery("", "is not JSON"), answer: null };
|
|
348
383
|
}
|
|
349
384
|
}
|
|
350
|
-
const
|
|
385
|
+
const deadline = deadlineOf(options.timeoutMs);
|
|
386
|
+
const bearer = await untilStopped(
|
|
387
|
+
bearerFor(config, options.path, options.withToken),
|
|
388
|
+
void 0,
|
|
389
|
+
deadline
|
|
390
|
+
);
|
|
391
|
+
if (bearer === EXPIRED || bearer === ABORTED)
|
|
392
|
+
return { error: timedOut(deadline), answer: null };
|
|
351
393
|
if ("error" in bearer) return { error: bearer.error, answer: null };
|
|
352
|
-
const result = await callWith(
|
|
353
|
-
|
|
394
|
+
const result = await callWith(
|
|
395
|
+
config,
|
|
396
|
+
options,
|
|
397
|
+
payload,
|
|
398
|
+
bearer.token,
|
|
399
|
+
deadline
|
|
400
|
+
);
|
|
401
|
+
const again = await untilStopped(
|
|
402
|
+
retryBearer(config, options.path, result.error, bearer),
|
|
403
|
+
void 0,
|
|
404
|
+
deadline
|
|
405
|
+
);
|
|
406
|
+
if (again === EXPIRED || again === ABORTED)
|
|
407
|
+
return { error: timedOut(deadline), answer: null };
|
|
354
408
|
if (again === null) return result;
|
|
355
409
|
if ("error" in again) return { error: again.error, answer: null };
|
|
356
|
-
return callWith(config, options, payload, again.token);
|
|
410
|
+
return callWith(config, options, payload, again.token, deadline);
|
|
357
411
|
}
|
|
358
|
-
async function callWith(config, options, payload, token) {
|
|
412
|
+
async function callWith(config, options, payload, token, deadline = null) {
|
|
359
413
|
const store = storeFor(config, options.path);
|
|
360
414
|
const headers = {
|
|
361
415
|
...options.headers,
|
|
@@ -375,7 +429,7 @@ async function callWith(config, options, payload, token) {
|
|
|
375
429
|
headers["Content-Type"] = "application/json";
|
|
376
430
|
init.body = payload;
|
|
377
431
|
}
|
|
378
|
-
const received = await receive(config, options, init);
|
|
432
|
+
const received = await receive(config, options, init, deadline);
|
|
379
433
|
if ("error" in received) return { error: received.error, answer: null };
|
|
380
434
|
const { status, headers: responseHeaders, text } = received;
|
|
381
435
|
let body;
|
|
@@ -437,6 +491,27 @@ function untilAborted(promise, signal) {
|
|
|
437
491
|
);
|
|
438
492
|
});
|
|
439
493
|
}
|
|
494
|
+
function untilStopped(promise, signal, deadline) {
|
|
495
|
+
if (signal?.aborted) return Promise.resolve(ABORTED);
|
|
496
|
+
if (!signal && deadline === null) return promise;
|
|
497
|
+
return new Promise((resolve, reject) => {
|
|
498
|
+
let timer;
|
|
499
|
+
const stop = () => finish(ABORTED);
|
|
500
|
+
const finish = (value) => {
|
|
501
|
+
clearTimeout(timer);
|
|
502
|
+
signal?.removeEventListener("abort", stop);
|
|
503
|
+
resolve(value);
|
|
504
|
+
};
|
|
505
|
+
signal?.addEventListener("abort", stop, { once: true });
|
|
506
|
+
if (deadline !== null)
|
|
507
|
+
timer = setTimeout(() => finish(EXPIRED), remainingMs(deadline));
|
|
508
|
+
promise.then(finish, (error) => {
|
|
509
|
+
clearTimeout(timer);
|
|
510
|
+
signal?.removeEventListener("abort", stop);
|
|
511
|
+
reject(error);
|
|
512
|
+
});
|
|
513
|
+
});
|
|
514
|
+
}
|
|
440
515
|
function isStream(body) {
|
|
441
516
|
return typeof ReadableStream === "function" && body instanceof ReadableStream;
|
|
442
517
|
}
|
|
@@ -496,6 +571,143 @@ async function sendWith(config, options, token) {
|
|
|
496
571
|
status
|
|
497
572
|
};
|
|
498
573
|
}
|
|
574
|
+
async function fetchUnread(config, options) {
|
|
575
|
+
const { signal } = options;
|
|
576
|
+
if (signal?.aborted) return { failed: "aborted", cause: signal.reason };
|
|
577
|
+
const deadline = deadlineOf(options.timeoutMs);
|
|
578
|
+
if (options.ready) {
|
|
579
|
+
const ready = await untilStopped(options.ready(), signal, deadline);
|
|
580
|
+
if (ready === ABORTED) return { failed: "aborted", cause: signal?.reason };
|
|
581
|
+
if (ready === EXPIRED) return { failed: "timeout", cause: null };
|
|
582
|
+
}
|
|
583
|
+
const own = options.headers.has("authorization");
|
|
584
|
+
const bearer = await untilStopped(
|
|
585
|
+
bearerFor(config, options.path, !own),
|
|
586
|
+
signal,
|
|
587
|
+
deadline
|
|
588
|
+
);
|
|
589
|
+
if (bearer === ABORTED) return { failed: "aborted", cause: signal?.reason };
|
|
590
|
+
if (bearer === EXPIRED) return { failed: "timeout", cause: null };
|
|
591
|
+
if ("error" in bearer) return { refused: bearer.error };
|
|
592
|
+
const first = await fetchOnce(config, options, bearer.token, deadline);
|
|
593
|
+
if (!("response" in first) || bearer.token === null) return first;
|
|
594
|
+
const refused = await refusesToken(first.response, signal, deadline);
|
|
595
|
+
if (refused === EXPIRED) {
|
|
596
|
+
discard(first.response);
|
|
597
|
+
return { failed: "timeout", cause: null };
|
|
598
|
+
}
|
|
599
|
+
if (refused === ABORTED) {
|
|
600
|
+
discard(first.response);
|
|
601
|
+
return { failed: "aborted", cause: signal?.reason };
|
|
602
|
+
}
|
|
603
|
+
if (!refused) return first;
|
|
604
|
+
await storeFor(config, options.path)?.clearIf(bearer.token);
|
|
605
|
+
if (isStream(options.body)) return first;
|
|
606
|
+
const again = await untilStopped(
|
|
607
|
+
retryBearer(config, options.path, invalidSession(), bearer),
|
|
608
|
+
signal,
|
|
609
|
+
deadline
|
|
610
|
+
);
|
|
611
|
+
if (again === EXPIRED) {
|
|
612
|
+
discard(first.response);
|
|
613
|
+
return { failed: "timeout", cause: null };
|
|
614
|
+
}
|
|
615
|
+
if (again === null || again !== ABORTED && "error" in again) return first;
|
|
616
|
+
discard(first.response);
|
|
617
|
+
if (again === ABORTED) return { failed: "aborted", cause: signal?.reason };
|
|
618
|
+
const second = await fetchOnce(config, options, again.token, deadline);
|
|
619
|
+
if (!("response" in second) || again.token === null) return second;
|
|
620
|
+
const refusedAgain = await refusesToken(second.response, signal, deadline);
|
|
621
|
+
if (refusedAgain === ABORTED) {
|
|
622
|
+
discard(second.response);
|
|
623
|
+
return { failed: "aborted", cause: signal?.reason };
|
|
624
|
+
}
|
|
625
|
+
if (refusedAgain === true || refusedAgain === EXPIRED)
|
|
626
|
+
await storeFor(config, options.path)?.clearIf(again.token);
|
|
627
|
+
return second;
|
|
628
|
+
}
|
|
629
|
+
async function refusesToken(response, signal, deadline) {
|
|
630
|
+
if (response.status !== 401 || response.headers.get("x-relay-error") !== "true")
|
|
631
|
+
return false;
|
|
632
|
+
let body;
|
|
633
|
+
try {
|
|
634
|
+
body = await untilStopped(response.clone().json(), signal, deadline);
|
|
635
|
+
} catch {
|
|
636
|
+
return signal?.aborted ? ABORTED : false;
|
|
637
|
+
}
|
|
638
|
+
if (body === ABORTED || body === EXPIRED) return body;
|
|
639
|
+
return isRecord(body) && isRecord(body.error) && body.error.code === "INVALID_SESSION";
|
|
640
|
+
}
|
|
641
|
+
var releases = /* @__PURE__ */ new WeakMap();
|
|
642
|
+
function discard(response) {
|
|
643
|
+
releases.get(response)?.();
|
|
644
|
+
releases.delete(response);
|
|
645
|
+
response.body?.cancel().catch(() => {
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
async function fetchOnce(config, options, token, deadline) {
|
|
649
|
+
const { signal } = options;
|
|
650
|
+
if (signal?.aborted) return { failed: "aborted", cause: signal.reason };
|
|
651
|
+
if (deadline !== null && remainingMs(deadline) === 0)
|
|
652
|
+
return { failed: "timeout", cause: null };
|
|
653
|
+
const headers = new Headers(options.headers);
|
|
654
|
+
headers.set("apikey", config.apiKey);
|
|
655
|
+
if (token !== null) headers.set("authorization", `Bearer ${token}`);
|
|
656
|
+
const init = {
|
|
657
|
+
method: options.method,
|
|
658
|
+
headers,
|
|
659
|
+
credentials: "omit"
|
|
660
|
+
};
|
|
661
|
+
if (options.body !== void 0) init.body = options.body;
|
|
662
|
+
if (isStream(options.body)) init.duplex = "half";
|
|
663
|
+
const controller = typeof AbortController === "function" ? new AbortController() : null;
|
|
664
|
+
if (controller) init.signal = controller.signal;
|
|
665
|
+
let timer;
|
|
666
|
+
let stop = () => {
|
|
667
|
+
};
|
|
668
|
+
const stopped = new Promise((resolve) => {
|
|
669
|
+
stop = resolve;
|
|
670
|
+
});
|
|
671
|
+
const onAbort = () => {
|
|
672
|
+
stop({ failed: "aborted", cause: signal?.reason });
|
|
673
|
+
controller?.abort(signal?.reason);
|
|
674
|
+
};
|
|
675
|
+
if (deadline !== null)
|
|
676
|
+
timer = setTimeout(() => {
|
|
677
|
+
stop({ failed: "timeout", cause: null });
|
|
678
|
+
controller?.abort();
|
|
679
|
+
}, remainingMs(deadline));
|
|
680
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
681
|
+
const request = (async () => {
|
|
682
|
+
try {
|
|
683
|
+
const fetchImpl = config.fetch ?? globalThis.fetch;
|
|
684
|
+
return {
|
|
685
|
+
response: await fetchImpl(
|
|
686
|
+
`${config.baseUrl}/_naro/v1${options.path}`,
|
|
687
|
+
init
|
|
688
|
+
)
|
|
689
|
+
};
|
|
690
|
+
} catch (cause) {
|
|
691
|
+
return { failed: "network", cause };
|
|
692
|
+
}
|
|
693
|
+
})();
|
|
694
|
+
const release = () => signal?.removeEventListener("abort", onAbort);
|
|
695
|
+
let outcome;
|
|
696
|
+
try {
|
|
697
|
+
outcome = await Promise.race([request, stopped]);
|
|
698
|
+
return outcome;
|
|
699
|
+
} finally {
|
|
700
|
+
clearTimeout(timer);
|
|
701
|
+
if (outcome !== void 0 && "response" in outcome)
|
|
702
|
+
releases.set(outcome.response, release);
|
|
703
|
+
else {
|
|
704
|
+
release();
|
|
705
|
+
void request.then((late) => {
|
|
706
|
+
if ("response" in late) discard(late.response);
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
499
711
|
var RETRY_AFTER_CODES = /* @__PURE__ */ new Set([
|
|
500
712
|
"RATE_LIMITED",
|
|
501
713
|
"WRITE_QUOTA_EXCEEDED",
|
|
@@ -504,7 +716,12 @@ var RETRY_AFTER_CODES = /* @__PURE__ */ new Set([
|
|
|
504
716
|
"OBJECT_RATE_LIMITED",
|
|
505
717
|
"STORAGE_OPS_QUOTA_EXCEEDED",
|
|
506
718
|
// Realtime's REST broadcast over the project's message budget (x-retry-after: 1).
|
|
507
|
-
"TOO_MANY_MESSAGES"
|
|
719
|
+
"TOO_MANY_MESSAGES",
|
|
720
|
+
// Naro Functions (design F20): a team over its monthly function CPU, and
|
|
721
|
+
// the control plane's Cloudflare limit. `invoke` reads x-retry-after on
|
|
722
|
+
// every relay error anyway (invoke/client.ts).
|
|
723
|
+
"FUNCTIONS_QUOTA_EXCEEDED",
|
|
724
|
+
"FUNCTIONS_RATE_LIMITED"
|
|
508
725
|
]);
|
|
509
726
|
function withRetryAfter(error, headers) {
|
|
510
727
|
const header = headers.get("x-retry-after")?.trim() ?? "";
|
|
@@ -828,14 +1045,14 @@ var AuthClient = class {
|
|
|
828
1045
|
*/
|
|
829
1046
|
async signInWithOAuth(options) {
|
|
830
1047
|
const { provider } = options;
|
|
831
|
-
const
|
|
1048
|
+
const failed2 = (error) => ({
|
|
832
1049
|
data: { provider, url: null },
|
|
833
1050
|
error
|
|
834
1051
|
});
|
|
835
|
-
if (!OAUTH_PROVIDERS.includes(provider)) return
|
|
1052
|
+
if (!OAUTH_PROVIDERS.includes(provider)) return failed2(invalidProvider());
|
|
836
1053
|
await this.#flow.recovery.forget();
|
|
837
1054
|
const pair = await createPkcePair();
|
|
838
|
-
if (!pair) return
|
|
1055
|
+
if (!pair) return failed2(cryptoUnavailable());
|
|
839
1056
|
await this.#flow.verifiers.add(pair.challenge, pair.verifier);
|
|
840
1057
|
const redirectTo = options.redirectTo ?? currentPage();
|
|
841
1058
|
const url = new URL(
|
|
@@ -1408,6 +1625,288 @@ var TokenStore = class {
|
|
|
1408
1625
|
}
|
|
1409
1626
|
};
|
|
1410
1627
|
|
|
1628
|
+
// src/invoke/body.ts
|
|
1629
|
+
function prepareBody(body) {
|
|
1630
|
+
if (body === void 0) return { body: void 0, contentType: null };
|
|
1631
|
+
if (typeof body === "string") return { body, contentType: "text/plain" };
|
|
1632
|
+
if (typeof Blob === "function" && body instanceof Blob)
|
|
1633
|
+
return { body, contentType: "application/octet-stream" };
|
|
1634
|
+
if (body instanceof ArrayBuffer || ArrayBuffer.isView(body))
|
|
1635
|
+
return { body, contentType: "application/octet-stream" };
|
|
1636
|
+
if (typeof FormData === "function" && body instanceof FormData)
|
|
1637
|
+
return { body, contentType: null };
|
|
1638
|
+
if (typeof URLSearchParams === "function" && body instanceof URLSearchParams)
|
|
1639
|
+
return { body, contentType: null };
|
|
1640
|
+
if (typeof ReadableStream === "function" && body instanceof ReadableStream)
|
|
1641
|
+
return { body, contentType: null };
|
|
1642
|
+
try {
|
|
1643
|
+
const text = JSON.stringify(body);
|
|
1644
|
+
return text === void 0 ? null : { body: text, contentType: "application/json" };
|
|
1645
|
+
} catch {
|
|
1646
|
+
return null;
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
async function readAnswer(response, method) {
|
|
1650
|
+
if (response.status === 204 || method === "HEAD") return { data: null };
|
|
1651
|
+
const type = (response.headers.get("content-type") ?? "").split(";")[0]?.trim().toLowerCase();
|
|
1652
|
+
try {
|
|
1653
|
+
if (type === "application/json") {
|
|
1654
|
+
const text = await response.text();
|
|
1655
|
+
return {
|
|
1656
|
+
data: text.trim() === "" ? null : JSON.parse(text)
|
|
1657
|
+
};
|
|
1658
|
+
}
|
|
1659
|
+
if (type === "application/octet-stream" || type === "application/pdf")
|
|
1660
|
+
return { data: await response.blob() };
|
|
1661
|
+
if (type === "text/event-stream") return { data: response };
|
|
1662
|
+
if (type === "multipart/form-data")
|
|
1663
|
+
return { data: await response.formData() };
|
|
1664
|
+
return { data: await response.text() };
|
|
1665
|
+
} catch (cause) {
|
|
1666
|
+
return { unreadable: cause };
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
// src/invoke/errors.ts
|
|
1671
|
+
var FunctionsError = class extends Error {
|
|
1672
|
+
code;
|
|
1673
|
+
context;
|
|
1674
|
+
constructor(code, message, context, details) {
|
|
1675
|
+
super(message);
|
|
1676
|
+
this.name = "FunctionsError";
|
|
1677
|
+
this.code = code;
|
|
1678
|
+
this.context = context;
|
|
1679
|
+
if (details !== void 0)
|
|
1680
|
+
Object.defineProperty(this, "details", {
|
|
1681
|
+
value: details,
|
|
1682
|
+
enumerable: true
|
|
1683
|
+
});
|
|
1684
|
+
}
|
|
1685
|
+
};
|
|
1686
|
+
var FunctionsHttpError = class extends FunctionsError {
|
|
1687
|
+
constructor(response) {
|
|
1688
|
+
super(
|
|
1689
|
+
"FUNCTION_HTTP_ERROR",
|
|
1690
|
+
`The function answered with HTTP ${String(response.status)}.`,
|
|
1691
|
+
response
|
|
1692
|
+
);
|
|
1693
|
+
this.name = "FunctionsHttpError";
|
|
1694
|
+
}
|
|
1695
|
+
};
|
|
1696
|
+
var FunctionsRelayError = class extends FunctionsError {
|
|
1697
|
+
constructor(code, message, response, details) {
|
|
1698
|
+
super(code, message, response, details);
|
|
1699
|
+
this.name = "FunctionsRelayError";
|
|
1700
|
+
}
|
|
1701
|
+
};
|
|
1702
|
+
var FunctionsFetchError = class extends FunctionsError {
|
|
1703
|
+
constructor(code, message, cause, details) {
|
|
1704
|
+
super(code, message, cause, details);
|
|
1705
|
+
this.name = "FunctionsFetchError";
|
|
1706
|
+
}
|
|
1707
|
+
};
|
|
1708
|
+
var FUNCTIONS_MESSAGES = {
|
|
1709
|
+
timeout: (ms) => `The function did not answer within ${String(ms)} ms.`,
|
|
1710
|
+
aborted: "The function call was stopped before the function answered.",
|
|
1711
|
+
invalidBody: "functions.invoke could not send this body: JSON cannot hold it (a BigInt or a cycle). Pass a string, a Blob, bytes, FormData, URLSearchParams or a stream instead.",
|
|
1712
|
+
invalidTimeout: "functions.invoke's timeout is whole milliseconds from 1 to 2147483647.",
|
|
1713
|
+
invalidHeaders: "functions.invoke's headers are not valid HTTP header names and values.",
|
|
1714
|
+
invalidName: "functions.invoke's name is a string: the function's name, then any path and query.",
|
|
1715
|
+
unencodableName: "functions.invoke's name is not well-formed Unicode: it has a lone surrogate.",
|
|
1716
|
+
dotSegments: `functions.invoke's name cannot be "." or "..", and ".." in its path cannot leave /functions/.`,
|
|
1717
|
+
invalidSignal: "functions.invoke's signal is not an AbortSignal.",
|
|
1718
|
+
bodyWithGetOrHead: "functions.invoke cannot send a body with GET or HEAD.",
|
|
1719
|
+
/** `RELAY_ERROR`: a HEAD answer has no body, so Naro's code is not in it. */
|
|
1720
|
+
headRelay: (status) => `Naro answered HTTP ${String(status)} for the function, but a HEAD answer has no body to say why. Call it with GET to read the reason.`,
|
|
1721
|
+
unreadable: (status) => `The function answered HTTP ${String(status)}, but its body could not be read as its content-type says.`
|
|
1722
|
+
};
|
|
1723
|
+
|
|
1724
|
+
// src/invoke/client.ts
|
|
1725
|
+
var MAX_TIMER_MS = 2147483647;
|
|
1726
|
+
function functionPath(name) {
|
|
1727
|
+
const cut = name.search(/[/?#]/);
|
|
1728
|
+
const head = cut === -1 ? name : name.slice(0, cut);
|
|
1729
|
+
const tail = cut === -1 ? "" : name.slice(cut);
|
|
1730
|
+
return `/functions/${encodeURIComponent(head)}${tail}`;
|
|
1731
|
+
}
|
|
1732
|
+
function dotSegment(segment) {
|
|
1733
|
+
const dots = segment.toLowerCase().replace(/%2e/g, ".");
|
|
1734
|
+
return dots === "." ? 1 : dots === ".." ? 2 : 0;
|
|
1735
|
+
}
|
|
1736
|
+
function leavesFunctions(input) {
|
|
1737
|
+
const name = input.replace(/[\t\n\r]/g, "");
|
|
1738
|
+
const cut = name.search(/[/?#]/);
|
|
1739
|
+
const head = cut === -1 ? name : name.slice(0, cut);
|
|
1740
|
+
if (head === "." || head === "..") return true;
|
|
1741
|
+
if (cut === -1 || name[cut] !== "/") return false;
|
|
1742
|
+
const rest = name.slice(cut + 1);
|
|
1743
|
+
const end = rest.search(/[?#]/);
|
|
1744
|
+
let depth = 1;
|
|
1745
|
+
for (const segment of (end === -1 ? rest : rest.slice(0, end)).split(
|
|
1746
|
+
/[/\\]/
|
|
1747
|
+
)) {
|
|
1748
|
+
const dots = dotSegment(segment);
|
|
1749
|
+
if (dots === 0) depth += 1;
|
|
1750
|
+
if (dots === 2) depth -= 1;
|
|
1751
|
+
if (depth < 0) return true;
|
|
1752
|
+
}
|
|
1753
|
+
return false;
|
|
1754
|
+
}
|
|
1755
|
+
function pathOf(name) {
|
|
1756
|
+
if (typeof name !== "string")
|
|
1757
|
+
return { invalid: FUNCTIONS_MESSAGES.invalidName };
|
|
1758
|
+
if (leavesFunctions(name)) return { invalid: FUNCTIONS_MESSAGES.dotSegments };
|
|
1759
|
+
try {
|
|
1760
|
+
return { path: functionPath(name) };
|
|
1761
|
+
} catch {
|
|
1762
|
+
return { invalid: FUNCTIONS_MESSAGES.unencodableName };
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
function isSignalOrNone(value) {
|
|
1766
|
+
if (value === void 0 || value === null) return true;
|
|
1767
|
+
if (typeof value !== "object") return false;
|
|
1768
|
+
const signal = value;
|
|
1769
|
+
return typeof signal.aborted === "boolean" && typeof signal.addEventListener === "function" && typeof signal.removeEventListener === "function";
|
|
1770
|
+
}
|
|
1771
|
+
function invalidOptions(message) {
|
|
1772
|
+
return failed(new FunctionsFetchError("INVALID_OPTIONS", message, null));
|
|
1773
|
+
}
|
|
1774
|
+
function failed(error) {
|
|
1775
|
+
return { data: null, error };
|
|
1776
|
+
}
|
|
1777
|
+
function fromNaroError(error, cause) {
|
|
1778
|
+
return new FunctionsFetchError(
|
|
1779
|
+
error.code,
|
|
1780
|
+
error.message,
|
|
1781
|
+
cause,
|
|
1782
|
+
error.details
|
|
1783
|
+
);
|
|
1784
|
+
}
|
|
1785
|
+
function failure(kind, cause, timeout) {
|
|
1786
|
+
if (kind === "timeout")
|
|
1787
|
+
return new FunctionsFetchError(
|
|
1788
|
+
"TIMEOUT",
|
|
1789
|
+
FUNCTIONS_MESSAGES.timeout(timeout ?? 0),
|
|
1790
|
+
cause
|
|
1791
|
+
);
|
|
1792
|
+
if (kind === "aborted")
|
|
1793
|
+
return new FunctionsFetchError(
|
|
1794
|
+
"ABORTED",
|
|
1795
|
+
FUNCTIONS_MESSAGES.aborted,
|
|
1796
|
+
cause
|
|
1797
|
+
);
|
|
1798
|
+
return fromNaroError(networkError(cause), cause);
|
|
1799
|
+
}
|
|
1800
|
+
function unreadable(cause, status, signal) {
|
|
1801
|
+
if (signal?.aborted)
|
|
1802
|
+
return new FunctionsFetchError(
|
|
1803
|
+
"ABORTED",
|
|
1804
|
+
FUNCTIONS_MESSAGES.aborted,
|
|
1805
|
+
cause
|
|
1806
|
+
);
|
|
1807
|
+
if (cause instanceof SyntaxError)
|
|
1808
|
+
return new FunctionsFetchError(
|
|
1809
|
+
"INVALID_RESPONSE",
|
|
1810
|
+
FUNCTIONS_MESSAGES.unreadable(status),
|
|
1811
|
+
cause
|
|
1812
|
+
);
|
|
1813
|
+
return fromNaroError(networkError(cause), cause);
|
|
1814
|
+
}
|
|
1815
|
+
async function relayError(response, head) {
|
|
1816
|
+
let body;
|
|
1817
|
+
try {
|
|
1818
|
+
body = await response.clone().json();
|
|
1819
|
+
} catch {
|
|
1820
|
+
body = void 0;
|
|
1821
|
+
}
|
|
1822
|
+
const { status } = response;
|
|
1823
|
+
const error = withRetryAfter(
|
|
1824
|
+
head && body === void 0 ? {
|
|
1825
|
+
code: "RELAY_ERROR",
|
|
1826
|
+
message: FUNCTIONS_MESSAGES.headRelay(status),
|
|
1827
|
+
details: { status }
|
|
1828
|
+
} : errorFromBody(status, body) ?? invalidResponse(status),
|
|
1829
|
+
response.headers
|
|
1830
|
+
);
|
|
1831
|
+
return new FunctionsRelayError(
|
|
1832
|
+
error.code,
|
|
1833
|
+
error.message,
|
|
1834
|
+
response,
|
|
1835
|
+
error.details
|
|
1836
|
+
);
|
|
1837
|
+
}
|
|
1838
|
+
var FunctionsClient = class {
|
|
1839
|
+
#http;
|
|
1840
|
+
#auth;
|
|
1841
|
+
constructor(http, auth) {
|
|
1842
|
+
this.#http = http;
|
|
1843
|
+
this.#auth = auth;
|
|
1844
|
+
}
|
|
1845
|
+
async invoke(name, options = {}) {
|
|
1846
|
+
const method = options.method ?? "POST";
|
|
1847
|
+
const { timeout, signal } = options;
|
|
1848
|
+
const target = pathOf(name);
|
|
1849
|
+
if ("invalid" in target) return invalidOptions(target.invalid);
|
|
1850
|
+
if (timeout !== void 0 && !(Number.isInteger(timeout) && timeout > 0 && timeout <= MAX_TIMER_MS))
|
|
1851
|
+
return invalidOptions(FUNCTIONS_MESSAGES.invalidTimeout);
|
|
1852
|
+
if (!isSignalOrNone(signal))
|
|
1853
|
+
return invalidOptions(FUNCTIONS_MESSAGES.invalidSignal);
|
|
1854
|
+
const verb = typeof method === "string" ? method.toUpperCase() : "";
|
|
1855
|
+
if (options.body !== void 0 && (verb === "GET" || verb === "HEAD"))
|
|
1856
|
+
return invalidOptions(FUNCTIONS_MESSAGES.bodyWithGetOrHead);
|
|
1857
|
+
let headers;
|
|
1858
|
+
try {
|
|
1859
|
+
headers = new Headers(options.headers);
|
|
1860
|
+
} catch (cause) {
|
|
1861
|
+
return failed(
|
|
1862
|
+
new FunctionsFetchError(
|
|
1863
|
+
"INVALID_OPTIONS",
|
|
1864
|
+
FUNCTIONS_MESSAGES.invalidHeaders,
|
|
1865
|
+
cause
|
|
1866
|
+
)
|
|
1867
|
+
);
|
|
1868
|
+
}
|
|
1869
|
+
const prepared = prepareBody(options.body);
|
|
1870
|
+
if (prepared === null)
|
|
1871
|
+
return failed(
|
|
1872
|
+
new FunctionsFetchError(
|
|
1873
|
+
"INVALID_BODY",
|
|
1874
|
+
FUNCTIONS_MESSAGES.invalidBody,
|
|
1875
|
+
null
|
|
1876
|
+
)
|
|
1877
|
+
);
|
|
1878
|
+
if (prepared.contentType !== null && !headers.has("content-type"))
|
|
1879
|
+
headers.set("content-type", prepared.contentType);
|
|
1880
|
+
const result = await fetchUnread(this.#http, {
|
|
1881
|
+
method,
|
|
1882
|
+
path: target.path,
|
|
1883
|
+
headers,
|
|
1884
|
+
body: prepared.body,
|
|
1885
|
+
timeoutMs: timeout,
|
|
1886
|
+
signal,
|
|
1887
|
+
// A sign-in the page URL carried goes first, as for a query (P2-6) —
|
|
1888
|
+
// within the call's timeout and signal; the exchange is not cut.
|
|
1889
|
+
ready: () => this.#auth.ready()
|
|
1890
|
+
});
|
|
1891
|
+
if ("refused" in result) return failed(fromNaroError(result.refused, null));
|
|
1892
|
+
if ("failed" in result)
|
|
1893
|
+
return failed(failure(result.failed, result.cause, timeout));
|
|
1894
|
+
const { response } = result;
|
|
1895
|
+
if (!response.ok) {
|
|
1896
|
+
const error = response.headers.get("x-relay-error") === "true" ? await relayError(response, verb === "HEAD") : new FunctionsHttpError(response);
|
|
1897
|
+
return { data: null, error, response };
|
|
1898
|
+
}
|
|
1899
|
+
const answer = await readAnswer(response, method);
|
|
1900
|
+
if ("unreadable" in answer)
|
|
1901
|
+
return {
|
|
1902
|
+
data: null,
|
|
1903
|
+
error: unreadable(answer.unreadable, response.status, signal),
|
|
1904
|
+
response
|
|
1905
|
+
};
|
|
1906
|
+
return { data: answer.data, error: null, response };
|
|
1907
|
+
}
|
|
1908
|
+
};
|
|
1909
|
+
|
|
1411
1910
|
// src/query/columns.ts
|
|
1412
1911
|
function isSpace(char) {
|
|
1413
1912
|
return char.trim() === "";
|
|
@@ -2917,19 +3416,19 @@ var RealtimeSocket = class {
|
|
|
2917
3416
|
#closed(ws, code, reason, waitedMs) {
|
|
2918
3417
|
if (this.#ws !== ws) return;
|
|
2919
3418
|
const wasOpen = this.#state === "open";
|
|
2920
|
-
const
|
|
3419
|
+
const timedOut2 = waitedMs !== null;
|
|
2921
3420
|
detach(ws);
|
|
2922
3421
|
this.#ws = null;
|
|
2923
3422
|
this.#clearTimers();
|
|
2924
|
-
if (
|
|
3423
|
+
if (timedOut2) closeQuietly(ws, 1e3);
|
|
2925
3424
|
if (!wasOpen) this.#unopened += 1;
|
|
2926
3425
|
const said = this.#said;
|
|
2927
3426
|
this.#said = null;
|
|
2928
|
-
if (!
|
|
2929
|
-
const action =
|
|
3427
|
+
if (!timedOut2 && code === 4008) this.#flooded = true;
|
|
3428
|
+
const action = timedOut2 ? { kind: "retry", minMs: 0 } : closeAction(code, said?.code ?? reason);
|
|
2930
3429
|
if (action.kind === "stop")
|
|
2931
3430
|
return this.#stop(said ?? realtimeStopped(code, reason));
|
|
2932
|
-
const error =
|
|
3431
|
+
const error = timedOut2 ? realtimeTimedOut(waitedMs) : said ?? realtimeClosed(code);
|
|
2933
3432
|
this.#failReplies(error);
|
|
2934
3433
|
this.#state = "waiting";
|
|
2935
3434
|
const delay = delayAfter(
|
|
@@ -2942,7 +3441,7 @@ var RealtimeSocket = class {
|
|
|
2942
3441
|
safely(
|
|
2943
3442
|
() => this.#config.events.dropped(
|
|
2944
3443
|
error,
|
|
2945
|
-
|
|
3444
|
+
timedOut2 ? "TIMED_OUT" : "CHANNEL_ERROR"
|
|
2946
3445
|
)
|
|
2947
3446
|
);
|
|
2948
3447
|
}
|
|
@@ -4339,13 +4838,13 @@ async function uploadInParts(a) {
|
|
|
4339
4838
|
}).catch(() => void 0);
|
|
4340
4839
|
};
|
|
4341
4840
|
const etags = [];
|
|
4342
|
-
const
|
|
4841
|
+
const failure2 = {
|
|
4343
4842
|
error: signal?.aborted ? uploadAborted() : null
|
|
4344
4843
|
};
|
|
4345
4844
|
let next = 1;
|
|
4346
4845
|
let sentBytes = 0;
|
|
4347
4846
|
const onAbort = () => {
|
|
4348
|
-
|
|
4847
|
+
failure2.error ??= uploadAborted();
|
|
4349
4848
|
};
|
|
4350
4849
|
const tryPart = async (n, bytes) => {
|
|
4351
4850
|
const sent2 = await send(a.http, {
|
|
@@ -4374,18 +4873,18 @@ async function uploadInParts(a) {
|
|
|
4374
4873
|
progress(sentBytes);
|
|
4375
4874
|
return null;
|
|
4376
4875
|
}
|
|
4377
|
-
if (attempt2 === ATTEMPTS ||
|
|
4876
|
+
if (attempt2 === ATTEMPTS || failure2.error !== null || !retryable(tried.error, tried.status))
|
|
4378
4877
|
return tried.error;
|
|
4379
4878
|
await sleep(waitAfter(tried.error, attempt2), signal);
|
|
4380
|
-
if (
|
|
4879
|
+
if (failure2.error !== null) return tried.error;
|
|
4381
4880
|
}
|
|
4382
4881
|
};
|
|
4383
4882
|
const worker = async () => {
|
|
4384
|
-
while (
|
|
4883
|
+
while (failure2.error === null && next <= partCount) {
|
|
4385
4884
|
const n = next;
|
|
4386
4885
|
next += 1;
|
|
4387
4886
|
const error = await sendPart(n);
|
|
4388
|
-
if (error !== null)
|
|
4887
|
+
if (error !== null) failure2.error ??= error;
|
|
4389
4888
|
}
|
|
4390
4889
|
};
|
|
4391
4890
|
signal?.addEventListener("abort", onAbort);
|
|
@@ -4393,9 +4892,9 @@ async function uploadInParts(a) {
|
|
|
4393
4892
|
Array.from({ length: Math.min(CONCURRENCY, partCount) }, worker)
|
|
4394
4893
|
);
|
|
4395
4894
|
signal?.removeEventListener("abort", onAbort);
|
|
4396
|
-
if (
|
|
4895
|
+
if (failure2.error !== null) {
|
|
4397
4896
|
await callOff();
|
|
4398
|
-
return fail(
|
|
4897
|
+
return fail(failure2.error);
|
|
4399
4898
|
}
|
|
4400
4899
|
const completed = await call(a.http, {
|
|
4401
4900
|
method: "POST",
|
|
@@ -4626,8 +5125,8 @@ var StorageFileApi = class {
|
|
|
4626
5125
|
const payload = payloadOf(body, options.contentType);
|
|
4627
5126
|
if (!payload.ok) return fail(payload.error);
|
|
4628
5127
|
const cache = `max-age=${options.cacheControl ?? "3600"}`;
|
|
4629
|
-
const
|
|
4630
|
-
if (
|
|
5128
|
+
const unreadable2 = headerProblem(options.contentType, cache);
|
|
5129
|
+
if (unreadable2 !== null) return fail(unreadable2);
|
|
4631
5130
|
const { signal } = options;
|
|
4632
5131
|
if (signal?.aborted) return fail(uploadAborted());
|
|
4633
5132
|
await this.#auth.ready();
|
|
@@ -4928,8 +5427,8 @@ var StorageFileApi = class {
|
|
|
4928
5427
|
const payload = payloadOf(body, given.contentType);
|
|
4929
5428
|
if (!payload.ok) return fail(payload.error);
|
|
4930
5429
|
const cache = `max-age=${given.cacheControl ?? "3600"}`;
|
|
4931
|
-
const
|
|
4932
|
-
if (
|
|
5430
|
+
const unreadable2 = headerProblem(given.contentType, cache);
|
|
5431
|
+
if (unreadable2 !== null) return fail(unreadable2);
|
|
4933
5432
|
const { signal } = given;
|
|
4934
5433
|
if (signal?.aborted) return fail(uploadAborted());
|
|
4935
5434
|
const uploaded = payload.size > MAX_SINGLE_UPLOAD_BYTES ? await uploadInParts({
|
|
@@ -5010,12 +5509,14 @@ var CONFIG_MESSAGES = {
|
|
|
5010
5509
|
bothKeys: "Pass createClient an anonKey or a serviceKey, not both.",
|
|
5011
5510
|
serviceKeyOffServer: SERVICE_KEY_OFF_SERVER_MESSAGE,
|
|
5012
5511
|
fetch: "This runtime has no fetch. Pass one to createClient({ fetch }).",
|
|
5013
|
-
realtime: "createClient's realtime options take a WebSocket class, and heartbeatIntervalMs and timeoutMs in whole milliseconds from 1 to 2147483647."
|
|
5512
|
+
realtime: "createClient's realtime options take a WebSocket class, and heartbeatIntervalMs and timeoutMs in whole milliseconds from 1 to 2147483647.",
|
|
5513
|
+
accessTokenWithServiceKey: "Pass createClient an accessToken or a serviceKey, not both: the service key skips every policy, so the user's token would not count.",
|
|
5514
|
+
accessToken: "createClient's accessToken is a function that returns the user's access token, or null for none."
|
|
5014
5515
|
};
|
|
5015
5516
|
var SERVICE_KEY_PREFIX = "naro_sk_";
|
|
5016
|
-
var
|
|
5517
|
+
var MAX_TIMER_MS2 = 2147483647;
|
|
5017
5518
|
function isMilliseconds(value) {
|
|
5018
|
-
return typeof value === "number" && Number.isInteger(value) && value > 0 && value <=
|
|
5519
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0 && value <= MAX_TIMER_MS2;
|
|
5019
5520
|
}
|
|
5020
5521
|
function realtimeOptionsOf(value) {
|
|
5021
5522
|
if (value === void 0) return {};
|
|
@@ -5065,6 +5566,35 @@ function apiKeyOf(options) {
|
|
|
5065
5566
|
throw new Error(CONFIG_MESSAGES.serviceKeyOffServer);
|
|
5066
5567
|
return { key, service };
|
|
5067
5568
|
}
|
|
5569
|
+
function accessTokenOf(value) {
|
|
5570
|
+
if (value === void 0) return void 0;
|
|
5571
|
+
if (typeof value !== "function") throw new Error(CONFIG_MESSAGES.accessToken);
|
|
5572
|
+
const read = value;
|
|
5573
|
+
return {
|
|
5574
|
+
async get() {
|
|
5575
|
+
const token = await read();
|
|
5576
|
+
if (token === null || token === void 0 || token === "") return null;
|
|
5577
|
+
if (typeof token !== "string")
|
|
5578
|
+
throw new TypeError(`accessToken returned a ${typeof token}`);
|
|
5579
|
+
if (!/^[\x21-\x7E]+$/.test(token))
|
|
5580
|
+
throw new TypeError(
|
|
5581
|
+
"accessToken returned a string that is not a token"
|
|
5582
|
+
);
|
|
5583
|
+
return token;
|
|
5584
|
+
}
|
|
5585
|
+
};
|
|
5586
|
+
}
|
|
5587
|
+
function refusingAuth() {
|
|
5588
|
+
return new Proxy(/* @__PURE__ */ Object.create(null), {
|
|
5589
|
+
get(_target, property) {
|
|
5590
|
+
if (typeof property === "symbol" || property === "then" || property === "toJSON" || property === "constructor" || property === "asymmetricMatch")
|
|
5591
|
+
return void 0;
|
|
5592
|
+
if (property === "toString") return Object.prototype.toString;
|
|
5593
|
+
if (property === "valueOf") return Object.prototype.valueOf;
|
|
5594
|
+
throw new Error(accessTokenAuthMessage(property));
|
|
5595
|
+
}
|
|
5596
|
+
});
|
|
5597
|
+
}
|
|
5068
5598
|
function queryRunner(http, auth) {
|
|
5069
5599
|
return async (query) => {
|
|
5070
5600
|
await auth.ready();
|
|
@@ -5093,7 +5623,14 @@ var NaroClient = class {
|
|
|
5093
5623
|
* them, as supabase-js does.
|
|
5094
5624
|
*/
|
|
5095
5625
|
storage;
|
|
5626
|
+
/**
|
|
5627
|
+
* The project's Naro Functions (design §9): `functions.invoke(name, {
|
|
5628
|
+
* body })`, as in supabase-js.
|
|
5629
|
+
*/
|
|
5630
|
+
functions;
|
|
5096
5631
|
#run;
|
|
5632
|
+
/** Made with `accessToken`: `auth` and `channel()` refuse. */
|
|
5633
|
+
#accessToken;
|
|
5097
5634
|
#newRealtime;
|
|
5098
5635
|
/**
|
|
5099
5636
|
* Made by the first channel(), not by the constructor: it listens to every
|
|
@@ -5104,37 +5641,38 @@ var NaroClient = class {
|
|
|
5104
5641
|
constructor(options) {
|
|
5105
5642
|
const baseUrl = baseUrlOf(options.url);
|
|
5106
5643
|
const { key: apiKey, service } = apiKeyOf(options);
|
|
5644
|
+
const accessToken = accessTokenOf(options.accessToken);
|
|
5645
|
+
if (accessToken !== void 0 && service)
|
|
5646
|
+
throw new Error(CONFIG_MESSAGES.accessTokenWithServiceKey);
|
|
5107
5647
|
if (options.fetch === void 0 && typeof globalThis.fetch !== "function")
|
|
5108
5648
|
throw new Error(CONFIG_MESSAGES.fetch);
|
|
5109
5649
|
const realtime = realtimeOptionsOf(options.realtime);
|
|
5110
|
-
const storage = options.auth?.storage ?? defaultStorage();
|
|
5650
|
+
const storage = accessToken !== void 0 ? memoryStorage() : options.auth?.storage ?? defaultStorage();
|
|
5111
5651
|
const http = {
|
|
5112
5652
|
baseUrl,
|
|
5113
5653
|
apiKey,
|
|
5114
5654
|
fetch: options.fetch,
|
|
5115
5655
|
tokens: new TokenStore(storage, storageKey(baseUrl)),
|
|
5116
|
-
|
|
5117
|
-
|
|
5656
|
+
service,
|
|
5657
|
+
...accessToken !== void 0 ? { accessToken } : { accessJwt: new TokenStore(storage, accessJwtKey(baseUrl)) }
|
|
5118
5658
|
};
|
|
5119
|
-
|
|
5659
|
+
const auth = new AuthClient(http, {
|
|
5120
5660
|
verifiers: new FlowVerifiers(
|
|
5121
5661
|
new TokenStore(storage, verifierKey(baseUrl))
|
|
5122
5662
|
),
|
|
5123
5663
|
recovery: new RecoveryToken(
|
|
5124
5664
|
new TokenStore(storage, recoveryKey(baseUrl))
|
|
5125
5665
|
),
|
|
5126
|
-
detectSessionInUrl: options.auth?.detectSessionInUrl ?? true,
|
|
5666
|
+
detectSessionInUrl: accessToken === void 0 && (options.auth?.detectSessionInUrl ?? true),
|
|
5127
5667
|
shared: { storage, key: storageKey(baseUrl) },
|
|
5128
5668
|
serviceKey: service
|
|
5129
5669
|
});
|
|
5130
|
-
this.#
|
|
5131
|
-
this.
|
|
5132
|
-
this.#
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
options: realtime
|
|
5137
|
-
});
|
|
5670
|
+
this.#accessToken = accessToken !== void 0;
|
|
5671
|
+
this.auth = this.#accessToken ? refusingAuth() : auth;
|
|
5672
|
+
this.#run = queryRunner(http, auth);
|
|
5673
|
+
this.storage = new StorageClient(http, auth);
|
|
5674
|
+
this.functions = new FunctionsClient(http, auth);
|
|
5675
|
+
this.#newRealtime = () => new RealtimeClient({ http, auth, service, options: realtime });
|
|
5138
5676
|
}
|
|
5139
5677
|
/** Start a query on `table`: `.select()`, `.insert()`, `.update()` or `.delete()`. */
|
|
5140
5678
|
from(table) {
|
|
@@ -5146,6 +5684,7 @@ var NaroClient = class {
|
|
|
5146
5684
|
* `removeChannel`. Nothing connects until the first `subscribe()`.
|
|
5147
5685
|
*/
|
|
5148
5686
|
channel(name, options) {
|
|
5687
|
+
if (this.#accessToken) throw new Error(ACCESS_TOKEN_CHANNEL_MESSAGE);
|
|
5149
5688
|
return this.#client().channel(name, options);
|
|
5150
5689
|
}
|
|
5151
5690
|
/** The channels made with `channel()` and not yet removed. */
|
|
@@ -5157,7 +5696,9 @@ var NaroClient = class {
|
|
|
5157
5696
|
* one; the connection closes with the last channel.
|
|
5158
5697
|
*/
|
|
5159
5698
|
removeChannel(channel) {
|
|
5160
|
-
|
|
5699
|
+
if (this.#realtime === void 0)
|
|
5700
|
+
return Promise.resolve({ data: null, error: null });
|
|
5701
|
+
return this.#realtime.removeChannel(channel);
|
|
5161
5702
|
}
|
|
5162
5703
|
/** `removeChannel()` for every channel; the connection closes. */
|
|
5163
5704
|
removeAllChannels() {
|
|
@@ -5174,6 +5715,10 @@ function createClient(options) {
|
|
|
5174
5715
|
return new NaroClient(options);
|
|
5175
5716
|
}
|
|
5176
5717
|
export {
|
|
5718
|
+
FunctionsError,
|
|
5719
|
+
FunctionsFetchError,
|
|
5720
|
+
FunctionsHttpError,
|
|
5721
|
+
FunctionsRelayError,
|
|
5177
5722
|
NaroClient,
|
|
5178
5723
|
createClient,
|
|
5179
5724
|
objectRoute,
|