vouchington-tooling 0.38.0 → 0.39.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 +11 -8
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +1 -1
- package/dist/pg-schema-snapshot/index.d.mts +1 -1
- package/dist/pg-schema-snapshot/index.mjs +1 -1
- package/dist/pg-schema-snapshot/replay-context.d.mts +2 -0
- package/dist/pg-schema-snapshot/replay-context.mjs +35 -0
- package/dist/sql-ast/index-shapes.d.mts +6 -29
- package/dist/sql-ast/index-shapes.mjs +22 -79
- package/dist/sql-ast/index.d.mts +0 -4
- package/dist/sql-ast/index.mjs +0 -2
- package/dist/sql-ast/no-mistakes-peer.d.mts +86 -0
- package/dist/sql-ast/no-mistakes-peer.mjs +13 -0
- package/docs/postgres-replay-context.md +7 -1
- package/package.json +4 -4
- package/skills/bounded-iteration/SKILL.md +28 -5
- package/skills/bounded-iteration/references/sweep-patterns.md +375 -5
- package/skills/manifest.json +4 -4
- package/skills/postgres-node-performance-tuning/references/performance-patterns.md +7 -4
- package/skills/postgres-partitioning-uuid-v7/SKILL.md +9 -0
- package/skills/postgres-partitioning-uuid-v7/references/partition-lifecycle.md +36 -0
- package/dist/sql-ast/constraint-do-block.d.mts +0 -11
- package/dist/sql-ast/constraint-do-block.mjs +0 -84
- package/dist/sql-ast/constraint-shared.d.mts +0 -19
- package/dist/sql-ast/constraint-shared.mjs +0 -20
- package/dist/sql-ast/constraint.d.mts +0 -2
- package/dist/sql-ast/constraint.mjs +0 -63
- package/dist/sql-ast/implicit-indexes.d.mts +0 -3
- package/dist/sql-ast/implicit-indexes.mjs +0 -66
- package/dist/sql-ast/index-metadata.d.mts +0 -19
- package/dist/sql-ast/index-metadata.mjs +0 -100
package/README.md
CHANGED
|
@@ -698,14 +698,17 @@ lifecycle decisions.
|
|
|
698
698
|
|
|
699
699
|
## SQL index shapes
|
|
700
700
|
|
|
701
|
-
`vouchington-tooling/sql-ast` exports `extractIndexShapes(sql)`. Await
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
701
|
+
`vouchington-tooling/sql-ast` exports asynchronous `extractIndexShapes(sql)`. Await its result;
|
|
702
|
+
it lazily loads the optional `no-mistakes` peer and requires version 0.81.0 or newer when
|
|
703
|
+
called with nonblank SQL. Other SQL exports do not require this peer. It does not need
|
|
704
|
+
`initSqlAst()`. It returns
|
|
705
|
+
`{ idxname, table, shapeKey }` for each named top-level `CREATE INDEX`. Consumers can group
|
|
706
|
+
identical keys to detect collisions. Keys use upstream structural identity, including effective
|
|
707
|
+
sort/null ordering, quoted relation identity, expressions, predicates, operator classes, and
|
|
708
|
+
`INCLUDE` columns. Key serialization is opaque and must not be persisted across versions.
|
|
709
|
+
Any parse diagnostic rejects the whole input. Anonymous indexes and indexes inside `DO` blocks
|
|
710
|
+
are omitted. Consumer call sites must change from synchronous calls to `await` when adopting
|
|
711
|
+
this release.
|
|
709
712
|
|
|
710
713
|
## Publication writer inventory facts
|
|
711
714
|
|
package/dist/index.d.mts
CHANGED
|
@@ -12,8 +12,8 @@ export { buildSessionFrictionReport, classifyFrictionObservation, FRICTION_LOG_M
|
|
|
12
12
|
export type { FrictionEvent, FrictionEventKind, FrictionLogOptions, FrictionLogReadResult, FrictionObservation, JournalEntry, JournalLoader, JournalLoadResult, PermissionRequestObservation, SessionFrictionReport, SessionFrictionReportOptions, ToolResultObservation, } from './session-friction/index.mts';
|
|
13
13
|
export { EphemeralListenerAttemptsExhaustedError, isRunnerReservedPort, listenOnRunnerUnreservedEphemeralPort, loadRunnerPortPolicy, runnerPortPolicy, validateRunnerPortPolicy, } from './runner-port-policy/index.mts';
|
|
14
14
|
export type { EphemeralListenerOptions, RunnerPortPolicy } from './runner-port-policy/index.mts';
|
|
15
|
-
export {
|
|
16
|
-
export type {
|
|
15
|
+
export { extractCreateTableMetadata, extractDefaultFunction, extractFuncCallArgColumnNames, initSqlAst, lineOfUtf8ByteOffset, MissingSqlAstParserError, parseSql, } from './sql-ast/index.mts';
|
|
16
|
+
export type { SqlCreateTableColumn, SqlCreateTableMetadata } from './sql-ast/index.mts';
|
|
17
17
|
export { dollarQuoteEnd, lineOf, maskSqlQuotedText, readDollarQuoteDelimiter, readStringLiteral, splitSqlStatements, sqlFragments, stripSqlComments, } from './sql-scanner/index.mts';
|
|
18
18
|
export { auditCiJobRuntime, parseWorkflowNameMatch } from './gha-runtime-audit/index.mts';
|
|
19
19
|
export type { GhApiExecutor, RuntimeAuditOptions, RuntimeAuditResult, RuntimeAuditWorkflowFilter, RuntimeJobResult, RuntimeSample, } from './gha-runtime-audit/index.mts';
|
package/dist/index.mjs
CHANGED
|
@@ -6,7 +6,7 @@ export { runRetrospectiveFacts } from "./retrospective-facts/index.mjs";
|
|
|
6
6
|
export { appendJournal, assertSessionId, cleanupSnapshotPartitions, partitionSnapshot, probeBlackboard, readJournal, resolveBlackboardConnection, } from "./agent-blackboard/index.mjs";
|
|
7
7
|
export { buildSessionFrictionReport, classifyFrictionObservation, FRICTION_LOG_MAX_EVENTS, isConformingCiFailureBlock, normalizeCommandPrefix, readFrictionLog, recordFriction, } from "./session-friction/index.mjs";
|
|
8
8
|
export { EphemeralListenerAttemptsExhaustedError, isRunnerReservedPort, listenOnRunnerUnreservedEphemeralPort, loadRunnerPortPolicy, runnerPortPolicy, validateRunnerPortPolicy, } from "./runner-port-policy/index.mjs";
|
|
9
|
-
export {
|
|
9
|
+
export { extractCreateTableMetadata, extractDefaultFunction, extractFuncCallArgColumnNames, initSqlAst, lineOfUtf8ByteOffset, MissingSqlAstParserError, parseSql, } from "./sql-ast/index.mjs";
|
|
10
10
|
export { dollarQuoteEnd, lineOf, maskSqlQuotedText, readDollarQuoteDelimiter, readStringLiteral, splitSqlStatements, sqlFragments, stripSqlComments, } from "./sql-scanner/index.mjs";
|
|
11
11
|
export { auditCiJobRuntime, parseWorkflowNameMatch } from "./gha-runtime-audit/index.mjs";
|
|
12
12
|
export { checkGhaWorkspacePolicy } from "./gha-workspace-policy/index.mjs";
|
|
@@ -4,7 +4,7 @@ export type { CatalogColumnRow, CatalogConstraintRow, CatalogEnumValueRow, Catal
|
|
|
4
4
|
export { generateSchemaSnapshot, stableStringify, writeSchemaSnapshot } from './generate.mts';
|
|
5
5
|
export { detectRenamedIndexes, indexShapeKey } from './index-rename-detect.mts';
|
|
6
6
|
export type { RenamedIndex, SchemaIndexRenameSnapshot } from './index-rename-detect.mts';
|
|
7
|
-
export { createPostgresReplayContext } from './replay-context.mts';
|
|
7
|
+
export { createPostgresReplayContext, createPostgresReplayContextFromSchema, } from './replay-context.mts';
|
|
8
8
|
export type { GeneratedColumnReferences, PostgresReplayContext } from './replay-context.mts';
|
|
9
9
|
export { renderSchemaMarkdown } from './render-markdown.mts';
|
|
10
10
|
export type { SchemaMarkdownFiles } from './render-markdown.mts';
|
|
@@ -2,6 +2,6 @@ export { buildSchemaSnapshot, buildTableSnapshots } from "./build-snapshot.mjs";
|
|
|
2
2
|
export { readColumns, readConstraints, readEnums, readExtensions, readFunctions, readIndexes, readPolicies, readSchemaCatalog, readTables, readTriggers, readViews, } from "./catalog-queries.mjs";
|
|
3
3
|
export { generateSchemaSnapshot, stableStringify, writeSchemaSnapshot } from "./generate.mjs";
|
|
4
4
|
export { detectRenamedIndexes, indexShapeKey } from "./index-rename-detect.mjs";
|
|
5
|
-
export { createPostgresReplayContext } from "./replay-context.mjs";
|
|
5
|
+
export { createPostgresReplayContext, createPostgresReplayContextFromSchema, } from "./replay-context.mjs";
|
|
6
6
|
export { renderSchemaMarkdown } from "./render-markdown.mjs";
|
|
7
7
|
export { renderTableDocument } from "./render-tables.mjs";
|
|
@@ -11,6 +11,8 @@ export type PostgresReplayContext = {
|
|
|
11
11
|
/** Memoized per table; unknown tables return undefined and empty dependency sets return an empty map. */
|
|
12
12
|
generatedDependenciesForTable(table: string): ReadonlyMap<string, ReadonlySet<string>> | undefined;
|
|
13
13
|
};
|
|
14
|
+
/** Collects generated-expression facts with the optional SQL peer, once per snapshot. */
|
|
15
|
+
export declare function createPostgresReplayContextFromSchema(schema: SchemaSnapshot): Promise<PostgresReplayContext>;
|
|
14
16
|
/**
|
|
15
17
|
* Projects injected schema snapshot data and parser-derived generated-expression references for
|
|
16
18
|
* replay checks. The caller owns snapshot loading and SQL parsing. Every STORED generated column
|
|
@@ -1,3 +1,38 @@
|
|
|
1
|
+
import { loadPostgresParser } from "../sql-ast/no-mistakes-peer.mjs";
|
|
2
|
+
/** Collects generated-expression facts with the optional SQL peer, once per snapshot. */
|
|
3
|
+
export async function createPostgresReplayContextFromSchema(schema) {
|
|
4
|
+
const definitions = Object.entries(schema.tables).flatMap(([table, snapshotTable]) => Object.entries(snapshotTable.columns).flatMap(([column, definition]) => definition.generated === 'stored' && definition.generatedExpression !== null
|
|
5
|
+
? [{ table, column, expression: definition.generatedExpression }]
|
|
6
|
+
: []));
|
|
7
|
+
if (definitions.length === 0) {
|
|
8
|
+
return createPostgresReplayContext({ schema, generatedColumnReferences: [] });
|
|
9
|
+
}
|
|
10
|
+
const { parsePostgresSql } = await loadPostgresParser('createPostgresReplayContextFromSchema');
|
|
11
|
+
const parsed = await parsePostgresSql(definitions.map(({ table, column, expression }) => ({
|
|
12
|
+
sql: `SELECT (${expression})`,
|
|
13
|
+
fileName: `schema-snapshot:${table}.${column}`,
|
|
14
|
+
})));
|
|
15
|
+
const generatedColumnReferences = definitions.map(({ table, column }, index) => {
|
|
16
|
+
const facts = parsed[index];
|
|
17
|
+
const statement = facts?.statements[0];
|
|
18
|
+
if (!facts ||
|
|
19
|
+
facts.diagnostics.length > 0 ||
|
|
20
|
+
facts.statements.length !== 1 ||
|
|
21
|
+
statement?.kind !== 'select' ||
|
|
22
|
+
!statement.query.complete ||
|
|
23
|
+
statement.query.unsupported.length > 0) {
|
|
24
|
+
throw new Error(`Unable to collect generated-column references for ${table}.${column}`);
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
table,
|
|
28
|
+
column,
|
|
29
|
+
sourceColumns: [
|
|
30
|
+
...new Set(statement.query.columns.map((reference) => reference.name.parts.at(-1).identity)),
|
|
31
|
+
],
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
return createPostgresReplayContext({ schema, generatedColumnReferences });
|
|
35
|
+
}
|
|
1
36
|
/**
|
|
2
37
|
* Projects injected schema snapshot data and parser-derived generated-expression references for
|
|
3
38
|
* replay checks. The caller owns snapshot loading and SQL parsing. Every STORED generated column
|
|
@@ -1,34 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* indexed column/expression with its *effective* sort order and null ordering, and the predicate
|
|
7
|
-
* with position-dependent `location` fields stripped) rather than from raw source text. This
|
|
8
|
-
* canonicalizes cosmetic syntax variance that leaves the physical index unchanged:
|
|
9
|
-
* - implicit vs. explicit `ASC` (`SORTBY_DEFAULT` is normalized to `SORTBY_ASC`, PostgreSQL's
|
|
10
|
-
* actual default for btree ordering);
|
|
11
|
-
* - implicit vs. explicit `NULLS FIRST`/`NULLS LAST` (PostgreSQL's real default depends on sort
|
|
12
|
-
* direction — `NULLS LAST` for ascending, `NULLS FIRST` for descending — so
|
|
13
|
-
* `SORTBY_NULLS_DEFAULT` is resolved against the effective ordering rather than compared as-is);
|
|
14
|
-
* - redundant parentheses in a predicate or expression (parentheses are pure grouping and leave no
|
|
15
|
-
* trace in the parsed AST once `location` offsets — which shift under added parens even though
|
|
16
|
-
* the tree shape is unchanged — are stripped before comparison).
|
|
17
|
-
*
|
|
18
|
-
* Operator classes, collations, and INCLUDE columns are intentionally NOT modeled; callers whose
|
|
19
|
-
* generators use them must extend the projection before using this collision policy for those SQL.
|
|
20
|
-
*
|
|
21
|
-
* @libpg-query/parser has no deparse/SQL-regeneration function, so this hashes a normalized JSON
|
|
22
|
-
* projection of the relevant AST subtrees instead of reconstructing SQL text.
|
|
23
|
-
*
|
|
24
|
-
* DO $$ ... END $$; blocks are opaque to the top-level parser — they appear as a single DoStmt,
|
|
25
|
-
* not individual IndexStmt nodes — so nested CREATE INDEX statements are not covered.
|
|
26
|
-
*
|
|
27
|
-
* Unparseable SQL throws rather than returning an empty shape list.
|
|
28
|
-
* Requires initSqlAst() to have resolved before the first call.
|
|
2
|
+
* Returns collision keys for named top-level CREATE INDEX statements using released SQL facts.
|
|
3
|
+
* Effective ordering, expressions, predicates, operator classes, and INCLUDE columns participate
|
|
4
|
+
* in the upstream structural identity. DO blocks remain opaque to this top-level projection.
|
|
5
|
+
* Any parse diagnostic rejects the complete input instead of returning partial collision facts.
|
|
29
6
|
*/
|
|
30
|
-
export declare function extractIndexShapes(sql: string): {
|
|
7
|
+
export declare function extractIndexShapes(sql: string): Promise<{
|
|
31
8
|
idxname: string;
|
|
32
9
|
table: string;
|
|
33
10
|
shapeKey: string;
|
|
34
|
-
}[]
|
|
11
|
+
}[]>;
|
|
@@ -1,85 +1,28 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { loadPostgresParser } from "./no-mistakes-peer.mjs";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* indexed column/expression with its *effective* sort order and null ordering, and the predicate
|
|
8
|
-
* with position-dependent `location` fields stripped) rather than from raw source text. This
|
|
9
|
-
* canonicalizes cosmetic syntax variance that leaves the physical index unchanged:
|
|
10
|
-
* - implicit vs. explicit `ASC` (`SORTBY_DEFAULT` is normalized to `SORTBY_ASC`, PostgreSQL's
|
|
11
|
-
* actual default for btree ordering);
|
|
12
|
-
* - implicit vs. explicit `NULLS FIRST`/`NULLS LAST` (PostgreSQL's real default depends on sort
|
|
13
|
-
* direction — `NULLS LAST` for ascending, `NULLS FIRST` for descending — so
|
|
14
|
-
* `SORTBY_NULLS_DEFAULT` is resolved against the effective ordering rather than compared as-is);
|
|
15
|
-
* - redundant parentheses in a predicate or expression (parentheses are pure grouping and leave no
|
|
16
|
-
* trace in the parsed AST once `location` offsets — which shift under added parens even though
|
|
17
|
-
* the tree shape is unchanged — are stripped before comparison).
|
|
18
|
-
*
|
|
19
|
-
* Operator classes, collations, and INCLUDE columns are intentionally NOT modeled; callers whose
|
|
20
|
-
* generators use them must extend the projection before using this collision policy for those SQL.
|
|
21
|
-
*
|
|
22
|
-
* @libpg-query/parser has no deparse/SQL-regeneration function, so this hashes a normalized JSON
|
|
23
|
-
* projection of the relevant AST subtrees instead of reconstructing SQL text.
|
|
24
|
-
*
|
|
25
|
-
* DO $$ ... END $$; blocks are opaque to the top-level parser — they appear as a single DoStmt,
|
|
26
|
-
* not individual IndexStmt nodes — so nested CREATE INDEX statements are not covered.
|
|
27
|
-
*
|
|
28
|
-
* Unparseable SQL throws rather than returning an empty shape list.
|
|
29
|
-
* Requires initSqlAst() to have resolved before the first call.
|
|
3
|
+
* Returns collision keys for named top-level CREATE INDEX statements using released SQL facts.
|
|
4
|
+
* Effective ordering, expressions, predicates, operator classes, and INCLUDE columns participate
|
|
5
|
+
* in the upstream structural identity. DO blocks remain opaque to this top-level projection.
|
|
6
|
+
* Any parse diagnostic rejects the complete input instead of returning partial collision facts.
|
|
30
7
|
*/
|
|
31
|
-
export function extractIndexShapes(sql) {
|
|
8
|
+
export async function extractIndexShapes(sql) {
|
|
32
9
|
if (!sql.trim())
|
|
33
10
|
return [];
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (!node || !('IndexStmt' in node))
|
|
39
|
-
continue;
|
|
40
|
-
const indexStmt = node.IndexStmt;
|
|
41
|
-
const idxname = indexStmt.idxname;
|
|
42
|
-
if (!idxname)
|
|
43
|
-
continue;
|
|
44
|
-
const table = indexStmt.relation?.relname ?? '';
|
|
45
|
-
const canonical = {
|
|
46
|
-
table,
|
|
47
|
-
unique: Boolean(indexStmt.unique),
|
|
48
|
-
accessMethod: indexStmt.accessMethod ?? 'btree',
|
|
49
|
-
indexParams: (indexStmt.indexParams ?? []).map(normalizeIndexParam),
|
|
50
|
-
whereClause: indexStmt.whereClause ? stripLocations(indexStmt.whereClause) : null,
|
|
51
|
-
};
|
|
52
|
-
shapes.push({ idxname, table, shapeKey: JSON.stringify(canonical) });
|
|
11
|
+
const { parsePostgresSql } = await loadPostgresParser('extractIndexShapes');
|
|
12
|
+
const facts = await parsePostgresSql({ sql });
|
|
13
|
+
if (facts.diagnostics.length > 0) {
|
|
14
|
+
throw new Error(`SQL syntax error: ${facts.diagnostics.map(({ message }) => message).join('; ')}`);
|
|
53
15
|
}
|
|
54
|
-
return
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return {
|
|
67
|
-
key: elem.name ?? stripLocations(elem.expr),
|
|
68
|
-
ordering,
|
|
69
|
-
nullsOrdering,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
function stripLocations(value) {
|
|
73
|
-
if (Array.isArray(value))
|
|
74
|
-
return value.map(stripLocations);
|
|
75
|
-
if (value && typeof value === 'object') {
|
|
76
|
-
const out = {};
|
|
77
|
-
for (const [key, val] of Object.entries(value)) {
|
|
78
|
-
if (key === 'location')
|
|
79
|
-
continue;
|
|
80
|
-
out[key] = stripLocations(val);
|
|
81
|
-
}
|
|
82
|
-
return out;
|
|
83
|
-
}
|
|
84
|
-
return value;
|
|
16
|
+
return facts.statements.flatMap((statement) => {
|
|
17
|
+
if (statement.kind !== 'createIndex' || !statement.index.name)
|
|
18
|
+
return [];
|
|
19
|
+
const { index } = statement;
|
|
20
|
+
return [
|
|
21
|
+
{
|
|
22
|
+
idxname: index.name.parts.at(-1).value,
|
|
23
|
+
table: index.table.parts.at(-1).value,
|
|
24
|
+
shapeKey: index.structuralIdentity,
|
|
25
|
+
},
|
|
26
|
+
];
|
|
27
|
+
});
|
|
85
28
|
}
|
package/dist/sql-ast/index.d.mts
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
export { extractMigrationConstraintMetadata } from './constraint.mts';
|
|
2
|
-
export type { ForeignKey, SqlMigrationConstraintMetadata } from './constraint-shared.mts';
|
|
3
1
|
export { extractCreateTableMetadata } from './create-table.mts';
|
|
4
2
|
export type { SqlCreateTableColumn, SqlCreateTableColumnConstraint, SqlCreateTableMetadata, } from './create-table.mts';
|
|
5
3
|
export { extractDefaultFunction, extractFuncCallArgColumnNames } from './default-function.mts';
|
|
6
|
-
export { extractCreateIndexMetadata } from './index-metadata.mts';
|
|
7
4
|
export { extractIndexShapes } from './index-shapes.mts';
|
|
8
|
-
export type { SqlCreateIndexMetadata, SqlIndexParam } from './index-metadata.mts';
|
|
9
5
|
export { lineOfUtf8ByteOffset } from './line-of-offset.mts';
|
|
10
6
|
export { initSqlAst, MissingSqlAstParserError, parseSql } from './parser.mts';
|
|
11
7
|
export { extractViewDeclarations } from './view-declarations.mts';
|
package/dist/sql-ast/index.mjs
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
export { extractMigrationConstraintMetadata } from "./constraint.mjs";
|
|
2
1
|
export { extractCreateTableMetadata } from "./create-table.mjs";
|
|
3
2
|
export { extractDefaultFunction, extractFuncCallArgColumnNames } from "./default-function.mjs";
|
|
4
|
-
export { extractCreateIndexMetadata } from "./index-metadata.mjs";
|
|
5
3
|
export { extractIndexShapes } from "./index-shapes.mjs";
|
|
6
4
|
export { lineOfUtf8ByteOffset } from "./line-of-offset.mjs";
|
|
7
5
|
export { initSqlAst, MissingSqlAstParserError, parseSql } from "./parser.mjs";
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export declare function loadPostgresParser(caller: string): Promise<{
|
|
2
|
+
default: typeof import("no-mistakes");
|
|
3
|
+
analyzeTypeScriptModules(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").TypeScriptModulesOptions>): Promise<import("no-mistakes").TypeScriptModulesReport>;
|
|
4
|
+
parsePostgresSql(source: import("no-mistakes").PostgresSqlSource): Promise<import("no-mistakes").PostgresSqlFacts>;
|
|
5
|
+
parsePostgresSql(sources: import("no-mistakes").PostgresSqlSource[]): Promise<import("no-mistakes").PostgresSqlFacts[]>;
|
|
6
|
+
dependencies(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").TraversePathsOptions>): Promise<import("no-mistakes").ImportClosureResult>;
|
|
7
|
+
dependencies(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").TraverseGraphOptions>): Promise<import("no-mistakes").DependencyResult>;
|
|
8
|
+
dependents(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").TraverseOptions>): Promise<import("no-mistakes").DependencyResult>;
|
|
9
|
+
related(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").TraverseOptions>): Promise<import("no-mistakes").DependencyResult>;
|
|
10
|
+
analyzeProject(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").AnalyzeProjectOptions>): Promise<import("no-mistakes").AnalyzeProjectResult>;
|
|
11
|
+
writePlanningImpactArtifacts(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").WritePlanningImpactArtifactsOptions>): Promise<import("no-mistakes").PlanningImpactArtifacts>;
|
|
12
|
+
symbols(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").SymbolsSignatureImpactOptions>): Promise<import("no-mistakes").SignatureImpactResult>;
|
|
13
|
+
symbols(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").SymbolsListOptions>): Promise<import("no-mistakes").SymbolsResult>;
|
|
14
|
+
symbols(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").SymbolsOptions>): Promise<import("no-mistakes").SymbolsResult | import("no-mistakes").SignatureImpactResult>;
|
|
15
|
+
importUsages(options?: import("no-mistakes").WithInvocationOptions<import("no-mistakes").ImportUsagesOptions>): Promise<import("no-mistakes").ImportUsagesResult>;
|
|
16
|
+
importers(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").ImportersOptions>): Promise<import("no-mistakes").ImportersResult>;
|
|
17
|
+
exportsOf(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").ExportsOfOptions>): Promise<import("no-mistakes").ExportsOfResult>;
|
|
18
|
+
deadExports(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").DeadExportsOptions>): Promise<import("no-mistakes").DeadExportsResult>;
|
|
19
|
+
callSites(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").CallSitesOptions>): Promise<import("no-mistakes").CallSitesResult>;
|
|
20
|
+
resolveCheck(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").ResolveCheckOptions>): Promise<import("no-mistakes").ResolveCheckResult>;
|
|
21
|
+
resolveCheck(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").ResolveCheckFilesOptions>): Promise<import("no-mistakes").ResolveCheckBatchResult>;
|
|
22
|
+
fetches(options?: import("no-mistakes").WithInvocationOptions<import("no-mistakes").FetchesOptions>): Promise<import("no-mistakes").FetchReport>;
|
|
23
|
+
flow(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").FlowOptions>): Promise<import("no-mistakes").FlowReport>;
|
|
24
|
+
check(options?: import("no-mistakes").WithInvocationOptions<import("no-mistakes").CheckOptions>): Promise<import("no-mistakes").CheckReport>;
|
|
25
|
+
resolveConfig(options?: import("no-mistakes").WithInvocationOptions<import("no-mistakes").ProjectOptions>): Promise<import("no-mistakes").ResolvedConfig>;
|
|
26
|
+
validateMermaidMarkdown(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").MermaidValidationOptions>): Promise<import("no-mistakes").MermaidValidationResult>;
|
|
27
|
+
testsPlan(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").TestsPlanOptions>): Promise<import("no-mistakes").TestPlan>;
|
|
28
|
+
testsImpact(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").TestsImpactOptions>): Promise<import("no-mistakes").TestPlan>;
|
|
29
|
+
testsTargets(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").TestsTargetsOptions>): Promise<import("no-mistakes").TestsTargetsReport>;
|
|
30
|
+
testsWhy(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").TestsWhyOptions>): Promise<Record<string, import("no-mistakes").WhyStep[]>>;
|
|
31
|
+
testsComment(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").TestsPlanDocumentOptions>): Promise<string>;
|
|
32
|
+
testsGraph(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").TestsPlanDocumentOptions>): Promise<import("no-mistakes").TestGraph>;
|
|
33
|
+
testsGraphMermaid(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").TestsPlanDocumentOptions>): Promise<string>;
|
|
34
|
+
playwrightCheck(options?: import("no-mistakes").WithInvocationOptions<import("no-mistakes").PlaywrightOptions>): Promise<unknown>;
|
|
35
|
+
playwrightEdges(options?: import("no-mistakes").WithInvocationOptions<import("no-mistakes").PlaywrightOptions>): Promise<unknown>;
|
|
36
|
+
playwrightRelated(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").PlaywrightRelatedOptions>): Promise<unknown>;
|
|
37
|
+
playwrightTests(options?: import("no-mistakes").WithInvocationOptions<import("no-mistakes").PlaywrightOptions>): Promise<unknown>;
|
|
38
|
+
queues(options?: import("no-mistakes").WithInvocationOptions<import("no-mistakes").ProjectOptions>): Promise<import("no-mistakes").QueueReport>;
|
|
39
|
+
queueEdges(options?: import("no-mistakes").WithInvocationOptions<import("no-mistakes").ProjectOptions>): Promise<import("no-mistakes").QueueEdge[]>;
|
|
40
|
+
queueRelated(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").ProjectOptions>): Promise<import("no-mistakes").QueueEdge[]>;
|
|
41
|
+
queueCheck(options?: import("no-mistakes").WithInvocationOptions<import("no-mistakes").ProjectOptions>): Promise<import("no-mistakes").QueueCheckFinding[]>;
|
|
42
|
+
serverRoutes(options?: import("no-mistakes").WithInvocationOptions<import("no-mistakes").ProjectOptions>): Promise<import("no-mistakes").ServerRoutesReport>;
|
|
43
|
+
serverRouteList(options?: import("no-mistakes").WithInvocationOptions<import("no-mistakes").ProjectOptions>): Promise<unknown[]>;
|
|
44
|
+
serverRouteEdges(options?: import("no-mistakes").WithInvocationOptions<import("no-mistakes").ProjectOptions>): Promise<import("no-mistakes").GraphEdge[]>;
|
|
45
|
+
serverRouteRelated(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").ProjectOptions>): Promise<import("no-mistakes").GraphEdge[]>;
|
|
46
|
+
serverContracts(options?: import("no-mistakes").WithInvocationOptions<import("no-mistakes").ProjectOptions>): Promise<import("no-mistakes").ServerContractsReport>;
|
|
47
|
+
reactAnalyze(options?: import("no-mistakes").WithInvocationOptions<import("no-mistakes").ProjectOptions>): Promise<import("no-mistakes").ReactComponentFacts[]>;
|
|
48
|
+
reactCheck(options?: import("no-mistakes").WithInvocationOptions<import("no-mistakes").ProjectOptions>): Promise<import("no-mistakes").ReactViolation[]>;
|
|
49
|
+
reactUsages(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").ProjectOptions & {
|
|
50
|
+
target: string;
|
|
51
|
+
}>): Promise<import("no-mistakes").ReactUsagesReport>;
|
|
52
|
+
generatePostgresCatalog(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").PostgresCatalogOptions & {
|
|
53
|
+
coverage: "ordering";
|
|
54
|
+
}>): Promise<import("no-mistakes").PostgresOrderingCatalog>;
|
|
55
|
+
generatePostgresCatalog(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").PostgresCatalogOptions & {
|
|
56
|
+
coverage?: "complete";
|
|
57
|
+
}>): Promise<import("no-mistakes").PostgresCompleteCatalog>;
|
|
58
|
+
generatePostgresCatalog(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").PostgresCatalogOptions>): Promise<import("no-mistakes").PostgresCatalog>;
|
|
59
|
+
lockfileDiff(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").LockfileDiffOptions>): Promise<import("no-mistakes").LockfileDiffEntry[]>;
|
|
60
|
+
ciImpact(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").CiImpactOptions>): Promise<import("no-mistakes").CiImpactReport>;
|
|
61
|
+
ciEnv(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").CiEnvOptions>): Promise<import("no-mistakes").CiEnvReport>;
|
|
62
|
+
ciTopology(options?: import("no-mistakes").WithInvocationOptions<import("no-mistakes").CiTopologyOptions>): Promise<import("no-mistakes").WorkflowTopology>;
|
|
63
|
+
ciTopologyImpact(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").CiTopologyImpactOptions>): Promise<import("no-mistakes").CiTopologyImpactReport>;
|
|
64
|
+
createWorkflowTopologyIndex(topology: import("no-mistakes").WorkflowTopology): import("no-mistakes").WorkflowTopologyIndex;
|
|
65
|
+
impactedChecks(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").ImpactedChecksOptions>): Promise<import("no-mistakes").ImpactedChecksReport>;
|
|
66
|
+
dataPw(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").DataPwOptions>): Promise<import("no-mistakes").DataPwReport>;
|
|
67
|
+
effects(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").EffectsOptions>): Promise<import("no-mistakes").EffectsReport>;
|
|
68
|
+
rscCallers(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").RscCallersOptions>): Promise<import("no-mistakes").RscCallersReport>;
|
|
69
|
+
registryExtension(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").RegistryExtensionOptions>): Promise<import("no-mistakes").RegistryExtensionReport>;
|
|
70
|
+
infraResourceRefs(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").InfraOptions & {
|
|
71
|
+
address: string;
|
|
72
|
+
}>): Promise<import("no-mistakes").ResourceRefRow[]>;
|
|
73
|
+
infraOutputs(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").InfraOptions & {
|
|
74
|
+
moduleDir: string;
|
|
75
|
+
}>): Promise<import("no-mistakes").ModuleOutputsResult>;
|
|
76
|
+
infraTestFor(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").InfraOptions & {
|
|
77
|
+
tfFile: string;
|
|
78
|
+
}>): Promise<import("no-mistakes").TestForRow[]>;
|
|
79
|
+
swiftImporters(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").SwiftOptions & {
|
|
80
|
+
file: string;
|
|
81
|
+
}>): Promise<import("no-mistakes").SwiftImporterRow[]>;
|
|
82
|
+
swiftTestTargets(options: import("no-mistakes").WithInvocationOptions<import("no-mistakes").SwiftOptions & {
|
|
83
|
+
file: string;
|
|
84
|
+
}>): Promise<import("no-mistakes").SwiftTestTargetRow[]>;
|
|
85
|
+
version(): Promise<string>;
|
|
86
|
+
}>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
export async function loadPostgresParser(caller) {
|
|
3
|
+
const entry = import.meta.resolve('no-mistakes');
|
|
4
|
+
const metadata = JSON.parse(await readFile(new URL('package.json', entry), 'utf8'));
|
|
5
|
+
const version = /^(\d+)\.(\d+)\.(\d+)(-[\w.-]+)?(?:\+[\w.-]+)?$/.exec(metadata.version);
|
|
6
|
+
const major = Number(version?.[1]);
|
|
7
|
+
const minor = Number(version?.[2]);
|
|
8
|
+
const patch = Number(version?.[3]);
|
|
9
|
+
if (!version || (major === 0 && (minor < 81 || (minor === 81 && patch === 0 && version[4])))) {
|
|
10
|
+
throw new Error(`${caller} requires no-mistakes >=0.81.0`);
|
|
11
|
+
}
|
|
12
|
+
return import('no-mistakes');
|
|
13
|
+
}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# PostgreSQL replay context
|
|
2
2
|
|
|
3
|
+
`await createPostgresReplayContextFromSchema(schema)` collects STORED generated-expression
|
|
4
|
+
references using the optional `no-mistakes >=0.81.0` SQL peer, then returns the memoized replay context.
|
|
5
|
+
It rejects malformed expressions, additional statements, and unsupported queries instead of
|
|
6
|
+
treating missing facts as an empty dependency set. Snapshots without STORED expressions do not
|
|
7
|
+
load the peer. An incompatible peer is rejected before loading its parser. Snapshot loading and INSERT policy remain the caller's responsibility.
|
|
8
|
+
|
|
3
9
|
`createPostgresReplayContext` projects a caller-owned schema snapshot and generated-column
|
|
4
10
|
reference facts into the two lookups used by replay checks. It does not load snapshots, parse SQL,
|
|
5
11
|
or decide INSERT policy.
|
|
@@ -7,7 +13,7 @@ or decide INSERT policy.
|
|
|
7
13
|
The caller supplies one `{ table, column, sourceColumns }` fact for each snapshot column whose
|
|
8
14
|
`generated` value is `stored` and whose `generatedExpression` is present. Build `sourceColumns` from
|
|
9
15
|
the parser's typed expression column references, retaining each reference's final identifier
|
|
10
|
-
|
|
16
|
+
identity (unquoted names fold to lowercase; quoted names preserve case). Supply an empty array for a valid expression with no column references. Missing facts
|
|
11
17
|
throw when that table's dependencies are requested, so parser failures cannot silently become an
|
|
12
18
|
empty dependency set.
|
|
13
19
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vouchington-tooling",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.39.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": {
|
|
@@ -380,14 +380,14 @@
|
|
|
380
380
|
"@modelcontextprotocol/sdk": "1.31.0",
|
|
381
381
|
"@types/picomatch": "^4.0.3",
|
|
382
382
|
"@types/spdx-expression-parse": "4.0.0",
|
|
383
|
-
"agent-blackboard": "^0.6.
|
|
384
|
-
"no-mistakes": "0.
|
|
383
|
+
"agent-blackboard": "^0.6.1",
|
|
384
|
+
"no-mistakes": "0.81.0",
|
|
385
385
|
"smol-toml": "1.9.0",
|
|
386
386
|
"typescript5": "npm:typescript@5.9.3"
|
|
387
387
|
},
|
|
388
388
|
"peerDependencies": {
|
|
389
389
|
"@modelcontextprotocol/sdk": "^1.31.0",
|
|
390
|
-
"agent-blackboard": "^0.6.
|
|
390
|
+
"agent-blackboard": "^0.6.1",
|
|
391
391
|
"no-mistakes": "*",
|
|
392
392
|
"typescript": ">=5",
|
|
393
393
|
"vitest": ">=5"
|
|
@@ -15,6 +15,10 @@ A run's cost is the rows it reads and the work it starts, not the memory it hold
|
|
|
15
15
|
serves: a work-item or dirty-marker row, a parent id, a due timestamp with a partial index, or
|
|
16
16
|
a time or UUIDv7 window past a high-water mark. Column-to-column comparisons, `OR` of unrelated
|
|
17
17
|
conditions, and `EXISTS` as the only condition are not filters. (`postgres-required-predicates`)
|
|
18
|
+
A per-row threshold becomes one bucket per distinct value, comparing the stored fact with a
|
|
19
|
+
bound, never with another column; `timestamptz + interval` is not immutable, so it cannot be
|
|
20
|
+
indexed. See [per-row interval](references/sweep-patterns.md#per-row-interval-one-bucket-per-threshold)
|
|
21
|
+
and [age milestones](references/sweep-patterns.md#age-milestones-from-the-uuidv7-key).
|
|
18
22
|
2. **Cap every run.** Every loop has a per-run cap (batches, rows, or a deadline), reports whether
|
|
19
23
|
work remains, and resumes from a persisted position. Never loop until empty, and never default
|
|
20
24
|
a cap to infinity.
|
|
@@ -36,12 +40,31 @@ A run's cost is the rows it reads and the work it starts, not the memory it hold
|
|
|
36
40
|
8. **Tunables are runtime configuration.** Batch sizes, page sizes, caps, and windows come from
|
|
37
41
|
the consumer's runtime configuration with a validated default and a hard maximum, and are bound
|
|
38
42
|
as SQL parameters. `LIMIT 1` and an external API's page maximum are contracts, not tunables.
|
|
39
|
-
9. **
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
9. **One dispatcher per signal.** Split an `OR` of unrelated reasons into one dispatcher per
|
|
44
|
+
reason, each with its own index-served predicate, position, and schedule, all enqueuing the same
|
|
45
|
+
idempotent job. Writes that cause a signal enqueue directly; the sweep is the backstop. See
|
|
46
|
+
[one dispatcher per signal](references/sweep-patterns.md#one-dispatcher-per-signal).
|
|
47
|
+
10. **Deduplicate jobs by entity and triggering event.** A job id keyed only by the entity drops a
|
|
48
|
+
new trigger while the last job runs or is retained. Key it by entity and event; dedup lasts
|
|
49
|
+
only while the record is retained, and a failed retained job still claims its id. See
|
|
50
|
+
[queue job ids](references/sweep-patterns.md#queue-job-ids-for-deduplication).
|
|
51
|
+
11. **Repair through an existing reconciler.** Repeat a fire-and-forget side effect (cache fill,
|
|
52
|
+
filter add, enqueue) idempotently from the reconciler that already sweeps recently changed
|
|
53
|
+
rows. Do not add a sweeper. See
|
|
54
|
+
[repair](references/sweep-patterns.md#repair-through-an-existing-reconciler).
|
|
55
|
+
12. **Membership filters are never rebuilt on a schedule.** Add on write, rebuild in full only on
|
|
56
|
+
an operator action or a detected failure, and stop trusting the filter after a failed add.
|
|
57
|
+
Deleted keys and growth only cost false positives. See
|
|
58
|
+
[membership filters](references/sweep-patterns.md#membership-filters).
|
|
59
|
+
13. **Global rankings stay one periodic aggregate.** A result over every row needs every score;
|
|
60
|
+
incremental per-row inputs move the cost to every write without freshening the ranking. Give
|
|
61
|
+
the pass an EXPLAIN budget. See [global ranking](references/sweep-patterns.md#global-ranking).
|
|
62
|
+
14. **External listings carry the provider's continuation token.** Cap pages per run and enqueue a
|
|
63
|
+
continuation with the token; restarting at page one lets failing front items starve the rest.
|
|
64
|
+
See [external listing](references/sweep-patterns.md#external-listing-with-a-continuation-token).
|
|
65
|
+
15. **Verify with a plan.** EXPLAIN at representative cardinality shows no Seq Scan on an
|
|
43
66
|
unbounded table, and an index that supplies the `ORDER BY`, so the scan stops at the
|
|
44
|
-
`LIMIT` instead of reading and sorting every match.
|
|
67
|
+
`LIMIT` instead of reading and sorting every match. Compare rows read with rows returned.
|
|
45
68
|
|
|
46
69
|
For work-item, lease, and cursor tables, read [postgres-schema-design](../postgres-schema-design/SKILL.md).
|
|
47
70
|
For query shape and streaming, read [performance patterns](../postgres-node-performance-tuning/references/performance-patterns.md).
|