@tabnas/transduce 0.1.1 → 0.2.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.
Files changed (83) hide show
  1. package/README.md +18 -0
  2. package/dist/guard.d.ts +1 -3
  3. package/dist/guard.js +8 -9
  4. package/dist/guard.js.map +1 -1
  5. package/dist/lines.d.ts +1 -2
  6. package/dist/lines.js +46 -89
  7. package/dist/lines.js.map +1 -1
  8. package/dist/matcher.d.ts +1 -3
  9. package/dist/matcher.js +12 -13
  10. package/dist/matcher.js.map +1 -1
  11. package/dist/parser-source.d.ts +1 -2
  12. package/dist/parser-source.js +11 -13
  13. package/dist/parser-source.js.map +1 -1
  14. package/dist/route.d.ts +1 -32
  15. package/dist/route.js +8 -32
  16. package/dist/route.js.map +1 -1
  17. package/dist/routers.d.ts +2 -0
  18. package/dist/routers.js +24 -0
  19. package/dist/routers.js.map +1 -0
  20. package/dist/rule-events.d.ts +1 -4
  21. package/dist/rule-events.js +40 -44
  22. package/dist/rule-events.js.map +1 -1
  23. package/dist/scan.d.ts +1 -10
  24. package/dist/scan.js +4 -17
  25. package/dist/scan.js.map +1 -1
  26. package/dist/source.d.ts +1 -4
  27. package/dist/source.js +20 -22
  28. package/dist/source.js.map +1 -1
  29. package/dist/table-from-json.d.ts +1 -5
  30. package/dist/table-from-json.js +25 -29
  31. package/dist/table-from-json.js.map +1 -1
  32. package/dist/transduce.d.ts +23 -19
  33. package/dist/transduce.js +66 -56
  34. package/dist/transduce.js.map +1 -1
  35. package/dist/tsconfig.tsbuildinfo +1 -1
  36. package/package.json +12 -4
  37. package/src/guard.ts +10 -4
  38. package/src/lines.ts +45 -6
  39. package/src/matcher.ts +1 -6
  40. package/src/parser-source.ts +2 -4
  41. package/src/route.ts +18 -70
  42. package/src/routers.ts +64 -0
  43. package/src/rule-events.ts +17 -7
  44. package/src/scan.ts +1 -19
  45. package/src/source.ts +12 -6
  46. package/src/table-from-json.ts +20 -9
  47. package/src/transduce.ts +36 -19
  48. package/dist/datum.d.ts +0 -64
  49. package/dist/datum.js +0 -391
  50. package/dist/datum.js.map +0 -1
  51. package/dist/error.d.ts +0 -43
  52. package/dist/error.js +0 -169
  53. package/dist/error.js.map +0 -1
  54. package/dist/event.d.ts +0 -56
  55. package/dist/event.js +0 -101
  56. package/dist/event.js.map +0 -1
  57. package/dist/json.d.ts +0 -4
  58. package/dist/json.js +0 -92
  59. package/dist/json.js.map +0 -1
  60. package/dist/limits.d.ts +0 -36
  61. package/dist/limits.js +0 -133
  62. package/dist/limits.js.map +0 -1
  63. package/dist/selector.d.ts +0 -40
  64. package/dist/selector.js +0 -154
  65. package/dist/selector.js.map +0 -1
  66. package/dist/sink.d.ts +0 -29
  67. package/dist/sink.js +0 -184
  68. package/dist/sink.js.map +0 -1
  69. package/dist/table.d.ts +0 -81
  70. package/dist/table.js +0 -141
  71. package/dist/table.js.map +0 -1
  72. package/dist/value.d.ts +0 -16
  73. package/dist/value.js +0 -96
  74. package/dist/value.js.map +0 -1
  75. package/src/datum.ts +0 -407
  76. package/src/error.ts +0 -198
  77. package/src/event.ts +0 -134
  78. package/src/json.ts +0 -82
  79. package/src/limits.ts +0 -165
  80. package/src/selector.ts +0 -191
  81. package/src/sink.ts +0 -199
  82. package/src/table.ts +0 -213
  83. package/src/value.ts +0 -93
