@styx-api/core 0.1.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 (88) hide show
  1. package/dist/index.cjs +7947 -0
  2. package/dist/index.d.cts +1143 -0
  3. package/dist/index.d.cts.map +1 -0
  4. package/dist/index.d.mts +1143 -0
  5. package/dist/index.d.mts.map +1 -0
  6. package/dist/index.mjs +7877 -0
  7. package/dist/index.mjs.map +1 -0
  8. package/package.json +55 -0
  9. package/src/backend/backend.ts +95 -0
  10. package/src/backend/boutiques/boutiques.ts +1049 -0
  11. package/src/backend/boutiques/index.ts +1 -0
  12. package/src/backend/code-builder.ts +49 -0
  13. package/src/backend/collect-field-info.ts +50 -0
  14. package/src/backend/collect-named-types.ts +103 -0
  15. package/src/backend/collect-output-fields.ts +222 -0
  16. package/src/backend/find-doc.ts +38 -0
  17. package/src/backend/find-struct-node.ts +66 -0
  18. package/src/backend/index.ts +39 -0
  19. package/src/backend/python/arg-builder.ts +454 -0
  20. package/src/backend/python/emit.ts +638 -0
  21. package/src/backend/python/index.ts +9 -0
  22. package/src/backend/python/outputs-emit.ts +430 -0
  23. package/src/backend/python/packaging.ts +173 -0
  24. package/src/backend/python/python.ts +558 -0
  25. package/src/backend/python/snippet.ts +84 -0
  26. package/src/backend/python/typemap.ts +131 -0
  27. package/src/backend/python/types.ts +8 -0
  28. package/src/backend/python/validate-emit.ts +356 -0
  29. package/src/backend/resolve-field-binding.ts +41 -0
  30. package/src/backend/resolve-output-tokens.ts +80 -0
  31. package/src/backend/schema/index.ts +2 -0
  32. package/src/backend/schema/jsonschema.ts +303 -0
  33. package/src/backend/scope.ts +50 -0
  34. package/src/backend/sig-entries.ts +97 -0
  35. package/src/backend/snippet-core.ts +185 -0
  36. package/src/backend/string-case.ts +30 -0
  37. package/src/backend/styxdefs-compat.ts +21 -0
  38. package/src/backend/type-keys.ts +52 -0
  39. package/src/backend/typescript/arg-builder.ts +420 -0
  40. package/src/backend/typescript/emit.ts +450 -0
  41. package/src/backend/typescript/index.ts +10 -0
  42. package/src/backend/typescript/outputs-emit.ts +389 -0
  43. package/src/backend/typescript/packaging.ts +130 -0
  44. package/src/backend/typescript/snippet.ts +60 -0
  45. package/src/backend/typescript/typemap.ts +47 -0
  46. package/src/backend/typescript/types.ts +8 -0
  47. package/src/backend/typescript/typescript.ts +507 -0
  48. package/src/backend/typescript/validate-emit.ts +341 -0
  49. package/src/backend/union-variants.ts +42 -0
  50. package/src/backend/validate-walk.ts +111 -0
  51. package/src/bindings/binding.ts +77 -0
  52. package/src/bindings/format.ts +176 -0
  53. package/src/bindings/index.ts +16 -0
  54. package/src/bindings/output-gate.ts +50 -0
  55. package/src/bindings/resolved-output.ts +56 -0
  56. package/src/bindings/types.ts +16 -0
  57. package/src/frontend/argdump/index.ts +1 -0
  58. package/src/frontend/argdump/parser.ts +914 -0
  59. package/src/frontend/boutiques/destruct-template.ts +50 -0
  60. package/src/frontend/boutiques/index.ts +1 -0
  61. package/src/frontend/boutiques/parser.ts +676 -0
  62. package/src/frontend/boutiques/split-command.ts +69 -0
  63. package/src/frontend/detect-format.ts +42 -0
  64. package/src/frontend/frontend.ts +31 -0
  65. package/src/frontend/index.ts +9 -0
  66. package/src/frontend/workbench/index.ts +1 -0
  67. package/src/frontend/workbench/parser.ts +351 -0
  68. package/src/index.ts +41 -0
  69. package/src/ir/builders.ts +69 -0
  70. package/src/ir/format.ts +157 -0
  71. package/src/ir/index.ts +32 -0
  72. package/src/ir/meta.ts +91 -0
  73. package/src/ir/node.ts +95 -0
  74. package/src/ir/passes/canonicalize.ts +108 -0
  75. package/src/ir/passes/flatten.ts +73 -0
  76. package/src/ir/passes/index.ts +7 -0
  77. package/src/ir/passes/pass.ts +86 -0
  78. package/src/ir/passes/pipeline.ts +21 -0
  79. package/src/ir/passes/remove-empty.ts +76 -0
  80. package/src/ir/passes/simplify.ts +179 -0
  81. package/src/ir/types.ts +15 -0
  82. package/src/manifest/context.ts +36 -0
  83. package/src/manifest/index.ts +3 -0
  84. package/src/manifest/types.ts +15 -0
  85. package/src/solver/assign-access.ts +218 -0
  86. package/src/solver/index.ts +4 -0
  87. package/src/solver/resolve-outputs.ts +233 -0
  88. package/src/solver/solver.ts +319 -0
