@craftainer/crud-sdk 0.8.0 → 0.8.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 +15 -1
- package/dist/cache/index.mjs +1 -1
- package/dist/{client-CzbFW5YG.mjs → client-DikBkRMm.mjs} +7 -214
- package/dist/client-DikBkRMm.mjs.map +1 -0
- package/dist/codegen/cli.mjs +32 -16
- package/dist/codegen/cli.mjs.map +1 -1
- package/dist/errors-ZdOt0_zk.mjs +232 -0
- package/dist/errors-ZdOt0_zk.mjs.map +1 -0
- package/dist/{events-Dfz-rzC8.mjs → events-swCOTQOS.mjs} +27 -8
- package/dist/{events-Dfz-rzC8.mjs.map → events-swCOTQOS.mjs.map} +1 -1
- package/dist/index.mjs +2 -1
- package/dist/tanstack/index.mjs +1 -1
- package/package.json +1 -1
- package/dist/client-CzbFW5YG.mjs.map +0 -1
package/dist/codegen/cli.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.mjs","names":[],"sources":["../../src/codegen/generate.ts","../../src/codegen/run.ts","../../src/codegen/cli.ts"],"sourcesContent":["import { CrudError } from \"../errors.ts\";\nimport type { JsonObject, Manifest, Resource } from \"../manifest.ts\";\n\n/** The part of `json-schema-to-typescript`'s `compile` this tool uses. */\nexport type Compile = (\n schema: JsonObject,\n name: string,\n options: Record<string, unknown>,\n) => Promise<string>;\n\n/** Loads the optional peer `json-schema-to-typescript`; a clear error when it is missing. */\nexport async function loadCompiler(): Promise<Compile> {\n try {\n const mod = await import(\"json-schema-to-typescript\");\n return mod.compile as unknown as Compile;\n } catch (error) {\n throw new CrudError(\n \"crud-sdk-codegen needs the optional peer dependency json-schema-to-typescript: \" +\n \"install it (for example `pnpm add -D json-schema-to-typescript`) and run again\",\n { cause: error },\n );\n }\n}\n\nconst OPTIONS = {\n bannerComment: \"\",\n additionalProperties: false,\n // Inline `$defs` so two resources never declare the same helper type twice.\n declareExternallyReferenced: false,\n};\n\nconst DATA_KEYWORDS: ReadonlySet<string> = new Set([\n \"default\",\n \"const\",\n \"enum\",\n \"examples\",\n]);\n\n/**\n * Drops `title` annotations below the root: the compiler turns each into a\n * named type alias, which would collide across resources (and `Name` says\n * little). A property that is itself called `title` stays.\n */\nfunction withoutTitles(node: unknown, isPropertyMap = false): unknown {\n if (Array.isArray(node)) {\n return node.map((item) => withoutTitles(item));\n }\n if (typeof node !== \"object\" || node === null) {\n return node;\n }\n const out: JsonObject = {};\n for (const [key, value] of Object.entries(node)) {\n if (isPropertyMap || key !== \"title\" || typeof value !== \"string\") {\n // Data keywords hold values, not schemas: leave them whole.\n out[key] =\n !isPropertyMap && DATA_KEYWORDS.has(key)\n ? value\n : withoutTitles(value, !isPropertyMap && key === \"properties\");\n }\n }\n return out;\n}\n\n/** `heroes` + `v2` -> `HeroesV2` (the prefix of the generated interface names). */\nexport function typeName(\n resource: Pick<Resource, \"name\" | \"api_version\">,\n): string {\n const words = `${resource.name} ${resource.api_version}`.split(\n /[^A-Za-z0-9]+/,\n );\n return words.map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(\"\");\n}\n\n/** A `--manifest` URL without credentials, query or fragment (they may hold secrets and are not provenance). */\nexport function describeSource(source: string): string {\n if (!/^https?:\\/\\//i.test(source)) {\n return source;\n }\n try {\n const url = new URL(source);\n url.username = \"\";\n url.password = \"\";\n url.search = \"\";\n url.hash = \"\";\n return url.href;\n } catch {\n return source;\n }\n}\n\n/**\n * The interface name of every resource and view, or a `CrudError` when two\n * resources would share one (`hero-x` and `hero_x`) or a name is not a valid\n * TypeScript identifier (a leading digit, or nothing left after stripping).\n */\nfunction interfaceNames(manifest: Manifest): Map<Resource, string> {\n const owners = new Map<string, string>();\n const names = new Map<Resource, string>();\n for (const resource of manifest.resources) {\n const label = `${resource.name} ${resource.api_version}`;\n const name = typeName(resource);\n if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name)) {\n throw new CrudError(\n `resource \"${label}\" maps to \"${name}\", which is not a valid TypeScript identifier`,\n );\n }\n const clash = owners.get(name);\n if (clash !== undefined) {\n throw new CrudError(\n `resources \"${clash}\" and \"${label}\" both map to the type name \"${name}\"`,\n );\n }\n owners.set(name, label);\n names.set(resource, name);\n }\n return names;\n}\n\n/**\n * TypeScript source with one interface per resource and record view:\n * `<Name><Version>Read`, `...Create`, `...Update` and, when the resource has\n * one, `...Draft`. The output has no timestamp, so it is reproducible.\n */\nexport async function generateTypes(\n manifest: Manifest,\n source: string,\n compile: Compile,\n): Promise<string> {\n const chunks = [\n `// Generated by crud-sdk-codegen from ${describeSource(source)} (${manifest.service.name} ${manifest.service.version}).`,\n \"// Do not edit by hand: regenerate.\",\n \"\",\n ];\n for (const [resource, base] of interfaceNames(manifest)) {\n const views = [\n [\"Read\", resource.schemas.read],\n [\"Create\", resource.schemas.create],\n [\"Update\", resource.schemas.update],\n [\"Draft\", resource.schemas.draft],\n ] as const;\n for (const [view, schema] of views) {\n if (schema !== null) {\n const name = base + view;\n // The root title names the interface.\n const root = { ...(withoutTitles(schema) as JsonObject), title: name };\n chunks.push((await compile(root, name, OPTIONS)).trimEnd(), \"\");\n }\n }\n }\n return chunks.join(\"\\n\");\n}\n","import { readFile, writeFile } from \"node:fs/promises\";\nimport { createClient } from \"../client.ts\";\nimport { parseManifest } from \"../manifest.ts\";\nimport type { Manifest } from \"../manifest.ts\";\nimport { generateTypes, loadCompiler } from \"./generate.ts\";\nimport type { Compile } from \"./generate.ts\";\n\nconst USAGE = `Usage: crud-sdk-codegen --manifest <url|file> --out <file> [--allow-insecure-http]\n\nWrites TypeScript interfaces (Read, Create, Update per resource) from the\nJSON Schemas of a CRUD manifest. A URL is the manifest itself, or a server\norigin (path \"/\") to discover it through the api-catalog. The manifest is\nper caller: set CRUD_SDK_TOKEN to a bearer token to see more than the\nanonymous view. The token is refused over plain http except to loopback;\n--allow-insecure-http lifts that. Needs the optional peer json-schema-to-typescript.\n`;\n\n/** What {@link run} touches outside itself; tests replace them. */\nexport interface Deps {\n readonly fetch?: typeof fetch | undefined;\n readonly env?: Readonly<Record<string, string | undefined>> | undefined;\n readonly loadCompiler?: (() => Promise<Compile>) | undefined;\n readonly readFile?: ((path: string) => Promise<string>) | undefined;\n readonly writeFile?:\n ((path: string, text: string) => Promise<void>) | undefined;\n readonly stdout?: ((text: string) => void) | undefined;\n readonly stderr?: ((text: string) => void) | undefined;\n}\n\nfunction parseArgs(\n argv: readonly string[],\n): { manifest: string; out: string; allowInsecureHttp: boolean } | undefined {\n let manifest: string | undefined;\n let out: string | undefined;\n let allowInsecureHttp = false;\n for (let i = 0; i < argv.length; i += 1) {\n if (argv[i] === \"--allow-insecure-http\") {\n allowInsecureHttp = true;\n } else if (argv[i] === \"--manifest\") {\n manifest = argv[(i += 1)];\n } else if (argv[i] === \"--out\") {\n out = argv[(i += 1)];\n } else {\n return undefined;\n }\n }\n return manifest === undefined || out === undefined\n ? undefined\n : { manifest, out, allowInsecureHttp };\n}\n\nasync function readManifest(\n source: string,\n allowInsecureHttp: boolean,\n deps: Deps,\n): Promise<Manifest> {\n if (!/^https?:\\/\\//i.test(source)) {\n const text = await (deps.readFile ?? ((p) => readFile(p, \"utf8\")))(source);\n return parseManifest(JSON.parse(text));\n }\n const url = new URL(source);\n const token = deps.env?.[\"CRUD_SDK_TOKEN\"];\n const client = createClient({\n baseUrl: url.href,\n fetch: deps.fetch,\n allowInsecureHttp,\n tokenProvider:\n token === undefined || token === \"\" ? undefined : () => token,\n ...(url.pathname === \"/\" ? {} : { manifestUrl: url.href }),\n });\n return client.manifest();\n}\n\n/** Runs the CLI; resolves to the process exit code. */\nexport async function run(\n argv: readonly string[],\n deps: Deps = {},\n): Promise<number> {\n const stdout = deps.stdout ?? ((text) => process.stdout.write(text));\n const stderr = deps.stderr ?? ((text) => process.stderr.write(text));\n const args = parseArgs(argv);\n if (args === undefined) {\n stderr(USAGE);\n return 2;\n }\n try {\n const manifest = await readManifest(\n args.manifest,\n args.allowInsecureHttp,\n deps,\n );\n const compile = await (deps.loadCompiler ?? loadCompiler)();\n const text = await generateTypes(manifest, args.manifest, compile);\n await (deps.writeFile ?? ((p, t) => writeFile(p, t, \"utf8\")))(\n args.out,\n text,\n );\n stdout(`wrote ${args.out} (${manifest.resources.length} resources)\\n`);\n return 0;\n } catch (error) {\n stderr(\n `crud-sdk-codegen: ${error instanceof Error ? error.message : String(error)}\\n`,\n );\n return 1;\n }\n}\n","#!/usr/bin/env node\n// `crud-sdk-codegen` bin: writes TypeScript types from a CRUD manifest.\n// Needs the optional peer `json-schema-to-typescript`.\nimport { run } from \"./run.ts\";\n\nprocess.exitCode = await run(process.argv.slice(2), { env: process.env });\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.mjs","names":[],"sources":["../../src/codegen/generate.ts","../../src/codegen/run.ts","../../src/codegen/cli.ts"],"sourcesContent":["import { CrudError, describeUrl } from \"../errors.ts\";\nimport type { JsonObject, Manifest, Resource } from \"../manifest.ts\";\n\n/** The part of `json-schema-to-typescript`'s `compile` this tool uses. */\nexport type Compile = (\n schema: JsonObject,\n name: string,\n options: Record<string, unknown>,\n) => Promise<string>;\n\n/** Loads the optional peer `json-schema-to-typescript`; a clear error when it is missing. */\nexport async function loadCompiler(): Promise<Compile> {\n try {\n const mod = await import(\"json-schema-to-typescript\");\n return mod.compile as unknown as Compile;\n } catch (error) {\n throw new CrudError(\n \"crud-sdk-codegen needs the optional peer dependency json-schema-to-typescript: \" +\n \"install it (for example `pnpm add -D json-schema-to-typescript`) and run again\",\n { cause: error },\n );\n }\n}\n\nconst OPTIONS = {\n bannerComment: \"\",\n additionalProperties: false,\n // Inline `$defs` so two resources never declare the same helper type twice.\n declareExternallyReferenced: false,\n // The schemas are server-supplied: never read a file or call a URL for a `$ref`.\n $refOptions: { resolve: { file: false, http: false, external: false } },\n};\n\n/** Keywords the compiler copies into the output verbatim (`tsType`) or uses to name code (`tsEnumNames`). */\nconst CODE_KEYWORDS: ReadonlySet<string> = new Set([\"tsType\", \"tsEnumNames\"]);\n\nconst DATA_KEYWORDS: ReadonlySet<string> = new Set([\n \"default\",\n \"const\",\n \"enum\",\n \"examples\",\n]);\n\n/** Keywords whose object holds schemas under author-chosen names, not keywords. */\nconst NAME_MAPS = new Set([\n \"properties\",\n \"patternProperties\",\n \"$defs\",\n \"definitions\",\n \"dependentSchemas\",\n]);\n\n/**\n * Prepares a server-supplied schema for the compiler:\n *\n * - drops `title` annotations below the root: the compiler turns each into a\n * named type alias, which would collide across resources (and `Name` says\n * little). A property that is itself called `title` stays;\n * - drops `tsType` and `tsEnumNames`, which the compiler would write into the\n * generated source as code;\n * - rejects a `$ref` that leaves the document (anything not starting with `#`).\n */\nfunction withoutTitles(node: unknown, isPropertyMap = false): unknown {\n if (Array.isArray(node)) {\n return node.map((item) => withoutTitles(item));\n }\n if (typeof node !== \"object\" || node === null) {\n return node;\n }\n const out: JsonObject = {};\n for (const [key, value] of Object.entries(node)) {\n if (!isPropertyMap && CODE_KEYWORDS.has(key)) {\n continue;\n }\n if (\n !isPropertyMap &&\n key === \"$ref\" &&\n typeof value === \"string\" &&\n !value.startsWith(\"#\")\n ) {\n throw new CrudError(\n `the schema has a $ref that leaves the manifest (\"${value.slice(0, 80)}\"): only \"#...\" references are supported`,\n );\n }\n if (isPropertyMap || key !== \"title\" || typeof value !== \"string\") {\n // Data keywords hold values, not schemas: leave them whole.\n out[key] =\n !isPropertyMap && DATA_KEYWORDS.has(key)\n ? value\n : withoutTitles(value, !isPropertyMap && NAME_MAPS.has(key));\n }\n }\n return out;\n}\n\n/** `heroes` + `v2` -> `HeroesV2` (the prefix of the generated interface names). */\nexport function typeName(\n resource: Pick<Resource, \"name\" | \"api_version\">,\n): string {\n const words = `${resource.name} ${resource.api_version}`.split(\n /[^A-Za-z0-9]+/,\n );\n return words.map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(\"\");\n}\n\n/** A `--manifest` URL without credentials, query or fragment (they may hold secrets and are not provenance). */\nexport function describeSource(source: string): string {\n if (!/^https?:\\/\\//i.test(source)) {\n return source;\n }\n return describeUrl(source);\n}\n\n/**\n * The interface name of every resource and view, or a `CrudError` when two\n * resources would share one (`hero-x` and `hero_x`) or a name is not a valid\n * TypeScript identifier (a leading digit, or nothing left after stripping).\n */\nfunction interfaceNames(manifest: Manifest): Map<Resource, string> {\n const owners = new Map<string, string>();\n const names = new Map<Resource, string>();\n for (const resource of manifest.resources) {\n const label = `${resource.name} ${resource.api_version}`;\n const name = typeName(resource);\n if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name)) {\n throw new CrudError(\n `resource \"${label}\" maps to \"${name}\", which is not a valid TypeScript identifier`,\n );\n }\n const clash = owners.get(name);\n if (clash !== undefined) {\n throw new CrudError(\n `resources \"${clash}\" and \"${label}\" both map to the type name \"${name}\"`,\n );\n }\n owners.set(name, label);\n names.set(resource, name);\n }\n return names;\n}\n\n/**\n * TypeScript source with one interface per resource and record view:\n * `<Name><Version>Read`, `...Create`, `...Update` and, when the resource has\n * one, `...Draft`. The output has no timestamp, so it is reproducible.\n */\nexport async function generateTypes(\n manifest: Manifest,\n source: string,\n compile: Compile,\n): Promise<string> {\n const chunks = [\n `// Generated by crud-sdk-codegen from ${describeSource(source)} (${manifest.service.name} ${manifest.service.version}).`,\n \"// Do not edit by hand: regenerate.\",\n \"\",\n ];\n for (const [resource, base] of interfaceNames(manifest)) {\n const views = [\n [\"Read\", resource.schemas.read],\n [\"Create\", resource.schemas.create],\n [\"Update\", resource.schemas.update],\n [\"Draft\", resource.schemas.draft],\n ] as const;\n for (const [view, schema] of views) {\n if (schema !== null) {\n const name = base + view;\n // The root title names the interface.\n const root = { ...(withoutTitles(schema) as JsonObject), title: name };\n chunks.push((await compile(root, name, OPTIONS)).trimEnd(), \"\");\n }\n }\n }\n return chunks.join(\"\\n\");\n}\n","import { readFile, writeFile } from \"node:fs/promises\";\nimport { createClient } from \"../client.ts\";\nimport { parseManifest } from \"../manifest.ts\";\nimport type { Manifest } from \"../manifest.ts\";\nimport { generateTypes, loadCompiler } from \"./generate.ts\";\nimport type { Compile } from \"./generate.ts\";\n\nconst USAGE = `Usage: crud-sdk-codegen --manifest <url|file> --out <file> [--allow-insecure-http]\n\nWrites TypeScript interfaces (Read, Create, Update per resource) from the\nJSON Schemas of a CRUD manifest. A URL is the manifest itself, or a server\norigin (path \"/\") to discover it through the api-catalog. The manifest is\nper caller: set CRUD_SDK_TOKEN to a bearer token to see more than the\nanonymous view. The token is refused over plain http except to loopback;\n--allow-insecure-http lifts that. Needs the optional peer json-schema-to-typescript.\n`;\n\n/** What {@link run} touches outside itself; tests replace them. */\nexport interface Deps {\n readonly fetch?: typeof fetch | undefined;\n readonly env?: Readonly<Record<string, string | undefined>> | undefined;\n readonly loadCompiler?: (() => Promise<Compile>) | undefined;\n readonly readFile?: ((path: string) => Promise<string>) | undefined;\n readonly writeFile?:\n ((path: string, text: string) => Promise<void>) | undefined;\n readonly stdout?: ((text: string) => void) | undefined;\n readonly stderr?: ((text: string) => void) | undefined;\n}\n\nfunction parseArgs(\n argv: readonly string[],\n): { manifest: string; out: string; allowInsecureHttp: boolean } | undefined {\n let manifest: string | undefined;\n let out: string | undefined;\n let allowInsecureHttp = false;\n for (let i = 0; i < argv.length; i += 1) {\n if (argv[i] === \"--allow-insecure-http\") {\n allowInsecureHttp = true;\n } else if (argv[i] === \"--manifest\") {\n manifest = argv[(i += 1)];\n } else if (argv[i] === \"--out\") {\n out = argv[(i += 1)];\n } else {\n return undefined;\n }\n }\n return manifest === undefined || out === undefined\n ? undefined\n : { manifest, out, allowInsecureHttp };\n}\n\nasync function readManifest(\n source: string,\n allowInsecureHttp: boolean,\n deps: Deps,\n): Promise<Manifest> {\n if (!/^https?:\\/\\//i.test(source)) {\n const text = await (deps.readFile ?? ((p) => readFile(p, \"utf8\")))(source);\n return parseManifest(JSON.parse(text));\n }\n const url = new URL(source);\n const token = deps.env?.[\"CRUD_SDK_TOKEN\"];\n const client = createClient({\n baseUrl: url.href,\n fetch: deps.fetch,\n allowInsecureHttp,\n tokenProvider:\n token === undefined || token === \"\" ? undefined : () => token,\n ...(url.pathname === \"/\" ? {} : { manifestUrl: url.href }),\n });\n return client.manifest();\n}\n\n/** Runs the CLI; resolves to the process exit code. */\nexport async function run(\n argv: readonly string[],\n deps: Deps = {},\n): Promise<number> {\n const stdout = deps.stdout ?? ((text) => process.stdout.write(text));\n const stderr = deps.stderr ?? ((text) => process.stderr.write(text));\n const args = parseArgs(argv);\n if (args === undefined) {\n stderr(USAGE);\n return 2;\n }\n try {\n const manifest = await readManifest(\n args.manifest,\n args.allowInsecureHttp,\n deps,\n );\n const compile = await (deps.loadCompiler ?? loadCompiler)();\n const text = await generateTypes(manifest, args.manifest, compile);\n await (deps.writeFile ?? ((p, t) => writeFile(p, t, \"utf8\")))(\n args.out,\n text,\n );\n stdout(`wrote ${args.out} (${manifest.resources.length} resources)\\n`);\n return 0;\n } catch (error) {\n stderr(\n `crud-sdk-codegen: ${error instanceof Error ? error.message : String(error)}\\n`,\n );\n return 1;\n }\n}\n","#!/usr/bin/env node\n// `crud-sdk-codegen` bin: writes TypeScript types from a CRUD manifest.\n// Needs the optional peer `json-schema-to-typescript`.\nimport { run } from \"./run.ts\";\n\nprocess.exitCode = await run(process.argv.slice(2), { env: process.env });\n"],"mappings":"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"}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
//#region src/errors.ts
|
|
2
|
+
/**
|
|
3
|
+
* Error hierarchy: client-side errors and RFC 9457 problem details (ADR-0004).
|
|
4
|
+
* Mirrors the Python SDK class for class.
|
|
5
|
+
*/
|
|
6
|
+
/** Base class of every error this package throws on purpose. */
|
|
7
|
+
var CrudError = class extends Error {
|
|
8
|
+
constructor(message, options) {
|
|
9
|
+
super(message, options);
|
|
10
|
+
this.name = new.target.name;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
/** The call was refused before any request was sent. */
|
|
14
|
+
var ClientError = class extends CrudError {};
|
|
15
|
+
/** The resource does not support the operation (manifest capability or path missing). */
|
|
16
|
+
var CapabilityError = class extends ClientError {};
|
|
17
|
+
/** A filter, sort or paging argument is not valid for the resource. */
|
|
18
|
+
var InvalidQueryError = class extends ClientError {};
|
|
19
|
+
/**
|
|
20
|
+
* A request carrying an `Authorization` header was about to go over plain
|
|
21
|
+
* `http:` to a non-loopback host. Nothing was sent. Use `https:`, or opt in
|
|
22
|
+
* with `allowInsecureHttp`.
|
|
23
|
+
*/
|
|
24
|
+
var InsecureTransportError = class extends ClientError {};
|
|
25
|
+
/** The discovery document or the manifest is missing, unusable or of an unknown version. */
|
|
26
|
+
var ManifestError = class extends ClientError {};
|
|
27
|
+
/** The manifest lists no such resource for this caller. */
|
|
28
|
+
var UnknownResourceError = class extends ClientError {};
|
|
29
|
+
/** A successful response whose body is not the JSON the client expects; `cause` holds the parse error. */
|
|
30
|
+
var ResponseError = class extends CrudError {};
|
|
31
|
+
/** The request never produced a response; `cause` holds the transport error. */
|
|
32
|
+
var ConnectionFailed = class extends CrudError {};
|
|
33
|
+
const NO_TYPE = "about:blank";
|
|
34
|
+
/** Longest server-supplied text kept in `Error.message`; the full value stays on `detail` / `errors`. */
|
|
35
|
+
const MAX_MESSAGE_TEXT = 200;
|
|
36
|
+
function clip(text) {
|
|
37
|
+
return text.length > MAX_MESSAGE_TEXT ? `${text.slice(0, MAX_MESSAGE_TEXT)}...` : text;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* A URL for an error message: no credentials, query or fragment (filter values
|
|
41
|
+
* can be personal data and error messages travel to toasts and telemetry).
|
|
42
|
+
*/
|
|
43
|
+
function describeUrl(url) {
|
|
44
|
+
try {
|
|
45
|
+
const parsed = new URL(url);
|
|
46
|
+
parsed.username = "";
|
|
47
|
+
parsed.password = "";
|
|
48
|
+
parsed.search = "";
|
|
49
|
+
parsed.hash = "";
|
|
50
|
+
return parsed.href;
|
|
51
|
+
} catch {
|
|
52
|
+
return url.replace(/[?#].*$/s, "").replace(/^([a-z][a-z0-9+.-]*:\/\/)[^/]*@/i, "$1");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** An error response, as RFC 9457 problem details. */
|
|
56
|
+
var ProblemError = class extends CrudError {
|
|
57
|
+
status;
|
|
58
|
+
title;
|
|
59
|
+
detail;
|
|
60
|
+
type;
|
|
61
|
+
instance;
|
|
62
|
+
errors;
|
|
63
|
+
headers;
|
|
64
|
+
response;
|
|
65
|
+
constructor(init) {
|
|
66
|
+
const errors = init.errors ?? [];
|
|
67
|
+
let message = `${init.status} ${clip(init.title)}`;
|
|
68
|
+
if (init.detail) message += `: ${clip(init.detail)}`;
|
|
69
|
+
else if (errors.length > 0) message += `: ${clip(errors.map((e) => `${e.loc.join(".")}: ${e.msg}`).join("; "))}`;
|
|
70
|
+
super(message);
|
|
71
|
+
this.status = init.status;
|
|
72
|
+
this.title = init.title;
|
|
73
|
+
this.detail = init.detail;
|
|
74
|
+
this.type = init.type ?? NO_TYPE;
|
|
75
|
+
this.instance = init.instance;
|
|
76
|
+
this.errors = errors;
|
|
77
|
+
this.headers = init.headers ?? new Headers();
|
|
78
|
+
this.response = init.response;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
/** 400. */
|
|
82
|
+
var BadRequest = class extends ProblemError {};
|
|
83
|
+
/** 401; `wwwAuthenticate` keeps the challenge. */
|
|
84
|
+
var Unauthorized = class extends ProblemError {
|
|
85
|
+
get wwwAuthenticate() {
|
|
86
|
+
return this.headers.get("www-authenticate") ?? void 0;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
/** 403. */
|
|
90
|
+
var Forbidden = class extends ProblemError {};
|
|
91
|
+
/** 404. */
|
|
92
|
+
var NotFound = class extends ProblemError {};
|
|
93
|
+
/** 412: a stale `If-Match`. */
|
|
94
|
+
var PreconditionFailed = class extends ProblemError {};
|
|
95
|
+
/** 422; `errors` holds normalized `{loc, msg, type}` entries. */
|
|
96
|
+
var ValidationFailed = class extends ProblemError {};
|
|
97
|
+
/** 423. */
|
|
98
|
+
var Locked = class extends ProblemError {};
|
|
99
|
+
/** 429; `retryAfter` is the `Retry-After` header in seconds, when it is a number. */
|
|
100
|
+
var RateLimited = class extends ProblemError {
|
|
101
|
+
get retryAfter() {
|
|
102
|
+
const raw = this.headers.get("retry-after");
|
|
103
|
+
if (raw === null || raw.trim() === "") return;
|
|
104
|
+
const seconds = Number(raw);
|
|
105
|
+
return Number.isFinite(seconds) && seconds >= 0 ? seconds : void 0;
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
/** 5xx. */
|
|
109
|
+
var ServerError = class extends ProblemError {};
|
|
110
|
+
const BY_STATUS = /* @__PURE__ */ new Map([
|
|
111
|
+
[400, BadRequest],
|
|
112
|
+
[401, Unauthorized],
|
|
113
|
+
[403, Forbidden],
|
|
114
|
+
[404, NotFound],
|
|
115
|
+
[412, PreconditionFailed],
|
|
116
|
+
[422, ValidationFailed],
|
|
117
|
+
[423, Locked],
|
|
118
|
+
[429, RateLimited]
|
|
119
|
+
]);
|
|
120
|
+
const PHRASES = /* @__PURE__ */ new Map([
|
|
121
|
+
[100, "Continue"],
|
|
122
|
+
[101, "Switching Protocols"],
|
|
123
|
+
[102, "Processing"],
|
|
124
|
+
[103, "Early Hints"],
|
|
125
|
+
[200, "OK"],
|
|
126
|
+
[201, "Created"],
|
|
127
|
+
[202, "Accepted"],
|
|
128
|
+
[203, "Non-Authoritative Information"],
|
|
129
|
+
[204, "No Content"],
|
|
130
|
+
[205, "Reset Content"],
|
|
131
|
+
[206, "Partial Content"],
|
|
132
|
+
[207, "Multi-Status"],
|
|
133
|
+
[208, "Already Reported"],
|
|
134
|
+
[226, "IM Used"],
|
|
135
|
+
[300, "Multiple Choices"],
|
|
136
|
+
[301, "Moved Permanently"],
|
|
137
|
+
[302, "Found"],
|
|
138
|
+
[303, "See Other"],
|
|
139
|
+
[304, "Not Modified"],
|
|
140
|
+
[305, "Use Proxy"],
|
|
141
|
+
[307, "Temporary Redirect"],
|
|
142
|
+
[308, "Permanent Redirect"],
|
|
143
|
+
[400, "Bad Request"],
|
|
144
|
+
[401, "Unauthorized"],
|
|
145
|
+
[402, "Payment Required"],
|
|
146
|
+
[403, "Forbidden"],
|
|
147
|
+
[404, "Not Found"],
|
|
148
|
+
[405, "Method Not Allowed"],
|
|
149
|
+
[406, "Not Acceptable"],
|
|
150
|
+
[407, "Proxy Authentication Required"],
|
|
151
|
+
[408, "Request Timeout"],
|
|
152
|
+
[409, "Conflict"],
|
|
153
|
+
[410, "Gone"],
|
|
154
|
+
[411, "Length Required"],
|
|
155
|
+
[412, "Precondition Failed"],
|
|
156
|
+
[413, "Content Too Large"],
|
|
157
|
+
[414, "URI Too Long"],
|
|
158
|
+
[415, "Unsupported Media Type"],
|
|
159
|
+
[416, "Range Not Satisfiable"],
|
|
160
|
+
[417, "Expectation Failed"],
|
|
161
|
+
[418, "I'm a Teapot"],
|
|
162
|
+
[421, "Misdirected Request"],
|
|
163
|
+
[422, "Unprocessable Content"],
|
|
164
|
+
[423, "Locked"],
|
|
165
|
+
[424, "Failed Dependency"],
|
|
166
|
+
[425, "Too Early"],
|
|
167
|
+
[426, "Upgrade Required"],
|
|
168
|
+
[428, "Precondition Required"],
|
|
169
|
+
[429, "Too Many Requests"],
|
|
170
|
+
[431, "Request Header Fields Too Large"],
|
|
171
|
+
[451, "Unavailable For Legal Reasons"],
|
|
172
|
+
[500, "Internal Server Error"],
|
|
173
|
+
[501, "Not Implemented"],
|
|
174
|
+
[502, "Bad Gateway"],
|
|
175
|
+
[503, "Service Unavailable"],
|
|
176
|
+
[504, "Gateway Timeout"],
|
|
177
|
+
[505, "HTTP Version Not Supported"],
|
|
178
|
+
[506, "Variant Also Negotiates"],
|
|
179
|
+
[507, "Insufficient Storage"],
|
|
180
|
+
[508, "Loop Detected"],
|
|
181
|
+
[510, "Not Extended"],
|
|
182
|
+
[511, "Network Authentication Required"]
|
|
183
|
+
]);
|
|
184
|
+
const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
185
|
+
function normalizeErrors(items) {
|
|
186
|
+
return items.map((item) => {
|
|
187
|
+
if (isObject(item)) return {
|
|
188
|
+
loc: Array.isArray(item["loc"]) ? item["loc"] : [],
|
|
189
|
+
msg: String(item["msg"] ?? ""),
|
|
190
|
+
type: String(item["type"] ?? "")
|
|
191
|
+
};
|
|
192
|
+
return {
|
|
193
|
+
loc: [],
|
|
194
|
+
msg: String(item),
|
|
195
|
+
type: ""
|
|
196
|
+
};
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Builds the matching `ProblemError` subclass from an error response. A body
|
|
201
|
+
* that is not a JSON object yields a synthesized problem from the status
|
|
202
|
+
* phrase. Reads a clone, so `response.body` stays readable.
|
|
203
|
+
*/
|
|
204
|
+
async function problemFromResponse(response) {
|
|
205
|
+
const status = response.status;
|
|
206
|
+
const phrase = PHRASES.get(status) ?? "Error";
|
|
207
|
+
let parsed;
|
|
208
|
+
try {
|
|
209
|
+
parsed = JSON.parse(await response.clone().text());
|
|
210
|
+
} catch {
|
|
211
|
+
parsed = void 0;
|
|
212
|
+
}
|
|
213
|
+
const body = isObject(parsed) ? parsed : {};
|
|
214
|
+
const rawDetail = body["detail"];
|
|
215
|
+
const rawErrors = Array.isArray(rawDetail) ? rawDetail : body["errors"];
|
|
216
|
+
const Cls = BY_STATUS.get(status) ?? (status >= 500 ? ServerError : ProblemError);
|
|
217
|
+
const instance = body["instance"];
|
|
218
|
+
return new Cls({
|
|
219
|
+
status,
|
|
220
|
+
title: String(body["title"] || phrase),
|
|
221
|
+
detail: typeof rawDetail === "string" ? rawDetail : void 0,
|
|
222
|
+
type: String(body["type"] || NO_TYPE),
|
|
223
|
+
instance: typeof instance === "string" ? instance : void 0,
|
|
224
|
+
errors: Array.isArray(rawErrors) ? normalizeErrors(rawErrors) : [],
|
|
225
|
+
headers: response.headers,
|
|
226
|
+
response
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
//#endregion
|
|
230
|
+
export { Unauthorized as _, CrudError as a, describeUrl as b, InvalidQueryError as c, NotFound as d, PreconditionFailed as f, ServerError as g, ResponseError as h, ConnectionFailed as i, Locked as l, RateLimited as m, CapabilityError as n, Forbidden as o, ProblemError as p, ClientError as r, InsecureTransportError as s, BadRequest as t, ManifestError as u, UnknownResourceError as v, problemFromResponse as x, ValidationFailed as y };
|
|
231
|
+
|
|
232
|
+
//# sourceMappingURL=errors-ZdOt0_zk.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors-ZdOt0_zk.mjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * Error hierarchy: client-side errors and RFC 9457 problem details (ADR-0004).\n * Mirrors the Python SDK class for class.\n */\n\n/** Base class of every error this package throws on purpose. */\nexport class CrudError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = new.target.name;\n }\n}\n\n/** The call was refused before any request was sent. */\nexport class ClientError extends CrudError {}\n\n/** The resource does not support the operation (manifest capability or path missing). */\nexport class CapabilityError extends ClientError {}\n\n/** A filter, sort or paging argument is not valid for the resource. */\nexport class InvalidQueryError extends ClientError {}\n\n/**\n * A request carrying an `Authorization` header was about to go over plain\n * `http:` to a non-loopback host. Nothing was sent. Use `https:`, or opt in\n * with `allowInsecureHttp`.\n */\nexport class InsecureTransportError extends ClientError {}\n\n/** The discovery document or the manifest is missing, unusable or of an unknown version. */\nexport class ManifestError extends ClientError {}\n\n/** The manifest lists no such resource for this caller. */\nexport class UnknownResourceError extends ClientError {}\n\n/** A successful response whose body is not the JSON the client expects; `cause` holds the parse error. */\nexport class ResponseError extends CrudError {}\n\n/** The request never produced a response; `cause` holds the transport error. */\nexport class ConnectionFailed extends CrudError {}\n\n/** One normalized validation error (`422`). */\nexport interface ProblemDetail {\n readonly loc: readonly (string | number)[];\n readonly msg: string;\n readonly type: string;\n}\n\n/** Members of a {@link ProblemError}. */\nexport interface ProblemInit {\n readonly status: number;\n readonly title: string;\n readonly detail?: string | undefined;\n readonly type?: string | undefined;\n readonly instance?: string | undefined;\n readonly errors?: readonly ProblemDetail[] | undefined;\n readonly headers?: Headers | undefined;\n readonly response?: Response | undefined;\n}\n\nconst NO_TYPE = \"about:blank\";\n\n/** Longest server-supplied text kept in `Error.message`; the full value stays on `detail` / `errors`. */\nconst MAX_MESSAGE_TEXT = 200;\n\nfunction clip(text: string): string {\n return text.length > MAX_MESSAGE_TEXT\n ? `${text.slice(0, MAX_MESSAGE_TEXT)}...`\n : text;\n}\n\n/**\n * A URL for an error message: no credentials, query or fragment (filter values\n * can be personal data and error messages travel to toasts and telemetry).\n */\nexport function describeUrl(url: string): string {\n try {\n const parsed = new URL(url);\n parsed.username = \"\";\n parsed.password = \"\";\n parsed.search = \"\";\n parsed.hash = \"\";\n return parsed.href;\n } catch {\n return url\n .replace(/[?#].*$/s, \"\")\n .replace(/^([a-z][a-z0-9+.-]*:\\/\\/)[^/]*@/i, \"$1\");\n }\n}\n\n/** An error response, as RFC 9457 problem details. */\nexport class ProblemError extends CrudError {\n readonly status: number;\n readonly title: string;\n readonly detail: string | undefined;\n readonly type: string;\n readonly instance: string | undefined;\n readonly errors: readonly ProblemDetail[];\n readonly headers: Headers;\n readonly response: Response | undefined;\n\n constructor(init: ProblemInit) {\n const errors = init.errors ?? [];\n let message = `${init.status} ${clip(init.title)}`;\n if (init.detail) {\n message += `: ${clip(init.detail)}`;\n } else if (errors.length > 0) {\n message += `: ${clip(errors.map((e) => `${e.loc.join(\".\")}: ${e.msg}`).join(\"; \"))}`;\n }\n super(message);\n this.status = init.status;\n this.title = init.title;\n this.detail = init.detail;\n this.type = init.type ?? NO_TYPE;\n this.instance = init.instance;\n this.errors = errors;\n this.headers = init.headers ?? new Headers();\n this.response = init.response;\n }\n}\n\n/** 400. */\nexport class BadRequest extends ProblemError {}\n\n/** 401; `wwwAuthenticate` keeps the challenge. */\nexport class Unauthorized extends ProblemError {\n get wwwAuthenticate(): string | undefined {\n return this.headers.get(\"www-authenticate\") ?? undefined;\n }\n}\n\n/** 403. */\nexport class Forbidden extends ProblemError {}\n\n/** 404. */\nexport class NotFound extends ProblemError {}\n\n/** 412: a stale `If-Match`. */\nexport class PreconditionFailed extends ProblemError {}\n\n/** 422; `errors` holds normalized `{loc, msg, type}` entries. */\nexport class ValidationFailed extends ProblemError {}\n\n/** 423. */\nexport class Locked extends ProblemError {}\n\n/** 429; `retryAfter` is the `Retry-After` header in seconds, when it is a number. */\nexport class RateLimited extends ProblemError {\n get retryAfter(): number | undefined {\n const raw = this.headers.get(\"retry-after\");\n if (raw === null || raw.trim() === \"\") {\n return undefined;\n }\n const seconds = Number(raw);\n return Number.isFinite(seconds) && seconds >= 0 ? seconds : undefined;\n }\n}\n\n/** 5xx. */\nexport class ServerError extends ProblemError {}\n\nconst BY_STATUS: ReadonlyMap<number, typeof ProblemError> = new Map([\n [400, BadRequest],\n [401, Unauthorized],\n [403, Forbidden],\n [404, NotFound],\n [412, PreconditionFailed],\n [422, ValidationFailed],\n [423, Locked],\n [429, RateLimited],\n]);\n\n// Python's `http.HTTPStatus` phrases, so both SDKs synthesize the same title.\nconst PHRASES: ReadonlyMap<number, string> = new Map([\n [100, \"Continue\"],\n [101, \"Switching Protocols\"],\n [102, \"Processing\"],\n [103, \"Early Hints\"],\n [200, \"OK\"],\n [201, \"Created\"],\n [202, \"Accepted\"],\n [203, \"Non-Authoritative Information\"],\n [204, \"No Content\"],\n [205, \"Reset Content\"],\n [206, \"Partial Content\"],\n [207, \"Multi-Status\"],\n [208, \"Already Reported\"],\n [226, \"IM Used\"],\n [300, \"Multiple Choices\"],\n [301, \"Moved Permanently\"],\n [302, \"Found\"],\n [303, \"See Other\"],\n [304, \"Not Modified\"],\n [305, \"Use Proxy\"],\n [307, \"Temporary Redirect\"],\n [308, \"Permanent Redirect\"],\n [400, \"Bad Request\"],\n [401, \"Unauthorized\"],\n [402, \"Payment Required\"],\n [403, \"Forbidden\"],\n [404, \"Not Found\"],\n [405, \"Method Not Allowed\"],\n [406, \"Not Acceptable\"],\n [407, \"Proxy Authentication Required\"],\n [408, \"Request Timeout\"],\n [409, \"Conflict\"],\n [410, \"Gone\"],\n [411, \"Length Required\"],\n [412, \"Precondition Failed\"],\n [413, \"Content Too Large\"],\n [414, \"URI Too Long\"],\n [415, \"Unsupported Media Type\"],\n [416, \"Range Not Satisfiable\"],\n [417, \"Expectation Failed\"],\n [418, \"I'm a Teapot\"],\n [421, \"Misdirected Request\"],\n [422, \"Unprocessable Content\"],\n [423, \"Locked\"],\n [424, \"Failed Dependency\"],\n [425, \"Too Early\"],\n [426, \"Upgrade Required\"],\n [428, \"Precondition Required\"],\n [429, \"Too Many Requests\"],\n [431, \"Request Header Fields Too Large\"],\n [451, \"Unavailable For Legal Reasons\"],\n [500, \"Internal Server Error\"],\n [501, \"Not Implemented\"],\n [502, \"Bad Gateway\"],\n [503, \"Service Unavailable\"],\n [504, \"Gateway Timeout\"],\n [505, \"HTTP Version Not Supported\"],\n [506, \"Variant Also Negotiates\"],\n [507, \"Insufficient Storage\"],\n [508, \"Loop Detected\"],\n [510, \"Not Extended\"],\n [511, \"Network Authentication Required\"],\n]);\n\ntype Obj = Record<string, unknown>;\n\nconst isObject = (value: unknown): value is Obj =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nfunction normalizeErrors(items: readonly unknown[]): ProblemDetail[] {\n return items.map((item) => {\n if (isObject(item)) {\n return {\n loc: Array.isArray(item[\"loc\"])\n ? (item[\"loc\"] as (string | number)[])\n : [],\n msg: String(item[\"msg\"] ?? \"\"),\n type: String(item[\"type\"] ?? \"\"),\n };\n }\n return { loc: [], msg: String(item), type: \"\" };\n });\n}\n\n/**\n * Builds the matching `ProblemError` subclass from an error response. A body\n * that is not a JSON object yields a synthesized problem from the status\n * phrase. Reads a clone, so `response.body` stays readable.\n */\nexport async function problemFromResponse(\n response: Response,\n): Promise<ProblemError> {\n const status = response.status;\n const phrase = PHRASES.get(status) ?? \"Error\";\n let parsed: unknown;\n try {\n parsed = JSON.parse(await response.clone().text());\n } catch {\n parsed = undefined;\n }\n const body: Obj = isObject(parsed) ? parsed : {};\n const rawDetail = body[\"detail\"];\n const rawErrors = Array.isArray(rawDetail) ? rawDetail : body[\"errors\"];\n const Cls =\n BY_STATUS.get(status) ?? (status >= 500 ? ServerError : ProblemError);\n const instance = body[\"instance\"];\n return new Cls({\n status,\n title: String(body[\"title\"] || phrase),\n detail: typeof rawDetail === \"string\" ? rawDetail : undefined,\n type: String(body[\"type\"] || NO_TYPE),\n instance: typeof instance === \"string\" ? instance : undefined,\n errors: Array.isArray(rawErrors) ? normalizeErrors(rawErrors) : [],\n headers: response.headers,\n response,\n });\n}\n"],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { b as describeUrl } from "./errors-ZdOt0_zk.mjs";
|
|
1
2
|
//#region src/cache/policy.ts
|
|
2
3
|
function seconds(value) {
|
|
3
4
|
return typeof value === "number" && Number.isFinite(value) ? Math.max(value, 0) : 0;
|
|
@@ -452,6 +453,8 @@ const BACKOFF_START_MS = 1e3;
|
|
|
452
453
|
const BACKOFF_MAX_MS = 3e4;
|
|
453
454
|
const DEFAULT_KEEPALIVE_S = 15;
|
|
454
455
|
const DEAD_AFTER_KEEPALIVES = 3;
|
|
456
|
+
/** Cap on the SSE parser's buffered characters (one unterminated line or event); past it the stream is dropped. */
|
|
457
|
+
const MAX_BUFFER_CHARS = 1048576;
|
|
455
458
|
/** `setTimeout` clamps a longer delay to 1 ms, which would declare the stream dead at once. */
|
|
456
459
|
const MAX_TIMER_MS = 2 ** 31 - 1;
|
|
457
460
|
const ACTIONS = /* @__PURE__ */ new Set([
|
|
@@ -493,7 +496,7 @@ function warnOnDifferingOptions(hub, later) {
|
|
|
493
496
|
const first = hubSettings(hub.events);
|
|
494
497
|
const next = hubSettings(later.events);
|
|
495
498
|
const differing = Object.keys(first).filter((key) => first[key] !== next[key]);
|
|
496
|
-
if (differing.length > 0) console.warn(`crud-sdk: the event hub for ${hub.url} was opened with other events options; the first subscriber's win and these are ignored: ${differing.join(", ")}`);
|
|
499
|
+
if (differing.length > 0) console.warn(`crud-sdk: the event hub for ${describeUrl(hub.url)} was opened with other events options; the first subscriber's win and these are ignored: ${differing.join(", ")}`);
|
|
497
500
|
}
|
|
498
501
|
const registry = /* @__PURE__ */ new WeakMap();
|
|
499
502
|
function sleep(ms, signal) {
|
|
@@ -608,16 +611,32 @@ var Hub = class {
|
|
|
608
611
|
const reader = response.body.getReader();
|
|
609
612
|
const decoder = new TextDecoder();
|
|
610
613
|
const pending = [];
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
+
let overflow = false;
|
|
615
|
+
const parser = createParser({
|
|
616
|
+
maxBufferSize: MAX_BUFFER_CHARS,
|
|
617
|
+
onEvent: (message) => {
|
|
618
|
+
pending.push(message);
|
|
619
|
+
},
|
|
620
|
+
onError: (error) => {
|
|
621
|
+
overflow ||= error.type === "max-buffer-size-exceeded";
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
const closed = new Promise((_resolve, reject) => {
|
|
625
|
+
conn.signal.addEventListener("abort", () => {
|
|
626
|
+
reject(/* @__PURE__ */ new Error("event stream closed"));
|
|
627
|
+
}, { once: true });
|
|
628
|
+
});
|
|
629
|
+
closed.catch(() => void 0);
|
|
614
630
|
for (;;) {
|
|
615
631
|
const { done, value } = await reader.read();
|
|
616
632
|
if (done) return;
|
|
617
|
-
parser.feed(decoder.decode(value, { stream: true }));
|
|
618
|
-
clearTimeout(timer);
|
|
619
|
-
for (const message of pending.splice(0)) await this.handle(message);
|
|
620
633
|
arm();
|
|
634
|
+
parser.feed(decoder.decode(value, { stream: true }));
|
|
635
|
+
for (const message of pending.splice(0)) {
|
|
636
|
+
arm();
|
|
637
|
+
await Promise.race([this.handle(message), closed]);
|
|
638
|
+
}
|
|
639
|
+
if (overflow) throw new Error("event stream line or event too large");
|
|
621
640
|
}
|
|
622
641
|
} finally {
|
|
623
642
|
clearTimeout(timer);
|
|
@@ -709,4 +728,4 @@ function subscribeEvents(options) {
|
|
|
709
728
|
//#endregion
|
|
710
729
|
export { tanstackCachePolicy as a, cachePolicyFromManifest as i, subscribeEvents as n, resetOnce as r, getEventHub as t };
|
|
711
730
|
|
|
712
|
-
//# sourceMappingURL=events-
|
|
731
|
+
//# sourceMappingURL=events-swCOTQOS.mjs.map
|