vouchington-tooling 0.39.0 → 0.40.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.
package/README.md CHANGED
@@ -512,6 +512,18 @@ the dedicated process group to exit after the direct child closes so descendants
512
512
  `isProcessGroupAlive` and `waitForProcessGroupExit` are also available when callers need the same
513
513
  process-group probe and bounded-drain semantics outside a browser session.
514
514
 
515
+ ### Generated arbiter input
516
+
517
+ The `pg-schema-snapshot` export also provides `projectGeneratedArbiterInput(conflict)` for
518
+ `PostgresSqlConflict` facts from `no-mistakes@0.83.0` or newer. It projects normalized key and
519
+ partial-predicate column identities, ordered assignment columns, direct non-EXCLUDED references,
520
+ and assignment indirection. Named constraints and unresolved expression keys have undefined
521
+ `keyColumns`; missing or incomplete expression coverage sets `complete` to false. Callers must
522
+ reject incomplete projections and retain their own snapshot-dependent replay verdict. The adapter
523
+ loads no optional parser peer at runtime and implements no replay-safety policy. Its public
524
+ `GeneratedArbiterConflict` contract is structural, so importing snapshot declarations does not
525
+ require the optional peer either.
526
+
515
527
  ### Contract schema type queries
516
528
 
517
529
  `getExportedTypeFacts` and `getCallRowTypeFacts` use an existing TypeScript `Program` from the
@@ -686,6 +698,16 @@ reexports, and literal dynamic/require loads. Missing or incomplete facts for a
686
698
  throw; external specifiers resolve to `null`. The default file inventory limit is 10,000
687
699
  files and can be reduced with `maxFiles`.
688
700
 
701
+ ## Static schema-allowlist pairs
702
+
703
+ `extractStaticStringPairs(source)` from `pg-schema-snapshot` collects the first two string literals
704
+ of every array expression in ESTree field traversal order, including arrays with extra elements.
705
+ Conditional alternatives precede consequents, preserving the existing parser walker. It
706
+ preserves duplicates and decoded string values. It does not evaluate imports, exported constants,
707
+ spreads, or templates, and malformed source throws. Schema-allowlist consumers retain their own
708
+ file selection, key filtering, deduplication, snapshot comparison, and diagnostics. Parsing is
709
+ source-only and does not build a TypeScript compiler program or execute the source.
710
+
689
711
  ## SQL view declarations
690
712
 
691
713
  `vouchington-tooling/sql-ast` exports `extractViewDeclarations(sql)` and the
