rastack 0.0.46 → 0.0.48

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.
Files changed (49) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/compile/entities.d.ts +0 -49
  3. package/dist/compile/entities.js +110 -22
  4. package/dist/compile/index.d.ts +1 -1
  5. package/dist/compile/index.js +1 -1
  6. package/dist/compile/program.js +1 -1
  7. package/dist/define/db.d.ts +1 -1
  8. package/dist/define/db.js +1 -1
  9. package/dist/import/index.d.ts +18 -0
  10. package/dist/import/index.js +57 -0
  11. package/dist/import/pdf.d.ts +25 -0
  12. package/dist/import/pdf.js +326 -0
  13. package/dist/import/tabular.d.ts +76 -0
  14. package/dist/import/tabular.js +98 -0
  15. package/dist/import/xlsx.d.ts +18 -0
  16. package/dist/import/xlsx.js +160 -0
  17. package/dist/rastack-import.d.ts +22 -0
  18. package/dist/rastack-import.js +165 -0
  19. package/dist/rastack.d.ts +1 -0
  20. package/dist/rastack.js +7 -0
  21. package/dist/validate/adapters.d.ts +42 -0
  22. package/dist/validate/adapters.js +109 -0
  23. package/dist/validate/index.d.ts +2 -0
  24. package/dist/validate/index.js +18 -0
  25. package/dist/validate/machine.d.ts +175 -0
  26. package/dist/validate/machine.js +347 -0
  27. package/dist/wasm/rastack_wasm_bg.wasm +0 -0
  28. package/hooks/form/form.ts +16 -5
  29. package/hooks/form/interfaces.ts +15 -0
  30. package/hooks/form/structure.ts +28 -0
  31. package/package.json +1 -1
  32. package/src/compile/entities.ts +161 -59
  33. package/src/compile/index.ts +1 -1
  34. package/src/compile/program.ts +1 -1
  35. package/src/define/db.ts +1 -1
  36. package/src/import/index.ts +41 -0
  37. package/src/import/pdf.ts +304 -0
  38. package/src/import/tabular.ts +159 -0
  39. package/src/import/xlsx.ts +186 -0
  40. package/src/rastack-import.ts +203 -0
  41. package/src/rastack.ts +7 -0
  42. package/src/validate/adapters.ts +118 -0
  43. package/src/validate/index.ts +2 -0
  44. package/src/validate/machine.ts +525 -0
  45. package/test/entities.spec.ts +99 -0
  46. package/test/import.spec.ts +241 -0
  47. package/test/validate.spec.ts +319 -0
  48. package/validate.ts +10 -0
  49. package/wasm/rastack_wasm_bg.wasm +0 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [0.0.48](https://github.com/theserverkid/reactapistack/compare/v0.0.47...v0.0.48) (2026-07-13)
6
+
7
+ ### [0.0.47](https://github.com/theserverkid/reactapistack/compare/v0.0.46...v0.0.47) (2026-07-13)
8
+
5
9
  ### [0.0.46](https://github.com/theserverkid/reactapistack/compare/v0.0.45...v0.0.46) (2026-07-13)
6
10
 
7
11
  ### [0.0.45](https://github.com/theserverkid/reactapistack/compare/v0.0.44...v0.0.45) (2026-07-13)
@@ -1,54 +1,5 @@
1
1
  import * as ts from "typescript";
2
2
  import { ResourceModel } from "./model";
3
- /**
4
- * Interface-driven resource inference — "the entities in the app are a list of
5
- * interfaces".
6
- *
7
- * Instead of (or alongside) the `resource()` DSL, an app can describe its data
8
- * as plain TypeScript interfaces and consume them through typed hooks. The
9
- * analyser works the resource graph out from the *usage*:
10
- *
11
- * 1. **Roots come from the hooks.** Every hook call that carries a type
12
- * argument — `useEntityList<Airport>(…)`, `useQuery<Terminal>(…)` — marks
13
- * the interface behind that type argument as an entity. An interface can
14
- * also opt in explicitly with a `@rastackResource` JSDoc tag.
15
- * 2. **Datatypes come from the interfaces.** Each property's TypeScript type
16
- * maps to a manifest scalar (`string` → string, `number` → float,
17
- * `boolean` → bool, `Date` → datetime). Constraints are carried in the
18
- * type system with `DbConfig` brands from `rastack/types` —
19
- * `code: string & Unique & MaxLength<3>`, `age: Int` — or, equivalently,
20
- * as JSDoc tags (`@unique`, `@maxLength 3`, `@int`, `@uuid`, `@default`).
21
- * Optional / `| null` properties become nullable fields.
22
- * 3. **Foreign keys come from the type system.** A property whose type is
23
- * another entity interface is a foreign key, and the referenced interface
24
- * is pulled into the resource graph transitively — the closure of the
25
- * hook-rooted interfaces is the app's resource set.
26
- * 4. **Circular dependencies are detected.** The FK graph is walked for
27
- * cycles; a cycle is an *error* when every edge in it is required (no row
28
- * could ever be inserted) and a *warning* when it is breakable through a
29
- * nullable field (e.g. an optional self-referencing `manager`).
30
- *
31
- * ```ts
32
- * // entities/airports.ts
33
- * export interface Airport {
34
- * code: string; // JSDoc tags refine: @maxLength 3, @unique
35
- * name: string;
36
- * }
37
- * export interface Terminal {
38
- * airport: Airport; // FK — inferred from the property type
39
- * label: string;
40
- * }
41
- *
42
- * // app/screen.tsx — the hook call is what roots the graph
43
- * const terminals = useEntityList<Terminal>("terminals");
44
- * // → resources: airports.terminal (root) + airports.airport (transitive)
45
- * ```
46
- *
47
- * Naming: `model` is the lower-camel interface name (a leading `I` prefix as
48
- * in generated `IFlight` interfaces is stripped, so hook usage of generated
49
- * types round-trips onto the same resource); `app` defaults to the defining
50
- * file's basename. Both are overridable with `@rastackApp` / `@rastackModel` tags.
51
- */
52
3
  export interface EntityDiagnostic {
53
4
  severity: "error" | "warning";
54
5
  message: string;
@@ -43,17 +43,20 @@ function analyzeEntities(program, fileNames) {
43
43
  const diagnostics = [];
44
44
  const sourceFiles = (0, program_1.resourceSourceFiles)(program, fileNames);
45
45
  const scanned = new Set(sourceFiles.map((sf) => path.resolve(sf.fileName)));
46
- // Every interface declared in the scanned files is a *candidate* entity.
46
+ // Every class/interface declared in the scanned files is a *candidate* entity.
47
47
  const candidates = new Set();
48
48
  for (const sf of sourceFiles) {
49
49
  ts.forEachChild(sf, function walk(node) {
50
50
  if (ts.isInterfaceDeclaration(node))
51
51
  candidates.add(node);
52
+ if (ts.isClassDeclaration(node) && node.name)
53
+ candidates.add(node);
52
54
  ts.forEachChild(node, walk);
53
55
  });
54
56
  }
55
- // 1. Roots: interfaces used as type arguments of hook calls, plus explicit
56
- // `@rastackResource` opt-ins.
57
+ // 1. Roots: declarations used as type arguments of hook calls, explicit
58
+ // `@rastackResource` opt-ins, and plain data classes (declaring one *is*
59
+ // the opt-in).
57
60
  const roots = [];
58
61
  const rooted = new Set();
59
62
  const addRoot = (decl) => {
@@ -66,7 +69,7 @@ function analyzeEntities(program, fileNames) {
66
69
  ts.forEachChild(sf, function walk(node) {
67
70
  if (ts.isCallExpression(node) && isHookCall(node)) {
68
71
  for (const typeArg of node.typeArguments ?? []) {
69
- const decl = interfaceBehindTypeNode(typeArg, checker, scanned);
72
+ const decl = entityBehindTypeNode(typeArg, checker, scanned);
70
73
  if (decl && candidates.has(decl))
71
74
  addRoot(decl);
72
75
  }
@@ -77,9 +80,11 @@ function analyzeEntities(program, fileNames) {
77
80
  for (const decl of candidates) {
78
81
  if (jsdocTags(decl).has("rastackresource"))
79
82
  addRoot(decl);
83
+ else if (ts.isClassDeclaration(decl) && isDataClass(decl))
84
+ addRoot(decl);
80
85
  }
81
- // 2. Transitive closure over FK references: an entity's interface-typed
82
- // property pulls the referenced interface in as an entity too.
86
+ // 2. Transitive closure over FK references: an entity's entity-typed
87
+ // property pulls the referenced declaration in as an entity too.
83
88
  const propsByDecl = new Map();
84
89
  const analyzed = (decl) => {
85
90
  let props = propsByDecl.get(decl);
@@ -166,36 +171,93 @@ function isHookCall(call) {
166
171
  : undefined;
167
172
  return !!name && /^use[A-Z0-9_]/.test(name);
168
173
  }
169
- /** Resolve a type node to an interface declared in one of the scanned files. */
170
- function interfaceBehindTypeNode(node, checker, scanned) {
171
- return interfaceBehindType(checker.getTypeFromTypeNode(node), scanned);
174
+ /** Resolve a type node to a class/interface declared in a scanned file. */
175
+ function entityBehindTypeNode(node, checker, scanned) {
176
+ return entityBehindType(checker.getTypeFromTypeNode(node), scanned);
172
177
  }
173
- function interfaceBehindType(type, scanned) {
178
+ function entityBehindType(type, scanned) {
174
179
  const symbol = type.aliasSymbol ?? type.getSymbol();
175
180
  for (const decl of symbol?.declarations ?? []) {
176
- if (ts.isInterfaceDeclaration(decl) &&
181
+ if ((ts.isInterfaceDeclaration(decl) ||
182
+ (ts.isClassDeclaration(decl) && decl.name)) &&
177
183
  scanned.has(path.resolve(decl.getSourceFile().fileName))) {
178
184
  return decl;
179
185
  }
180
186
  }
181
187
  return undefined;
182
188
  }
189
+ /**
190
+ * A *plain data class* — the class-authored equivalent of a `resource()` call,
191
+ * so it roots itself. It must carry no heritage clause (a subclass of
192
+ * `React.Component` or a service base is app code, not a table), not be
193
+ * abstract, contain nothing but property declarations, and have at least one
194
+ * instance property. Anything richer can still opt in via a hook or the
195
+ * `@rastackResource` tag.
196
+ */
197
+ function isDataClass(decl) {
198
+ if (decl.heritageClauses?.length)
199
+ return false;
200
+ if (ts.getCombinedModifierFlags(decl) &
201
+ (ts.ModifierFlags.Abstract | ts.ModifierFlags.Ambient)) {
202
+ return false;
203
+ }
204
+ let properties = 0;
205
+ for (const member of decl.members) {
206
+ if (!ts.isPropertyDeclaration(member))
207
+ return false; // method/ctor/accessor
208
+ if (ts.getCombinedModifierFlags(member) & ts.ModifierFlags.Static)
209
+ continue;
210
+ properties += 1;
211
+ }
212
+ return properties > 0;
213
+ }
183
214
  function analyzeProperties(decl, checker, scanned) {
184
215
  const props = [];
185
216
  for (const member of decl.members) {
186
- if (!ts.isPropertySignature(member) || !member.type)
217
+ let prop;
218
+ if (ts.isPropertySignature(member) && member.type) {
219
+ prop = member;
220
+ }
221
+ else if (ts.isPropertyDeclaration(member) &&
222
+ (member.type || member.initializer) &&
223
+ !ts.isPrivateIdentifier(member.name) &&
224
+ !(ts.getCombinedModifierFlags(member) &
225
+ (ts.ModifierFlags.Static |
226
+ ts.ModifierFlags.Private |
227
+ ts.ModifierFlags.Protected))) {
228
+ prop = member;
229
+ }
230
+ if (!prop)
187
231
  continue;
188
- const name = ts.isIdentifier(member.name) || ts.isStringLiteralLike(member.name)
189
- ? member.name.text
232
+ const name = ts.isIdentifier(prop.name) || ts.isStringLiteralLike(prop.name)
233
+ ? prop.name.text
190
234
  : undefined;
191
235
  if (!name)
192
236
  continue;
193
- props.push(analyzeProperty(name, member, checker, scanned));
237
+ props.push(analyzeProperty(name, prop, checker, scanned));
194
238
  }
195
239
  return props;
196
240
  }
197
241
  function analyzeProperty(name, member, checker, scanned) {
198
242
  let nullable = !!member.questionToken;
243
+ // A class field with no annotation infers its (widened) type from the
244
+ // initializer — `region = "EU"` is a string column defaulting to "EU".
245
+ if (!member.type) {
246
+ const type = checker.getTypeAtLocation(member);
247
+ const base = classifyType(type, scanned);
248
+ if (base === undefined) {
249
+ return { name, nullable, skip: "unsupported type — skipped" };
250
+ }
251
+ if (typeof base !== "string")
252
+ return { name, nullable, target: base };
253
+ const config = configFromTags(jsdocTags(member));
254
+ if (config.default === undefined) {
255
+ const dflt = initializerDefault(member);
256
+ if (dflt !== undefined)
257
+ config.default = dflt;
258
+ }
259
+ return scalarProp(name, base, config, nullable);
260
+ }
199
261
  // Unwrap parens and unions: strip null/undefined (→ nullable), and require a
200
262
  // single remaining arm (unions of string literals collapse to `string`).
201
263
  let typeNode = unwrapParens(member.type);
@@ -219,7 +281,7 @@ function analyzeProperty(name, member, checker, scanned) {
219
281
  // but is intentionally not an error. Arrays of scalars have no manifest shape.
220
282
  const element = arrayElement(typeNode);
221
283
  if (element) {
222
- if (interfaceBehindTypeNode(element, checker, scanned)) {
284
+ if (entityBehindTypeNode(element, checker, scanned)) {
223
285
  return { name, nullable, inverse: true };
224
286
  }
225
287
  return { name, nullable, skip: "array fields are not supported — skipped" };
@@ -230,19 +292,44 @@ function analyzeProperty(name, member, checker, scanned) {
230
292
  return { name, nullable, skip: "unsupported type — skipped" };
231
293
  }
232
294
  if (typeof base !== "string") {
233
- return { name, nullable, target: base }; // entity interface → FK
295
+ return { name, nullable, target: base }; // entity class/interface → FK
234
296
  }
235
297
  const config = {
236
298
  ...configFromTags(jsdocTags(member)),
237
299
  ...configFromType(type, checker, member),
238
300
  };
301
+ if (config.default === undefined) {
302
+ const dflt = initializerDefault(member);
303
+ if (dflt !== undefined)
304
+ config.default = dflt;
305
+ }
239
306
  return scalarProp(name, base, config, nullable);
240
307
  }
308
+ /** The literal value of a class field's initializer, used as the default. */
309
+ function initializerDefault(member) {
310
+ if (!ts.isPropertyDeclaration(member) || !member.initializer)
311
+ return undefined;
312
+ const init = member.initializer;
313
+ if (ts.isStringLiteralLike(init))
314
+ return init.text;
315
+ if (ts.isNumericLiteral(init))
316
+ return Number(init.text);
317
+ if (ts.isPrefixUnaryExpression(init) &&
318
+ init.operator === ts.SyntaxKind.MinusToken &&
319
+ ts.isNumericLiteral(init.operand)) {
320
+ return -Number(init.operand.text);
321
+ }
322
+ if (init.kind === ts.SyntaxKind.TrueKeyword)
323
+ return true;
324
+ if (init.kind === ts.SyntaxKind.FalseKeyword)
325
+ return false;
326
+ return undefined;
327
+ }
241
328
  /**
242
- * What a property type *is*: a manifest scalar, an entity interface (→ FK), or
243
- * unsupported. Intersections classify by their first classifiable constituent,
244
- * which is what lets `DbConfig` brands ride along — `string & Unique` is a
245
- * string; the brand carries configuration, not a datatype.
329
+ * What a property type *is*: a manifest scalar, an entity class/interface
330
+ * (→ FK), or unsupported. Intersections classify by their first classifiable
331
+ * constituent, which is what lets `DbConfig` brands ride along — `string &
332
+ * Unique` is a string; the brand carries configuration, not a datatype.
246
333
  */
247
334
  function classifyType(type, scanned) {
248
335
  if (type.flags & ts.TypeFlags.StringLike)
@@ -261,7 +348,7 @@ function classifyType(type, scanned) {
261
348
  }
262
349
  if (type.getProperty("__rastackDb"))
263
350
  return undefined; // a bare config brand
264
- const decl = interfaceBehindType(type, scanned);
351
+ const decl = entityBehindType(type, scanned);
265
352
  if (decl)
266
353
  return decl;
267
354
  if (type.getSymbol()?.getName() === "Date")
@@ -390,6 +477,7 @@ function arrayElement(node) {
390
477
  }
391
478
  function entityNaming(decl) {
392
479
  const tags = jsdocTags(decl);
480
+ // Class candidates are only ever collected with a name (see analyzeEntities).
393
481
  const raw = decl.name.text;
394
482
  // Generated entity interfaces are `I`-prefixed (`IFlight`); strip the prefix
395
483
  // so hook usage of generated types resolves to the same resource.
@@ -19,7 +19,7 @@ export interface CompileResult {
19
19
  * beside `app/airports/index.tsx` — instead of in one top-level `resources/`
20
20
  * folder. `.tsx` files carry no `resource(...)` definitions but are scanned
21
21
  * for the typed hook calls (`useEntityList<Airport>(…)`) that root the
22
- * interface-driven entity graph; co-located helpers are harmless.
22
+ * class/interface entity graph; co-located helpers are harmless.
23
23
  */
24
24
  export declare function resolveResourceFiles(input: string): string[];
25
25
  export interface ProjectAnalysis {
@@ -77,7 +77,7 @@ const IGNORE_DIRS = new Set([
77
77
  * beside `app/airports/index.tsx` — instead of in one top-level `resources/`
78
78
  * folder. `.tsx` files carry no `resource(...)` definitions but are scanned
79
79
  * for the typed hook calls (`useEntityList<Airport>(…)`) that root the
80
- * interface-driven entity graph; co-located helpers are harmless.
80
+ * class/interface entity graph; co-located helpers are harmless.
81
81
  */
82
82
  function resolveResourceFiles(input) {
83
83
  const stat = fs.existsSync(input) ? fs.statSync(input) : undefined;
@@ -55,7 +55,7 @@ function createProgram(fileNames) {
55
55
  esModuleInterop: true,
56
56
  noEmit: true,
57
57
  // `.tsx` app files are scanned for the typed hook calls that root the
58
- // interface-driven entity graph.
58
+ // class/interface entity graph.
59
59
  jsx: ts.JsxEmit.ReactJSX,
60
60
  baseUrl: process.cwd(),
61
61
  paths: {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Type-level column configuration for interface-driven entities.
2
+ * Type-level column configuration for class- and interface-driven entities.
3
3
  *
4
4
  * Interface entities are plain TypeScript, so their column constraints are
5
5
  * expressed *in the type system*: intersect a property type with one or more
package/dist/define/db.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /**
3
- * Type-level column configuration for interface-driven entities.
3
+ * Type-level column configuration for class- and interface-driven entities.
4
4
  *
5
5
  * Interface entities are plain TypeScript, so their column constraints are
6
6
  * expressed *in the type system*: intersect a property type with one or more
@@ -0,0 +1,18 @@
1
+ /**
2
+ * `rastack import` — load external data (CSV, Excel, PDF) and validate it
3
+ * against a resource's constraint state machine.
4
+ *
5
+ * `parseTabular` dispatches on the file extension to the format parsers, all
6
+ * of which produce the same {@link TabularData}; `validateDataset` (in
7
+ * `tabular.ts`) is the shared, pure validation core.
8
+ */
9
+ import type { TabularData } from "./tabular";
10
+ export * from "./tabular";
11
+ export { parseXlsx } from "./xlsx";
12
+ export { parsePdfTable } from "./pdf";
13
+ /** The source formats `rastack import` understands. */
14
+ export type TabularFormat = "csv" | "xlsx" | "pdf";
15
+ /** Infer the source format from a filename, or `null` if unsupported. */
16
+ export declare function formatOf(fileName: string): TabularFormat | null;
17
+ /** Parse a source file's bytes into headers + rows, by format. */
18
+ export declare function parseTabular(format: TabularFormat, data: Buffer): TabularData;
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ /**
3
+ * `rastack import` — load external data (CSV, Excel, PDF) and validate it
4
+ * against a resource's constraint state machine.
5
+ *
6
+ * `parseTabular` dispatches on the file extension to the format parsers, all
7
+ * of which produce the same {@link TabularData}; `validateDataset` (in
8
+ * `tabular.ts`) is the shared, pure validation core.
9
+ */
10
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ var desc = Object.getOwnPropertyDescriptor(m, k);
13
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
+ desc = { enumerable: true, get: function() { return m[k]; } };
15
+ }
16
+ Object.defineProperty(o, k2, desc);
17
+ }) : (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ o[k2] = m[k];
20
+ }));
21
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
22
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
23
+ };
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.parsePdfTable = exports.parseXlsx = void 0;
26
+ exports.formatOf = formatOf;
27
+ exports.parseTabular = parseTabular;
28
+ const csv_schema_1 = require("../csv-schema");
29
+ const xlsx_1 = require("./xlsx");
30
+ const pdf_1 = require("./pdf");
31
+ __exportStar(require("./tabular"), exports);
32
+ var xlsx_2 = require("./xlsx");
33
+ Object.defineProperty(exports, "parseXlsx", { enumerable: true, get: function () { return xlsx_2.parseXlsx; } });
34
+ var pdf_2 = require("./pdf");
35
+ Object.defineProperty(exports, "parsePdfTable", { enumerable: true, get: function () { return pdf_2.parsePdfTable; } });
36
+ /** Infer the source format from a filename, or `null` if unsupported. */
37
+ function formatOf(fileName) {
38
+ const ext = fileName.toLowerCase().split(".").pop();
39
+ if (ext === "csv")
40
+ return "csv";
41
+ if (ext === "xlsx" || ext === "xlsm")
42
+ return "xlsx";
43
+ if (ext === "pdf")
44
+ return "pdf";
45
+ return null;
46
+ }
47
+ /** Parse a source file's bytes into headers + rows, by format. */
48
+ function parseTabular(format, data) {
49
+ switch (format) {
50
+ case "csv":
51
+ return (0, csv_schema_1.parseCsv)(data.toString("utf-8"));
52
+ case "xlsx":
53
+ return (0, xlsx_1.parseXlsx)(data);
54
+ case "pdf":
55
+ return (0, pdf_1.parsePdfTable)(data);
56
+ }
57
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * A tiny, dependency-free PDF *table* extractor for `rastack import`.
3
+ *
4
+ * PDFs carry no table structure — only positioned text — so this is
5
+ * deliberately a best-effort extractor for the common case of a machine-
6
+ * generated tabular report: it decodes every content stream (FlateDecode via
7
+ * node's zlib, plus raw streams), replays the text-positioning operators
8
+ * (`Tm`/`Td`/`TD`/`T*`) to give each shown string (`Tj`/`TJ`/`'`) an (x, y),
9
+ * clusters strings that share a baseline into rows, and orders each row's
10
+ * cells left-to-right. The first row becomes the header.
11
+ *
12
+ * Anything a text-line model can't represent — merged cells, wrapped cell
13
+ * text, scanned images — won't survive; for those, export the source data to
14
+ * CSV/XLSX instead. Every extracted value still goes through the constraint
15
+ * state machine, so a misread cell is *rejected with a located error*, never
16
+ * silently imported.
17
+ */
18
+ import type { TabularData } from "./tabular";
19
+ /**
20
+ * Parse a PDF buffer into headers + rows: text chunks sharing a baseline form
21
+ * a row (top of the page first), ordered left-to-right; the first row is the
22
+ * header. Rows with a different cell count than the header are padded or
23
+ * truncated — the state machine will flag what doesn't validate.
24
+ */
25
+ export declare function parsePdfTable(buf: Buffer): TabularData;