@@ -30,13 +30,11 @@
30
30
  // event the adapter could turn into a value (YAML's empty document is
31
31
  // `null`) has its value walked instead.
32
32
 
33
+ import { AbortFlag, Ev, Fail, Flow, Limits, Metrics, Sink } from '@tabnas/alchemy/shared'
34
+
33
35
  import { isIncremental } from './capability'
34
- import { Fail } from './error'
35
- import { Ev } from './event'
36
36
  import { Guarded } from './guard'
37
- import { AbortFlag, Limits, Metrics } from './limits'
38
37
  import { Adapter, notStreamable, prepare } from './rule-events'
39
- import { Flow, Sink } from './sink'
40
38
  import { Prune, Source, SourceMode, engineFailure, walkValue } from './source'
41
39
 
42
40
  // A tabnas parser applied to one text, as a source.
package/src/route.ts CHANGED
@@ -15,76 +15,24 @@
15
15
  // stream the check could not foresee still fails rather than mixing two
16
16
  // values.
17
17
 
18
- import { Datum, DatumBuilder, Duplicates } from './datum'
19
- import { Fail } from './error'
20
- import { JsonEvent } from './event'
21
- import { Limits, Metrics } from './limits'
22
- import { CaptureId, Matcher } from './matcher'
23
- import { Path, Selector } from './selector'
24
- import { Flow, Sink } from './sink'
25
-
26
- // What a capture keeps of its match: build the value and deliver it whole
27
- // (`materialize`), or deliver only that the value occurred, and where,
28
- // when it ends (`observe`).
29
- export type CaptureMode = 'materialize' | 'observe'
30
-
31
- // The byte budget one materialized capture may not exceed, named after the
32
- // `Limits` field the failure reports.
33
- export type Budget = { bytes: number; name: string }
34
-
35
- // One capture: a tag for the consumer, the selector to match, the mode,
36
- // and for a `materialize` capture an optional budget (else the router's
37
- // `max_capture_bytes`).
38
- export class CaptureSpec {
39
- readonly tag: string
40
- readonly selector: Selector
41
- readonly mode: CaptureMode
42
- budget: Budget | null = null
43
-
44
- constructor(tag: string, selector: Selector, mode: CaptureMode) {
45
- this.tag = tag
46
- this.selector = selector
47
- this.mode = mode
48
- }
49
-
50
- static materialize(tag: string, selector: Selector): CaptureSpec {
51
- return new CaptureSpec(tag, selector, 'materialize')
52
- }
53
-
54
- static observe(tag: string, selector: Selector): CaptureSpec {
55
- return new CaptureSpec(tag, selector, 'observe')
56
- }
57
-
58
- // This spec with its own budget, whose failure names `name`.
59
- withBudget(bytes: number, name: string): CaptureSpec {
60
- this.budget = { bytes, name }
61
- return this
62
- }
63
- }
64
-
65
- // One completed match: the spec's position in the router's list, its tag,
66
- // the concrete path, and the value for a `materialize` capture (`null`
67
- // for `observe`).
68
- export type Selected = {
69
- id: CaptureId
70
- tag: string
71
- path: Path
72
- value: Datum | null
73
- }
74
-
75
- // The consumer of a router's matches.
76
- export interface RouteSink {
77
- // A capture's value is beginning. Nothing has been retained for it yet,
78
- // so a consumer that knows the value is out of order can refuse it here
79
- // at no cost (throw a `Fail`); the router adds the path to a failure
80
- // that has none.
81
- began?(id: CaptureId, tag: string): void
82
-
83
- selected(selected: Selected): Flow
84
-
85
- // The document ended, validated. Exactly once, after the last match.
86
- end(): Flow
87
- }
18
+ import {
19
+ CaptureId,
20
+ CaptureSpec,
21
+ Datum,
22
+ DatumBuilder,
23
+ Duplicates,
24
+ Fail,
25
+ Flow,
26
+ JsonEvent,
27
+ Limits,
28
+ Metrics,
29
+ Path,
30
+ RouteSink,
31
+ Selected,
32
+ Sink,
33
+ } from '@tabnas/alchemy/shared'
34
+
35
+ import { Matcher } from './matcher'
88
36
 
