@dogsbay/types 0.2.0-beta.157 → 0.2.0-beta.159

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/format.d.ts CHANGED
@@ -252,6 +252,11 @@ export interface ImportResult {
252
252
  exclude?: string[];
253
253
  /** Folders of the mount, relative to it, left out whole (VitePress: `public`, a mount of its own). */
254
254
  skip?: string[];
255
+ /**
256
+ * Only these files of the mount, relative to it. Read by `@dogsbay/corpus` `mountCopies` (the
257
+ * Dogsbay MD export, the browser imports) only: set it only for Dogsbay MD output.
258
+ */
259
+ files?: string[];
255
260
  /**
256
261
  * Public directory the mount lands in. Defaults to `_assets` — the name
257
262
  * every IMPORTER rewrites its refs to. Hand-authored content differs: an
package/dist/index.d.ts CHANGED
@@ -11,3 +11,5 @@ export { WELL_KNOWN_FIELDS, INERT_FIELDS, AGGREGATOR_OWNED, EMPTY_POLICY, typeOf
11
11
  export type { FieldType, FieldSpec, PropSpec, TaxonomySpec, FrontmatterPolicy } from "./frontmatter-schema.js";
12
12
  export { checkFrontmatter, frontmatterPolicyFromConfig } from "./frontmatter-check.js";
13
13
  export type { FrontmatterFinding } from "./frontmatter-check.js";
14
+ export type { ImportLimits, LimitName, LimitHost } from "./limits.js";
15
+ export { DEFAULT_LIMITS, LIMIT_DESCRIPTIONS, LIMIT_REACHED, resolveLimits, parseLimit, formatLimit, limitMessage, utf8Bytes } from "./limits.js";
package/dist/index.js CHANGED
@@ -3,3 +3,4 @@ export { treeToJson, chunkByHeadings, extractText } from "./json.js";
3
3
  export { STATUS_VALUES, parseMeta } from "./meta.js";
4
4
  export { WELL_KNOWN_FIELDS, INERT_FIELDS, AGGREGATOR_OWNED, EMPTY_POLICY, typeOf, matchesType, nearMisses, isSafeAutoFix, frontmatterKeyLines, globMatches, contentRelativePath, requiredFieldsFor, } from "./frontmatter-schema.js";
5
5
  export { checkFrontmatter, frontmatterPolicyFromConfig } from "./frontmatter-check.js";
6
+ export { DEFAULT_LIMITS, LIMIT_DESCRIPTIONS, LIMIT_REACHED, resolveLimits, parseLimit, formatLimit, limitMessage, utf8Bytes } from "./limits.js";
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Import limits: the RESOURCE limits an import puts on someone else's repo, in one place
3
+ * (plans/import-limits.md). They exist so a hostile repo cannot make an import read gigabytes or
4
+ * build a table of a million rows; for the operator's own repo they are only a way for content
5
+ * to turn into a note, so `trusted` sets none.
6
+ *
7
+ * Who may set them: the OPERATOR (CLI flags, a host's `migrate()` options), never the repo being
8
+ * imported. Safety rules (containment, no repo code run, escaping) are NOT limits and are not
9
+ * here: they hold whether the repo is trusted or not.
10
+ *
11
+ * Each default is measured against the largest real value it guards, with the headroom noted.
12
+ * A limit that trips is REPORTED (`LIMIT_REACHED`), never silent.
13
+ */
14
+ export interface ImportLimits {
15
+ /** One file read as data (bytes). */
16
+ fileBytes: number;
17
+ /** Files in one data folder. */
18
+ folderFiles: number;
19
+ /** All data read in one import (bytes). */
20
+ importBytes: number;
21
+ /** Data one catalogue (a table built from data files) may read (bytes). */
22
+ catalogueBytes: number;
23
+ /** Rows in one catalogue. */
24
+ catalogueRows: number;
25
+ /** Characters of text in one table cell. */
26
+ cellChars: number;
27
+ /** Bytes of one code example the import transforms (TypeScript → JavaScript with prettier). */
28
+ exampleBytes: number;
29
+ }
30
+ export type LimitName = keyof ImportLimits;
31
+ /**
32
+ * The defaults, for a repo that is not trusted. Measured on cloudflare-docs (6e85ac29,
33
+ * 2026-11-03), the largest real use of each today:
34
+ */
35
+ export declare const DEFAULT_LIMITS: Readonly<ImportLimits>;
36
+ /** What each limit guards, for the report. */
37
+ export declare const LIMIT_DESCRIPTIONS: Readonly<Record<LimitName, string>>;
38
+ /** The ledger code a tripped limit is reported under. */
39
+ export declare const LIMIT_REACHED = "limit-reached";
40
+ /**
41
+ * The limits an import runs with: the defaults, or none when `trusted`, then each given value.
42
+ * A given value must be a positive number (or Infinity); anything else throws, naming it — a typo
43
+ * must not run with a limit the operator did not mean.
44
+ */
45
+ export declare function resolveLimits(given?: Partial<Record<string, unknown>>, opts?: {
46
+ trusted?: boolean;
47
+ }): ImportLimits;
48
+ /**
49
+ * One `--limit name=value` flag: `catalogueBytes=500MB`, `catalogueRows=50000`, `cellChars=none`.
50
+ * Sizes take `KB`/`MB`/`GB` (powers of 1,000) or `KiB`/`MiB`/`GiB`; `none` is no limit.
51
+ */
52
+ export declare function parseLimit(flag: string): [LimitName, number];
53
+ /** A limit's value as a person reads it: `256 MB`, `20,000`, `none`. */
54
+ export declare function formatLimit(name: LimitName, value: number): string;
55
+ /**
56
+ * How an operator raises a limit, by where the import runs: the CLI's flags, `site build`'s config,
57
+ * a host's options (the playground). The report names the one that works where it is read.
58
+ */
59
+ export type LimitHost = "cli" | "config" | "host";
60
+ /**
61
+ * The report for a tripped limit: what was not done, the measured value against the limit, and
62
+ * how to raise it where the import runs.
63
+ */
64
+ export declare function limitMessage(what: string, name: LimitName, measured: number | undefined, limit: number, host?: LimitHost): string;
65
+ /** UTF-8 bytes of a string, without encoding it: limits on bytes count bytes, not UTF-16 units. */
66
+ export declare function utf8Bytes(s: string): number;
package/dist/limits.js ADDED
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Import limits: the RESOURCE limits an import puts on someone else's repo, in one place
3
+ * (plans/import-limits.md). They exist so a hostile repo cannot make an import read gigabytes or
4
+ * build a table of a million rows; for the operator's own repo they are only a way for content
5
+ * to turn into a note, so `trusted` sets none.
6
+ *
7
+ * Who may set them: the OPERATOR (CLI flags, a host's `migrate()` options), never the repo being
8
+ * imported. Safety rules (containment, no repo code run, escaping) are NOT limits and are not
9
+ * here: they hold whether the repo is trusted or not.
10
+ *
11
+ * Each default is measured against the largest real value it guards, with the headroom noted.
12
+ * A limit that trips is REPORTED (`LIMIT_REACHED`), never silent.
13
+ */
14
+ /**
15
+ * The defaults, for a repo that is not trusted. Measured on cloudflare-docs (6e85ac29,
16
+ * 2026-11-03), the largest real use of each today:
17
+ */
18
+ export const DEFAULT_LIMITS = Object.freeze({
19
+ // largest model JSON 496 KB → 32×
20
+ fileBytes: 16_000_000,
21
+ // catalog-models holds 199 files → 100×
22
+ folderFiles: 20_000,
23
+ // all the data files in Cloudflare's src/content (JSON/YAML outside the pages): 15.6 MB → 64×;
24
+ // the whole src/content, pages included, is 54.7 MB
25
+ importBytes: 1_000_000_000,
26
+ // the model catalogue reads 14.1 MB of JSON → 18×
27
+ catalogueBytes: 256_000_000,
28
+ // 272 models, 193 fields → 70×
29
+ catalogueRows: 20_000,
30
+ // longest model description 611 characters → 16×
31
+ cellChars: 10_000,
32
+ // largest <TypeScriptExample> 11.5 KB (median 409 bytes) → 87×; 1 MB is ~1.8 s of prettier
33
+ exampleBytes: 1_000_000,
34
+ });
35
+ /** What each limit guards, for the report. */
36
+ export const LIMIT_DESCRIPTIONS = Object.freeze({
37
+ fileBytes: "bytes in one data file",
38
+ folderFiles: "files in one data folder",
39
+ importBytes: "data read in one import",
40
+ catalogueBytes: "data one catalogue reads",
41
+ catalogueRows: "rows in one catalogue",
42
+ cellChars: "characters in one table cell",
43
+ exampleBytes: "bytes in one code example to transform",
44
+ });
45
+ /** The ledger code a tripped limit is reported under. */
46
+ export const LIMIT_REACHED = "limit-reached";
47
+ /** Every limit unset: what `trusted` gives. */
48
+ const NONE = Object.freeze({
49
+ fileBytes: Infinity,
50
+ folderFiles: Infinity,
51
+ importBytes: Infinity,
52
+ catalogueBytes: Infinity,
53
+ catalogueRows: Infinity,
54
+ cellChars: Infinity,
55
+ exampleBytes: Infinity,
56
+ });
57
+ const isLimitName = (k) => Object.hasOwn(DEFAULT_LIMITS, k);
58
+ /**
59
+ * The limits an import runs with: the defaults, or none when `trusted`, then each given value.
60
+ * A given value must be a positive number (or Infinity); anything else throws, naming it — a typo
61
+ * must not run with a limit the operator did not mean.
62
+ */
63
+ export function resolveLimits(given, opts = {}) {
64
+ const out = { ...(opts.trusted ? NONE : DEFAULT_LIMITS) };
65
+ for (const [k, v] of Object.entries(given ?? {})) {
66
+ if (v === undefined)
67
+ continue;
68
+ if (!isLimitName(k))
69
+ throw new Error(`No such limit: ${k} (there is ${Object.keys(DEFAULT_LIMITS).join(", ")})`);
70
+ // a whole number ≥ 1, or Infinity (none): `cellChars=0.5` cut a cell to one character and an ellipsis
71
+ if (typeof v !== "number" || !(v === Infinity || (Number.isInteger(v) && v >= 1)))
72
+ throw new Error(`Limit ${k} must be a whole number of at least 1 (or none), not ${JSON.stringify(v)}`);
73
+ out[k] = v;
74
+ }
75
+ return out;
76
+ }
77
+ /**
78
+ * One `--limit name=value` flag: `catalogueBytes=500MB`, `catalogueRows=50000`, `cellChars=none`.
79
+ * Sizes take `KB`/`MB`/`GB` (powers of 1,000) or `KiB`/`MiB`/`GiB`; `none` is no limit.
80
+ */
81
+ export function parseLimit(flag) {
82
+ const m = /^([A-Za-z]+)=(.+)$/.exec(flag.trim());
83
+ if (!m)
84
+ throw new Error(`--limit takes name=value, not ${JSON.stringify(flag)}`);
85
+ const [, name, raw] = m;
86
+ if (!isLimitName(name))
87
+ throw new Error(`No such limit: ${name} (there is ${Object.keys(DEFAULT_LIMITS).join(", ")})`);
88
+ if (/^(none|unlimited|infinity)$/i.test(raw))
89
+ return [name, Infinity];
90
+ const v = /^(\d+(?:\.\d+)?)\s*([KMG]i?B)?$/i.exec(raw.replace(/_/g, ""));
91
+ if (!v)
92
+ throw new Error(`--limit ${name}: ${JSON.stringify(raw)} is not a number (or 500MB, or none)`);
93
+ const unit = (v[2] ?? "").toUpperCase();
94
+ if (unit && !name.endsWith("Bytes"))
95
+ throw new Error(`--limit ${name} is a count, not a size: ${JSON.stringify(raw)}`);
96
+ const scale = { "": 1, KB: 1e3, MB: 1e6, GB: 1e9, KIB: 1024, MIB: 1024 ** 2, GIB: 1024 ** 3 };
97
+ // a size rounds to whole bytes; a count must be whole
98
+ const n = unit ? Math.round(Number(v[1]) * scale[unit]) : Number(v[1]);
99
+ // only the word `none` means no limit: 400 digits is a typo, not Infinity
100
+ if (!Number.isFinite(n) || n > Number.MAX_SAFE_INTEGER)
101
+ throw new Error(`--limit ${name}: ${JSON.stringify(raw)} is too large (use none for no limit)`);
102
+ if (!Number.isInteger(n) || n < 1)
103
+ throw new Error(`--limit ${name} must be a whole number of at least 1, not ${JSON.stringify(raw)}`);
104
+ return [name, n];
105
+ }
106
+ /** A limit's value as a person reads it: `256 MB`, `20,000`, `none`. */
107
+ export function formatLimit(name, value) {
108
+ if (!Number.isFinite(value))
109
+ return "none";
110
+ if (name.endsWith("Bytes")) {
111
+ if (value >= 1e9)
112
+ return `${+(value / 1e9).toFixed(1)} GB`;
113
+ if (value >= 1e6)
114
+ return `${+(value / 1e6).toFixed(1)} MB`;
115
+ if (value >= 1e3)
116
+ return `${+(value / 1e3).toFixed(1)} KB`;
117
+ return `${value} bytes`;
118
+ }
119
+ return value.toLocaleString("en-US");
120
+ }
121
+ const HOW = {
122
+ cli: (name) => `--limit ${name}=…, or --trusted for a repo you trust`,
123
+ config: (name) => `\`limits: { ${name}: … }\` in dogsbay.config.yml, or \`limits: { trusted: true }\``,
124
+ host: (name) => `the import's \`limits.${name}\` option, or \`trusted\` for a repo you trust`,
125
+ };
126
+ /**
127
+ * The report for a tripped limit: what was not done, the measured value against the limit, and
128
+ * how to raise it where the import runs.
129
+ */
130
+ export function limitMessage(what, name, measured, limit, host = "cli") {
131
+ const over = measured === undefined ? `over the ${formatLimit(name, limit)} limit` : `${formatLimit(name, measured)}, over the ${formatLimit(name, limit)} limit`;
132
+ return `${what}: ${LIMIT_DESCRIPTIONS[name]} ${over} (${HOW[host](name)})`;
133
+ }
134
+ /** UTF-8 bytes of a string, without encoding it: limits on bytes count bytes, not UTF-16 units. */
135
+ export function utf8Bytes(s) {
136
+ let n = 0;
137
+ for (let i = 0; i < s.length; i++) {
138
+ const c = s.charCodeAt(i);
139
+ if (c < 0x80)
140
+ n += 1;
141
+ else if (c < 0x800)
142
+ n += 2;
143
+ else if (c >= 0xd800 && c <= 0xdbff && i + 1 < s.length && (s.charCodeAt(i + 1) & 0xfc00) === 0xdc00) {
144
+ n += 4;
145
+ i++;
146
+ }
147
+ else
148
+ n += 3;
149
+ }
150
+ return n;
151
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dogsbay/types",
3
- "version": "0.2.0-beta.157",
3
+ "version": "0.2.0-beta.159",
4
4
  "description": "Universal document tree model for Dogsbay format loaders and renderers",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",