@@ -0,0 +1,49 @@
1
+ /** Minimal structural facts; public declarations do not require the optional SQL parser peer. */
2
+ export type GeneratedArbiterName = {
3
+ parts: readonly {
4
+ identity: string;
5
+ }[];
6
+ };
7
+ type GeneratedArbiterRoot = {
8
+ kind: 'columnReference';
9
+ name: GeneratedArbiterName;
10
+ } | {
11
+ kind: 'parenthesized';
12
+ expression: GeneratedArbiterRoot;
13
+ } | {
14
+ kind: 'functionCall' | 'cast' | 'nullTest' | 'distinctness' | 'parameter' | 'typedLiteral' | 'literal' | 'binary' | 'unary' | 'case' | 'subquery' | 'other';
15
+ };
16
+ export type GeneratedArbiterExpression = {
17
+ columns: readonly GeneratedArbiterName[];
18
+ root: GeneratedArbiterRoot;
19
+ childrenComplete?: boolean;
20
+ };
21
+ export type GeneratedArbiterConflict = {
22
+ target: {
23
+ kind: 'columns';
24
+ columns: readonly {
25
+ identity: string;
26
+ }[];
27
+ } | {
28
+ kind: 'expressions';
29
+ expressions: readonly GeneratedArbiterExpression[];
30
+ } | {
31
+ kind: 'constraint' | 'omitted';
32
+ };
33
+ predicate: GeneratedArbiterExpression | null;
34
+ action: {
35
+ kind: 'doNothing';
36
+ } | {
37
+ kind: 'doUpdate';
38
+ assignments: readonly {
39
+ columns: readonly GeneratedArbiterName[];
40
+ expression: GeneratedArbiterExpression;
41
+ complete: boolean;
42
+ target?: {
43
+ subscripts: readonly unknown[];
44
+ indirection?: readonly unknown[];
45
+ };
46
+ }[];
47
+ };
48
+ };
49
+ export {};
@@ -0,0 +1,21 @@
1
+ import type { GeneratedArbiterConflict } from './generated-arbiter-facts.mts';
2
+ export type GeneratedArbiterAssignmentInput = {
3
+ columns: readonly string[];
4
+ /** A direct column reference, excluding the two-part EXCLUDED namespace. */
5
+ referencedColumn: string | undefined;
6
+ hasIndirection: boolean;
7
+ complete: boolean;
8
+ };
9
+ export type GeneratedArbiterInput = {
10
+ action: 'doNothing' | 'doUpdate';
11
+ /** Undefined for named constraints, omitted keys, or unresolved expression keys. */
12
+ keyColumns: ReadonlySet<string> | undefined;
13
+ predicateColumns: ReadonlySet<string>;
14
+ assignments: readonly GeneratedArbiterAssignmentInput[];
15
+ /** False means callers must reject rather than infer replay safety from partial facts. */
16
+ complete: boolean;
17
+ };
18
+ /** Projects released SQL facts for a snapshot-dependent generated-column replay verdict.
19
+ * This reports syntax and identities only; callers own protection and safety policy.
20
+ */
21
+ export declare function projectGeneratedArbiterInput(conflict: GeneratedArbiterConflict): GeneratedArbiterInput;
@@ -0,0 +1,73 @@
1
+ /** Projects released SQL facts for a snapshot-dependent generated-column replay verdict.
2
+ * This reports syntax and identities only; callers own protection and safety policy.
3
+ */
4
+ export function projectGeneratedArbiterInput(conflict) {
5
+ let complete = true;
6
+ const predicateColumns = new Set();
7
+ if (conflict.predicate) {
8
+ complete = expressionComplete(conflict.predicate);
9
+ collectColumns(conflict.predicate, predicateColumns);
10
+ }
11
+ let keyColumns;
12
+ if (conflict.target.kind === 'columns' && conflict.target.columns.length > 0) {
13
+ keyColumns = new Set(conflict.target.columns.map((column) => column.identity));
14
+ }
15
+ else if (conflict.target.kind === 'expressions') {
16
+ const columns = new Set();
17
+ let resolved = conflict.target.expressions.length > 0;
18
+ for (const expression of conflict.target.expressions) {
19
+ const references = new Set();
20
+ collectColumns(expression, references);
21
+ const represented = expressionComplete(expression);
22
+ complete &&= represented;
23
+ if (!represented || references.size !== 1)
24
+ resolved = false;
25
+ for (const column of references)
26
+ columns.add(column);
27
+ }
28
+ if (resolved)
29
+ keyColumns = columns;
30
+ }
31
+ const assignments = conflict.action.kind === 'doUpdate'
32
+ ? conflict.action.assignments.map((assignment) => {
33
+ const columns = assignment.columns.map(lastIdentity);
34
+ const represented = assignment.complete &&
35
+ columns.every((column) => column !== undefined) &&
36
+ expressionComplete(assignment.expression);
37
+ complete &&= represented;
38
+ let root = assignment.expression.root;
39
+ while (root.kind === 'parenthesized')
40
+ root = root.expression;
41
+ return {
42
+ columns: columns.filter((column) => column !== undefined),
43
+ referencedColumn: root.kind === 'columnReference' && !isExcluded(root.name)
44
+ ? lastIdentity(root.name)
45
+ : undefined,
46
+ hasIndirection: Boolean(assignment.target &&
47
+ (assignment.target.subscripts.length > 0 ||
48
+ (assignment.target.indirection?.length ?? 0) > 0)),
49
+ complete: represented,
50
+ };
51
+ })
52
+ : [];
53
+ return { action: conflict.action.kind, keyColumns, predicateColumns, assignments, complete };
54
+ }
55
+ function lastIdentity(name) {
56
+ return name.parts.at(-1)?.identity;
57
+ }
58
+ function isExcluded(name) {
59
+ return name.parts.length === 2 && name.parts[0]?.identity === 'excluded';
60
+ }
61
+ function collectColumns(expression, columns) {
62
+ for (const name of expression.columns) {
63
+ const column = lastIdentity(name);
64
+ if (!isExcluded(name) && column !== undefined)
65
+ columns.add(column);
66
+ }
67
+ }
68
+ function expressionComplete(expression) {
69
+ // Older optional peers do not carry completeness; do not silently admit their partial facts.
70
+ return ('childrenComplete' in expression &&
71
+ expression.childrenComplete === true &&
72
+ expression.columns.every((name) => lastIdentity(name) !== undefined));
73
+ }
@@ -1,3 +1,6 @@
1
+ export type { GeneratedArbiterConflict } from './generated-arbiter-facts.mts';
2
+ export { projectGeneratedArbiterInput } from './generated-arbiter-input.mts';
3
+ export type { GeneratedArbiterInput, GeneratedArbiterAssignmentInput, } from './generated-arbiter-input.mts';
1
4
  export { buildSchemaSnapshot, buildTableSnapshots } from './build-snapshot.mts';
2
5
  export { readColumns, readConstraints, readEnums, readExtensions, readFunctions, readIndexes, readPolicies, readSchemaCatalog, readTables, readTriggers, readViews, } from './catalog-queries.mts';
