@acornly/core 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter.d.ts +27 -2
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +5 -0
- package/dist/adapter.js.map +1 -1
- package/dist/anchoring/canvas.d.ts +33 -0
- package/dist/anchoring/canvas.d.ts.map +1 -0
- package/dist/anchoring/canvas.js +112 -0
- package/dist/anchoring/canvas.js.map +1 -0
- package/dist/anchoring/context-provider.d.ts +63 -0
- package/dist/anchoring/context-provider.d.ts.map +1 -0
- package/dist/anchoring/context-provider.js +78 -0
- package/dist/anchoring/context-provider.js.map +1 -0
- package/dist/anchoring/describe.d.ts +36 -2
- package/dist/anchoring/describe.d.ts.map +1 -1
- package/dist/anchoring/describe.js +102 -20
- package/dist/anchoring/describe.js.map +1 -1
- package/dist/anchoring/fingerprint.d.ts +47 -13
- package/dist/anchoring/fingerprint.d.ts.map +1 -1
- package/dist/anchoring/fingerprint.js +58 -68
- package/dist/anchoring/fingerprint.js.map +1 -1
- package/dist/anchoring/index.d.ts +5 -0
- package/dist/anchoring/index.d.ts.map +1 -1
- package/dist/anchoring/index.js +5 -0
- package/dist/anchoring/index.js.map +1 -1
- package/dist/anchoring/media.d.ts +2 -1
- package/dist/anchoring/media.d.ts.map +1 -1
- package/dist/anchoring/media.js +7 -2
- package/dist/anchoring/media.js.map +1 -1
- package/dist/anchoring/pdf.d.ts +204 -0
- package/dist/anchoring/pdf.d.ts.map +1 -0
- package/dist/anchoring/pdf.js +608 -0
- package/dist/anchoring/pdf.js.map +1 -0
- package/dist/anchoring/profiles.d.ts +140 -0
- package/dist/anchoring/profiles.d.ts.map +1 -0
- package/dist/anchoring/profiles.js +682 -0
- package/dist/anchoring/profiles.js.map +1 -0
- package/dist/anchoring/providers.d.ts +16 -1
- package/dist/anchoring/providers.d.ts.map +1 -1
- package/dist/anchoring/providers.js +15 -0
- package/dist/anchoring/providers.js.map +1 -1
- package/dist/anchoring/resolver.d.ts +23 -5
- package/dist/anchoring/resolver.d.ts.map +1 -1
- package/dist/anchoring/resolver.js +32 -13
- package/dist/anchoring/resolver.js.map +1 -1
- package/dist/anchoring/site-profiles.d.ts +31 -0
- package/dist/anchoring/site-profiles.d.ts.map +1 -0
- package/dist/anchoring/site-profiles.js +27 -0
- package/dist/anchoring/site-profiles.js.map +1 -0
- package/dist/body-prepare.d.ts +33 -0
- package/dist/body-prepare.d.ts.map +1 -0
- package/dist/body-prepare.js +88 -0
- package/dist/body-prepare.js.map +1 -0
- package/dist/body-size.d.ts +20 -0
- package/dist/body-size.d.ts.map +1 -0
- package/dist/body-size.js +137 -0
- package/dist/body-size.js.map +1 -0
- package/dist/body.d.ts +9 -42
- package/dist/body.d.ts.map +1 -1
- package/dist/body.js +6 -207
- package/dist/body.js.map +1 -1
- package/dist/composite.d.ts +17 -1
- package/dist/composite.d.ts.map +1 -1
- package/dist/composite.js +27 -2
- package/dist/composite.js.map +1 -1
- package/dist/document-key.d.ts +36 -0
- package/dist/document-key.d.ts.map +1 -0
- package/dist/document-key.js +40 -0
- package/dist/document-key.js.map +1 -0
- package/dist/document-url.d.ts +9 -0
- package/dist/document-url.d.ts.map +1 -1
- package/dist/document-url.js +9 -0
- package/dist/document-url.js.map +1 -1
- package/dist/ids.d.ts +4 -4
- package/dist/ids.d.ts.map +1 -1
- package/dist/ids.js +4 -4
- package/dist/ids.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/kinds.d.ts +7 -2
- package/dist/kinds.d.ts.map +1 -1
- package/dist/kinds.js +12 -3
- package/dist/kinds.js.map +1 -1
- package/dist/model.d.ts +36 -5
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +7 -1
- package/dist/model.js.map +1 -1
- package/dist/page-placement.d.ts +2 -2
- package/dist/page-placement.js +3 -2
- package/dist/page-placement.js.map +1 -1
- package/dist/pdf-key.d.ts +10 -0
- package/dist/pdf-key.d.ts.map +1 -0
- package/dist/pdf-key.js +22 -0
- package/dist/pdf-key.js.map +1 -0
- package/dist/placement.d.ts +20 -4
- package/dist/placement.d.ts.map +1 -1
- package/dist/placement.js +41 -5
- package/dist/placement.js.map +1 -1
- package/dist/region-image.d.ts +45 -0
- package/dist/region-image.d.ts.map +1 -0
- package/dist/region-image.js +76 -0
- package/dist/region-image.js.map +1 -0
- package/dist/scope.d.ts +17 -2
- package/dist/scope.d.ts.map +1 -1
- package/dist/scope.js +28 -4
- package/dist/scope.js.map +1 -1
- package/dist/store/store.d.ts +1 -1
- package/dist/store/store.d.ts.map +1 -1
- package/dist/store/store.js +11 -8
- package/dist/store/store.js.map +1 -1
- package/dist/testing/anchor-provider-contract.d.ts +3 -2
- package/dist/testing/anchor-provider-contract.d.ts.map +1 -1
- package/dist/testing/anchor-provider-contract.js +75 -22
- package/dist/testing/anchor-provider-contract.js.map +1 -1
- package/dist/testing/contract.d.ts +6 -0
- package/dist/testing/contract.d.ts.map +1 -1
- package/dist/testing/contract.js +22 -0
- package/dist/testing/contract.js.map +1 -1
- package/dist/testing/thread-contract.d.ts.map +1 -1
- package/dist/testing/thread-contract.js +2 -1
- package/dist/testing/thread-contract.js.map +1 -1
- package/dist/thread-write.d.ts +35 -0
- package/dist/thread-write.d.ts.map +1 -0
- package/dist/thread-write.js +69 -0
- package/dist/thread-write.js.map +1 -0
- package/dist/thread.d.ts +0 -21
- package/dist/thread.d.ts.map +1 -1
- package/dist/thread.js +2 -53
- package/dist/thread.js.map +1 -1
- package/dist/w3c.d.ts.map +1 -1
- package/dist/w3c.js +35 -6
- package/dist/w3c.js.map +1 -1
- package/package.json +6 -2
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checking a typed body part before it is written, and a body type's schema on a value. Kept
|
|
3
|
+
* apart from `body.ts`, which the read path loads, since only writes and renderers of typed parts
|
|
4
|
+
* need them (#617).
|
|
5
|
+
*/
|
|
6
|
+
import { BodyTypes, type BodyTypeDefinition, type BodyTypeLookup } from "./body.js";
|
|
7
|
+
import type { ContextualBody } from "./model.js";
|
|
8
|
+
/** App-defined body types by name, with `prepare` to check a typed part before it is written. */
|
|
9
|
+
export declare class BodyTypeRegistry extends BodyTypes {
|
|
10
|
+
/** Checks a typed part before it is written (see `prepareBody`). Returns the part to store. */
|
|
11
|
+
prepare(part: ContextualBody): ContextualBody;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Checks a typed part before it is written: a registered type's schema must accept the value,
|
|
15
|
+
* and its summary is recomputed with `summarize`. Unknown types (made by another app or a newer
|
|
16
|
+
* client) pass through unchanged but must have a valid namespaced name and carry a summary.
|
|
17
|
+
* The value is stored as given, not as the schema's output, so a part re-saved unchanged still
|
|
18
|
+
* passes. Returns the part to store. `types` is the registry to look types up in.
|
|
19
|
+
*/
|
|
20
|
+
export declare function prepareBody(types: BodyTypeLookup, part: ContextualBody): ContextualBody;
|
|
21
|
+
/**
|
|
22
|
+
* Runs a body type's schema on a value. Stored values may have been written by another client
|
|
23
|
+
* that never ran the schema, so renderers check them again before calling `render`.
|
|
24
|
+
* Returns the schema's output value when it passes.
|
|
25
|
+
*/
|
|
26
|
+
export declare function checkBodyValue<T>(definition: BodyTypeDefinition<T>, value: unknown): {
|
|
27
|
+
ok: true;
|
|
28
|
+
value: T;
|
|
29
|
+
} | {
|
|
30
|
+
ok: false;
|
|
31
|
+
message?: string;
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=body-prepare.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"body-prepare.d.ts","sourceRoot":"","sources":["../src/body-prepare.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,SAAS,EAGT,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACpB,MAAM,WAAW,CAAC;AACnB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,iGAAiG;AACjG,qBAAa,gBAAiB,SAAQ,SAAS;IAC7C,+FAA+F;IAC/F,OAAO,CAAC,IAAI,EAAE,cAAc,GAAG,cAAc;CAG9C;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,cAAc,GAAG,cAAc,CA8BvF;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAC9B,UAAU,EAAE,kBAAkB,CAAC,CAAC,CAAC,EACjC,KAAK,EAAE,OAAO,GACb;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CA0B1D"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checking a typed body part before it is written, and a body type's schema on a value. Kept
|
|
3
|
+
* apart from `body.ts`, which the read path loads, since only writes and renderers of typed parts
|
|
4
|
+
* need them (#617).
|
|
5
|
+
*/
|
|
6
|
+
import { BodyTypes, InvalidBodyError, isBodyTypeName, } from "./body.js";
|
|
7
|
+
/** App-defined body types by name, with `prepare` to check a typed part before it is written. */
|
|
8
|
+
export class BodyTypeRegistry extends BodyTypes {
|
|
9
|
+
/** Checks a typed part before it is written (see `prepareBody`). Returns the part to store. */
|
|
10
|
+
prepare(part) {
|
|
11
|
+
return prepareBody(this, part);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Checks a typed part before it is written: a registered type's schema must accept the value,
|
|
16
|
+
* and its summary is recomputed with `summarize`. Unknown types (made by another app or a newer
|
|
17
|
+
* client) pass through unchanged but must have a valid namespaced name and carry a summary.
|
|
18
|
+
* The value is stored as given, not as the schema's output, so a part re-saved unchanged still
|
|
19
|
+
* passes. Returns the part to store. `types` is the registry to look types up in.
|
|
20
|
+
*/
|
|
21
|
+
export function prepareBody(types, part) {
|
|
22
|
+
if (!isBodyTypeName(part.schema)) {
|
|
23
|
+
throw new InvalidBodyError(`"${part.schema}" is not a valid namespaced body type name`);
|
|
24
|
+
}
|
|
25
|
+
const definition = types.get(part.schema);
|
|
26
|
+
if (!definition) {
|
|
27
|
+
if (typeof part.summary !== "string") {
|
|
28
|
+
throw new InvalidBodyError(`Body part "${part.schema}" needs a plain-text summary`);
|
|
29
|
+
}
|
|
30
|
+
return part;
|
|
31
|
+
}
|
|
32
|
+
const checked = checkBodyValue(definition, part.value);
|
|
33
|
+
if (!checked.ok) {
|
|
34
|
+
throw new InvalidBodyError(checked.message
|
|
35
|
+
? `Body part "${part.schema}" does not match its schema: ${checked.message}`
|
|
36
|
+
: `Body part "${part.schema}" does not match its schema`);
|
|
37
|
+
}
|
|
38
|
+
let summary;
|
|
39
|
+
try {
|
|
40
|
+
summary = definition.summarize(checked.value);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
44
|
+
throw new InvalidBodyError(`summarize for "${part.schema}" failed: ${message}`);
|
|
45
|
+
}
|
|
46
|
+
if (typeof summary !== "string") {
|
|
47
|
+
throw new InvalidBodyError(`summarize for "${part.schema}" must return a string`);
|
|
48
|
+
}
|
|
49
|
+
return { ...part, summary };
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Runs a body type's schema on a value. Stored values may have been written by another client
|
|
53
|
+
* that never ran the schema, so renderers check them again before calling `render`.
|
|
54
|
+
* Returns the schema's output value when it passes.
|
|
55
|
+
*/
|
|
56
|
+
export function checkBodyValue(definition, value) {
|
|
57
|
+
const schema = definition.schema;
|
|
58
|
+
if (!schema)
|
|
59
|
+
return { ok: true, value: value };
|
|
60
|
+
if (typeof schema === "function") {
|
|
61
|
+
let result;
|
|
62
|
+
try {
|
|
63
|
+
result = schema(value);
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
return { ok: false, message: error instanceof Error ? error.message : String(error) };
|
|
67
|
+
}
|
|
68
|
+
if (result === true)
|
|
69
|
+
return { ok: true, value: value };
|
|
70
|
+
return typeof result === "string" && result ? { ok: false, message: result } : { ok: false };
|
|
71
|
+
}
|
|
72
|
+
let result;
|
|
73
|
+
try {
|
|
74
|
+
result = schema["~standard"].validate(value);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
return { ok: false, message: error instanceof Error ? error.message : String(error) };
|
|
78
|
+
}
|
|
79
|
+
if (typeof result?.then === "function") {
|
|
80
|
+
void Promise.resolve(result).catch(() => { });
|
|
81
|
+
return { ok: false, message: "async schemas are not supported; use a synchronous one" };
|
|
82
|
+
}
|
|
83
|
+
const r = result;
|
|
84
|
+
if (r.issues)
|
|
85
|
+
return { ok: false, message: r.issues.map((i) => i.message).join("; ") };
|
|
86
|
+
return { ok: true, value: r.value };
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=body-prepare.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"body-prepare.js","sourceRoot":"","sources":["../src/body-prepare.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Body } from "./model.js";
|
|
2
|
+
/** Thrown on write when a body is over {@link MAX_BODY_CHARS} or {@link MAX_BODY_PARTS}. */
|
|
3
|
+
export declare class BodyTooLargeError extends Error {
|
|
4
|
+
readonly length: number;
|
|
5
|
+
readonly limit: number;
|
|
6
|
+
readonly unit: "characters" | "parts";
|
|
7
|
+
constructor(length: number, limit?: number, unit?: "characters" | "parts");
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Number of characters a body counts against {@link MAX_BODY_CHARS}: every string in it, and the
|
|
11
|
+
* JSON of typed values with numbers counted at their full written-out length.
|
|
12
|
+
*/
|
|
13
|
+
export declare function bodyLength(bodies: readonly Body[]): number;
|
|
14
|
+
/**
|
|
15
|
+
* Checks a body before it is written: at most {@link MAX_BODY_PARTS} parts and
|
|
16
|
+
* {@link MAX_BODY_CHARS} characters ({@link BodyTooLargeError}), JSON values, and no characters
|
|
17
|
+
* the database cannot store (NUL, unpaired surrogates) ({@link InvalidBodyError}).
|
|
18
|
+
*/
|
|
19
|
+
export declare function assertBodySize(bodies: readonly Body[], limit?: number): void;
|
|
20
|
+
//# sourceMappingURL=body-size.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"body-size.d.ts","sourceRoot":"","sources":["../src/body-size.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,IAAI,EAAkB,MAAM,YAAY,CAAC;AAEvD,4FAA4F;AAC5F,qBAAa,iBAAkB,SAAQ,KAAK;IAExC,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM;IACtB,QAAQ,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO;gBAF5B,MAAM,EAAE,MAAM,EACd,KAAK,GAAE,MAAuB,EAC9B,IAAI,GAAE,YAAY,GAAG,OAAsB;CAKvD;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,SAAS,IAAI,EAAE,GAAG,MAAM,CAc1D;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,IAAI,EAAE,EAAE,KAAK,GAAE,MAAuB,GAAG,IAAI,CAmB5F"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The size cap on note bodies, checked before a note is written. A module of its own, so a page
|
|
3
|
+
* that only shows notes does not load it with the rest of `body.ts`.
|
|
4
|
+
*/
|
|
5
|
+
import { InvalidBodyError, MAX_BODY_CHARS, MAX_BODY_PARTS } from "./body.js";
|
|
6
|
+
/** Thrown on write when a body is over {@link MAX_BODY_CHARS} or {@link MAX_BODY_PARTS}. */
|
|
7
|
+
export class BodyTooLargeError extends Error {
|
|
8
|
+
length;
|
|
9
|
+
limit;
|
|
10
|
+
unit;
|
|
11
|
+
constructor(length, limit = MAX_BODY_CHARS, unit = "characters") {
|
|
12
|
+
super(`The note body has ${length} ${unit}; the limit is ${limit}`);
|
|
13
|
+
this.length = length;
|
|
14
|
+
this.limit = limit;
|
|
15
|
+
this.unit = unit;
|
|
16
|
+
this.name = "BodyTooLargeError";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Number of characters a body counts against {@link MAX_BODY_CHARS}: every string in it, and the
|
|
21
|
+
* JSON of typed values with numbers counted at their full written-out length.
|
|
22
|
+
*/
|
|
23
|
+
export function bodyLength(bodies) {
|
|
24
|
+
let n = 0;
|
|
25
|
+
for (const b of bodies) {
|
|
26
|
+
// Names and MIME types count too, so no field of a part is unbounded.
|
|
27
|
+
if (b.purpose !== undefined)
|
|
28
|
+
n += codePoints(b.purpose);
|
|
29
|
+
if (b.type === "TextualBody") {
|
|
30
|
+
n += codePoints(b.value);
|
|
31
|
+
if (b.format !== undefined)
|
|
32
|
+
n += codePoints(b.format);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
n += codePoints(b.schema) + codePoints(b.summary) + codePoints(jsonText(b));
|
|
36
|
+
n += exponentDigits(b.value);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return n;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Checks a body before it is written: at most {@link MAX_BODY_PARTS} parts and
|
|
43
|
+
* {@link MAX_BODY_CHARS} characters ({@link BodyTooLargeError}), JSON values, and no characters
|
|
44
|
+
* the database cannot store (NUL, unpaired surrogates) ({@link InvalidBodyError}).
|
|
45
|
+
*/
|
|
46
|
+
export function assertBodySize(bodies, limit = MAX_BODY_CHARS) {
|
|
47
|
+
if (bodies.length > MAX_BODY_PARTS) {
|
|
48
|
+
throw new BodyTooLargeError(bodies.length, MAX_BODY_PARTS, "parts");
|
|
49
|
+
}
|
|
50
|
+
const length = bodyLength(bodies);
|
|
51
|
+
if (length > limit)
|
|
52
|
+
throw new BodyTooLargeError(length, limit);
|
|
53
|
+
for (const b of bodies) {
|
|
54
|
+
const texts = b.type === "TextualBody"
|
|
55
|
+
? [b.value, b.format, b.purpose]
|
|
56
|
+
: [b.schema, b.summary, b.purpose, ...jsonStrings(b.value)];
|
|
57
|
+
for (const text of texts) {
|
|
58
|
+
if (text !== undefined && UNSTORABLE.test(text)) {
|
|
59
|
+
throw new InvalidBodyError("A note body cannot contain NUL or unpaired surrogate characters");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** NUL, and surrogates that are not part of a pair: Postgres `jsonb` rejects both. */
|
|
65
|
+
// eslint-disable-next-line no-control-regex -- NUL is exactly what this looks for.
|
|
66
|
+
const UNSTORABLE = /\u0000|[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/;
|
|
67
|
+
/** Every string in a JSON value, object keys included. */
|
|
68
|
+
function jsonStrings(value, out = []) {
|
|
69
|
+
if (typeof value === "string")
|
|
70
|
+
out.push(value);
|
|
71
|
+
else if (Array.isArray(value))
|
|
72
|
+
for (const v of value)
|
|
73
|
+
jsonStrings(v, out);
|
|
74
|
+
else if (typeof value === "object" && value !== null) {
|
|
75
|
+
for (const [k, v] of Object.entries(value)) {
|
|
76
|
+
out.push(k);
|
|
77
|
+
jsonStrings(v, out);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
function jsonText(part) {
|
|
83
|
+
let json;
|
|
84
|
+
try {
|
|
85
|
+
json = JSON.stringify(part.value);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
json = undefined;
|
|
89
|
+
}
|
|
90
|
+
if (json === undefined) {
|
|
91
|
+
throw new InvalidBodyError(`The value of body part "${part.schema}" must be JSON`);
|
|
92
|
+
}
|
|
93
|
+
return json;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Extra characters the numbers in a JSON value take once the database stores them: Postgres
|
|
97
|
+
* writes numbers out in full (`1e+300` becomes 301 digits), so a value that is short as JSON
|
|
98
|
+
* could be long in storage. Counting them keeps a body within the Cloud's byte limit.
|
|
99
|
+
*/
|
|
100
|
+
function exponentDigits(value) {
|
|
101
|
+
if (typeof value === "number") {
|
|
102
|
+
const json = JSON.stringify(value);
|
|
103
|
+
const e = json.indexOf("e");
|
|
104
|
+
if (e < 0)
|
|
105
|
+
return 0;
|
|
106
|
+
const sign = value < 0 ? 1 : 0;
|
|
107
|
+
const digits = json.slice(sign, e).replace(".", "").length;
|
|
108
|
+
const exponent = Number(json.slice(e + 1));
|
|
109
|
+
// `1.5e+21` is written `1500000000000000000000`; `1.5e-7` is written `0.00000015`.
|
|
110
|
+
const plain = sign + (exponent > 0 ? Math.max(digits, exponent + 1) : 1 - exponent + digits);
|
|
111
|
+
return plain - json.length;
|
|
112
|
+
}
|
|
113
|
+
let n = 0;
|
|
114
|
+
if (Array.isArray(value))
|
|
115
|
+
for (const v of value)
|
|
116
|
+
n += exponentDigits(v);
|
|
117
|
+
else if (typeof value === "object" && value !== null) {
|
|
118
|
+
for (const v of Object.values(value))
|
|
119
|
+
n += exponentDigits(v);
|
|
120
|
+
}
|
|
121
|
+
return n;
|
|
122
|
+
}
|
|
123
|
+
function codePoints(s) {
|
|
124
|
+
let n = 0;
|
|
125
|
+
for (let i = 0; i < s.length; i++) {
|
|
126
|
+
const c = s.charCodeAt(i);
|
|
127
|
+
// A high surrogate followed by a low one is one code point.
|
|
128
|
+
if (c >= 0xd800 && c <= 0xdbff && i + 1 < s.length) {
|
|
129
|
+
const d = s.charCodeAt(i + 1);
|
|
130
|
+
if (d >= 0xdc00 && d <= 0xdfff)
|
|
131
|
+
i++;
|
|
132
|
+
}
|
|
133
|
+
n++;
|
|
134
|
+
}
|
|
135
|
+
return n;
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=body-size.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"body-size.js","sourceRoot":"","sources":["../src/body-size.ts"],"names":[],"mappings":"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"}
|
package/dist/body.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Note bodies:
|
|
3
|
-
*
|
|
2
|
+
* Note bodies: their limits, plain-text summaries and the registry of app-defined body types. The
|
|
3
|
+
* checks needed only on write are apart: sizes in `body-size.ts`, typed parts in `body-prepare.ts`. See "Context types > Markdown,
|
|
4
|
+
* and room to grow" in the design doc.
|
|
4
5
|
*/
|
|
5
6
|
import type { Body, ContextualBody } from "./model.js";
|
|
6
7
|
/** The most characters (Unicode code points) a note's body may hold, across all its parts. */
|
|
@@ -14,13 +15,6 @@ export declare const MARKDOWN_FORMAT = "text/markdown";
|
|
|
14
15
|
* `contexthive` is the product's old name, kept reserved so no app can take it over.
|
|
15
16
|
*/
|
|
16
17
|
export declare const RESERVED_BODY_NAMESPACES: readonly string[];
|
|
17
|
-
/** Thrown on write when a body is over {@link MAX_BODY_CHARS} or {@link MAX_BODY_PARTS}. */
|
|
18
|
-
export declare class BodyTooLargeError extends Error {
|
|
19
|
-
readonly length: number;
|
|
20
|
-
readonly limit: number;
|
|
21
|
-
readonly unit: "characters" | "parts";
|
|
22
|
-
constructor(length: number, limit?: number, unit?: "characters" | "parts");
|
|
23
|
-
}
|
|
24
18
|
/** Thrown on write when a typed body part fails its type's schema or lacks a summary. */
|
|
25
19
|
export declare class InvalidBodyError extends Error {
|
|
26
20
|
constructor(message: string);
|
|
@@ -66,8 +60,12 @@ export interface BodyTypeLookup {
|
|
|
66
60
|
get(name: string): BodyTypeDefinition | undefined;
|
|
67
61
|
has(name: string): boolean;
|
|
68
62
|
}
|
|
69
|
-
/**
|
|
70
|
-
|
|
63
|
+
/**
|
|
64
|
+
* App-defined body types by name, without the check a part passes before it is written: what a
|
|
65
|
+
* page that only shows notes needs. The SDK keeps one per instance (`ctx.registerBodyType`) and
|
|
66
|
+
* checks parts with `prepareBody` on write; `BodyTypeRegistry` adds that check as `prepare`.
|
|
67
|
+
*/
|
|
68
|
+
export declare class BodyTypes implements BodyTypeLookup {
|
|
71
69
|
private readonly types;
|
|
72
70
|
/**
|
|
73
71
|
* Adds a type. Names are namespaced (`acme/status`); built-in types have no namespace and the
|
|
@@ -77,38 +75,7 @@ export declare class BodyTypeRegistry implements BodyTypeLookup {
|
|
|
77
75
|
register<T>(name: string, definition: BodyTypeDefinition<T>): () => void;
|
|
78
76
|
get(name: string): BodyTypeDefinition | undefined;
|
|
79
77
|
has(name: string): boolean;
|
|
80
|
-
/**
|
|
81
|
-
* Checks a typed part before it is written: a registered type's schema must accept the value,
|
|
82
|
-
* and its summary is recomputed with `summarize`. Unknown types (made by another app or a newer
|
|
83
|
-
* client) pass through unchanged but must have a valid namespaced name and carry a summary.
|
|
84
|
-
* The value is stored as given, not as the schema's output, so a part re-saved unchanged still
|
|
85
|
-
* passes. Returns the part to store.
|
|
86
|
-
*/
|
|
87
|
-
prepare(part: ContextualBody): ContextualBody;
|
|
88
78
|
}
|
|
89
|
-
/**
|
|
90
|
-
* Runs a body type's schema on a value. Stored values may have been written by another client
|
|
91
|
-
* that never ran the schema, so renderers check them again before calling `render`.
|
|
92
|
-
* Returns the schema's output value when it passes.
|
|
93
|
-
*/
|
|
94
|
-
export declare function checkBodyValue<T>(definition: BodyTypeDefinition<T>, value: unknown): {
|
|
95
|
-
ok: true;
|
|
96
|
-
value: T;
|
|
97
|
-
} | {
|
|
98
|
-
ok: false;
|
|
99
|
-
message?: string;
|
|
100
|
-
};
|
|
101
|
-
/**
|
|
102
|
-
* Number of characters a body counts against {@link MAX_BODY_CHARS}: every string in it, and the
|
|
103
|
-
* JSON of typed values with numbers counted at their full written-out length.
|
|
104
|
-
*/
|
|
105
|
-
export declare function bodyLength(bodies: readonly Body[]): number;
|
|
106
|
-
/**
|
|
107
|
-
* Checks a body before it is written: at most {@link MAX_BODY_PARTS} parts and
|
|
108
|
-
* {@link MAX_BODY_CHARS} characters ({@link BodyTooLargeError}), JSON values, and no characters
|
|
109
|
-
* the database cannot store (NUL, unpaired surrogates) ({@link InvalidBodyError}).
|
|
110
|
-
*/
|
|
111
|
-
export declare function assertBodySize(bodies: readonly Body[], limit?: number): void;
|
|
112
79
|
/**
|
|
113
80
|
* The plain text a client shows for a part when it cannot render it: a typed part's `summary`, or
|
|
114
81
|
* a textual part's value as written (Markdown source included).
|
package/dist/body.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"body.d.ts","sourceRoot":"","sources":["../src/body.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"body.d.ts","sourceRoot":"","sources":["../src/body.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEvD,8FAA8F;AAC9F,eAAO,MAAM,cAAc,QAAS,CAAC;AAErC,qFAAqF;AACrF,eAAO,MAAM,cAAc,KAAK,CAAC;AAEjC,4FAA4F;AAC5F,eAAO,MAAM,eAAe,kBAAkB,CAAC;AAE/C;;;GAGG;AACH,eAAO,MAAM,wBAAwB,EAAE,SAAS,MAAM,EAA4C,CAAC;AAMnG,yFAAyF;AACzF,qBAAa,gBAAiB,SAAQ,KAAK;gBAC7B,OAAO,EAAE,MAAM;CAI5B;AAED;;;;GAIG;AACH,MAAM,MAAM,UAAU,CAAC,CAAC,GAAG,OAAO,IAChC,CAAC,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,GAAG,MAAM,CAAC,GAAG,kBAAkB,CAAC,CAAC,CAAC,CAAC;AAEjE,sEAAsE;AACtE,MAAM,WAAW,kBAAkB,CAAC,CAAC,GAAG,OAAO;IAC7C,QAAQ,CAAC,WAAW,EAAE;QACpB,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;QACpB,QAAQ,CAAC,QAAQ,EAAE,CACjB,KAAK,EAAE,OAAO,KAEZ;YAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;YAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAA;SAAE,GAClD;YAAE,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;gBAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;aAAE,CAAC,CAAA;SAAE,GAChE,OAAO,CAAC,OAAO,CAAC,CAAC;KACtB,CAAC;CACH;AAED,6FAA6F;AAC7F,MAAM,MAAM,gBAAgB,GAAG,IAAI,GAAG,MAAM,CAAC;AAE7C,MAAM,WAAW,kBAAkB,CAAC,CAAC,GAAG,OAAO;IAC7C,yEAAyE;IACzE,MAAM,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;IACvB;;;;OAIG;IACH,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,cAAc,KAAK,gBAAgB,CAAC;IAC9D,iGAAiG;IACjG,SAAS,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,CAAC;CACjC;AAED,0FAA0F;AAC1F,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAOpD;AAED,kEAAkE;AAClE,MAAM,WAAW,cAAc;IAC7B,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAAC;IAClD,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CAC5B;AAED;;;;GAIG;AACH,qBAAa,SAAU,YAAW,cAAc;IAC9C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAyC;IAE/D;;;;OAIG;IACH,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI;IA2BxE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS;IAIjD,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;CAG3B;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAE9C;AAED,mFAAmF;AACnF,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAGlD"}
|
package/dist/body.js
CHANGED
|
@@ -11,19 +11,6 @@ export const MARKDOWN_FORMAT = "text/markdown";
|
|
|
11
11
|
export const RESERVED_BODY_NAMESPACES = ["acornly", "contexthive", "w3c", "oa"];
|
|
12
12
|
const TYPE_NAME = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)*\/[a-z0-9][a-z0-9._-]*$/;
|
|
13
13
|
const MAX_TYPE_NAME = 100;
|
|
14
|
-
/** Thrown on write when a body is over {@link MAX_BODY_CHARS} or {@link MAX_BODY_PARTS}. */
|
|
15
|
-
export class BodyTooLargeError extends Error {
|
|
16
|
-
length;
|
|
17
|
-
limit;
|
|
18
|
-
unit;
|
|
19
|
-
constructor(length, limit = MAX_BODY_CHARS, unit = "characters") {
|
|
20
|
-
super(`The note body has ${length} ${unit}; the limit is ${limit}`);
|
|
21
|
-
this.length = length;
|
|
22
|
-
this.limit = limit;
|
|
23
|
-
this.unit = unit;
|
|
24
|
-
this.name = "BodyTooLargeError";
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
14
|
/** Thrown on write when a typed body part fails its type's schema or lacks a summary. */
|
|
28
15
|
export class InvalidBodyError extends Error {
|
|
29
16
|
constructor(message) {
|
|
@@ -40,8 +27,12 @@ export function isBodyTypeName(name) {
|
|
|
40
27
|
const namespace = name.slice(0, name.indexOf("/"));
|
|
41
28
|
return !RESERVED_BODY_NAMESPACES.some((r) => namespace === r || namespace.startsWith(`${r}.`));
|
|
42
29
|
}
|
|
43
|
-
/**
|
|
44
|
-
|
|
30
|
+
/**
|
|
31
|
+
* App-defined body types by name, without the check a part passes before it is written: what a
|
|
32
|
+
* page that only shows notes needs. The SDK keeps one per instance (`ctx.registerBodyType`) and
|
|
33
|
+
* checks parts with `prepareBody` on write; `BodyTypeRegistry` adds that check as `prepare`.
|
|
34
|
+
*/
|
|
35
|
+
export class BodyTypes {
|
|
45
36
|
types = new Map();
|
|
46
37
|
/**
|
|
47
38
|
* Adds a type. Names are namespaced (`acme/status`); built-in types have no namespace and the
|
|
@@ -80,129 +71,7 @@ export class BodyTypeRegistry {
|
|
|
80
71
|
has(name) {
|
|
81
72
|
return this.types.has(name);
|
|
82
73
|
}
|
|
83
|
-
/**
|
|
84
|
-
* Checks a typed part before it is written: a registered type's schema must accept the value,
|
|
85
|
-
* and its summary is recomputed with `summarize`. Unknown types (made by another app or a newer
|
|
86
|
-
* client) pass through unchanged but must have a valid namespaced name and carry a summary.
|
|
87
|
-
* The value is stored as given, not as the schema's output, so a part re-saved unchanged still
|
|
88
|
-
* passes. Returns the part to store.
|
|
89
|
-
*/
|
|
90
|
-
prepare(part) {
|
|
91
|
-
if (!isBodyTypeName(part.schema)) {
|
|
92
|
-
throw new InvalidBodyError(`"${part.schema}" is not a valid namespaced body type name`);
|
|
93
|
-
}
|
|
94
|
-
const definition = this.types.get(part.schema);
|
|
95
|
-
if (!definition) {
|
|
96
|
-
if (typeof part.summary !== "string") {
|
|
97
|
-
throw new InvalidBodyError(`Body part "${part.schema}" needs a plain-text summary`);
|
|
98
|
-
}
|
|
99
|
-
return part;
|
|
100
|
-
}
|
|
101
|
-
const checked = checkBodyValue(definition, part.value);
|
|
102
|
-
if (!checked.ok) {
|
|
103
|
-
throw new InvalidBodyError(checked.message
|
|
104
|
-
? `Body part "${part.schema}" does not match its schema: ${checked.message}`
|
|
105
|
-
: `Body part "${part.schema}" does not match its schema`);
|
|
106
|
-
}
|
|
107
|
-
let summary;
|
|
108
|
-
try {
|
|
109
|
-
summary = definition.summarize(checked.value);
|
|
110
|
-
}
|
|
111
|
-
catch (error) {
|
|
112
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
113
|
-
throw new InvalidBodyError(`summarize for "${part.schema}" failed: ${message}`);
|
|
114
|
-
}
|
|
115
|
-
if (typeof summary !== "string") {
|
|
116
|
-
throw new InvalidBodyError(`summarize for "${part.schema}" must return a string`);
|
|
117
|
-
}
|
|
118
|
-
return { ...part, summary };
|
|
119
|
-
}
|
|
120
74
|
}
|
|
121
|
-
/**
|
|
122
|
-
* Runs a body type's schema on a value. Stored values may have been written by another client
|
|
123
|
-
* that never ran the schema, so renderers check them again before calling `render`.
|
|
124
|
-
* Returns the schema's output value when it passes.
|
|
125
|
-
*/
|
|
126
|
-
export function checkBodyValue(definition, value) {
|
|
127
|
-
const schema = definition.schema;
|
|
128
|
-
if (!schema)
|
|
129
|
-
return { ok: true, value: value };
|
|
130
|
-
if (typeof schema === "function") {
|
|
131
|
-
let result;
|
|
132
|
-
try {
|
|
133
|
-
result = schema(value);
|
|
134
|
-
}
|
|
135
|
-
catch (error) {
|
|
136
|
-
return { ok: false, message: error instanceof Error ? error.message : String(error) };
|
|
137
|
-
}
|
|
138
|
-
if (result === true)
|
|
139
|
-
return { ok: true, value: value };
|
|
140
|
-
return typeof result === "string" && result ? { ok: false, message: result } : { ok: false };
|
|
141
|
-
}
|
|
142
|
-
let result;
|
|
143
|
-
try {
|
|
144
|
-
result = schema["~standard"].validate(value);
|
|
145
|
-
}
|
|
146
|
-
catch (error) {
|
|
147
|
-
return { ok: false, message: error instanceof Error ? error.message : String(error) };
|
|
148
|
-
}
|
|
149
|
-
if (typeof result?.then === "function") {
|
|
150
|
-
void Promise.resolve(result).catch(() => { });
|
|
151
|
-
return { ok: false, message: "async schemas are not supported; use a synchronous one" };
|
|
152
|
-
}
|
|
153
|
-
const r = result;
|
|
154
|
-
if (r.issues)
|
|
155
|
-
return { ok: false, message: r.issues.map((i) => i.message).join("; ") };
|
|
156
|
-
return { ok: true, value: r.value };
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Number of characters a body counts against {@link MAX_BODY_CHARS}: every string in it, and the
|
|
160
|
-
* JSON of typed values with numbers counted at their full written-out length.
|
|
161
|
-
*/
|
|
162
|
-
export function bodyLength(bodies) {
|
|
163
|
-
let n = 0;
|
|
164
|
-
for (const b of bodies) {
|
|
165
|
-
// Names and MIME types count too, so no field of a part is unbounded.
|
|
166
|
-
if (b.purpose !== undefined)
|
|
167
|
-
n += codePoints(b.purpose);
|
|
168
|
-
if (b.type === "TextualBody") {
|
|
169
|
-
n += codePoints(b.value);
|
|
170
|
-
if (b.format !== undefined)
|
|
171
|
-
n += codePoints(b.format);
|
|
172
|
-
}
|
|
173
|
-
else {
|
|
174
|
-
n += codePoints(b.schema) + codePoints(b.summary) + codePoints(jsonText(b));
|
|
175
|
-
n += exponentDigits(b.value);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
return n;
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* Checks a body before it is written: at most {@link MAX_BODY_PARTS} parts and
|
|
182
|
-
* {@link MAX_BODY_CHARS} characters ({@link BodyTooLargeError}), JSON values, and no characters
|
|
183
|
-
* the database cannot store (NUL, unpaired surrogates) ({@link InvalidBodyError}).
|
|
184
|
-
*/
|
|
185
|
-
export function assertBodySize(bodies, limit = MAX_BODY_CHARS) {
|
|
186
|
-
if (bodies.length > MAX_BODY_PARTS) {
|
|
187
|
-
throw new BodyTooLargeError(bodies.length, MAX_BODY_PARTS, "parts");
|
|
188
|
-
}
|
|
189
|
-
const length = bodyLength(bodies);
|
|
190
|
-
if (length > limit)
|
|
191
|
-
throw new BodyTooLargeError(length, limit);
|
|
192
|
-
for (const b of bodies) {
|
|
193
|
-
const texts = b.type === "TextualBody"
|
|
194
|
-
? [b.value, b.format, b.purpose]
|
|
195
|
-
: [b.schema, b.summary, b.purpose, ...jsonStrings(b.value)];
|
|
196
|
-
for (const text of texts) {
|
|
197
|
-
if (text !== undefined && UNSTORABLE.test(text)) {
|
|
198
|
-
throw new InvalidBodyError("A note body cannot contain NUL or unpaired surrogate characters");
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
/** NUL, and surrogates that are not part of a pair: Postgres `jsonb` rejects both. */
|
|
204
|
-
// eslint-disable-next-line no-control-regex -- NUL is exactly what this looks for.
|
|
205
|
-
const UNSTORABLE = /\u0000|[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/;
|
|
206
75
|
/**
|
|
207
76
|
* The plain text a client shows for a part when it cannot render it: a typed part's `summary`, or
|
|
208
77
|
* a textual part's value as written (Markdown source included).
|
|
@@ -216,79 +85,9 @@ export function isMarkdownBody(part) {
|
|
|
216
85
|
return false;
|
|
217
86
|
return part.format === undefined || mimeEssence(part.format) === MARKDOWN_FORMAT;
|
|
218
87
|
}
|
|
219
|
-
/** Every string in a JSON value, object keys included. */
|
|
220
|
-
function jsonStrings(value, out = []) {
|
|
221
|
-
if (typeof value === "string")
|
|
222
|
-
out.push(value);
|
|
223
|
-
else if (Array.isArray(value))
|
|
224
|
-
for (const v of value)
|
|
225
|
-
jsonStrings(v, out);
|
|
226
|
-
else if (typeof value === "object" && value !== null) {
|
|
227
|
-
for (const [k, v] of Object.entries(value)) {
|
|
228
|
-
out.push(k);
|
|
229
|
-
jsonStrings(v, out);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
return out;
|
|
233
|
-
}
|
|
234
|
-
function jsonText(part) {
|
|
235
|
-
let json;
|
|
236
|
-
try {
|
|
237
|
-
json = JSON.stringify(part.value);
|
|
238
|
-
}
|
|
239
|
-
catch {
|
|
240
|
-
json = undefined;
|
|
241
|
-
}
|
|
242
|
-
if (json === undefined) {
|
|
243
|
-
throw new InvalidBodyError(`The value of body part "${part.schema}" must be JSON`);
|
|
244
|
-
}
|
|
245
|
-
return json;
|
|
246
|
-
}
|
|
247
|
-
/**
|
|
248
|
-
* Extra characters the numbers in a JSON value take once the database stores them: Postgres
|
|
249
|
-
* writes numbers out in full (`1e+300` becomes 301 digits), so a value that is short as JSON
|
|
250
|
-
* could be long in storage. Counting them keeps a body within the Cloud's byte limit.
|
|
251
|
-
*/
|
|
252
|
-
function exponentDigits(value) {
|
|
253
|
-
if (typeof value === "number") {
|
|
254
|
-
const json = JSON.stringify(value);
|
|
255
|
-
const e = json.indexOf("e");
|
|
256
|
-
if (e < 0)
|
|
257
|
-
return 0;
|
|
258
|
-
const sign = value < 0 ? 1 : 0;
|
|
259
|
-
const digits = json.slice(sign, e).replace(".", "").length;
|
|
260
|
-
const exponent = Number(json.slice(e + 1));
|
|
261
|
-
// `1.5e+21` is written `1500000000000000000000`; `1.5e-7` is written `0.00000015`.
|
|
262
|
-
const plain = sign + (exponent > 0 ? Math.max(digits, exponent + 1) : 1 - exponent + digits);
|
|
263
|
-
return plain - json.length;
|
|
264
|
-
}
|
|
265
|
-
let n = 0;
|
|
266
|
-
if (Array.isArray(value))
|
|
267
|
-
for (const v of value)
|
|
268
|
-
n += exponentDigits(v);
|
|
269
|
-
else if (typeof value === "object" && value !== null) {
|
|
270
|
-
for (const v of Object.values(value))
|
|
271
|
-
n += exponentDigits(v);
|
|
272
|
-
}
|
|
273
|
-
return n;
|
|
274
|
-
}
|
|
275
88
|
function mimeEssence(format) {
|
|
276
89
|
return format.split(";")[0].trim().toLowerCase();
|
|
277
90
|
}
|
|
278
|
-
function codePoints(s) {
|
|
279
|
-
let n = 0;
|
|
280
|
-
for (let i = 0; i < s.length; i++) {
|
|
281
|
-
const c = s.charCodeAt(i);
|
|
282
|
-
// A high surrogate followed by a low one is one code point.
|
|
283
|
-
if (c >= 0xd800 && c <= 0xdbff && i + 1 < s.length) {
|
|
284
|
-
const d = s.charCodeAt(i + 1);
|
|
285
|
-
if (d >= 0xdc00 && d <= 0xdfff)
|
|
286
|
-
i++;
|
|
287
|
-
}
|
|
288
|
-
n++;
|
|
289
|
-
}
|
|
290
|
-
return n;
|
|
291
|
-
}
|
|
292
91
|
function isSchema(schema) {
|
|
293
92
|
if (typeof schema === "function")
|
|
294
93
|
return true;
|