@@ -0,0 +1,176 @@
1
+ import type { Expr } from "../ir/node.js";
2
+ import type { AccessPath, BindingRegistry, OutputValidationResult } from "./binding.js";
3
+ import type { SolveResult } from "./index.js";
4
+ import { outputGate } from "./output-gate.js";
5
+ import type { GateAtom, OutputScope, ResolvedOutput, ResolvedToken } from "./resolved-output.js";
6
+ import type { BoundType } from "./types.js";
7
+
8
+ export interface FormatExtras {
9
+ scopes?: OutputScope[];
10
+ diagnostics?: OutputValidationResult;
11
+ }
12
+
13
+ export function formatSolveResult(result: SolveResult, expr: Expr, extras?: FormatExtras): string {
14
+ const binding = result.resolve(expr);
15
+ const rootLine = binding ? `${binding.name}: ${formatType(binding.type)}` : "(no binding)";
16
+
17
+ const sections = [rootLine];
18
+
19
+ const gatedBindings = [...result.bindings.values()].filter(
20
+ (b) => b.gate.length > 0 && b !== binding,
21
+ );
22
+ if (gatedBindings.length > 0) {
23
+ sections.push("", "gates:");
24
+ for (const b of gatedBindings) {
25
+ sections.push(` ${b.name}: ${formatGate(b.gate, result.bindings)}`);
26
+ }
27
+ }
28
+
29
+ const accessBindings = [...result.bindings.values()].filter((b) => b.access.length > 0);
30
+ if (accessBindings.length > 0) {
31
+ sections.push("", "access:");
32
+ for (const b of accessBindings) {
33
+ sections.push(` ${b.name}: ${formatAccess(b.access, result.bindings)}`);
34
+ }
35
+ }
36
+
37
+ if (extras?.scopes?.length) {
38
+ sections.push("", "outputs:");
39
+ for (const scope of extras.scopes) {
40
+ const scopeBinding = result.bindings.get(scope.scope);
41
+ const header = scopeBinding ? ` on ${scopeBinding.name}:` : " on <unbound>:";
42
+ sections.push(header);
43
+ const scopeGate = scopeBinding?.gate ?? [];
44
+ for (const out of scope.outputs) {
45
+ sections.push(formatResolvedOutput(out, scopeGate, result.bindings, 2));
46
+ }
47
+ }
48
+ }
49
+
50
+ const diags = [...(extras?.diagnostics?.errors ?? []), ...(extras?.diagnostics?.warnings ?? [])];
51
+ if (diags.length > 0) {
52
+ sections.push("", "diagnostics:");
53
+ for (const d of diags) sections.push(` [${d.level}] ${d.output}: ${d.message}`);
54
+ }
55
+
56
+ return sections.join("\n");
57
+ }
58
+
59
+ function formatResolvedOutput(
60
+ out: ResolvedOutput,
61
+ scopeGate: GateAtom[],
62
+ bindings: BindingRegistry,
63
+ indent: number,
64
+ ): string {
65
+ const pad = " ".repeat(indent);
66
+ const media = out.mediaTypes?.length ? ` (${out.mediaTypes.join(", ")})` : "";
67
+ const tokens = out.tokens.map((t) => formatResolvedToken(t, bindings)).join(" + ") || `""`;
68
+ const gateAtoms = outputGate(scopeGate, out, bindings);
69
+ const optional = gateAtoms.some((a) => a.kind === "present" || a.kind === "variant");
70
+ const optTag = optional ? " [optional]" : "";
71
+ const gate =
72
+ gateAtoms.length > 0
73
+ ? ` when (${gateAtoms.map((a) => formatGateAtom(a, bindings)).join(" AND ")})`
74
+ : "";
75
+ return `${pad}${out.name}${optTag}${media}: ${tokens}${gate}`;
76
+ }
77
+
78
+ function bindingName(bindings: BindingRegistry, id: string): string {
79
+ return bindings.get(id)?.name ?? `<${id}>`;
80
+ }
81
+
82
+ export function formatResolvedToken(token: ResolvedToken, bindings: BindingRegistry): string {
83
+ if (token.kind === "literal") return JSON.stringify(token.value);
84
+ const flags = [
85
+ token.stripExtensions?.length && `strip=${JSON.stringify(token.stripExtensions)}`,
86
+ token.fallback !== undefined && `fallback=${JSON.stringify(token.fallback)}`,
87
+ ].filter(Boolean);
88
+ const suffix = flags.length > 0 ? ` {${flags.join(", ")}}` : "";
89
+ return `ref(${bindingName(bindings, token.binding)})${suffix}`;
90
+ }
91
+
92
+ export function formatGateAtom(atom: GateAtom, bindings: BindingRegistry): string {
93
+ switch (atom.kind) {
94
+ case "present":
95
+ return `present(${bindingName(bindings, atom.binding)})`;
96
+ case "variant":
97
+ return `${bindingName(bindings, atom.binding)}=${atom.variant}`;
98
+ case "iter":
99
+ return `iter(${bindingName(bindings, atom.binding)})`;
100
+ }
101
+ }
102
+
103
+ export function formatGate(gate: GateAtom[], bindings: BindingRegistry): string {
104
+ if (gate.length === 0) return "(unconditional)";
105
+ return gate.map((a) => formatGateAtom(a, bindings)).join(" / ");
106
+ }
107
+
108
+ /**
109
+ * Render a binding's access path for debugging, e.g. `params.sub.x` or
110
+ * `<iter:things>.file`. An `iter` segment shows the repeat it loops, since the
111
+ * concrete loop variable is a backend emit-time detail.
112
+ */
113
+ export function formatAccess(access: AccessPath, bindings: BindingRegistry): string {
114
+ let out = "params";
115
+ for (const seg of access) {
116
+ out =
117
+ seg.kind === "field" ? `${out}.${seg.name}` : `<iter:${bindingName(bindings, seg.binding)}>`;
118
+ }
119
+ return out;
120
+ }
121
+
122
+ function formatType(type: BoundType, indent = 0): string {
123
+ const pad = " ".repeat(indent);
124
+ const inner = (t: BoundType) => formatType(t, indent + 1);
125
+
126
+ switch (type.kind) {
127
+ case "scalar":
128
+ return type.scalar;
129
+ case "bool":
130
+ return "bool";
131
+ case "count":
132
+ return "count";
133
+ case "literal":
134
+ return typeof type.value === "number" ? String(type.value) : `"${type.value}"`;
135
+ case "optional":
136
+ return `optional<${inner(type.inner)}>`;
137
+ case "list":
138
+ return `list<${inner(type.item)}>`;
139
+
140
+ case "struct": {
141
+ const entries = Object.entries(type.fields);
142
+ if (entries.length === 0) return "struct {}";
143
+ if (entries.length === 1) {
144
+ const [name, t] = entries[0]!;
145
+ return `struct { ${name}: ${formatType(t)} }`;
146
+ }
147
+ const fields = entries.map(([name, t]) => `${pad} ${name}: ${inner(t)}`).join("\n");
148
+ return `struct {\n${fields}\n${pad}}`;
149
+ }
150
+
151
+ case "union": {
152
+ if (type.variants.length === 0) return "union {}";
153
+
154
+ // If all variants are literals, display inline
155
+ const allLiterals = type.variants.every((v) => v.type.kind === "literal");
156
+ if (allLiterals) {
157
+ return type.variants
158
+ .map((v) =>
159
+ v.type.kind === "literal"
160
+ ? typeof v.type.value === "number"
161
+ ? String(v.type.value)
162
+ : `"${v.type.value}"`
163
+ : "?",
164
+ )
165
+ .join(" | ");
166
+ }
167
+
168
+ // Otherwise multi-line union
169
+ const variants = type.variants.map((v) => `${pad} | ${v.name}: ${inner(v.type)}`).join("\n");
170
+ return `union {\n${variants}\n${pad}}`;
171
+ }
172
+
173
+ default:
174
+ return ((_x: never) => "unknown")(type);
175
+ }
176
+ }
@@ -0,0 +1,16 @@
1
+ export type {
2
+ AccessPath,
3
+ AccessSegment,
4
+ Binding,
5
+ BindingId,
6
+ BindingRegistry,
7
+ OutputDiagnostic,
8
+ OutputDiagnosticLevel,
9
+ OutputValidationResult,
10
+ SolveResult,
11
+ } from "./binding.js";
12
+ export { createRegistry } from "./binding.js";
13
+ export { atomKey, outputGate } from "./output-gate.js";
14
+ export type { GateAtom, OutputScope, ResolvedOutput, ResolvedToken } from "./resolved-output.js";
15
+ export type { BoundType, BoundVariant } from "./types.js";
16
+ export { formatSolveResult } from "./format.js";
@@ -0,0 +1,50 @@
1
+ import type { BindingRegistry } from "./binding.js";
2
+ import type { GateAtom, ResolvedOutput } from "./resolved-output.js";
3
+
4
+ /**
5
+ * The unified wrapper sequence for a single output: the scope's gate, then for
6
+ * each ref token both the ref binding's path-gate and (if its type is itself
7
+ * "thick" - nullable or iterable) a self-atom on the ref binding. Atoms are
8
+ * deduped while preserving first-occurrence order.
9
+ *
10
+ * The self-atom encodes facts derivable from `Binding.type` alone:
11
+ * - `optional`, `bool`, `count` -> `present(binding)` (value may be absent)
12
+ * - `list` -> `iter(binding)` (iterate per element)
13
+ *
14
+ * `scopeGate` is the gate of the scope's struct binding (often `[]` at the
15
+ * root). Backends nest wrappers in array order; the atom kind decides whether
16
+ * it renders as a guard (`present` / `variant`) or a loop (`iter`).
17
+ */
18
+ export function outputGate(
19
+ scopeGate: GateAtom[],
20
+ output: ResolvedOutput,
21
+ bindings: BindingRegistry,
22
+ ): GateAtom[] {
23
+ const seen = new Set<string>();
24
+ const result: GateAtom[] = [];
25
+ const push = (atom: GateAtom): void => {
26
+ const key = atomKey(atom);
27
+ if (seen.has(key)) return;
28
+ seen.add(key);
29
+ result.push(atom);
30
+ };
31
+ for (const atom of scopeGate) push(atom);
32
+ for (const token of output.tokens) {
33
+ if (token.kind !== "ref") continue;
34
+ const refBinding = bindings.get(token.binding);
35
+ if (!refBinding) continue;
36
+ for (const atom of refBinding.gate) push(atom);
37
+ const kind = refBinding.type.kind;
38
+ if (kind === "optional" || kind === "bool" || kind === "count") {
39
+ push({ kind: "present", binding: refBinding.id });
40
+ } else if (kind === "list") {
41
+ push({ kind: "iter", binding: refBinding.id });
42
+ }
43
+ }
44
+ return result;
45
+ }
46
+
47
+ export function atomKey(atom: GateAtom): string {
48
+ if (atom.kind === "variant") return `v:${atom.binding}:${atom.variant}`;
49
+ return `${atom.kind[0]}:${atom.binding}`;
50
+ }
@@ -0,0 +1,56 @@
1
+ import type { Documentation, MediaTypeIdentifier } from "../ir/types.js";
2
+ import type { BindingId } from "./binding.js";
3
+
4
+ /**
5
+ * Resolved token in an output path template. Refs point at solved bindings;
6
+ * literals are emitted verbatim.
7
+ */
8
+ export type ResolvedToken =
9
+ | { kind: "literal"; value: string }
10
+ | {
11
+ kind: "ref";
12
+ binding: BindingId;
13
+ stripExtensions?: string[];
14
+ fallback?: string;
15
+ };
16
+
17
+ /**
18
+ * One wrapper layer on a binding's path from the root. Atoms are stored
19
+ * root-to-leaf in `Binding.gate`; backends nest wrappers in array order.
20
+ *
21
+ * - `present`: the binding is inside the active branch of `binding` (a non-null
22
+ * `optional`, `true` for a `bool`, `> 0` for a `count`). Reads as a boolean
23
+ * guard at codegen.
24
+ * - `variant`: the binding is inside the `variant` arm of the union bound by
25
+ * `binding`. Reads as a discriminator check.
26
+ * - `iter`: the binding is inside a `repeat` whose value is a `list`. Reads as
27
+ * a per-element loop variable bound to `binding`.
28
+ */
29
+ export type GateAtom =
30
+ | { kind: "present"; binding: BindingId }
31
+ | { kind: "variant"; binding: BindingId; variant: string }
32
+ | { kind: "iter"; binding: BindingId };
33
+
34
+ /**
35
+ * Output specification translated against the binding registry. Pure template
36
+ * data: a name and a token sequence. Per-output gating is derived at codegen
37
+ * time from the declaring scope's gate plus each ref binding's gate.
38
+ */
39
+ export interface ResolvedOutput {
40
+ name: string;
41
+ doc?: Documentation;
42
+ tokens: ResolvedToken[];
43
+ mediaTypes?: MediaTypeIdentifier[];
44
+ }
45
+
46
+ /**
47
+ * Outputs declared on one struct binding, grouped under that scope. The
48
+ * solver guarantees a binding on every output-carrying sequence (forcing a
49
+ * struct binding even when it would otherwise collapse), so the scope is
50
+ * always a real BindingId. Per-output gating derives from the scope binding's
51
+ * `gate` plus each ref binding's `gate`.
52
+ */
53
+ export interface OutputScope {
54
+ scope: BindingId;
55
+ outputs: ResolvedOutput[];
56
+ }
@@ -0,0 +1,16 @@
1
+ import type { ScalarKind } from "../ir/index.js";
2
+
3
+ export type BoundType =
4
+ | { kind: "scalar"; scalar: ScalarKind }
5
+ | { kind: "bool" }
6
+ | { kind: "count" }
7
+ | { kind: "literal"; value: string | number }
8
+ | { kind: "optional"; inner: BoundType }
9
+ | { kind: "list"; item: BoundType }
10
+ | { kind: "struct"; fields: Record<string, BoundType> }
11
+ | { kind: "union"; variants: BoundVariant[] };
12
+
13
+ export interface BoundVariant {
14
+ name?: string;
15
+ type: BoundType;
16
+ }
@@ -0,0 +1 @@
1
+ export { ArgdumpParser } from "./parser.js";