@jarenjs/linq 0.67.0 → 0.72.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -2
- package/docs/AI-PEN.md +98 -0
- package/docs/APP-PEN.md +3 -3
- package/docs/CHARTS-PEN.md +94 -0
- package/docs/CONTRACT-PEN.md +4 -4
- package/docs/DB-CLIENT.md +3 -3
- package/docs/FORMS-PEN.md +2 -2
- package/docs/JTLT-PEN.md +83 -0
- package/docs/LINQ-FORMAT.md +171 -43
- package/docs/MESSAGES-PEN.md +105 -0
- package/docs/MIGRATION-PEN.md +1 -1
- package/docs/MODEL-PEN.md +3 -3
- package/docs/PROJECT-PEN.md +75 -0
- package/docs/QUERY-PEN.md +6 -5
- package/docs/SCHEMA-PEN.md +70 -83
- package/package.json +27 -7
- package/src/ai/index.js +76 -0
- package/src/authored.js +38 -0
- package/src/charts/index.js +131 -0
- package/src/charts/vocabulary.js +119 -0
- package/src/expression.js +1 -1
- package/src/federate.js +6 -17
- package/src/jtlt/index.js +97 -0
- package/src/messages/index.js +96 -0
- package/src/messages/vocabulary.js +259 -0
- package/src/project/index.js +54 -0
- package/src/schema/builders.js +100 -9
- package/src/schema/emit.js +29 -13
- package/types/ai.d.ts +56 -0
- package/types/charts.d.ts +481 -0
- package/types/jtlt.d.ts +45 -0
- package/types/message-vocabulary.d.ts +96 -0
- package/types/messages.d.ts +32 -0
- package/types/project.d.ts +45 -0
- package/types/schema.d.ts +54 -0
package/docs/SCHEMA-PEN.md
CHANGED
|
@@ -31,11 +31,9 @@ import * as s from '@jarenjs/linq/schema';
|
|
|
31
31
|
builds JSON Schema 2020-12 documents: the structural keywords, the
|
|
32
32
|
constraints and the annotations, each with a method of its own; `$query`
|
|
33
33
|
captured through the chain's proxy; `$defs`/`$ref` recursion; the
|
|
34
|
-
normalizer's per-field predicates.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
[§6.2](#62-the-absences-twenty-five-keywords-with-no-method) lists them
|
|
38
|
-
by family. The type reading is
|
|
34
|
+
normalizer's per-field predicates. Every owned keyword has a dedicated spelling. Annotation-dependent,
|
|
35
|
+
negating and dynamic-reference methods preserve the existing phantom;
|
|
36
|
+
[§6.2](#62-annotation-and-legacy-boundaries) explains those boundaries. The type reading is
|
|
39
37
|
emit's (EMIT-FORMAT §5–§7), because the agreement pins them equal; a
|
|
40
38
|
constraint (`min`, `pattern`, `format`) never changes a type — the honest
|
|
41
39
|
widening emit documents — with one addition: a string with
|
|
@@ -297,6 +295,42 @@ Three rules the tables imply, spelled out:
|
|
|
297
295
|
so a promise that resolves one calls it with a function and is refused
|
|
298
296
|
by name (`JL0101`); keep builders out of async return positions.
|
|
299
297
|
|
|
298
|
+
### 2.11 Content, containment and resource identity
|
|
299
|
+
|
|
300
|
+
Each method preserves the current phantom and replaces a prior value in place.
|
|
301
|
+
The content methods describe encoded strings; `contentSchema` is annotation
|
|
302
|
+
information, not a promise that the validator checks the decoded value.
|
|
303
|
+
`contentValidation: true` enables encoding/media checks. Format bounds require
|
|
304
|
+
a format compiler that supports ordering. Schema-valued methods take builders
|
|
305
|
+
and share the root's definition-hoisting context.
|
|
306
|
+
|
|
307
|
+
| Method | Emits | `Infer` / `Input` | Status |
|
|
308
|
+
|---|---|---|---|
|
|
309
|
+
| `.id(uri)` | `$id` | `this` | native; string required |
|
|
310
|
+
| array `.minContains(n)`, `.maxContains(n)` | `minContains`, `maxContains` | `this` | native; non-negative integers |
|
|
311
|
+
| string `.contentEncoding(text)` | `contentEncoding` | `this` | native |
|
|
312
|
+
| string `.contentMediaType(text)` | `contentMediaType` | `this` | native |
|
|
313
|
+
| string `.contentSchema(builder)` | `contentSchema` | `this` | native; named definitions hoist |
|
|
314
|
+
| string `.formatMinimum(text)`, `.formatMaximum(text)` | `formatMinimum`, `formatMaximum` | `this` | native |
|
|
315
|
+
| string `.formatExclusiveMinimum(text)`, `.formatExclusiveMaximum(text)` | `formatExclusiveMinimum`, `formatExclusiveMaximum` | `this` | native |
|
|
316
|
+
|
|
317
|
+
### 2.12 Applicators, references and legacy keywords
|
|
318
|
+
|
|
319
|
+
All methods below are immutable; repeated calls replace the previous value.
|
|
320
|
+
Raw schemas enter through `from()`. Use `never()` for a false subschema.
|
|
321
|
+
|
|
322
|
+
| Method | Emits | `Infer` / `Input` | Status |
|
|
323
|
+
|---|---|---|---|
|
|
324
|
+
| `.not(builder)` | `not` | `this` | native |
|
|
325
|
+
| `.unevaluatedProperties(builder)`, `.unevaluatedItems(builder)` | `unevaluatedProperties`, `unevaluatedItems` | `this` | native |
|
|
326
|
+
| `.dependentSchemas(map)`, `.dependencies(map)` | `dependentSchemas`, `dependencies` | `this` | native; legacy dependencies also accept arrays of distinct member names |
|
|
327
|
+
| `.anchor(name)`, `.vocabulary(map)` | `$anchor`, `$vocabulary` | `this` | native |
|
|
328
|
+
| `.dynamicRef(uri)`, `.dynamicAnchor(name)` | `$dynamicRef`, `$dynamicAnchor` | `this` | native; no inferred reference identity |
|
|
329
|
+
| `.recursiveRef(uri)`, `.recursiveAnchor(boolean)` | `$recursiveRef`, `$recursiveAnchor` | `this` | native |
|
|
330
|
+
| `.definitions(map)`, `.additionalItems(builder)` | `definitions`, `additionalItems` | `this` | native; legacy vocabulary |
|
|
331
|
+
| `.dollarData(pointer)`, `.data(map)` | `$data`, `data` | `this` | native |
|
|
332
|
+
| `.legacyNullable(boolean)` | `nullable` | `unknown` | native; distinct from `.nullable()` |
|
|
333
|
+
|
|
300
334
|
## 3. Worked examples
|
|
301
335
|
|
|
302
336
|
One account service, built up. Every `js` fence below exports exactly one
|
|
@@ -830,25 +864,13 @@ JSON are still two, and still a collision.
|
|
|
830
864
|
|
|
831
865
|
| The spelling that trips it | The message | The spelling that works |
|
|
832
866
|
|---|---|---|
|
|
833
|
-
| `s.string().meta({ type: 'x' })` | `meta() cannot write 'type' — the pen owns that keyword; spell it through the builder method that emits it, keyword('type', value) where no method does, or wrap a hand-written schema with from()` | the method that emits it — or, for a
|
|
867
|
+
| `s.string().meta({ type: 'x' })` | `meta() cannot write 'type' — the pen owns that keyword; spell it through the builder method that emits it, keyword('type', value) where no method does, or wrap a hand-written schema with from()` | the method that emits it — or, for a raw value, `.keyword(…)` or `s.from({ … })` |
|
|
834
868
|
| `.check((o, x) => x.foo.eq(1))` | `a check() rule cannot bind 'foo' — its query evaluates with exactly 2 externals, 'root' and 'path'; anything else has nothing to bind to` | `x.root` and `x.path`, and nothing else |
|
|
835
869
|
|
|
836
|
-
The owned set is
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
`x-coerce`/`x-trim`. `meta()` is for everything else: a 2020-12
|
|
841
|
-
annotation the pen has no method for (`deprecated`, `readOnly`,
|
|
842
|
-
`writeOnly`), and any vendor extension. The type declaration carries the
|
|
843
|
-
same set as `OwnedKeyword`, so a forbidden key does not compile either
|
|
844
|
-
(§5). Twenty-five of the sixty-nine have no method to be spelled through,
|
|
845
|
-
which is why the message names `keyword()` as well;
|
|
846
|
-
[§6.2](#62-the-absences-twenty-five-keywords-with-no-method) lists them
|
|
847
|
-
and the two doors that stay open for them.
|
|
848
|
-
|
|
849
|
-
`check()`'s two externals are the two the validator binds on every
|
|
850
|
-
`$query` evaluation. The refusal is raised at BUILD time, earlier than
|
|
851
|
-
the validator's own compile error and with the same meaning.
|
|
870
|
+
The owned set is the vocabulary written by the dedicated methods. `meta()`
|
|
871
|
+
refuses those names so an annotation never changes the schema's assertions.
|
|
872
|
+
`keyword()` remains available for raw values and `from()` for whole schemas;
|
|
873
|
+
[§6.2](#62-annotation-and-legacy-boundaries) describes their type boundaries.
|
|
852
874
|
|
|
853
875
|
## 5. The types
|
|
854
876
|
|
|
@@ -1081,70 +1103,35 @@ having.
|
|
|
1081
1103
|
`packages/json/docs/QUERY-FORMAT.md` §8; a rule outside it is
|
|
1082
1104
|
application code, run beside validation rather than inside it.
|
|
1083
1105
|
|
|
1084
|
-
### 6.2
|
|
1106
|
+
### 6.2 Annotation and legacy boundaries
|
|
1085
1107
|
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
what a document asserts" and are kept out of `meta()` for that reason
|
|
1090
|
-
alone (the comment above the list says so). The second kind has no
|
|
1091
|
-
spelling of its own on this surface:
|
|
1108
|
+
Every owned keyword has a dedicated route. The executable census in
|
|
1109
|
+
`test/linq/schema-keyword-corpus.js` checks the actual emitted spelling;
|
|
1110
|
+
a route that returns a different keyword cannot satisfy it.
|
|
1092
1111
|
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
| content | `contentEncoding`, `contentMediaType`, `contentSchema` |
|
|
1100
|
-
| format bounds | `formatMinimum`, `formatMaximum`, `formatExclusiveMinimum`, `formatExclusiveMaximum` |
|
|
1101
|
-
| identification | `$id`, `$anchor`, `$vocabulary` |
|
|
1102
|
-
| dynamic references | `$dynamicRef`, `$dynamicAnchor`, `$recursiveRef`, `$recursiveAnchor` |
|
|
1103
|
-
| legacy and extension spellings | `definitions`, `additionalItems`, `$data`, `data` |
|
|
1104
|
-
|
|
1105
|
-
`@jarenjs/validate` compiles every one of them — `not` in `combine.js`,
|
|
1106
|
-
the unevaluated pair in `unevaluated.js`, `dependentSchemas` in
|
|
1107
|
-
`object.js`, the `contains` bounds in `array.js`, the content family in
|
|
1108
|
-
`content.js`, the dynamic references in `dynamic-ref.js`, `$data` in
|
|
1109
|
-
`dollar-data.js` — so the gap is this pen's surface, not the format and
|
|
1110
|
-
not the engine. It is tracked in
|
|
1111
|
-
[docs/ROADMAP.md](../../../docs/ROADMAP.md) under the data pair, and
|
|
1112
|
-
`test/linq/schema-pen.test.js` holds this table equal to the pen's own
|
|
1113
|
-
owned set, so a method that lands for one of them fails the suite until
|
|
1114
|
-
its row goes.
|
|
1115
|
-
|
|
1116
|
-
Two doors are open in the meantime, and both are rows of
|
|
1117
|
-
[§2.10](#210-the-document-and-the-builder-itself):
|
|
1112
|
+
`not`, unevaluated constraints, dependencies and dynamic references keep the
|
|
1113
|
+
existing phantom. They may reject more values, but TypeScript does not infer
|
|
1114
|
+
annotation tracking, negation or dynamic scope. `legacyNullable(boolean)`
|
|
1115
|
+
writes the OpenAPI `nullable` spelling exactly and deliberately broadens the
|
|
1116
|
+
phantom to `unknown`; `.nullable()` retains its inferred union and standard
|
|
1117
|
+
JSON Schema emission. These are separate operations.
|
|
1118
1118
|
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1119
|
+
`contentSchema` remains annotation information in the current validator.
|
|
1120
|
+
Encoding and media checks require `contentValidation: true`; ordered format
|
|
1121
|
+
bounds require a registered format compiler with comparison support.
|
|
1122
1122
|
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1123
|
+
`definitions` and `additionalItems` author draft-07 vocabulary. Declare that
|
|
1124
|
+
dialect in a raw root when using them; `document({ draft: '2020-12' })` does
|
|
1125
|
+
not translate a legacy tuple. Schema-valued methods accept builders, including
|
|
1126
|
+
`from(json)` and `never()` for an exact raw or boolean subschema. They share
|
|
1127
|
+
named definition identity with every other branch, including recursive ones.
|
|
1128
|
+
Normalizer annotations in branches the normalizer never visits are refused.
|
|
1126
1129
|
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
shortfall: a keyword with no method has no phantom to read either.
|
|
1133
|
-
|
|
1134
|
-
`definitions` is the draft-07 spelling of `$defs`, and `named()` writes
|
|
1135
|
-
`$defs`: the pen emits one definitions block under one name, so the older
|
|
1136
|
-
keyword is owned to keep a document from carrying both and reached, like
|
|
1137
|
-
the rest of the table, through `keyword()` or `from()`.
|
|
1138
|
-
|
|
1139
|
-
**`meta()` is not a third door.** Every name in the table is owned, so
|
|
1140
|
-
`meta()` refuses it with `JL0104`
|
|
1141
|
-
([§4.4](#44-jl0104--the-keyword-and-the-external)) — and the message
|
|
1142
|
-
names both doors that ARE open, `keyword()` and `from()`. One name is on
|
|
1143
|
-
the owned list for the opposite reason: `nullable` is refused because
|
|
1144
|
-
`.nullable()` already exists and emits a type union
|
|
1145
|
-
(`type: ['string', 'null']`) — `nullable` as a keyword is an OpenAPI
|
|
1146
|
-
spelling that 2020-12 does not carry, and refusing it is what keeps one
|
|
1147
|
-
document from claiming both.
|
|
1130
|
+
`dollarData(pointer)` builds a `$data` reference object; use its `.schema` as
|
|
1131
|
+
a raw constraint value. `data(map)` writes keyword-to-pointer bindings at the
|
|
1132
|
+
schema node. Both leave pointer resolution to the validator. `keyword()` and
|
|
1133
|
+
`from()` remain the escape hatches for future extensions, with no inferred
|
|
1134
|
+
narrowing. A boolean document cannot carry a keyword; widen it first.
|
|
1148
1135
|
|
|
1149
1136
|
### 6.3 When not to reach for this pen
|
|
1150
1137
|
|
|
@@ -1180,10 +1167,10 @@ hand, or generate it some other way, when:
|
|
|
1180
1167
|
|
|
1181
1168
|
## 7. Cost
|
|
1182
1169
|
|
|
1183
|
-
`@jarenjs/linq/schema` builds to **<!--fact:bundle.schema-->
|
|
1170
|
+
`@jarenjs/linq/schema` builds to **<!--fact:bundle.schema-->36,717<!--/fact--> bytes** as a minified,
|
|
1184
1171
|
tree-shaken ESM bundle — the figure `scripts/check-tree-shaking.js`
|
|
1185
1172
|
measures and `npm run test:tree-shaking` reports, published rounded
|
|
1186
|
-
(<!--fact:bundle.schema.kb-->
|
|
1173
|
+
(<!--fact:bundle.schema.kb-->37<!--/fact--> kB) beside the other nine subpath prices in
|
|
1187
1174
|
[docs/CONSUMING.md](../../../docs/CONSUMING.md).
|
|
1188
1175
|
|
|
1189
1176
|
The probe is a gate, not a report: building
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jarenjs/linq",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.72.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
7
7
|
"types": "./types/index.d.ts",
|
|
@@ -47,7 +47,27 @@
|
|
|
47
47
|
"types": "./types/db.d.ts",
|
|
48
48
|
"default": "./src/db/index.js"
|
|
49
49
|
},
|
|
50
|
-
"./package.json": "./package.json"
|
|
50
|
+
"./package.json": "./package.json",
|
|
51
|
+
"./charts": {
|
|
52
|
+
"types": "./types/charts.d.ts",
|
|
53
|
+
"default": "./src/charts/index.js"
|
|
54
|
+
},
|
|
55
|
+
"./project": {
|
|
56
|
+
"types": "./types/project.d.ts",
|
|
57
|
+
"default": "./src/project/index.js"
|
|
58
|
+
},
|
|
59
|
+
"./jtlt": {
|
|
60
|
+
"types": "./types/jtlt.d.ts",
|
|
61
|
+
"default": "./src/jtlt/index.js"
|
|
62
|
+
},
|
|
63
|
+
"./messages": {
|
|
64
|
+
"types": "./types/messages.d.ts",
|
|
65
|
+
"default": "./src/messages/index.js"
|
|
66
|
+
},
|
|
67
|
+
"./ai": {
|
|
68
|
+
"types": "./types/ai.d.ts",
|
|
69
|
+
"default": "./src/ai/index.js"
|
|
70
|
+
}
|
|
51
71
|
},
|
|
52
72
|
"files": [
|
|
53
73
|
"types/",
|
|
@@ -84,13 +104,13 @@
|
|
|
84
104
|
"prepack": "npm run build:types"
|
|
85
105
|
},
|
|
86
106
|
"dependencies": {
|
|
87
|
-
"@jarenjs/core": "^0.
|
|
88
|
-
"@jarenjs/json": "^0.
|
|
107
|
+
"@jarenjs/core": "^0.72.2",
|
|
108
|
+
"@jarenjs/json": "^0.72.2"
|
|
89
109
|
},
|
|
90
110
|
"peerDependencies": {
|
|
91
|
-
"@jarenjs/db": "^0.
|
|
92
|
-
"@jarenjs/formats": "^0.
|
|
93
|
-
"@jarenjs/validate": "^0.
|
|
111
|
+
"@jarenjs/db": "^0.72.2",
|
|
112
|
+
"@jarenjs/formats": "^0.72.2",
|
|
113
|
+
"@jarenjs/validate": "^0.72.2"
|
|
94
114
|
},
|
|
95
115
|
"peerDependenciesMeta": {
|
|
96
116
|
"@jarenjs/db": {
|
package/src/ai/index.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
/** The AI action document, authored without an AI client, environment or engine. */
|
|
3
|
+
import { DocumentBuilder, optionsOf, snapshot } from '../authored.js';
|
|
4
|
+
import { captureQuery } from '../capture-root.js';
|
|
5
|
+
import { LinqBuildError } from '../errors.js';
|
|
6
|
+
|
|
7
|
+
const FIELDS = {
|
|
8
|
+
chunk: ['strategy', 'size'], grep: ['pattern', 'flags', 'limit'], select: ['query'],
|
|
9
|
+
stat: [], peek: [], map: ['prompt'], reduce: ['query', 'outputSchema'], answer: ['chars'],
|
|
10
|
+
};
|
|
11
|
+
function make(op, from, as, options) {
|
|
12
|
+
if (typeof from !== 'string' || !from || (op !== 'answer' && (typeof as !== 'string' || !as)))
|
|
13
|
+
throw new LinqBuildError('JL0101', 'A step needs a source name and, except answer, a result name');
|
|
14
|
+
return snapshot({ op, from, ...(op === 'answer' ? {} : { as }), ...optionsOf(options, FIELDS[op], `${op}()`) });
|
|
15
|
+
}
|
|
16
|
+
function expression(value) {
|
|
17
|
+
// The program query receives only its input document: an undeclared external
|
|
18
|
+
// is the shared capture's JL0104 refusal, not an invented environment lookup.
|
|
19
|
+
return typeof value === 'function' ? captureQuery('program query', [], value, { fold: false }) : value;
|
|
20
|
+
}
|
|
21
|
+
/** Split a slot into a family of pieces. */
|
|
22
|
+
export function chunk(from, as, options = {}) { return make('chunk', from, as, options); }
|
|
23
|
+
/** Find matching slots with a bounded pattern and options. */
|
|
24
|
+
export function grep(from, as, options) { return make('grep', from, as, options); }
|
|
25
|
+
/** Query one JSON slot; a callback is captured as ordinary query JSON. */
|
|
26
|
+
export function select(from, as, query) { return make('select', from, as, { query: expression(query) }); }
|
|
27
|
+
/** Inspect counts and shape. */
|
|
28
|
+
export function stat(from, as) { return make('stat', from, as, {}); }
|
|
29
|
+
/** Inspect a slot's metadata and excerpt. */
|
|
30
|
+
export function peek(from, as) { return make('peek', from, as, {}); }
|
|
31
|
+
/** Ask one bounded prompt per member of a slot/family. */
|
|
32
|
+
export function map(from, as, prompt) { return make('map', from, as, { prompt }); }
|
|
33
|
+
/** Query the collected results of an earlier map. */
|
|
34
|
+
export function reduce(from, as, query, options = {}) {
|
|
35
|
+
return make('reduce', from, as, { query: expression(query), ...optionsOf(options, ['outputSchema'], 'reduce()') });
|
|
36
|
+
}
|
|
37
|
+
/** Declare the final answer slot and optional excerpt bound. */
|
|
38
|
+
export function answer(from, options = {}) { return make('answer', from, undefined, options); }
|
|
39
|
+
|
|
40
|
+
/** An immutable ordered program; binding types are phantoms. */
|
|
41
|
+
export class ProgramBuilder extends DocumentBuilder {
|
|
42
|
+
/** Append a public step. An answer closes this builder. @param {any} value */
|
|
43
|
+
step(value) {
|
|
44
|
+
if (this.schema.steps.at(-1)?.op === 'answer')
|
|
45
|
+
throw new LinqBuildError('JL0102', 'answer is terminal; start another program to append work');
|
|
46
|
+
if (value === null || typeof value !== 'object' || !Object.hasOwn(FIELDS, value.op))
|
|
47
|
+
throw new LinqBuildError('JL0101', 'step() needs a known program operation');
|
|
48
|
+
const step = optionsOf(value, ['op', 'from', ...(value.op === 'answer' ? [] : ['as']), ...FIELDS[value.op]], 'step()');
|
|
49
|
+
return this.with({ steps: [...this.schema.steps, step] });
|
|
50
|
+
}
|
|
51
|
+
/** Append a chunk step. */
|
|
52
|
+
chunk(from, as, options = {}) { return this.step(chunk(from, as, options)); }
|
|
53
|
+
/** Append a grep step. */
|
|
54
|
+
grep(from, as, options) { return this.step(grep(from, as, options)); }
|
|
55
|
+
/** Append a select step. */
|
|
56
|
+
select(from, as, query) { return this.step(select(from, as, query)); }
|
|
57
|
+
/** Append a stat step. */
|
|
58
|
+
stat(from, as) { return this.step(stat(from, as)); }
|
|
59
|
+
/** Append a peek step. */
|
|
60
|
+
peek(from, as) { return this.step(peek(from, as)); }
|
|
61
|
+
/** Append a map step. */
|
|
62
|
+
map(from, as, prompt) { return this.step(map(from, as, prompt)); }
|
|
63
|
+
/** Append a reduce step. */
|
|
64
|
+
reduce(from, as, query, options = {}) { return this.step(reduce(from, as, query, options)); }
|
|
65
|
+
/** Append the terminal answer. */
|
|
66
|
+
answer(from, options = {}) { return this.step(answer(from, options)); }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Declare input slot names for TypeScript; they never become document members. @param {readonly string[]} slots */
|
|
70
|
+
export function program(slots = []) {
|
|
71
|
+
if (!Array.isArray(slots) || slots.some((name) => typeof name !== 'string' || !name))
|
|
72
|
+
throw new LinqBuildError('JL0101', 'program() takes input slot names');
|
|
73
|
+
return new ProgramBuilder({ steps: [] });
|
|
74
|
+
}
|
|
75
|
+
/** A raw public program. Compilation remains authoritative for names and semantics. @param {any} document */
|
|
76
|
+
export function from(document) { return new ProgramBuilder(document); }
|
package/src/authored.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
/** Immutable JSON document storage shared by the authored-format pens. */
|
|
3
|
+
import { cloneJson, deepFreeze } from '@jarenjs/core/object';
|
|
4
|
+
import { requireJson, requireNameMap } from './json-boundary.js';
|
|
5
|
+
import { LinqBuildError } from './errors.js';
|
|
6
|
+
|
|
7
|
+
/** A snapshot, never a view of a caller-owned document. @param {any} value */
|
|
8
|
+
export function snapshot(value) {
|
|
9
|
+
return deepFreeze(cloneJson(requireJson(value, 'document')));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** The closed option boundary; extensions enter through the raw document factory. */
|
|
13
|
+
export function optionsOf(value, keys, what) {
|
|
14
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
15
|
+
throw new LinqBuildError('JL0101', `${what} takes a plain object`);
|
|
16
|
+
requireNameMap(value, what);
|
|
17
|
+
for (const key of Object.keys(value)) {
|
|
18
|
+
if (!keys.includes(key))
|
|
19
|
+
throw new LinqBuildError('JL0101', `${what} does not take '${key}' — use from() for a raw document`);
|
|
20
|
+
}
|
|
21
|
+
return snapshot(value);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** A standard document with fluent, immutable replacement. */
|
|
25
|
+
export class DocumentBuilder {
|
|
26
|
+
#schema;
|
|
27
|
+
/** @param {any} document */
|
|
28
|
+
constructor(document) { this.#schema = snapshot(document); }
|
|
29
|
+
/** The public document, deeply frozen. */
|
|
30
|
+
get schema() { return this.#schema; }
|
|
31
|
+
/** JSON serialization is exactly the public document. */
|
|
32
|
+
toJSON() { return this.#schema; }
|
|
33
|
+
/** Replace members without changing the original builder. @param {object} patch */
|
|
34
|
+
with(patch) {
|
|
35
|
+
const Kind = /** @type {any} */ (this.constructor);
|
|
36
|
+
return new Kind({ ...this.#schema, ...patch });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
/** Chart-definition factories; the chart engine is injected by the consumer. */
|
|
3
|
+
import { DocumentBuilder, optionsOf } from '../authored.js';
|
|
4
|
+
import { LinqBuildError } from '../errors.js';
|
|
5
|
+
import { CHART_FIELDS } from './vocabulary.js';
|
|
6
|
+
|
|
7
|
+
/** Immutable chart definition; methods keep the original discriminator. */
|
|
8
|
+
export class ChartBuilder extends DocumentBuilder {
|
|
9
|
+
/** @param {Record<string, any>} value */
|
|
10
|
+
options(value) {
|
|
11
|
+
const keys = CHART_FIELDS[this.schema.type];
|
|
12
|
+
if (keys === undefined) throw new LinqBuildError('JL0101', `unknown chart kind '${this.schema.type}'`);
|
|
13
|
+
return this.with(optionsOf(value, keys, `${this.schema.type}()`));
|
|
14
|
+
}
|
|
15
|
+
/** `title`. @param {any} value */
|
|
16
|
+
title(value) { return this.options({ title: value }); }
|
|
17
|
+
/** `stream`. @param {any} value */
|
|
18
|
+
stream(value) { return this.options({ stream: value }); }
|
|
19
|
+
/** `donut`. @param {any} value */
|
|
20
|
+
donut(value) { return this.options({ donut: value }); }
|
|
21
|
+
/** `slices`. @param {any} value */
|
|
22
|
+
slices(value) { return this.options({ slices: value }); }
|
|
23
|
+
/** `stacked`. @param {any} value */
|
|
24
|
+
stacked(value) { return this.options({ stacked: value }); }
|
|
25
|
+
/** `log`. @param {any} value */
|
|
26
|
+
log(value) { return this.options({ log: value }); }
|
|
27
|
+
/** `orient`. @param {any} value */
|
|
28
|
+
orient(value) { return this.options({ orient: value }); }
|
|
29
|
+
/** `catLabel`. @param {any} value */
|
|
30
|
+
catLabel(value) { return this.options({ catLabel: value }); }
|
|
31
|
+
/** `valLabel`. @param {any} value */
|
|
32
|
+
valLabel(value) { return this.options({ valLabel: value }); }
|
|
33
|
+
/** `categories`. @param {any} value */
|
|
34
|
+
categories(value) { return this.options({ categories: value }); }
|
|
35
|
+
/** `series`. @param {any} value */
|
|
36
|
+
series(value) { return this.options({ series: value }); }
|
|
37
|
+
/** `x`. @param {any} value */
|
|
38
|
+
x(value) { return this.options({ x: value }); }
|
|
39
|
+
/** `markers`. @param {any} value */
|
|
40
|
+
markers(value) { return this.options({ markers: value }); }
|
|
41
|
+
/** `xLabel`. @param {any} value */
|
|
42
|
+
xLabel(value) { return this.options({ xLabel: value }); }
|
|
43
|
+
/** `yLabel`. @param {any} value */
|
|
44
|
+
yLabel(value) { return this.options({ yLabel: value }); }
|
|
45
|
+
/** `domain`. @param {any} value */
|
|
46
|
+
domain(value) { return this.options({ domain: value }); }
|
|
47
|
+
/** `sampling`. @param {any} value */
|
|
48
|
+
sampling(value) { return this.options({ sampling: value }); }
|
|
49
|
+
/** `dateNames`. @param {any} value */
|
|
50
|
+
dateNames(value) { return this.options({ dateNames: value }); }
|
|
51
|
+
/** `timeFormats`. @param {any} value */
|
|
52
|
+
timeFormats(value) { return this.options({ timeFormats: value }); }
|
|
53
|
+
/** `candles`. @param {any} value */
|
|
54
|
+
candles(value) { return this.options({ candles: value }); }
|
|
55
|
+
/** `xLog`. @param {any} value */
|
|
56
|
+
xLog(value) { return this.options({ xLog: value }); }
|
|
57
|
+
/** `yLog`. @param {any} value */
|
|
58
|
+
yLog(value) { return this.options({ yLog: value }); }
|
|
59
|
+
/** `refY`. @param {any} value */
|
|
60
|
+
refY(value) { return this.options({ refY: value }); }
|
|
61
|
+
/** `refLabel`. @param {any} value */
|
|
62
|
+
refLabel(value) { return this.options({ refLabel: value }); }
|
|
63
|
+
/** `points`. @param {any} value */
|
|
64
|
+
points(value) { return this.options({ points: value }); }
|
|
65
|
+
/** `max`. @param {any} value */
|
|
66
|
+
max(value) { return this.options({ max: value }); }
|
|
67
|
+
/** `axes`. @param {any} value */
|
|
68
|
+
axes(value) { return this.options({ axes: value }); }
|
|
69
|
+
/** `value`. @param {any} value */
|
|
70
|
+
value(value) { return this.options({ value: value }); }
|
|
71
|
+
/** `min`. @param {any} value */
|
|
72
|
+
min(value) { return this.options({ min: value }); }
|
|
73
|
+
/** `unit`. @param {any} value */
|
|
74
|
+
unit(value) { return this.options({ unit: value }); }
|
|
75
|
+
/** `tone`. @param {any} value */
|
|
76
|
+
tone(value) { return this.options({ tone: value }); }
|
|
77
|
+
/** `boxes`. @param {any} value */
|
|
78
|
+
boxes(value) { return this.options({ boxes: value }); }
|
|
79
|
+
/** `xLabels`. @param {any} value */
|
|
80
|
+
xLabels(value) { return this.options({ xLabels: value }); }
|
|
81
|
+
/** `yLabels`. @param {any} value */
|
|
82
|
+
yLabels(value) { return this.options({ yLabels: value }); }
|
|
83
|
+
/** `values`. @param {any} value */
|
|
84
|
+
values(value) { return this.options({ values: value }); }
|
|
85
|
+
/** `aspect`. @param {any} value */
|
|
86
|
+
aspect(value) { return this.options({ aspect: value }); }
|
|
87
|
+
/** `items`. @param {any} value */
|
|
88
|
+
items(value) { return this.options({ items: value }); }
|
|
89
|
+
/** `xs`. @param {any} value */
|
|
90
|
+
xs(value) { return this.options({ xs: value }); }
|
|
91
|
+
/** `nodes`. @param {any} value */
|
|
92
|
+
nodes(value) { return this.options({ nodes: value }); }
|
|
93
|
+
/** `links`. @param {any} value */
|
|
94
|
+
links(value) { return this.options({ links: value }); }
|
|
95
|
+
/** `label`. @param {any} value */
|
|
96
|
+
label(value) { return this.options({ label: value }); }
|
|
97
|
+
/** `simplify`. @param {any} value */
|
|
98
|
+
simplify(value) { return this.options({ simplify: value }); }
|
|
99
|
+
/** `features`. @param {any} value */
|
|
100
|
+
features(value) { return this.options({ features: value }); }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** A raw public chart definition, including JSON extensions. @param {any} document */
|
|
104
|
+
export function from(document) { return new ChartBuilder(document); }
|
|
105
|
+
|
|
106
|
+
/** A pie chart. @param {object} [options] */
|
|
107
|
+
export function pie(options = {}) { return new ChartBuilder({ type: 'pie' }).options(options); }
|
|
108
|
+
/** A bar chart. @param {object} [options] */
|
|
109
|
+
export function bar(options = {}) { return new ChartBuilder({ type: 'bar' }).options(options); }
|
|
110
|
+
/** A line chart. @param {object} [options] */
|
|
111
|
+
export function line(options = {}) { return new ChartBuilder({ type: 'line' }).options(options); }
|
|
112
|
+
/** A scatter chart. @param {object} [options] */
|
|
113
|
+
export function scatter(options = {}) { return new ChartBuilder({ type: 'scatter' }).options(options); }
|
|
114
|
+
/** A candlestick chart. @param {object} [options] */
|
|
115
|
+
export function candlestick(options = {}) { return new ChartBuilder({ type: 'candlestick' }).options(options); }
|
|
116
|
+
/** A radar chart. @param {object} [options] */
|
|
117
|
+
export function radar(options = {}) { return new ChartBuilder({ type: 'radar' }).options(options); }
|
|
118
|
+
/** A gauge chart. @param {object} [options] */
|
|
119
|
+
export function gauge(options = {}) { return new ChartBuilder({ type: 'gauge' }).options(options); }
|
|
120
|
+
/** A boxplot chart. @param {object} [options] */
|
|
121
|
+
export function boxplot(options = {}) { return new ChartBuilder({ type: 'boxplot' }).options(options); }
|
|
122
|
+
/** A heatmap chart. @param {object} [options] */
|
|
123
|
+
export function heatmap(options = {}) { return new ChartBuilder({ type: 'heatmap' }).options(options); }
|
|
124
|
+
/** A treemap chart. @param {object} [options] */
|
|
125
|
+
export function treemap(options = {}) { return new ChartBuilder({ type: 'treemap' }).options(options); }
|
|
126
|
+
/** A streamgraph chart. @param {object} [options] */
|
|
127
|
+
export function streamgraph(options = {}) { return new ChartBuilder({ type: 'streamgraph' }).options(options); }
|
|
128
|
+
/** A sankey chart. @param {object} [options] */
|
|
129
|
+
export function sankey(options = {}) { return new ChartBuilder({ type: 'sankey' }).options(options); }
|
|
130
|
+
/** A map chart. @param {object} [options] */
|
|
131
|
+
export function map(options = {}) { return new ChartBuilder({ type: 'map' }).options(options); }
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// Derived from chart-definition.schema.json by scripts/generate-chart-pen.js.
|
|
2
|
+
/** The known member set for each public chart discriminator. */
|
|
3
|
+
export const CHART_FIELDS = Object.freeze({
|
|
4
|
+
"pie": [
|
|
5
|
+
"title",
|
|
6
|
+
"stream",
|
|
7
|
+
"donut",
|
|
8
|
+
"slices"
|
|
9
|
+
],
|
|
10
|
+
"bar": [
|
|
11
|
+
"title",
|
|
12
|
+
"stream",
|
|
13
|
+
"stacked",
|
|
14
|
+
"log",
|
|
15
|
+
"orient",
|
|
16
|
+
"catLabel",
|
|
17
|
+
"valLabel",
|
|
18
|
+
"categories",
|
|
19
|
+
"series"
|
|
20
|
+
],
|
|
21
|
+
"line": [
|
|
22
|
+
"title",
|
|
23
|
+
"stream",
|
|
24
|
+
"x",
|
|
25
|
+
"log",
|
|
26
|
+
"markers",
|
|
27
|
+
"xLabel",
|
|
28
|
+
"yLabel",
|
|
29
|
+
"domain",
|
|
30
|
+
"sampling",
|
|
31
|
+
"dateNames",
|
|
32
|
+
"timeFormats",
|
|
33
|
+
"series"
|
|
34
|
+
],
|
|
35
|
+
"scatter": [
|
|
36
|
+
"title",
|
|
37
|
+
"stream",
|
|
38
|
+
"xLog",
|
|
39
|
+
"yLog",
|
|
40
|
+
"refY",
|
|
41
|
+
"refLabel",
|
|
42
|
+
"xLabel",
|
|
43
|
+
"yLabel",
|
|
44
|
+
"points"
|
|
45
|
+
],
|
|
46
|
+
"candlestick": [
|
|
47
|
+
"title",
|
|
48
|
+
"stream",
|
|
49
|
+
"xLabel",
|
|
50
|
+
"yLabel",
|
|
51
|
+
"domain",
|
|
52
|
+
"dateNames",
|
|
53
|
+
"timeFormats",
|
|
54
|
+
"candles"
|
|
55
|
+
],
|
|
56
|
+
"radar": [
|
|
57
|
+
"title",
|
|
58
|
+
"stream",
|
|
59
|
+
"max",
|
|
60
|
+
"axes",
|
|
61
|
+
"series"
|
|
62
|
+
],
|
|
63
|
+
"gauge": [
|
|
64
|
+
"title",
|
|
65
|
+
"stream",
|
|
66
|
+
"value",
|
|
67
|
+
"min",
|
|
68
|
+
"max",
|
|
69
|
+
"unit",
|
|
70
|
+
"tone"
|
|
71
|
+
],
|
|
72
|
+
"boxplot": [
|
|
73
|
+
"title",
|
|
74
|
+
"stream",
|
|
75
|
+
"catLabel",
|
|
76
|
+
"valLabel",
|
|
77
|
+
"boxes"
|
|
78
|
+
],
|
|
79
|
+
"heatmap": [
|
|
80
|
+
"title",
|
|
81
|
+
"stream",
|
|
82
|
+
"log",
|
|
83
|
+
"xLabel",
|
|
84
|
+
"yLabel",
|
|
85
|
+
"xLabels",
|
|
86
|
+
"yLabels",
|
|
87
|
+
"values"
|
|
88
|
+
],
|
|
89
|
+
"treemap": [
|
|
90
|
+
"title",
|
|
91
|
+
"stream",
|
|
92
|
+
"aspect",
|
|
93
|
+
"items"
|
|
94
|
+
],
|
|
95
|
+
"streamgraph": [
|
|
96
|
+
"title",
|
|
97
|
+
"stream",
|
|
98
|
+
"xLabel",
|
|
99
|
+
"xs",
|
|
100
|
+
"series"
|
|
101
|
+
],
|
|
102
|
+
"sankey": [
|
|
103
|
+
"title",
|
|
104
|
+
"stream",
|
|
105
|
+
"nodes",
|
|
106
|
+
"links"
|
|
107
|
+
],
|
|
108
|
+
"map": [
|
|
109
|
+
"title",
|
|
110
|
+
"stream",
|
|
111
|
+
"value",
|
|
112
|
+
"label",
|
|
113
|
+
"log",
|
|
114
|
+
"aspect",
|
|
115
|
+
"simplify",
|
|
116
|
+
"features",
|
|
117
|
+
"points"
|
|
118
|
+
]
|
|
119
|
+
});
|
package/src/expression.js
CHANGED