3
6
  export type { CatalogColumnRow, CatalogConstraintRow, CatalogEnumValueRow, CatalogExtensionRow, CatalogFunctionRow, CatalogIndexRow, CatalogPolicyRow, CatalogTableRow, CatalogTriggerRow, CatalogViewRow, SchemaCatalog, } from './catalog-queries.mts';
@@ -10,3 +13,4 @@ export { renderSchemaMarkdown } from './render-markdown.mts';
10
13
  export type { SchemaMarkdownFiles } from './render-markdown.mts';
11
14
  export { renderTableDocument } from './render-tables.mts';
12
15
  export type { CatalogQuery, PartitionPolicy, SchemaColumnSnapshot, SchemaEnumSnapshot, SchemaExtensionSnapshot, SchemaForeignKeySnapshot, SchemaFunctionSnapshot, SchemaGrowthMaps, SchemaIndexKeySnapshot, SchemaIndexSnapshot, SchemaKeyConstraintSnapshot, SchemaPhysicalPartitionSnapshot, SchemaPolicySnapshot, SchemaSnapshot, SchemaTableSnapshot, SchemaViewSnapshot, } from './types.mts';
16
+ export { extractStaticStringPairs } from './static-string-pairs.mts';
@@ -1,3 +1,4 @@
1
+ export { projectGeneratedArbiterInput } from "./generated-arbiter-input.mjs";
1
2
  export { buildSchemaSnapshot, buildTableSnapshots } from "./build-snapshot.mjs";
2
3
  export { readColumns, readConstraints, readEnums, readExtensions, readFunctions, readIndexes, readPolicies, readSchemaCatalog, readTables, readTriggers, readViews, } from "./catalog-queries.mjs";
3
4
  export { generateSchemaSnapshot, stableStringify, writeSchemaSnapshot } from "./generate.mjs";
@@ -5,3 +6,4 @@ export { detectRenamedIndexes, indexShapeKey } from "./index-rename-detect.mjs";
5
6
  export { createPostgresReplayContext, createPostgresReplayContextFromSchema, } from "./replay-context.mjs";
6
7
  export { renderSchemaMarkdown } from "./render-markdown.mjs";
7
8
  export { renderTableDocument } from "./render-tables.mjs";
9
+ export { extractStaticStringPairs } from "./static-string-pairs.mjs";
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Collects the first two string literals of every source array in ESTree field traversal order.
3
+ * Order follows the existing parser walker, including conditional alternatives before consequents.
4
+ * Arrays may contain further elements. This does not evaluate constants, imports, or spreads;
5
+ * repeated pairs remain repeated so callers can apply their own selection and deduplication.
6
+ * Invalid source throws through the source-only parser.
7
+ */
8
+ export declare function extractStaticStringPairs(source: string): readonly (readonly [string, string])[];
@@ -0,0 +1,26 @@
1
+ import { isNode, parseSource, walk } from "../post-publication-inventory/readers/source-ast.mjs";
2
+ /**
3
+ * Collects the first two string literals of every source array in ESTree field traversal order.
4
+ * Order follows the existing parser walker, including conditional alternatives before consequents.
5
+ * Arrays may contain further elements. This does not evaluate constants, imports, or spreads;
6
+ * repeated pairs remain repeated so callers can apply their own selection and deduplication.
7
+ * Invalid source throws through the source-only parser.
8
+ */
9
+ export function extractStaticStringPairs(source) {
10
+ const pairs = [];
11
+ const { ast } = parseSource(source);
12
+ walk(ast, (node) => {
13
+ if (node.type !== 'ArrayExpression' || !Array.isArray(node.elements))
14
+ return;
15
+ const [first, second] = node.elements;
16
+ if (isNode(first) &&
17
+ isNode(second) &&
18
+ first.type === 'Literal' &&
19
+ second.type === 'Literal' &&
20
+ typeof first.value === 'string' &&
21
+ typeof second.value === 'string') {
22
+ pairs.push([first.value, second.value]);
23
+ }
24
+ });
25
+ return pairs;
26
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vouchington-tooling",
3
- "version": "0.39.0",
3
+ "version": "0.40.0",
4
4
  "description": "Vouchington CLI and extractable tooling libraries.",
5
5
  "homepage": "https://github.com/vouchington/vouchington-tooling/tree/main/packages/vouchington-tooling#readme",
6
6
  "bugs": {
@@ -381,7 +381,7 @@
381
381
  "@types/picomatch": "^4.0.3",
382
382
  "@types/spdx-expression-parse": "4.0.0",
383
383
  "agent-blackboard": "^0.6.1",
384
- "no-mistakes": "0.81.0",
384
+ "no-mistakes": "0.83.0",
385
385
  "smol-toml": "1.9.0",
386
386
  "typescript5": "npm:typescript@5.9.3"
387
387
  },