naro-js 0.6.1 → 0.7.1
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 +137 -1
- 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 +601 -52
- 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/dist/realtime/close.d.ts +6 -0
- package/package.json +16 -4
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,15 @@ 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
|
+
// The dispatcher's REST 429 of a project over its month of Realtime (3-2e):
|
|
721
|
+
// `x-retry-after` is the seconds to the 1st at 00:00 UTC.
|
|
722
|
+
"REALTIME_QUOTA_EXCEEDED",
|
|
723
|
+
// Naro Functions (design F20): a team over its monthly function CPU, and
|
|
724
|
+
// the control plane's Cloudflare limit. `invoke` reads x-retry-after on
|
|
725
|
+
// every relay error anyway (invoke/client.ts).
|
|
726
|
+
"FUNCTIONS_QUOTA_EXCEEDED",
|
|
727
|
+
"FUNCTIONS_RATE_LIMITED"
|
|
508
728
|
]);
|
|
509
729
|
function withRetryAfter(error, headers) {
|
|
510
730
|
const header = headers.get("x-retry-after")?.trim() ?? "";
|
|
@@ -828,14 +1048,14 @@ var AuthClient = class {
|
|
|
828
1048
|
*/
|
|
829
1049
|
async signInWithOAuth(options) {
|
|
830
1050
|
const { provider } = options;
|
|
831
|
-
const
|
|
1051
|
+
const failed2 = (error) => ({
|
|
832
1052
|
data: { provider, url: null },
|
|
833
1053
|
error
|
|
834
1054
|
});
|
|
835
|
-
if (!OAUTH_PROVIDERS.includes(provider)) return
|
|
1055
|
+
if (!OAUTH_PROVIDERS.includes(provider)) return failed2(invalidProvider());
|
|
836
1056
|
await this.#flow.recovery.forget();
|
|
837
1057
|
const pair = await createPkcePair();
|
|
838
|
-
if (!pair) return
|
|
1058
|
+
if (!pair) return failed2(cryptoUnavailable());
|
|
839
1059
|
await this.#flow.verifiers.add(pair.challenge, pair.verifier);
|
|
840
1060
|
const redirectTo = options.redirectTo ?? currentPage();
|
|
841
1061
|
const url = new URL(
|
|
@@ -1408,6 +1628,288 @@ var TokenStore = class {
|
|
|
1408
1628
|
}
|
|
1409
1629
|
};
|
|
1410
1630
|
|
|
1631
|
+
// src/invoke/body.ts
|
|
1632
|
+
function prepareBody(body) {
|
|
1633
|
+
if (body === void 0) return { body: void 0, contentType: null };
|
|
1634
|
+
if (typeof body === "string") return { body, contentType: "text/plain" };
|
|
1635
|
+
if (typeof Blob === "function" && body instanceof Blob)
|
|
1636
|
+
return { body, contentType: "application/octet-stream" };
|
|
1637
|
+
if (body instanceof ArrayBuffer || ArrayBuffer.isView(body))
|
|
1638
|
+
return { body, contentType: "application/octet-stream" };
|
|
1639
|
+
if (typeof FormData === "function" && body instanceof FormData)
|
|
1640
|
+
return { body, contentType: null };
|
|
1641
|
+
if (typeof URLSearchParams === "function" && body instanceof URLSearchParams)
|
|
1642
|
+
return { body, contentType: null };
|
|
1643
|
+
if (typeof ReadableStream === "function" && body instanceof ReadableStream)
|
|
1644
|
+
return { body, contentType: null };
|
|
1645
|
+
try {
|
|
1646
|
+
const text = JSON.stringify(body);
|
|
1647
|
+
return text === void 0 ? null : { body: text, contentType: "application/json" };
|
|
1648
|
+
} catch {
|
|
1649
|
+
return null;
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
async function readAnswer(response, method) {
|
|
1653
|
+
if (response.status === 204 || method === "HEAD") return { data: null };
|
|
1654
|
+
const type = (response.headers.get("content-type") ?? "").split(";")[0]?.trim().toLowerCase();
|
|
1655
|
+
try {
|
|
1656
|
+
if (type === "application/json") {
|
|
1657
|
+
const text = await response.text();
|
|
1658
|
+
return {
|
|
1659
|
+
data: text.trim() === "" ? null : JSON.parse(text)
|
|
1660
|
+
};
|
|
1661
|
+
}
|
|
1662
|
+
if (type === "application/octet-stream" || type === "application/pdf")
|
|
1663
|
+
return { data: await response.blob() };
|
|
1664
|
+
if (type === "text/event-stream") return { data: response };
|
|
1665
|
+
if (type === "multipart/form-data")
|
|
1666
|
+
return { data: await response.formData() };
|
|
1667
|
+
return { data: await response.text() };
|
|
1668
|
+
} catch (cause) {
|
|
1669
|
+
return { unreadable: cause };
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
// src/invoke/errors.ts
|
|
1674
|
+
var FunctionsError = class extends Error {
|
|
1675
|
+
code;
|
|
1676
|
+
context;
|
|
1677
|
+
constructor(code, message, context, details) {
|
|
1678
|
+
super(message);
|
|
1679
|
+
this.name = "FunctionsError";
|
|
1680
|
+
this.code = code;
|
|
1681
|
+
this.context = context;
|
|
1682
|
+
if (details !== void 0)
|
|
1683
|
+
Object.defineProperty(this, "details", {
|
|
1684
|
+
value: details,
|
|
1685
|
+
enumerable: true
|
|
1686
|
+
});
|
|
1687
|
+
}
|
|
1688
|
+
};
|
|
1689
|
+
var FunctionsHttpError = class extends FunctionsError {
|
|
1690
|
+
constructor(response) {
|
|
1691
|
+
super(
|
|
1692
|
+
"FUNCTION_HTTP_ERROR",
|
|
1693
|
+
`The function answered with HTTP ${String(response.status)}.`,
|
|
1694
|
+
response
|
|
1695
|
+
);
|
|
1696
|
+
this.name = "FunctionsHttpError";
|
|
1697
|
+
}
|
|
1698
|
+
};
|
|
1699
|
+
var FunctionsRelayError = class extends FunctionsError {
|
|
1700
|
+
constructor(code, message, response, details) {
|
|
1701
|
+
super(code, message, response, details);
|
|
1702
|
+
this.name = "FunctionsRelayError";
|
|
1703
|
+
}
|
|
1704
|
+
};
|
|
1705
|
+
var FunctionsFetchError = class extends FunctionsError {
|
|
1706
|
+
constructor(code, message, cause, details) {
|
|
1707
|
+
super(code, message, cause, details);
|
|
1708
|
+
this.name = "FunctionsFetchError";
|
|
1709
|
+
}
|
|
1710
|
+
};
|
|
1711
|
+
var FUNCTIONS_MESSAGES = {
|
|
1712
|
+
timeout: (ms) => `The function did not answer within ${String(ms)} ms.`,
|
|
1713
|
+
aborted: "The function call was stopped before the function answered.",
|
|
1714
|
+
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.",
|
|
1715
|
+
invalidTimeout: "functions.invoke's timeout is whole milliseconds from 1 to 2147483647.",
|
|
1716
|
+
invalidHeaders: "functions.invoke's headers are not valid HTTP header names and values.",
|
|
1717
|
+
invalidName: "functions.invoke's name is a string: the function's name, then any path and query.",
|
|
1718
|
+
unencodableName: "functions.invoke's name is not well-formed Unicode: it has a lone surrogate.",
|
|
1719
|
+
dotSegments: `functions.invoke's name cannot be "." or "..", and ".." in its path cannot leave /functions/.`,
|
|
1720
|
+
invalidSignal: "functions.invoke's signal is not an AbortSignal.",
|
|
1721
|
+
bodyWithGetOrHead: "functions.invoke cannot send a body with GET or HEAD.",
|
|
1722
|
+
/** `RELAY_ERROR`: a HEAD answer has no body, so Naro's code is not in it. */
|
|
1723
|
+
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.`,
|
|
1724
|
+
unreadable: (status) => `The function answered HTTP ${String(status)}, but its body could not be read as its content-type says.`
|
|
1725
|
+
};
|
|
1726
|
+
|
|
1727
|
+
// src/invoke/client.ts
|
|
1728
|
+
var MAX_TIMER_MS = 2147483647;
|
|
1729
|
+
function functionPath(name) {
|
|
1730
|
+
const cut = name.search(/[/?#]/);
|
|
1731
|
+
const head = cut === -1 ? name : name.slice(0, cut);
|
|
1732
|
+
const tail = cut === -1 ? "" : name.slice(cut);
|
|
1733
|
+
return `/functions/${encodeURIComponent(head)}${tail}`;
|
|
1734
|
+
}
|
|
1735
|
+
function dotSegment(segment) {
|
|
1736
|
+
const dots = segment.toLowerCase().replace(/%2e/g, ".");
|
|
1737
|
+
return dots === "." ? 1 : dots === ".." ? 2 : 0;
|
|
1738
|
+
}
|
|
1739
|
+
function leavesFunctions(input) {
|
|
1740
|
+
const name = input.replace(/[\t\n\r]/g, "");
|
|
1741
|
+
const cut = name.search(/[/?#]/);
|
|
1742
|
+
const head = cut === -1 ? name : name.slice(0, cut);
|
|
1743
|
+
if (head === "." || head === "..") return true;
|
|
1744
|
+
if (cut === -1 || name[cut] !== "/") return false;
|
|
1745
|
+
const rest = name.slice(cut + 1);
|
|
1746
|
+
const end = rest.search(/[?#]/);
|
|
1747
|
+
let depth = 1;
|
|
1748
|
+
for (const segment of (end === -1 ? rest : rest.slice(0, end)).split(
|
|
1749
|
+
/[/\\]/
|
|
1750
|
+
)) {
|
|
1751
|
+
const dots = dotSegment(segment);
|
|
1752
|
+
if (dots === 0) depth += 1;
|
|
1753
|
+
if (dots === 2) depth -= 1;
|
|
1754
|
+
if (depth < 0) return true;
|
|
1755
|
+
}
|
|
1756
|
+
return false;
|
|
1757
|
+
}
|
|
1758
|
+
function pathOf(name) {
|
|
1759
|
+
if (typeof name !== "string")
|
|
1760
|
+
return { invalid: FUNCTIONS_MESSAGES.invalidName };
|
|
1761
|
+
if (leavesFunctions(name)) return { invalid: FUNCTIONS_MESSAGES.dotSegments };
|
|
1762
|
+
try {
|
|
1763
|
+
return { path: functionPath(name) };
|
|
1764
|
+
} catch {
|
|
1765
|
+
return { invalid: FUNCTIONS_MESSAGES.unencodableName };
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
function isSignalOrNone(value) {
|
|
1769
|
+
if (value === void 0 || value === null) return true;
|
|
1770
|
+
if (typeof value !== "object") return false;
|
|
1771
|
+
const signal = value;
|
|
1772
|
+
return typeof signal.aborted === "boolean" && typeof signal.addEventListener === "function" && typeof signal.removeEventListener === "function";
|
|
1773
|
+
}
|
|
1774
|
+
function invalidOptions(message) {
|
|
1775
|
+
return failed(new FunctionsFetchError("INVALID_OPTIONS", message, null));
|
|
1776
|
+
}
|
|
1777
|
+
function failed(error) {
|
|
1778
|
+
return { data: null, error };
|
|
1779
|
+
}
|
|
1780
|
+
function fromNaroError(error, cause) {
|
|
1781
|
+
return new FunctionsFetchError(
|
|
1782
|
+
error.code,
|
|
1783
|
+
error.message,
|
|
1784
|
+
cause,
|
|
1785
|
+
error.details
|
|
1786
|
+
);
|
|
1787
|
+
}
|
|
1788
|
+
function failure(kind, cause, timeout) {
|
|
1789
|
+
if (kind === "timeout")
|
|
1790
|
+
return new FunctionsFetchError(
|
|
1791
|
+
"TIMEOUT",
|
|
1792
|
+
FUNCTIONS_MESSAGES.timeout(timeout ?? 0),
|
|
1793
|
+
cause
|
|
1794
|
+
);
|
|
1795
|
+
if (kind === "aborted")
|
|
1796
|
+
return new FunctionsFetchError(
|
|
1797
|
+
"ABORTED",
|
|
1798
|
+
FUNCTIONS_MESSAGES.aborted,
|
|
1799
|
+
cause
|
|
1800
|
+
);
|
|
1801
|
+
return fromNaroError(networkError(cause), cause);
|
|
1802
|
+
}
|
|
1803
|
+
function unreadable(cause, status, signal) {
|
|
1804
|
+
if (signal?.aborted)
|
|
1805
|
+
return new FunctionsFetchError(
|
|
1806
|
+
"ABORTED",
|
|
1807
|
+
FUNCTIONS_MESSAGES.aborted,
|
|
1808
|
+
cause
|
|
1809
|
+
);
|
|
1810
|
+
if (cause instanceof SyntaxError)
|
|
1811
|
+
return new FunctionsFetchError(
|
|
1812
|
+
"INVALID_RESPONSE",
|
|
1813
|
+
FUNCTIONS_MESSAGES.unreadable(status),
|
|
1814
|
+
cause
|
|
1815
|
+
);
|
|
1816
|
+
return fromNaroError(networkError(cause), cause);
|
|
1817
|
+
}
|
|
1818
|
+
async function relayError(response, head) {
|
|
1819
|
+
let body;
|
|
1820
|
+
try {
|
|
1821
|
+
body = await response.clone().json();
|
|
1822
|
+
} catch {
|
|
1823
|
+
body = void 0;
|
|
1824
|
+
}
|
|
1825
|
+
const { status } = response;
|
|
1826
|
+
const error = withRetryAfter(
|
|
1827
|
+
head && body === void 0 ? {
|
|
1828
|
+
code: "RELAY_ERROR",
|
|
1829
|
+
message: FUNCTIONS_MESSAGES.headRelay(status),
|
|
1830
|
+
details: { status }
|
|
1831
|
+
} : errorFromBody(status, body) ?? invalidResponse(status),
|
|
1832
|
+
response.headers
|
|
1833
|
+
);
|
|
1834
|
+
return new FunctionsRelayError(
|
|
1835
|
+
error.code,
|
|
1836
|
+
error.message,
|
|
1837
|
+
response,
|
|
1838
|
+
error.details
|
|
1839
|
+
);
|
|
1840
|
+
}
|
|
1841
|
+
var FunctionsClient = class {
|
|
1842
|
+
#http;
|
|
1843
|
+
#auth;
|
|
1844
|
+
constructor(http, auth) {
|
|
1845
|
+
this.#http = http;
|
|
1846
|
+
this.#auth = auth;
|
|
1847
|
+
}
|
|
1848
|
+
async invoke(name, options = {}) {
|
|
1849
|
+
const method = options.method ?? "POST";
|
|
1850
|
+
const { timeout, signal } = options;
|
|
1851
|
+
const target = pathOf(name);
|
|
1852
|
+
if ("invalid" in target) return invalidOptions(target.invalid);
|
|
1853
|
+
if (timeout !== void 0 && !(Number.isInteger(timeout) && timeout > 0 && timeout <= MAX_TIMER_MS))
|
|
1854
|
+
return invalidOptions(FUNCTIONS_MESSAGES.invalidTimeout);
|
|
1855
|
+
if (!isSignalOrNone(signal))
|
|
1856
|
+
return invalidOptions(FUNCTIONS_MESSAGES.invalidSignal);
|
|
1857
|
+
const verb = typeof method === "string" ? method.toUpperCase() : "";
|
|
1858
|
+
if (options.body !== void 0 && (verb === "GET" || verb === "HEAD"))
|
|
1859
|
+
return invalidOptions(FUNCTIONS_MESSAGES.bodyWithGetOrHead);
|
|
1860
|
+
let headers;
|
|
1861
|
+
try {
|
|
1862
|
+
headers = new Headers(options.headers);
|
|
1863
|
+
} catch (cause) {
|
|
1864
|
+
return failed(
|
|
1865
|
+
new FunctionsFetchError(
|
|
1866
|
+
"INVALID_OPTIONS",
|
|
1867
|
+
FUNCTIONS_MESSAGES.invalidHeaders,
|
|
1868
|
+
cause
|
|
1869
|
+
)
|
|
1870
|
+
);
|
|
1871
|
+
}
|
|
1872
|
+
const prepared = prepareBody(options.body);
|
|
1873
|
+
if (prepared === null)
|
|
1874
|
+
return failed(
|
|
1875
|
+
new FunctionsFetchError(
|
|
1876
|
+
"INVALID_BODY",
|
|
1877
|
+
FUNCTIONS_MESSAGES.invalidBody,
|
|
1878
|
+
null
|
|
1879
|
+
)
|
|
1880
|
+
);
|
|
1881
|
+
if (prepared.contentType !== null && !headers.has("content-type"))
|
|
1882
|
+
headers.set("content-type", prepared.contentType);
|
|
1883
|
+
const result = await fetchUnread(this.#http, {
|
|
1884
|
+
method,
|
|
1885
|
+
path: target.path,
|
|
1886
|
+
headers,
|
|
1887
|
+
body: prepared.body,
|
|
1888
|
+
timeoutMs: timeout,
|
|
1889
|
+
signal,
|
|
1890
|
+
// A sign-in the page URL carried goes first, as for a query (P2-6) —
|
|
1891
|
+
// within the call's timeout and signal; the exchange is not cut.
|
|
1892
|
+
ready: () => this.#auth.ready()
|
|
1893
|
+
});
|
|
1894
|
+
if ("refused" in result) return failed(fromNaroError(result.refused, null));
|
|
1895
|
+
if ("failed" in result)
|
|
1896
|
+
return failed(failure(result.failed, result.cause, timeout));
|
|
1897
|
+
const { response } = result;
|
|
1898
|
+
if (!response.ok) {
|
|
1899
|
+
const error = response.headers.get("x-relay-error") === "true" ? await relayError(response, verb === "HEAD") : new FunctionsHttpError(response);
|
|
1900
|
+
return { data: null, error, response };
|
|
1901
|
+
}
|
|
1902
|
+
const answer = await readAnswer(response, method);
|
|
1903
|
+
if ("unreadable" in answer)
|
|
1904
|
+
return {
|
|
1905
|
+
data: null,
|
|
1906
|
+
error: unreadable(answer.unreadable, response.status, signal),
|
|
1907
|
+
response
|
|
1908
|
+
};
|
|
1909
|
+
return { data: answer.data, error: null, response };
|
|
1910
|
+
}
|
|
1911
|
+
};
|
|
1912
|
+
|
|
1411
1913
|
// src/query/columns.ts
|
|
1412
1914
|
function isSpace(char) {
|
|
1413
1915
|
return char.trim() === "";
|
|
@@ -2173,6 +2675,7 @@ var TableBuilder = class {
|
|
|
2173
2675
|
var BACKOFF_STEPS_MS = [1e3, 2e3, 5e3, 1e4];
|
|
2174
2676
|
var RATE_LIMITED_MIN_MS = 5e3;
|
|
2175
2677
|
var IP_LIMIT_WAIT_MS = 6e4;
|
|
2678
|
+
var QUOTA_WAIT_MS = 9e5;
|
|
2176
2679
|
var RESTART_JITTER_MS = 1e4;
|
|
2177
2680
|
var UNOPENED_SLOWDOWN_AFTER = 6;
|
|
2178
2681
|
var UNOPENED_DELAY_MS = 6e4;
|
|
@@ -2189,7 +2692,7 @@ function closeAction(code, reason) {
|
|
|
2189
2692
|
if (code === 4401) return { kind: "session" };
|
|
2190
2693
|
if (code === 4503) return { kind: "restart" };
|
|
2191
2694
|
if (code === 4029)
|
|
2192
|
-
return reason === "IP_RATE_LIMITED" ? { kind: "wait", ms: IP_LIMIT_WAIT_MS } : { kind: "retry", minMs: RATE_LIMITED_MIN_MS };
|
|
2695
|
+
return reason === "IP_RATE_LIMITED" ? { kind: "wait", ms: IP_LIMIT_WAIT_MS } : reason === "REALTIME_QUOTA_EXCEEDED" ? { kind: "wait", ms: QUOTA_WAIT_MS } : { kind: "retry", minMs: RATE_LIMITED_MIN_MS };
|
|
2193
2696
|
return { kind: "retry", minMs: 0 };
|
|
2194
2697
|
}
|
|
2195
2698
|
function backoffMs(attempt2, random) {
|
|
@@ -2917,19 +3420,19 @@ var RealtimeSocket = class {
|
|
|
2917
3420
|
#closed(ws, code, reason, waitedMs) {
|
|
2918
3421
|
if (this.#ws !== ws) return;
|
|
2919
3422
|
const wasOpen = this.#state === "open";
|
|
2920
|
-
const
|
|
3423
|
+
const timedOut2 = waitedMs !== null;
|
|
2921
3424
|
detach(ws);
|
|
2922
3425
|
this.#ws = null;
|
|
2923
3426
|
this.#clearTimers();
|
|
2924
|
-
if (
|
|
3427
|
+
if (timedOut2) closeQuietly(ws, 1e3);
|
|
2925
3428
|
if (!wasOpen) this.#unopened += 1;
|
|
2926
3429
|
const said = this.#said;
|
|
2927
3430
|
this.#said = null;
|
|
2928
|
-
if (!
|
|
2929
|
-
const action =
|
|
3431
|
+
if (!timedOut2 && code === 4008) this.#flooded = true;
|
|
3432
|
+
const action = timedOut2 ? { kind: "retry", minMs: 0 } : closeAction(code, said?.code ?? reason);
|
|
2930
3433
|
if (action.kind === "stop")
|
|
2931
3434
|
return this.#stop(said ?? realtimeStopped(code, reason));
|
|
2932
|
-
const error =
|
|
3435
|
+
const error = timedOut2 ? realtimeTimedOut(waitedMs) : said ?? realtimeClosed(code);
|
|
2933
3436
|
this.#failReplies(error);
|
|
2934
3437
|
this.#state = "waiting";
|
|
2935
3438
|
const delay = delayAfter(
|
|
@@ -2942,7 +3445,7 @@ var RealtimeSocket = class {
|
|
|
2942
3445
|
safely(
|
|
2943
3446
|
() => this.#config.events.dropped(
|
|
2944
3447
|
error,
|
|
2945
|
-
|
|
3448
|
+
timedOut2 ? "TIMED_OUT" : "CHANNEL_ERROR"
|
|
2946
3449
|
)
|
|
2947
3450
|
);
|
|
2948
3451
|
}
|
|
@@ -4339,13 +4842,13 @@ async function uploadInParts(a) {
|
|
|
4339
4842
|
}).catch(() => void 0);
|
|
4340
4843
|
};
|
|
4341
4844
|
const etags = [];
|
|
4342
|
-
const
|
|
4845
|
+
const failure2 = {
|
|
4343
4846
|
error: signal?.aborted ? uploadAborted() : null
|
|
4344
4847
|
};
|
|
4345
4848
|
let next = 1;
|
|
4346
4849
|
let sentBytes = 0;
|
|
4347
4850
|
const onAbort = () => {
|
|
4348
|
-
|
|
4851
|
+
failure2.error ??= uploadAborted();
|
|
4349
4852
|
};
|
|
4350
4853
|
const tryPart = async (n, bytes) => {
|
|
4351
4854
|
const sent2 = await send(a.http, {
|
|
@@ -4374,18 +4877,18 @@ async function uploadInParts(a) {
|
|
|
4374
4877
|
progress(sentBytes);
|
|
4375
4878
|
return null;
|
|
4376
4879
|
}
|
|
4377
|
-
if (attempt2 === ATTEMPTS ||
|
|
4880
|
+
if (attempt2 === ATTEMPTS || failure2.error !== null || !retryable(tried.error, tried.status))
|
|
4378
4881
|
return tried.error;
|
|
4379
4882
|
await sleep(waitAfter(tried.error, attempt2), signal);
|
|
4380
|
-
if (
|
|
4883
|
+
if (failure2.error !== null) return tried.error;
|
|
4381
4884
|
}
|
|
4382
4885
|
};
|
|
4383
4886
|
const worker = async () => {
|
|
4384
|
-
while (
|
|
4887
|
+
while (failure2.error === null && next <= partCount) {
|
|
4385
4888
|
const n = next;
|
|
4386
4889
|
next += 1;
|
|
4387
4890
|
const error = await sendPart(n);
|
|
4388
|
-
if (error !== null)
|
|
4891
|
+
if (error !== null) failure2.error ??= error;
|
|
4389
4892
|
}
|
|
4390
4893
|
};
|
|
4391
4894
|
signal?.addEventListener("abort", onAbort);
|
|
@@ -4393,9 +4896,9 @@ async function uploadInParts(a) {
|
|
|
4393
4896
|
Array.from({ length: Math.min(CONCURRENCY, partCount) }, worker)
|
|
4394
4897
|
);
|
|
4395
4898
|
signal?.removeEventListener("abort", onAbort);
|
|
4396
|
-
if (
|
|
4899
|
+
if (failure2.error !== null) {
|
|
4397
4900
|
await callOff();
|
|
4398
|
-
return fail(
|
|
4901
|
+
return fail(failure2.error);
|
|
4399
4902
|
}
|
|
4400
4903
|
const completed = await call(a.http, {
|
|
4401
4904
|
method: "POST",
|
|
@@ -4626,8 +5129,8 @@ var StorageFileApi = class {
|
|
|
4626
5129
|
const payload = payloadOf(body, options.contentType);
|
|
4627
5130
|
if (!payload.ok) return fail(payload.error);
|
|
4628
5131
|
const cache = `max-age=${options.cacheControl ?? "3600"}`;
|
|
4629
|
-
const
|
|
4630
|
-
if (
|
|
5132
|
+
const unreadable2 = headerProblem(options.contentType, cache);
|
|
5133
|
+
if (unreadable2 !== null) return fail(unreadable2);
|
|
4631
5134
|
const { signal } = options;
|
|
4632
5135
|
if (signal?.aborted) return fail(uploadAborted());
|
|
4633
5136
|
await this.#auth.ready();
|
|
@@ -4928,8 +5431,8 @@ var StorageFileApi = class {
|
|
|
4928
5431
|
const payload = payloadOf(body, given.contentType);
|
|
4929
5432
|
if (!payload.ok) return fail(payload.error);
|
|
4930
5433
|
const cache = `max-age=${given.cacheControl ?? "3600"}`;
|
|
4931
|
-
const
|
|
4932
|
-
if (
|
|
5434
|
+
const unreadable2 = headerProblem(given.contentType, cache);
|
|
5435
|
+
if (unreadable2 !== null) return fail(unreadable2);
|
|
4933
5436
|
const { signal } = given;
|
|
4934
5437
|
if (signal?.aborted) return fail(uploadAborted());
|
|
4935
5438
|
const uploaded = payload.size > MAX_SINGLE_UPLOAD_BYTES ? await uploadInParts({
|
|
@@ -5010,12 +5513,14 @@ var CONFIG_MESSAGES = {
|
|
|
5010
5513
|
bothKeys: "Pass createClient an anonKey or a serviceKey, not both.",
|
|
5011
5514
|
serviceKeyOffServer: SERVICE_KEY_OFF_SERVER_MESSAGE,
|
|
5012
5515
|
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."
|
|
5516
|
+
realtime: "createClient's realtime options take a WebSocket class, and heartbeatIntervalMs and timeoutMs in whole milliseconds from 1 to 2147483647.",
|
|
5517
|
+
accessTokenWithServiceKey: "Pass createClient an accessToken or a serviceKey, not both: the service key skips every policy, so the user's token would not count.",
|
|
5518
|
+
accessToken: "createClient's accessToken is a function that returns the user's access token, or null for none."
|
|
5014
5519
|
};
|
|
5015
5520
|
var SERVICE_KEY_PREFIX = "naro_sk_";
|
|
5016
|
-
var
|
|
5521
|
+
var MAX_TIMER_MS2 = 2147483647;
|
|
5017
5522
|
function isMilliseconds(value) {
|
|
5018
|
-
return typeof value === "number" && Number.isInteger(value) && value > 0 && value <=
|
|
5523
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0 && value <= MAX_TIMER_MS2;
|
|
5019
5524
|
}
|
|
5020
5525
|
function realtimeOptionsOf(value) {
|
|
5021
5526
|
if (value === void 0) return {};
|
|
@@ -5065,6 +5570,35 @@ function apiKeyOf(options) {
|
|
|
5065
5570
|
throw new Error(CONFIG_MESSAGES.serviceKeyOffServer);
|
|
5066
5571
|
return { key, service };
|
|
5067
5572
|
}
|
|
5573
|
+
function accessTokenOf(value) {
|
|
5574
|
+
if (value === void 0) return void 0;
|
|
5575
|
+
if (typeof value !== "function") throw new Error(CONFIG_MESSAGES.accessToken);
|
|
5576
|
+
const read = value;
|
|
5577
|
+
return {
|
|
5578
|
+
async get() {
|
|
5579
|
+
const token = await read();
|
|
5580
|
+
if (token === null || token === void 0 || token === "") return null;
|
|
5581
|
+
if (typeof token !== "string")
|
|
5582
|
+
throw new TypeError(`accessToken returned a ${typeof token}`);
|
|
5583
|
+
if (!/^[\x21-\x7E]+$/.test(token))
|
|
5584
|
+
throw new TypeError(
|
|
5585
|
+
"accessToken returned a string that is not a token"
|
|
5586
|
+
);
|
|
5587
|
+
return token;
|
|
5588
|
+
}
|
|
5589
|
+
};
|
|
5590
|
+
}
|
|
5591
|
+
function refusingAuth() {
|
|
5592
|
+
return new Proxy(/* @__PURE__ */ Object.create(null), {
|
|
5593
|
+
get(_target, property) {
|
|
5594
|
+
if (typeof property === "symbol" || property === "then" || property === "toJSON" || property === "constructor" || property === "asymmetricMatch")
|
|
5595
|
+
return void 0;
|
|
5596
|
+
if (property === "toString") return Object.prototype.toString;
|
|
5597
|
+
if (property === "valueOf") return Object.prototype.valueOf;
|
|
5598
|
+
throw new Error(accessTokenAuthMessage(property));
|
|
5599
|
+
}
|
|
5600
|
+
});
|
|
5601
|
+
}
|
|
5068
5602
|
function queryRunner(http, auth) {
|
|
5069
5603
|
return async (query) => {
|
|
5070
5604
|
await auth.ready();
|
|
@@ -5093,7 +5627,14 @@ var NaroClient = class {
|
|
|
5093
5627
|
* them, as supabase-js does.
|
|
5094
5628
|
*/
|
|
5095
5629
|
storage;
|
|
5630
|
+
/**
|
|
5631
|
+
* The project's Naro Functions (design §9): `functions.invoke(name, {
|
|
5632
|
+
* body })`, as in supabase-js.
|
|
5633
|
+
*/
|
|
5634
|
+
functions;
|
|
5096
5635
|
#run;
|
|
5636
|
+
/** Made with `accessToken`: `auth` and `channel()` refuse. */
|
|
5637
|
+
#accessToken;
|
|
5097
5638
|
#newRealtime;
|
|
5098
5639
|
/**
|
|
5099
5640
|
* Made by the first channel(), not by the constructor: it listens to every
|
|
@@ -5104,37 +5645,38 @@ var NaroClient = class {
|
|
|
5104
5645
|
constructor(options) {
|
|
5105
5646
|
const baseUrl = baseUrlOf(options.url);
|
|
5106
5647
|
const { key: apiKey, service } = apiKeyOf(options);
|
|
5648
|
+
const accessToken = accessTokenOf(options.accessToken);
|
|
5649
|
+
if (accessToken !== void 0 && service)
|
|
5650
|
+
throw new Error(CONFIG_MESSAGES.accessTokenWithServiceKey);
|
|
5107
5651
|
if (options.fetch === void 0 && typeof globalThis.fetch !== "function")
|
|
5108
5652
|
throw new Error(CONFIG_MESSAGES.fetch);
|
|
5109
5653
|
const realtime = realtimeOptionsOf(options.realtime);
|
|
5110
|
-
const storage = options.auth?.storage ?? defaultStorage();
|
|
5654
|
+
const storage = accessToken !== void 0 ? memoryStorage() : options.auth?.storage ?? defaultStorage();
|
|
5111
5655
|
const http = {
|
|
5112
5656
|
baseUrl,
|
|
5113
5657
|
apiKey,
|
|
5114
5658
|
fetch: options.fetch,
|
|
5115
5659
|
tokens: new TokenStore(storage, storageKey(baseUrl)),
|
|
5116
|
-
|
|
5117
|
-
|
|
5660
|
+
service,
|
|
5661
|
+
...accessToken !== void 0 ? { accessToken } : { accessJwt: new TokenStore(storage, accessJwtKey(baseUrl)) }
|
|
5118
5662
|
};
|
|
5119
|
-
|
|
5663
|
+
const auth = new AuthClient(http, {
|
|
5120
5664
|
verifiers: new FlowVerifiers(
|
|
5121
5665
|
new TokenStore(storage, verifierKey(baseUrl))
|
|
5122
5666
|
),
|
|
5123
5667
|
recovery: new RecoveryToken(
|
|
5124
5668
|
new TokenStore(storage, recoveryKey(baseUrl))
|
|
5125
5669
|
),
|
|
5126
|
-
detectSessionInUrl: options.auth?.detectSessionInUrl ?? true,
|
|
5670
|
+
detectSessionInUrl: accessToken === void 0 && (options.auth?.detectSessionInUrl ?? true),
|
|
5127
5671
|
shared: { storage, key: storageKey(baseUrl) },
|
|
5128
5672
|
serviceKey: service
|
|
5129
5673
|
});
|
|
5130
|
-
this.#
|
|
5131
|
-
this.
|
|
5132
|
-
this.#
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
options: realtime
|
|
5137
|
-
});
|
|
5674
|
+
this.#accessToken = accessToken !== void 0;
|
|
5675
|
+
this.auth = this.#accessToken ? refusingAuth() : auth;
|
|
5676
|
+
this.#run = queryRunner(http, auth);
|
|
5677
|
+
this.storage = new StorageClient(http, auth);
|
|
5678
|
+
this.functions = new FunctionsClient(http, auth);
|
|
5679
|
+
this.#newRealtime = () => new RealtimeClient({ http, auth, service, options: realtime });
|
|
5138
5680
|
}
|
|
5139
5681
|
/** Start a query on `table`: `.select()`, `.insert()`, `.update()` or `.delete()`. */
|
|
5140
5682
|
from(table) {
|
|
@@ -5146,6 +5688,7 @@ var NaroClient = class {
|
|
|
5146
5688
|
* `removeChannel`. Nothing connects until the first `subscribe()`.
|
|
5147
5689
|
*/
|
|
5148
5690
|
channel(name, options) {
|
|
5691
|
+
if (this.#accessToken) throw new Error(ACCESS_TOKEN_CHANNEL_MESSAGE);
|
|
5149
5692
|
return this.#client().channel(name, options);
|
|
5150
5693
|
}
|
|
5151
5694
|
/** The channels made with `channel()` and not yet removed. */
|
|
@@ -5157,7 +5700,9 @@ var NaroClient = class {
|
|
|
5157
5700
|
* one; the connection closes with the last channel.
|
|
5158
5701
|
*/
|
|
5159
5702
|
removeChannel(channel) {
|
|
5160
|
-
|
|
5703
|
+
if (this.#realtime === void 0)
|
|
5704
|
+
return Promise.resolve({ data: null, error: null });
|
|
5705
|
+
return this.#realtime.removeChannel(channel);
|
|
5161
5706
|
}
|
|
5162
5707
|
/** `removeChannel()` for every channel; the connection closes. */
|
|
5163
5708
|
removeAllChannels() {
|
|
@@ -5174,6 +5719,10 @@ function createClient(options) {
|
|
|
5174
5719
|
return new NaroClient(options);
|
|
5175
5720
|
}
|
|
5176
5721
|
export {
|
|
5722
|
+
FunctionsError,
|
|
5723
|
+
FunctionsFetchError,
|
|
5724
|
+
FunctionsHttpError,
|
|
5725
|
+
FunctionsRelayError,
|
|
5177
5726
|
NaroClient,
|
|
5178
5727
|
createClient,
|
|
5179
5728
|
objectRoute,
|