@tangleai/linq 0.24.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,27 @@
1
+ # @tangleai/linq
2
+
3
+ ## 0.24.1
4
+
5
+ No changes in this release.
6
+
7
+ ## 0.24.0
8
+
9
+ No changes in this release.
10
+
11
+ ## 0.23.0
12
+
13
+ No changes in this release.
14
+
15
+ ## 0.22.0
16
+
17
+ ### Minor Changes
18
+
19
+ - Convert the migrated source, tests, benchmarks and hosts to strict TypeScript,
20
+ with JavaScript and declarations emitted through one release build. Move the
21
+ program pen from `@tangleai/jaren/program` and the Jaren integration barrel to
22
+ `@tangleai/linq/program`, preserving its JSON format and phantom binding types.
23
+ The new `@tangleai/linq` root exposes the program namespace and shared build error.
24
+
25
+ Match the embedder declarations to unknown widths before the first response,
26
+ retain precise ledger result variants, and enforce the refinement-pressure
27
+ instrument's stated 60-second deadline through the chat client's abort signal.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Joham (jklarenbeek@gmail.com)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # @tangleai/linq
2
+
3
+ Typed, immutable pens for Tangle AI document formats. A pen is a fluent authoring
4
+ API whose deliverable is plain, deeply frozen JSON. It does not execute that
5
+ document or duplicate its compiler. Shared snapshotting, option checks and query
6
+ capture come from `@jarenjs/linq/authoring`.
7
+
8
+ The package currently contains one pen, at `@tangleai/linq/program`:
9
+
10
+ ```ts
11
+ import { program } from '@tangleai/linq/program';
12
+
13
+ const document = program(['data'])
14
+ .select('data', 'count', value => value.get('items').count())
15
+ .answer('count')
16
+ .schema;
17
+ ```
18
+
19
+ The root supports `import { program as p } from '@tangleai/linq'`, followed by
20
+ `p.program(['data'])`. It also exports the same `LinqBuildError` class used by
21
+ Jaren pens. Each future pen will have a separate subpath. There are no additional
22
+ pens or query execution APIs in this package today; generic schema, model, flow,
23
+ contract and other Jaren pens remain in `@jarenjs/linq`.
24
+
25
+ The program pen tracks declared inputs, output names, slot/family kinds and the
26
+ terminal answer in TypeScript. Its eight operations are `chunk`, `grep`,
27
+ `select`, `stat`, `peek`, `map`, `reduce` and `answer`. Use `.schema` or `.toJSON()`
28
+ to obtain the document, then validate/compile/run it with `@tangleai/agents`.
29
+ Raw documents passed to `from()` make no binding-order or terminal-state inference.
30
+
31
+ See [the program reference](docs/PROGRAM-PEN.md) for exact options, emitted JSON,
32
+ refusals, type limits and executable examples. The package depends only on
33
+ `@jarenjs/linq`; its pen bundle is checked to exclude runtime engines and Tangle
34
+ model/context/agent mechanisms.
35
+
36
+ Replace imports from `@tangleai/jaren/program` with `@tangleai/linq/program`.
37
+ For pen symbols previously imported from the `@tangleai/jaren` barrel, import
38
+ from `@tangleai/linq/program` directly. Workspace exports point to strict
39
+ TypeScript; npm artifacts contain compiled ESM JavaScript and emitted declarations.
@@ -0,0 +1,102 @@
1
+ # The AI program pen
2
+
3
+ > `@tangleai/linq/program` — the public action program over environment slots. **Read it when**
4
+ > you want typed fixtures or host-authored programs without a model client.
5
+
6
+ ## 1. What it writes
7
+
8
+ The pen emits exactly `{ steps: [...] }`, the document consumed by the AI
9
+ program schema, compiler and runner. It imports no AI engine or environment.
10
+ `.schema` is an independent, deeply frozen JSON snapshot; `JSON.stringify()`
11
+ writes that document. Updates return a new builder.
12
+
13
+ `program(['corpus'])` declares input names for TypeScript; those names never
14
+ become extra document members or an embedded registry. The compiler checks the
15
+ real environment. Queries can be raw JSON or callbacks recorded over the input
16
+ document, using the same capture as other pens and no runtime query engine.
17
+
18
+ ## 2. The mapping table
19
+
20
+ Factories return frozen public steps. The same method on a program appends that
21
+ step. Every non-answer step writes `op`, `from` and `as` before its options.
22
+
23
+ | Factory / method | Emits | Type | Status |
24
+ |---|---|---|---|
25
+ | `program(slots?)` | empty `steps` | declared input names | native |
26
+ | `chunk(from, as, options?)`, `.chunk(...)` | chunk; strategy and size | result family | native |
27
+ | `grep(from, as, options)`, `.grep(...)` | grep; pattern, flags, limit | match-list slot | native |
28
+ | `select(from, as, query)`, `.select(...)` | select; query JSON | result slot | native |
29
+ | `stat(from, as)`, `.stat(...)` | stat | result slot | native |
30
+ | `peek(from, as)`, `.peek(...)` | peek | result slot | native |
31
+ | `map(from, as, prompt)`, `.map(...)` | map; bounded instruction | result family | native |
32
+ | `reduce(from, as, query, options?)`, `.reduce(...)` | reduce; query JSON and optional `outputSchema` | result slot | native |
33
+ | `answer(from, options?)`, `.answer(...)` | answer; optional chars, no as | terminal program | native |
34
+ | `.step(step)` | appends a public step | tracks its input/result names | native |
35
+ | `from(document)` | raw program | no binding-order inference | native |
36
+ | `.schema`, `.toJSON()` | frozen public JSON | program document | native |
37
+
38
+ `chunk` options are `strategy: 'size' | 'line' | 'separator'` and `size`.
39
+ `grep` requires `pattern`, with optional `flags: 'i' | 'm' | 'im' | ''` and
40
+ `limit`. `answer` takes `chars`. The schema owns numeric and string bounds.
41
+
42
+ ## 3. Worked examples
43
+
44
+ ```js
45
+ import { program } from '@tangleai/linq/program';
46
+ export const plan = program(['corpus'])
47
+ .chunk('corpus', 'pieces', { strategy: 'line', size: 200 })
48
+ .map('pieces', 'found', 'Return the number in this piece as JSON')
49
+ .reduce('found', 'count', { $count: '$[*]' })
50
+ .answer('count', { chars: 50 });
51
+ ```
52
+ ```json
53
+ {"steps":[{"op":"chunk","from":"corpus","as":"pieces","strategy":"line","size":200},{"op":"map","from":"pieces","as":"found","prompt":"Return the number in this piece as JSON"},{"op":"reduce","from":"found","as":"count","query":{"$count":"$[*]"}},{"op":"answer","from":"count","chars":50}]}
54
+ ```
55
+
56
+ Validate with `PROGRAM_SCHEMA` (or `programSchema({ queryRef })`), then
57
+ `compileProgram`/`programGate` with the query compiler and known environment
58
+ names. `createProgramRunner` does both shape and compile checks before execution.
59
+ The standalone compile gate checks semantics; it does not replace the shape
60
+ schema's numeric ranges, member closure or step-count cap.
61
+
62
+ ## 4. Refusals
63
+
64
+ | Code | Condition |
65
+ |---|---|
66
+ | `JL0101` | invalid slot/result name shape, unknown option/operation, non-JSON input, or a malformed input-name list |
67
+ | `JL0102` | appending a step after answer |
68
+ | `JL0104` | a query callback reads an external; program queries bind only their input document |
69
+
70
+ The schema/compiler own unknown environment names, duplicate result bindings,
71
+ missing answers, limits, query validity and family/slot semantics. `from()`
72
+ preserves raw programs for those checks rather than carrying a second compiler.
73
+
74
+ ## 5. The types
75
+
76
+ `ProgramBuilder<Bindings, Done>` tracks names and whether answer has closed the
77
+ program. `ProgramDocument`, `Step`, `StepDocument`, `StepOptions`, `Operation`,
78
+ `Query`, `ChunkOptions`, `GrepOptions` and `AnswerOptions` are types only.
79
+
80
+ A later step may read only a declared input or previous result. `select` and
81
+ `answer` read individual slots, not chunk/map families; `reduce` reads map
82
+ results. A result cannot be rebound, although an input slot can be shadowed as
83
+ in the compiler. Standalone step factories retain literal names and `.step()`
84
+ checks them on insertion. Raw documents claim neither binding order nor a
85
+ terminal state. No inferred data schema, budget or successful-run guarantee is
86
+ attached to a program.
87
+
88
+ ## 6. What it cannot spell
89
+
90
+ This is the action document, not a prompt/client wrapper. It neither pastes
91
+ corpus contents into a private envelope nor runs model calls in query expressions.
92
+ Only the existing runner's map step calls a model. Cancellation, concurrency,
93
+ sub-call budgets, storage and result interpretation remain runner concerns.
94
+
95
+ ## 7. Cost
96
+
97
+ The isolated program pen costs **<!--fact:bundle.program-->15,691<!--/fact--> bytes**
98
+ with Bun 1.4.0. `npm run test:tree-shaking` and the external packed consumer
99
+ measure the same probe, enforce an 18,000-byte ceiling, and reject retained
100
+ model mechanisms, runtime engines and the query chain. Nine modules remain.
101
+ The original Jaren esbuild measurement was 15,472 bytes; the different bundler's
102
+ output is a separate measurement, not a runtime performance comparison.
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@tangleai/linq",
3
+ "version": "0.24.1",
4
+ "description": "Typed, immutable document pens for Tangle AI, using the shared Jaren authoring primitives.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./src/index.js",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./src/index.d.ts",
11
+ "import": "./src/index.js",
12
+ "default": "./src/index.js"
13
+ },
14
+ "./program": {
15
+ "types": "./src/program.d.ts",
16
+ "import": "./src/program.js",
17
+ "default": "./src/program.js"
18
+ },
19
+ "./package.json": "./package.json"
20
+ },
21
+ "engines": {
22
+ "node": ">=24"
23
+ },
24
+ "sideEffects": false,
25
+ "dependencies": {
26
+ "@jarenjs/linq": "0.86.0"
27
+ },
28
+ "private": false,
29
+ "types": "./src/index.d.ts",
30
+ "files": [
31
+ "src/**/*.js",
32
+ "src/**/*.d.ts",
33
+ "docs/**/*.md",
34
+ "schemas/**/*.json",
35
+ "README.md",
36
+ "LICENSE",
37
+ "CHANGELOG.md"
38
+ ],
39
+ "publishConfig": {
40
+ "access": "public",
41
+ "registry": "https://registry.npmjs.org/"
42
+ },
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "git+https://github.com/jklarenbeek/tangleai.git",
46
+ "directory": "packages/linq"
47
+ }
48
+ }
@@ -0,0 +1,6 @@
1
+ import { LinqBuildError as JarenLinqBuildError } from '@jarenjs/linq';
2
+ /** The published coded constructor still inherits Error's signature in its declaration. */
3
+ export declare const LinqBuildError: {
4
+ new (code: string, reason: string, docPath?: string, cause?: Error): JarenLinqBuildError;
5
+ };
6
+ export type LinqBuildError = JarenLinqBuildError;
package/src/errors.js ADDED
@@ -0,0 +1,3 @@
1
+ import { LinqBuildError as JarenLinqBuildError } from '@jarenjs/linq';
2
+ /** The published coded constructor still inherits Error's signature in its declaration. */
3
+ export const LinqBuildError = JarenLinqBuildError;
package/src/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ /** Document authoring pens. Each pen also has its own subpath. */
2
+ export * as program from './program.ts';
3
+ export { LinqBuildError } from './errors.ts';
package/src/index.js ADDED
@@ -0,0 +1,3 @@
1
+ /** Document authoring pens. Each pen also has its own subpath. */
2
+ export * as program from "./program.js";
3
+ export { LinqBuildError } from "./errors.js";
@@ -0,0 +1,93 @@
1
+ /** Immutable action documents; execution and semantic validation belong to @tangleai/agents. */
2
+ import { DocumentBuilder } from '@jarenjs/linq/authoring';
3
+ import type { Json } from '@jarenjs/linq/schema';
4
+ import type { ExprBase, UnknownExpr } from '@jarenjs/linq';
5
+ export type Query = Json | ((value: UnknownExpr) => ExprBase<unknown> | Json);
6
+ export interface ChunkOptions {
7
+ readonly strategy?: 'size' | 'line' | 'separator';
8
+ readonly size?: number;
9
+ }
10
+ export interface GrepOptions {
11
+ readonly pattern: string;
12
+ readonly flags?: 'i' | 'm' | 'im' | '';
13
+ readonly limit?: number;
14
+ }
15
+ export interface AnswerOptions {
16
+ readonly chars?: number;
17
+ }
18
+ export interface ReduceOptions {
19
+ readonly outputSchema?: Json;
20
+ }
21
+ export interface StepOptions {
22
+ chunk: ChunkOptions;
23
+ grep: GrepOptions;
24
+ select: {
25
+ readonly query: Json;
26
+ };
27
+ stat: {};
28
+ peek: {};
29
+ map: {
30
+ readonly prompt: string;
31
+ };
32
+ reduce: {
33
+ readonly query: Json;
34
+ } & ReduceOptions;
35
+ answer: AnswerOptions;
36
+ }
37
+ export type Operation = keyof StepOptions;
38
+ export type Step<K extends Operation, F extends string = string, N extends string = string> = {
39
+ readonly op: K;
40
+ readonly from: F;
41
+ } & (K extends 'answer' ? {} : {
42
+ readonly as: N;
43
+ }) & StepOptions[K];
44
+ export type StepDocument = {
45
+ [K in Operation]: Step<K>;
46
+ }[Operation];
47
+ export interface ProgramDocument {
48
+ readonly steps: readonly StepDocument[];
49
+ }
50
+ type Kind = Exclude<Operation, 'answer'> | 'slot';
51
+ type Bindings = Record<string, Kind>;
52
+ type Names<B, K extends Kind = Kind> = {
53
+ [N in keyof B]: B[N] extends K ? N : never;
54
+ }[keyof B] & string;
55
+ type Single<B> = Names<B, Exclude<Kind, 'chunk' | 'map'>>;
56
+ type Fresh<B, N extends string> = N extends keyof B ? B[N] extends 'slot' ? N : never : N;
57
+ type Add<B, N extends string, K extends Kind> = Omit<B, N> & Record<N, K>;
58
+ type InputFor<B, K extends Operation> = K extends 'reduce' ? Names<B, 'map'> : K extends 'select' | 'answer' ? Single<B> : Names<B>;
59
+ type StepKeys<K extends Operation> = 'op' | 'from' | (K extends 'answer' ? never : 'as') | keyof StepOptions[K];
60
+ type CheckStep<B, T extends StepDocument> = Exclude<keyof T, StepKeys<T['op']>> extends never ? T['from'] extends InputFor<B, T['op']> ? T extends {
61
+ readonly as: infer N extends string;
62
+ } ? N extends Fresh<B, N> ? unknown : never : unknown : never : never;
63
+ type AfterStep<B, T extends StepDocument> = T extends {
64
+ readonly as: infer N extends string;
65
+ readonly op: infer K extends Kind;
66
+ } ? Add<B, N, K> : B;
67
+ export declare function chunk<const F extends string, const N extends string>(from: F, as: N, options?: ChunkOptions): Step<'chunk', F, N>;
68
+ export declare function grep<const F extends string, const N extends string>(from: F, as: N, options: GrepOptions): Step<'grep', F, N>;
69
+ export declare function select<const F extends string, const N extends string>(from: F, as: N, query: Query): Step<'select', F, N>;
70
+ export declare function stat<const F extends string, const N extends string>(from: F, as: N): Step<'stat', F, N>;
71
+ export declare function peek<const F extends string, const N extends string>(from: F, as: N): Step<'peek', F, N>;
72
+ export declare function map<const F extends string, const N extends string>(from: F, as: N, prompt: string): Step<'map', F, N>;
73
+ export declare function reduce<const F extends string, const N extends string>(from: F, as: N, query: Query, options?: ReduceOptions): Step<'reduce', F, N>;
74
+ export declare function answer<const F extends string>(from: F, options?: AnswerOptions): Step<'answer', F>;
75
+ /** Binding kinds and terminal state are phantom types: neither enters the JSON document. */
76
+ export declare class ProgramBuilder<B extends Bindings = {}, Done extends boolean = false> extends DocumentBuilder<ProgramDocument> {
77
+ readonly __bindings: B;
78
+ readonly __done: Done;
79
+ step<const T extends StepDocument>(this: ProgramBuilder<B, false>, step: T & CheckStep<B, T>): ProgramBuilder<AfterStep<B, T>, T['op'] extends 'answer' ? true : false>;
80
+ chunk<N extends string>(this: ProgramBuilder<B, false>, from: Names<B>, as: Fresh<B, N>, options?: ChunkOptions): ProgramBuilder<Add<B, N, 'chunk'>>;
81
+ grep<N extends string>(this: ProgramBuilder<B, false>, from: Names<B>, as: Fresh<B, N>, options: GrepOptions): ProgramBuilder<Add<B, N, 'grep'>>;
82
+ select<N extends string>(this: ProgramBuilder<B, false>, from: Single<B>, as: Fresh<B, N>, query: Query): ProgramBuilder<Add<B, N, 'select'>>;
83
+ stat<N extends string>(this: ProgramBuilder<B, false>, from: Names<B>, as: Fresh<B, N>): ProgramBuilder<Add<B, N, 'stat'>>;
84
+ peek<N extends string>(this: ProgramBuilder<B, false>, from: Names<B>, as: Fresh<B, N>): ProgramBuilder<Add<B, N, 'peek'>>;
85
+ map<N extends string>(this: ProgramBuilder<B, false>, from: Names<B>, as: Fresh<B, N>, prompt: string): ProgramBuilder<Add<B, N, 'map'>>;
86
+ reduce<N extends string>(this: ProgramBuilder<B, false>, from: Names<B, 'map'>, as: Fresh<B, N>, query: Query, options?: ReduceOptions): ProgramBuilder<Add<B, N, 'reduce'>>;
87
+ answer(this: ProgramBuilder<B, false>, from: Single<B>, options?: AnswerOptions): ProgramBuilder<B, true>;
88
+ }
89
+ /** Input names exist only in the binding type; they never become document members. */
90
+ export declare function program<const S extends readonly string[] = []>(slots?: S): ProgramBuilder<Record<S[number], 'slot'>>;
91
+ /** Raw documents make no binding-order or terminal-state inference. */
92
+ export declare function from(document: ProgramDocument): ProgramBuilder<Record<string, Kind>, boolean>;
93
+ export {};
package/src/program.js ADDED
@@ -0,0 +1,72 @@
1
+ /** Immutable action documents; execution and semantic validation belong to @tangleai/agents. */
2
+ import { DocumentBuilder, optionsOf, snapshot, captureQuery } from '@jarenjs/linq/authoring';
3
+ import { LinqBuildError } from "./errors.js";
4
+ const FIELDS = {
5
+ chunk: ['strategy', 'size'], grep: ['pattern', 'flags', 'limit'], select: ['query'],
6
+ stat: [], peek: [], map: ['prompt'], reduce: ['query', 'outputSchema'], answer: ['chars'],
7
+ };
8
+ function make(op, from, as, options) {
9
+ if (typeof from !== 'string' || !from || (op !== 'answer' && (typeof as !== 'string' || !as)))
10
+ throw new LinqBuildError('JL0101', 'A step needs a source name and, except answer, a result name');
11
+ return snapshot({ op, from, ...(op === 'answer' ? {} : { as }), ...optionsOf(options, FIELDS[op], `${op}()`) });
12
+ }
13
+ function expression(value) {
14
+ // Only the input document is bound; external captures retain the shared JL0104 refusal.
15
+ return (typeof value === 'function' ? captureQuery('program query', [], value, { fold: false }) : value);
16
+ }
17
+ export function chunk(from, as, options = {}) { return make('chunk', from, as, options); }
18
+ export function grep(from, as, options) { return make('grep', from, as, options); }
19
+ export function select(from, as, query) { return make('select', from, as, { query: expression(query) }); }
20
+ export function stat(from, as) { return make('stat', from, as, {}); }
21
+ export function peek(from, as) { return make('peek', from, as, {}); }
22
+ export function map(from, as, prompt) { return make('map', from, as, { prompt }); }
23
+ export function reduce(from, as, query, options = {}) { return make('reduce', from, as, { query: expression(query), ...optionsOf(options, ['outputSchema'], 'reduce()') }); }
24
+ export function answer(from, options = {}) { return make('answer', from, undefined, options); }
25
+ /** Binding kinds and terminal state are phantom types: neither enters the JSON document. */
26
+ export class ProgramBuilder extends DocumentBuilder {
27
+ step(step) {
28
+ return append(this, step);
29
+ }
30
+ chunk(from, as, options = {}) {
31
+ return append(this, chunk(from, as, options));
32
+ }
33
+ grep(from, as, options) {
34
+ return append(this, grep(from, as, options));
35
+ }
36
+ select(from, as, query) {
37
+ return append(this, select(from, as, query));
38
+ }
39
+ stat(from, as) {
40
+ return append(this, stat(from, as));
41
+ }
42
+ peek(from, as) {
43
+ return append(this, peek(from, as));
44
+ }
45
+ map(from, as, prompt) {
46
+ return append(this, map(from, as, prompt));
47
+ }
48
+ reduce(from, as, query, options = {}) {
49
+ return append(this, reduce(from, as, query, options));
50
+ }
51
+ answer(from, options = {}) {
52
+ return append(this, answer(from, options));
53
+ }
54
+ }
55
+ /** Input names exist only in the binding type; they never become document members. */
56
+ export function program(slots = []) {
57
+ if (!Array.isArray(slots) || slots.some((name) => typeof name !== 'string' || !name))
58
+ throw new LinqBuildError('JL0101', 'program() takes input slot names');
59
+ return new ProgramBuilder({ steps: [] });
60
+ }
61
+ /** Raw documents make no binding-order or terminal-state inference. */
62
+ export function from(document) {
63
+ return new ProgramBuilder(document);
64
+ }
65
+ function append(builder, value) {
66
+ if (builder.schema.steps.at(-1)?.op === 'answer')
67
+ throw new LinqBuildError('JL0102', 'answer is terminal; start another program to append work');
68
+ if (value === null || typeof value !== 'object' || !Object.hasOwn(FIELDS, value.op))
69
+ throw new LinqBuildError('JL0101', 'step() needs a known program operation');
70
+ const step = optionsOf(value, ['op', 'from', ...(value.op === 'answer' ? [] : ['as']), ...FIELDS[value.op]], 'step()');
71
+ return builder.with({ steps: [...builder.schema.steps, step] });
72
+ }