89
37
  // A route sink that records every match.
90
38
  export class SelectedRecorder implements RouteSink {
package/src/routers.ts ADDED
@@ -0,0 +1,64 @@
1
+ /* Copyright (c) 2026 tabnas, MIT License */
2
+
3
+ // This package's stages as alchemy's `Routers`: what an alchemy program's
4
+ // runtime builds its routes, tables, scans and guards from. Alchemy
5
+ // declares the interface (`@tabnas/alchemy/shared`) and imports no
6
+ // transducer; a host passes `routers` to its `compile`, with render's
7
+ // `renderers`: `compile(src, file, { routers, renderers })`.
8
+
9
+ import {
10
+ AbortFlag,
11
+ CaptureSpec,
12
+ Duplicates,
13
+ Flow,
14
+ Limits,
15
+ Metrics,
16
+ RouteSink,
17
+ Routers,
18
+ Scan,
19
+ Sink,
20
+ TableBinding,
21
+ TableSink,
22
+ Transition,
23
+ } from '@tabnas/alchemy/shared'
24
+
25
+ import { Guarded } from './guard'
26
+ import { Router } from './route'
27
+ import { ScanEmit } from './scan'
28
+ import { TableFromJson } from './table-from-json'
29
+
30
+ // Each method constructs the stage it names with the same parameters.
31
+ export const routers: Routers = Object.freeze({
32
+ router(
33
+ specs: readonly CaptureSpec[],
34
+ limits: Limits,
35
+ duplicates: Duplicates,
36
+ metrics: Metrics,
37
+ downstream: RouteSink,
38
+ ): Sink {
39
+ return new Router(specs, limits, duplicates, metrics, downstream)
40
+ },
41
+
42
+ tableFromJson(
43
+ binding: TableBinding,
44
+ limits: Limits,
45
+ duplicates: Duplicates,
46
+ metrics: Metrics,
47
+ sink: TableSink,
48
+ ): Sink {
49
+ return new TableFromJson(binding, limits, duplicates, metrics, sink)
50
+ },
51
+
52
+ scanEmit<S, I, O>(
53
+ initial: S,
54
+ step: (state: S, item: I) => Transition<S, O>,
55
+ finish: (state: S) => O[],
56
+ out: (output: O) => Flow,
57
+ ): Scan<I> {
58
+ return new ScanEmit<S, I, O>(initial, step, finish, out)
59
+ },
60
+
61
+ guarded(inner: Sink, limits: Limits, abort: AbortFlag, metrics: Metrics): Sink {
62
+ return new Guarded(inner, limits, abort, metrics)
63
+ },
64
+ })
@@ -50,16 +50,26 @@
50
50
  // the engine returns, which is why only the incremental source, which
51
51
  // discards that value, ever asks for it.
52
52
 
53
- import { Fail } from './error'
54
- import { Ev, JsonEvent } from './event'
53
+ import {
54
+ AbortFlag,
55
+ Ev,
56
+ Fail,
57
+ Flow,
58
+ JsonEvent,
59
+ Limits,
60
+ Metrics,
61
+ Selector,
62
+ Sink,
63
+ engineKeys,
64
+ engineScalar,
65
+ isEngineContainer,
66
+ isEngineMap,
67
+ isJsonNumber,
68
+ } from '@tabnas/alchemy/shared'
69
+
55
70
  import { Guarded } from './guard'
56
- import { isJsonNumber } from './json'
57
- import { AbortFlag, Limits, Metrics } from './limits'
58
71
  import { Matcher } from './matcher'
59
- import { Selector } from './selector'
60
- import { Flow, Sink } from './sink'
61
72
  import { Prune, walkValue } from './source'
62
- import { engineKeys, engineScalar, isEngineContainer, isEngineMap } from './value'
63
73
 
64
74
  // One open container.
65
75
  type Frame = {
package/src/scan.ts CHANGED
@@ -8,25 +8,7 @@
8
8
  // the functions own nothing. This is what the DSL's `scan-emit` lowers to,
9
9
  // and the table transducer is one instance of it.
10
10
 
11
- import { Fail } from './error'
12
- import { Flow } from './sink'
13
-
14
- // The result of one step: the next state and what to emit for it.
15
- export type Transition<S, O> = { state: S; outputs: O[] }
16
-
17
- export const Transition = Object.freeze({
18
- of<S, O>(state: S, outputs: O[]): Transition<S, O> {
19
- return { state, outputs }
20
- },
21
- // A step that emits nothing.
22
- stay<S, O>(state: S): Transition<S, O> {
23
- return { state, outputs: [] }
24
- },
25
- // A step that emits one item.
26
- emit<S, O>(state: S, output: O): Transition<S, O> {
27
- return { state, outputs: [output] }
28
- },
29
- })
11
+ import { Fail, Flow, Transition } from '@tabnas/alchemy/shared'
30
12
 
31
13
  // The operator. Feed items with `item`, then call `finish` exactly once
32
14
  // when the input completed successfully; never call it after a failure or
package/src/source.ts CHANGED
@@ -14,12 +14,18 @@
14
14
  // Every source emits through `Guarded`, which enforces the source limits,
15
15
  // polls the abort flag and counts the source metrics.
16
16
 
17
- import { Fail, engineCode, engineDetail } from './error'
18
- import { Ev } from './event'
19
- import { AbortFlag } from './limits'
20
- import { Selector } from './selector'
21
- import { Flow, Sink } from './sink'
22
- import { engineKeys, engineScalar } from './value'
17
+ import {
18
+ AbortFlag,
19
+ Ev,
20
+ Fail,
21
+ Flow,
22
+ Selector,
23
+ Sink,
24
+ engineCode,
25
+ engineDetail,
26
+ engineKeys,
27
+ engineScalar,
28
+ } from '@tabnas/alchemy/shared'
23
29
 
24
30
  // Which arrays the incremental source empties as it streams them: none
25
31
  // (`never`), the array whose elements the selector names (`under`: a
@@ -15,23 +15,34 @@
15
15
  // of members inside a row never matters, and a number keeps the lexeme the
16
16
  // source events carried.
17
17
 
18
- import { Datum, Duplicates, getPath } from './datum'
19
- import { Fail } from './error'
20
- import { JsonEvent } from './event'
21
- import { Limits, Metrics, NODE_BYTES, utf8Bytes } from './limits'
22
- import { CaptureId } from './matcher'
23
- import { CaptureSpec, RouteSink, Router, Selected } from './route'
24
- import { Path, Selector } from './selector'
25
- import { Flow, Sink } from './sink'
26
18
  import {
27
19
  BoundColumn,
20
+ CaptureId,
21
+ CaptureSpec,
28
22
  Cell,
29
23
  ColumnMapper,
24
+ Datum,
25
+ Duplicates,
26
+ Fail,
27
+ Flow,
28
+ JsonEvent,
29
+ Limits,
30
+ Metrics,
31
+ NODE_BYTES,
32
+ Path,
30
33
  PublicColumn,
34
+ RouteSink,
35
+ Selected,
36
+ Selector,
37
+ Sink,
31
38
  TableBinding,
32
39
  TableSink,
33
40
  boundColumn,
34
- } from './table'
41
+ getPath,
42
+ utf8Bytes,
43
+ } from '@tabnas/alchemy/shared'
44
+
45
+ import { Router } from './route'
35
46
 
36
47
  // Where the columns are, once known.
37
48
  type Columns =
package/src/transduce.ts CHANGED
@@ -20,9 +20,15 @@
20
20
  // Everything is synchronous and push-based: the parser calls the first
21
21
  // sink from inside its own callback, and a slow writer at the end of the
22
22
  // chain slows the parse at the start. That is the backpressure.
23
+ //
24
+ // The protocols, `Fail` and its codes, `Limits`, `Metrics`, `Selector`,
25
+ // `Datum` and the captures' types are alchemy's shared types
26
+ // (`@tabnas/alchemy/shared`), which this package imports and re-exports
27
+ // under the names it always exported them by. `routers` is this package's
28
+ // stages as alchemy's `Routers`, for a host to pass to alchemy's `compile`.
23
29
 
24
30
  // This package's version, as package.json declares it.
25
- export const VERSION = '0.1.1'
31
+ export const VERSION = '0.2.0'
26
32
 
27
33
  export {
28
34
  Datum,
@@ -35,23 +41,33 @@ export {
35
41
  toJSON,
36
42
  toText,
37
43
  walkDatum,
38
- } from './datum'
39
- export type { Duplicates } from './datum'
40
- export { Code, Fail, isFail } from './error'
41
- export type { Limit } from './error'
42
- export { Ev, eventEquals, eventText, isEnd, isScalar, isStart } from './event'
43
- export type { JsonEvent, JsonEventType } from './event'
44
- export { isJsonNumber, jsonNumber, jsonString, numberText } from './json'
45
- export { AbortFlag, LIMIT_NAMES, Limits, Metrics, NODE_BYTES, utf8Bytes } from './limits'
44
+ } from '@tabnas/alchemy/shared'
45
+ export type { Duplicates } from '@tabnas/alchemy/shared'
46
+ export { Code, Fail, isFail } from '@tabnas/alchemy/shared'
47
+ export type { Limit } from '@tabnas/alchemy/shared'
48
+ export { Ev, eventEquals, eventText, isEnd, isScalar, isStart } from '@tabnas/alchemy/shared'
49
+ export type { JsonEvent, JsonEventType } from '@tabnas/alchemy/shared'
50
+ export { isJsonNumber, jsonNumber, jsonString, numberText } from '@tabnas/alchemy/shared'
51
+ export {
52
+ AbortFlag,
53
+ LIMIT_NAMES,
54
+ Limits,
55
+ Metrics,
56
+ NODE_BYTES,
57
+ utf8Bytes,
58
+ } from '@tabnas/alchemy/shared'
46
59
  export { Matcher } from './matcher'
47
- export type { CaptureId, Hit, HitKind } from './matcher'
48
- export { CaptureSpec, FnRoute, Router, SelectedRecorder } from './route'
49
- export type { Budget, CaptureMode, RouteSink, Selected } from './route'
50
- export { ScanEmit, Transition } from './scan'
51
- export { Path, Selector, keyText, pathText } from './selector'
52
- export type { Segment, Step } from './selector'
53
- export { CountSink, EventRecorder, FnSink, TreeContract, replay } from './sink'
54
- export type { Flow, Sink } from './sink'
60
+ export type { Hit, HitKind } from './matcher'
61
+ export type { CaptureId } from '@tabnas/alchemy/shared'
62
+ export { FnRoute, Router, SelectedRecorder } from './route'
63
+ export { CaptureSpec } from '@tabnas/alchemy/shared'
64
+ export type { Budget, CaptureMode, RouteSink, Selected } from '@tabnas/alchemy/shared'
65
+ export { ScanEmit } from './scan'
66
+ export { Transition } from '@tabnas/alchemy/shared'
67
+ export { Path, Selector, keyText, pathText } from '@tabnas/alchemy/shared'
68
+ export type { Segment, Step } from '@tabnas/alchemy/shared'
69
+ export { CountSink, EventRecorder, FnSink, TreeContract, replay } from '@tabnas/alchemy/shared'
70
+ export type { Flow, Sink } from '@tabnas/alchemy/shared'
55
71
  export { Prune, SourceMode, ValueSource, engineFailure, walkValue } from './source'
56
72
  export type { Source } from './source'
57
73
  export { Guarded } from './guard'
@@ -59,7 +75,7 @@ export { ParserSource } from './parser-source'
59
75
  export { DEFAULT_CHUNK_BYTES, LineFormat, LinesSource } from './lines'
60
76
  export type { LinesChunk, LinesInput, LinesWriter } from './lines'
61
77
  export { INCREMENTAL, capability, isIncremental } from './capability'
62
- export { Cell, Schema, Table, boundColumn, columnFromMeta } from './table'
78
+ export { Cell, Schema, Table, boundColumn, columnFromMeta } from '@tabnas/alchemy/shared'
63
79
  export type {
64
80
  BoundColumn,
65
81
  ColumnMapper,
@@ -68,5 +84,6 @@ export type {
68
84
  TableBinding,
69
85
  TableEvent,
70
86
  TableSink,
71
- } from './table'
87
+ } from '@tabnas/alchemy/shared'
72
88
  export { TableFromJson } from './table-from-json'
89
+ export { routers } from './routers'
package/dist/datum.d.ts DELETED
@@ -1,64 +0,0 @@
1
- import { JsonEvent } from './event';
2
- import { Segment } from './selector';
3
- import { Flow, Sink } from './sink';
4
- export type Datum = {
5
- readonly type: 'null';
6
- } | {
7
- readonly type: 'bool';
8
- readonly value: boolean;
9
- } | {
10
- readonly type: 'number';
11
- readonly value: number;
12
- readonly lexeme: string | null;
13
- } | {
14
- readonly type: 'string';
15
- readonly value: string;
16
- } | {
17
- readonly type: 'array';
18
- readonly items: Datum[];
19
- } | {
20
- readonly type: 'object';
21
- readonly members: Map<string, Datum>;
22
- };
23
- export declare const Datum: Readonly<{
24
- null: {
25
- readonly type: 'null';
26
- };
27
- bool(value: boolean): Datum;
28
- number(value: number, lexeme?: string | null): Datum;
29
- string(value: string): Datum;
30
- array(items?: Datum[]): Datum;
31
- object(members?: Map<string, Datum> | Iterable<[string, Datum]>): Datum;
32
- byteSize: typeof byteSize;
33
- getPath: typeof getPath;
34
- takePath: typeof takePath;
35
- isContainer(d: Datum): boolean;
36
- fromTabnas: typeof fromTabnas;
37
- toJSON: typeof toJSON;
38
- fromJSON: typeof fromJSON;
39
- toText: typeof toText;
40
- }>;
41
- export declare function byteSize(d: Datum): number;
42
- export declare function getPath(d: Datum, path: readonly Segment[]): Datum | undefined;
43
- export declare function takePath(d: Datum, path: readonly Segment[]): Datum | undefined;
44
- export declare function fromTabnas(value: unknown): Datum;
45
- export declare function toJSON(d: Datum): unknown;
46
- export declare function fromJSON(value: unknown): Datum;
47
- export declare function toText(d: Datum): string;
48
- export declare function walkDatum(d: Datum, sink: Sink): Flow;
49
- export type Duplicates = 'reject' | 'last_wins' | 'first_wins';
50
- export declare class DatumBuilder {
51
- private stack;
52
- private done;
53
- private held;
54
- private readonly limit;
55
- private readonly limitName;
56
- private readonly duplicates;
57
- constructor(limit: number, limitName: string, duplicates: Duplicates);
58
- bytes(): number;
59
- finished(): boolean;
60
- take(): Datum | undefined;
61
- private charge;
62
- private place;
63
- event(ev: JsonEvent): void;
64
- }