@supabase/pg-delta 1.0.0-alpha.10 → 1.0.0-alpha.11
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/dist/cli/commands/declarative-export.js +12 -17
- package/dist/cli/commands/plan.js +10 -13
- package/dist/cli/commands/sync.js +8 -12
- package/dist/cli/utils/integrations.d.ts +30 -6
- package/dist/cli/utils/integrations.js +98 -6
- package/dist/core/change-utils.d.ts +9 -0
- package/dist/core/change-utils.js +71 -0
- package/dist/core/change.types.d.ts +22 -0
- package/dist/core/change.types.js +37 -1
- package/dist/core/depend.js +25 -0
- package/dist/core/export/file-mapper.d.ts +2 -2
- package/dist/core/integrations/filter/dsl.d.ts +78 -74
- package/dist/core/integrations/filter/dsl.js +127 -79
- package/dist/core/integrations/filter/flatten.d.ts +51 -0
- package/dist/core/integrations/filter/flatten.js +116 -0
- package/dist/core/integrations/integration-dsl.d.ts +17 -1
- package/dist/core/integrations/merge.d.ts +20 -0
- package/dist/core/integrations/merge.js +60 -0
- package/dist/core/integrations/serialize/dsl.d.ts +7 -4
- package/dist/core/integrations/serialize/dsl.js +2 -2
- package/dist/core/integrations/supabase.js +23 -8
- package/dist/core/objects/aggregate/changes/aggregate.types.d.ts +1 -0
- package/dist/core/objects/base.change.d.ts +10 -0
- package/dist/core/objects/base.change.js +10 -0
- package/dist/core/objects/base.model.d.ts +4 -1
- package/dist/core/objects/base.model.js +5 -2
- package/dist/core/objects/collation/changes/collation.types.d.ts +1 -0
- package/dist/core/objects/domain/changes/domain.create.d.ts +1 -1
- package/dist/core/objects/domain/changes/domain.create.js +7 -1
- package/dist/core/objects/domain/changes/domain.types.d.ts +1 -0
- package/dist/core/objects/event-trigger/changes/event-trigger.types.d.ts +1 -0
- package/dist/core/objects/extension/changes/extension.types.d.ts +1 -0
- package/dist/core/objects/foreign-data-wrapper/foreign-data-wrapper/changes/foreign-data-wrapper.types.d.ts +1 -0
- package/dist/core/objects/foreign-data-wrapper/foreign-data-wrapper.types.d.ts +1 -0
- package/dist/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.types.d.ts +1 -0
- package/dist/core/objects/foreign-data-wrapper/server/changes/server.types.d.ts +1 -0
- package/dist/core/objects/foreign-data-wrapper/user-mapping/changes/user-mapping.types.d.ts +1 -0
- package/dist/core/objects/index/changes/index.types.d.ts +1 -0
- package/dist/core/objects/language/changes/language.types.d.ts +1 -0
- package/dist/core/objects/materialized-view/changes/materialized-view.types.d.ts +1 -0
- package/dist/core/objects/procedure/changes/procedure.types.d.ts +1 -0
- package/dist/core/objects/publication/changes/publication.types.d.ts +1 -0
- package/dist/core/objects/rls-policy/changes/rls-policy.types.d.ts +1 -0
- package/dist/core/objects/role/changes/role.types.d.ts +1 -0
- package/dist/core/objects/rule/changes/rule.types.d.ts +1 -0
- package/dist/core/objects/schema/changes/schema.types.d.ts +1 -0
- package/dist/core/objects/sequence/changes/sequence.types.d.ts +1 -0
- package/dist/core/objects/subscription/changes/subscription.types.d.ts +1 -0
- package/dist/core/objects/table/changes/table.types.d.ts +1 -0
- package/dist/core/objects/trigger/changes/trigger.types.d.ts +1 -0
- package/dist/core/objects/type/composite-type/changes/composite-type.types.d.ts +1 -0
- package/dist/core/objects/type/enum/changes/enum.types.d.ts +1 -0
- package/dist/core/objects/type/range/changes/range.types.d.ts +1 -0
- package/dist/core/objects/type/type.types.d.ts +1 -0
- package/dist/core/objects/view/changes/view.types.d.ts +1 -0
- package/dist/core/objects/view/view.diff.js +24 -13
- package/dist/core/postgres-config.d.ts +2 -2
- package/dist/core/sort/custom-constraints.js +1 -1
- package/dist/core/sort/logical-sort.js +3 -24
- package/package.json +5 -1
- package/src/cli/commands/declarative-export.ts +19 -27
- package/src/cli/commands/plan.ts +14 -20
- package/src/cli/commands/sync.ts +8 -15
- package/src/cli/utils/integrations.test.ts +210 -3
- package/src/cli/utils/integrations.ts +134 -6
- package/src/core/catalog.snapshot.test.ts +11 -2
- package/src/core/change-utils.test.ts +61 -0
- package/src/core/change-utils.ts +73 -0
- package/src/core/change.types.ts +50 -0
- package/src/core/depend.ts +25 -0
- package/src/core/export/file-mapper.ts +7 -2
- package/src/core/integrations/filter/dsl.test.ts +299 -60
- package/src/core/integrations/filter/dsl.ts +208 -169
- package/src/core/integrations/filter/flatten.test.ts +282 -0
- package/src/core/integrations/filter/flatten.ts +150 -0
- package/src/core/integrations/integration-dsl.ts +17 -1
- package/src/core/integrations/merge.test.ts +128 -0
- package/src/core/integrations/merge.ts +72 -0
- package/src/core/integrations/serialize/dsl.test.ts +6 -6
- package/src/core/integrations/serialize/dsl.ts +7 -4
- package/src/core/integrations/supabase.ts +23 -8
- package/src/core/objects/aggregate/changes/aggregate.types.ts +1 -0
- package/src/core/objects/base.change.ts +10 -0
- package/src/core/objects/base.model.test.ts +43 -0
- package/src/core/objects/base.model.ts +5 -2
- package/src/core/objects/collation/changes/collation.types.ts +1 -0
- package/src/core/objects/domain/changes/domain.create.ts +17 -1
- package/src/core/objects/domain/changes/domain.types.ts +1 -0
- package/src/core/objects/event-trigger/changes/event-trigger.types.ts +1 -0
- package/src/core/objects/extension/changes/extension.types.ts +1 -0
- package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/changes/foreign-data-wrapper.types.ts +1 -0
- package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper.types.ts +1 -0
- package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.types.ts +1 -0
- package/src/core/objects/foreign-data-wrapper/server/changes/server.types.ts +1 -0
- package/src/core/objects/foreign-data-wrapper/user-mapping/changes/user-mapping.types.ts +1 -0
- package/src/core/objects/index/changes/index.types.ts +1 -0
- package/src/core/objects/language/changes/language.types.ts +1 -0
- package/src/core/objects/materialized-view/changes/materialized-view.types.ts +1 -0
- package/src/core/objects/procedure/changes/procedure.types.ts +1 -0
- package/src/core/objects/publication/changes/publication.types.ts +1 -0
- package/src/core/objects/rls-policy/changes/rls-policy.types.ts +1 -0
- package/src/core/objects/role/changes/role.types.ts +1 -0
- package/src/core/objects/rule/changes/rule.types.ts +1 -0
- package/src/core/objects/schema/changes/schema.types.ts +1 -0
- package/src/core/objects/sequence/changes/sequence.types.ts +1 -0
- package/src/core/objects/subscription/changes/subscription.types.ts +1 -0
- package/src/core/objects/table/changes/table.types.ts +1 -0
- package/src/core/objects/trigger/changes/trigger.types.ts +1 -0
- package/src/core/objects/type/composite-type/changes/composite-type.types.ts +1 -0
- package/src/core/objects/type/enum/changes/enum.types.ts +1 -0
- package/src/core/objects/type/range/changes/range.types.ts +1 -0
- package/src/core/objects/type/type.types.ts +1 -0
- package/src/core/objects/view/changes/view.types.ts +1 -0
- package/src/core/objects/view/view.diff.test.ts +96 -0
- package/src/core/objects/view/view.diff.ts +30 -15
- package/src/core/postgres-config.ts +2 -2
- package/src/core/sort/custom-constraints.ts +1 -1
- package/src/core/sort/logical-sort.ts +3 -27
- package/src/typedoc.ts +248 -0
- package/dist/core/integrations/filter/extractors.d.ts +0 -12
- package/dist/core/integrations/filter/extractors.js +0 -178
- package/src/core/integrations/filter/extractors.test.ts +0 -244
- package/src/core/integrations/filter/extractors.ts +0 -187
|
@@ -1,127 +1,189 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Filter DSL - A serializable domain-specific language for change filtering.
|
|
3
|
+
*
|
|
4
|
+
* Uses wildcard-based path matching on flattened change properties.
|
|
5
|
+
* Path patterns as keys, values as matchers. Multiple keys in one object = AND.
|
|
6
|
+
*
|
|
7
|
+
* Path convention:
|
|
8
|
+
* - Top-level change properties are bare keys: `objectType`, `operation`, `scope`, `member`, `grantee`
|
|
9
|
+
* - Model sub-object properties use `<objectType>/<field>`: `table/schema`, `role/name`
|
|
10
|
+
* - Wildcard `*` matches any single path segment: `* /schema` → `table/schema`, `view/schema`, etc.
|
|
11
|
+
* - Separator is `/`
|
|
12
|
+
*
|
|
13
|
+
* Value matching:
|
|
14
|
+
* - string → exact equality
|
|
15
|
+
* - string[] → value must be in array (inclusion)
|
|
16
|
+
* - boolean → exact equality
|
|
17
|
+
* - number → exact equality
|
|
18
|
+
* - { op: "regex", value: string | string[] } → regex test
|
|
19
|
+
*
|
|
20
|
+
* When the flat value is an array (e.g. `requires`), match succeeds if any element satisfies.
|
|
3
21
|
*/
|
|
4
22
|
|
|
5
23
|
import type { Change } from "../../change.types.ts";
|
|
6
|
-
import { PROPERTY_EXTRACTORS } from "./extractors.ts";
|
|
7
24
|
import type { ChangeFilter } from "./filter.types.ts";
|
|
25
|
+
import { compileWildcard, type FlatValue, flattenChange } from "./flatten.ts";
|
|
8
26
|
|
|
9
27
|
/**
|
|
10
|
-
*
|
|
28
|
+
* Regex operator for advanced value matching.
|
|
11
29
|
*/
|
|
12
|
-
type
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
scope?: Change["scope"];
|
|
30
|
+
type RegexOperator = {
|
|
31
|
+
op: "regex";
|
|
32
|
+
value: string | string[];
|
|
16
33
|
};
|
|
17
34
|
|
|
18
35
|
/**
|
|
19
|
-
*
|
|
20
|
-
* String value = exact match, Array value = value must be in array
|
|
36
|
+
* A value matcher for a path pattern key.
|
|
21
37
|
*/
|
|
22
|
-
type
|
|
23
|
-
schema?: string | string[];
|
|
24
|
-
owner?: string | string[];
|
|
25
|
-
member?: string | string[];
|
|
26
|
-
grantee?: string | string[];
|
|
27
|
-
publication?: string | string[];
|
|
28
|
-
extension?: string | string[];
|
|
29
|
-
procedureLanguage?: string | string[];
|
|
30
|
-
eventTriggerName?: string | string[];
|
|
31
|
-
procedureBinaryPath?: string | string[];
|
|
32
|
-
triggerFunctionSchema?: string | string[];
|
|
33
|
-
};
|
|
38
|
+
type ValueMatcher = string | string[] | boolean | number | RegexOperator;
|
|
34
39
|
|
|
35
40
|
/**
|
|
36
|
-
*
|
|
41
|
+
* Path pattern — matches against flattened change properties.
|
|
42
|
+
* Keys are path patterns (with optional wildcards), values are matchers.
|
|
43
|
+
* Multiple keys are combined with AND (all must match).
|
|
44
|
+
*
|
|
45
|
+
* Reserved keys: `and`, `or`, `not`, `cascade`.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```json
|
|
49
|
+
* { "objectType": "table", "operation": "create" }
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* @example Wildcard path matching any object's schema
|
|
53
|
+
* ```json
|
|
54
|
+
* { "* /schema": "public" }
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* @category Filter DSL
|
|
37
58
|
*/
|
|
38
|
-
type
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
* ```ts
|
|
46
|
-
* { not: { requiresMatching: ["type:auth.", "type:extensions."] } }
|
|
47
|
-
* ```
|
|
48
|
-
*/
|
|
49
|
-
requiresMatching?: string[];
|
|
59
|
+
export type PathPattern = {
|
|
60
|
+
[path: string]: ValueMatcher;
|
|
61
|
+
} & {
|
|
62
|
+
cascade?: boolean;
|
|
63
|
+
and?: never;
|
|
64
|
+
or?: never;
|
|
65
|
+
not?: never;
|
|
50
66
|
};
|
|
51
67
|
|
|
52
|
-
/**
|
|
53
|
-
* Property pattern - matches against change properties.
|
|
54
|
-
* Multiple properties are combined with AND (all must match).
|
|
55
|
-
*/
|
|
56
|
-
type PropertyPattern = CoreProperties &
|
|
57
|
-
ExtractedProperties &
|
|
58
|
-
SpecialProperties & {
|
|
59
|
-
/**
|
|
60
|
-
* When true, exclusions from this filter cascade to dependents (requires/pg_depend).
|
|
61
|
-
* Default false for DSL filters (opt-in).
|
|
62
|
-
*/
|
|
63
|
-
cascade?: boolean;
|
|
64
|
-
// Composition operators are NOT allowed in property patterns
|
|
65
|
-
and?: never;
|
|
66
|
-
or?: never;
|
|
67
|
-
not?: never;
|
|
68
|
-
};
|
|
69
|
-
|
|
70
68
|
/**
|
|
71
69
|
* Composition pattern - combines other patterns using logical operators.
|
|
72
|
-
* Composition operators are exclusive - cannot be mixed with
|
|
70
|
+
* Composition operators are exclusive - cannot be mixed with path keys.
|
|
73
71
|
*/
|
|
74
72
|
type CompositionPattern =
|
|
75
|
-
|
|
|
73
|
+
| {
|
|
76
74
|
and: FilterPattern[];
|
|
77
75
|
cascade?: boolean;
|
|
78
76
|
or?: never;
|
|
79
77
|
not?: never;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
} & {
|
|
83
|
-
[K in keyof ExtractedProperties]?: never;
|
|
84
|
-
} & {
|
|
85
|
-
[K in keyof SpecialProperties]?: never;
|
|
86
|
-
})
|
|
87
|
-
| ({
|
|
78
|
+
}
|
|
79
|
+
| {
|
|
88
80
|
or: FilterPattern[];
|
|
89
81
|
cascade?: boolean;
|
|
90
82
|
and?: never;
|
|
91
83
|
not?: never;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
} & {
|
|
95
|
-
[K in keyof ExtractedProperties]?: never;
|
|
96
|
-
} & {
|
|
97
|
-
[K in keyof SpecialProperties]?: never;
|
|
98
|
-
})
|
|
99
|
-
| ({
|
|
84
|
+
}
|
|
85
|
+
| {
|
|
100
86
|
not: FilterPattern;
|
|
101
87
|
cascade?: boolean;
|
|
102
88
|
and?: never;
|
|
103
89
|
or?: never;
|
|
104
|
-
}
|
|
105
|
-
[K in keyof CoreProperties]?: never;
|
|
106
|
-
} & {
|
|
107
|
-
[K in keyof ExtractedProperties]?: never;
|
|
108
|
-
} & {
|
|
109
|
-
[K in keyof SpecialProperties]?: never;
|
|
110
|
-
});
|
|
90
|
+
};
|
|
111
91
|
|
|
112
92
|
/**
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
93
|
+
* A single filter expression: either a {@link PathPattern} that matches against
|
|
94
|
+
* flattened change properties, or a composition pattern that combines other
|
|
95
|
+
* patterns using `and` / `or` / `not` logical operators.
|
|
96
|
+
*
|
|
97
|
+
* @example Exclude all changes in pg_catalog
|
|
98
|
+
* ```json
|
|
99
|
+
* { "not": { "* /schema": "pg_catalog" } }
|
|
100
|
+
* ```
|
|
101
|
+
*
|
|
102
|
+
* @category Filter DSL
|
|
117
103
|
*/
|
|
118
|
-
export type FilterPattern =
|
|
104
|
+
export type FilterPattern = PathPattern | CompositionPattern;
|
|
119
105
|
|
|
120
106
|
/**
|
|
121
|
-
* Filter DSL
|
|
107
|
+
* Top-level Filter DSL type — a single {@link FilterPattern} expression that
|
|
108
|
+
* determines which changes an integration includes or excludes.
|
|
109
|
+
*
|
|
110
|
+
* @example Include only table and view creates in public
|
|
111
|
+
* ```json
|
|
112
|
+
* {
|
|
113
|
+
* "and": [
|
|
114
|
+
* { "objectType": ["table", "view"] },
|
|
115
|
+
* { "operation": "create" },
|
|
116
|
+
* { "* /schema": "public" }
|
|
117
|
+
* ]
|
|
118
|
+
* }
|
|
119
|
+
* ```
|
|
120
|
+
*
|
|
121
|
+
* @category Filter DSL
|
|
122
122
|
*/
|
|
123
123
|
export type FilterDSL = FilterPattern;
|
|
124
124
|
|
|
125
|
+
// Reserved keys that are not path patterns
|
|
126
|
+
const RESERVED_KEYS = new Set(["and", "or", "not", "cascade"]);
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Match a flat value against a value matcher.
|
|
130
|
+
*
|
|
131
|
+
* When the flat value is an array, the match succeeds if any element satisfies.
|
|
132
|
+
*/
|
|
133
|
+
function matchValue(actual: FlatValue, expected: ValueMatcher): boolean {
|
|
134
|
+
if (actual === null || actual === undefined) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// String matcher → exact equality
|
|
139
|
+
if (typeof expected === "string") {
|
|
140
|
+
if (Array.isArray(actual)) {
|
|
141
|
+
return actual.some((v) => v === expected);
|
|
142
|
+
}
|
|
143
|
+
return actual === expected;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Boolean matcher → exact equality
|
|
147
|
+
if (typeof expected === "boolean") {
|
|
148
|
+
return actual === expected;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Number matcher → exact equality
|
|
152
|
+
if (typeof expected === "number") {
|
|
153
|
+
return actual === expected;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Array matcher → inclusion (value must be in array)
|
|
157
|
+
if (Array.isArray(expected)) {
|
|
158
|
+
if (Array.isArray(actual)) {
|
|
159
|
+
return actual.some((v) =>
|
|
160
|
+
(expected as ReadonlyArray<string | number>).includes(v),
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
return typeof actual === "string" && expected.includes(actual);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Regex operator
|
|
167
|
+
if (
|
|
168
|
+
typeof expected === "object" &&
|
|
169
|
+
expected !== null &&
|
|
170
|
+
"op" in expected &&
|
|
171
|
+
expected.op === "regex"
|
|
172
|
+
) {
|
|
173
|
+
const patterns = Array.isArray(expected.value)
|
|
174
|
+
? expected.value
|
|
175
|
+
: [expected.value];
|
|
176
|
+
if (Array.isArray(actual)) {
|
|
177
|
+
return actual.some((a) =>
|
|
178
|
+
patterns.some((p) => new RegExp(p).test(String(a))),
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
return patterns.some((p) => new RegExp(p).test(String(actual)));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
|
|
125
187
|
/**
|
|
126
188
|
* Evaluate a pattern against a change.
|
|
127
189
|
*
|
|
@@ -136,110 +198,45 @@ export function evaluatePattern(
|
|
|
136
198
|
// Handle composition operators first (they take precedence)
|
|
137
199
|
|
|
138
200
|
// NOT operator - negate the result
|
|
139
|
-
if (pattern.not) {
|
|
201
|
+
if ("not" in pattern && pattern.not) {
|
|
140
202
|
return !evaluatePattern(pattern.not, change);
|
|
141
203
|
}
|
|
142
204
|
|
|
143
205
|
// AND operator - all patterns must match
|
|
144
|
-
if (pattern.and) {
|
|
206
|
+
if ("and" in pattern && pattern.and) {
|
|
145
207
|
return pattern.and.every((p) => evaluatePattern(p, change));
|
|
146
208
|
}
|
|
147
209
|
|
|
148
210
|
// OR operator - any pattern must match
|
|
149
|
-
if (pattern.or) {
|
|
211
|
+
if ("or" in pattern && pattern.or) {
|
|
150
212
|
return pattern.or.some((p) => evaluatePattern(p, change));
|
|
151
213
|
}
|
|
152
214
|
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
|
|
156
|
-
// Match objectType
|
|
157
|
-
if (pattern.type) {
|
|
158
|
-
if (change.objectType !== pattern.type) {
|
|
159
|
-
return false;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Match operation
|
|
164
|
-
if (pattern.operation) {
|
|
165
|
-
if (change.operation !== pattern.operation) {
|
|
166
|
-
return false;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Match scope
|
|
171
|
-
if (pattern.scope) {
|
|
172
|
-
if (change.scope !== pattern.scope) {
|
|
173
|
-
return false;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Match requiresMatching (special property - prefix match on change.requires)
|
|
178
|
-
if (pattern.requiresMatching) {
|
|
179
|
-
const requires = change.requires ?? [];
|
|
180
|
-
const prefixes = pattern.requiresMatching;
|
|
181
|
-
const hasMatch = requires.some((r) =>
|
|
182
|
-
prefixes.some((p) => r.startsWith(p)),
|
|
183
|
-
);
|
|
184
|
-
if (!hasMatch) {
|
|
185
|
-
return false;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Match extracted properties
|
|
190
|
-
for (const [key, value] of Object.entries(pattern)) {
|
|
191
|
-
// Skip composition operators, core properties, special properties, and cascade
|
|
192
|
-
if (
|
|
193
|
-
[
|
|
194
|
-
"and",
|
|
195
|
-
"or",
|
|
196
|
-
"not",
|
|
197
|
-
"type",
|
|
198
|
-
"operation",
|
|
199
|
-
"scope",
|
|
200
|
-
"requiresMatching",
|
|
201
|
-
"cascade",
|
|
202
|
-
].includes(key)
|
|
203
|
-
) {
|
|
204
|
-
continue;
|
|
205
|
-
}
|
|
215
|
+
// Path pattern matching: flatten the change, then for each key in the pattern,
|
|
216
|
+
// wildcard-match against flat map paths and compare values.
|
|
217
|
+
const flat = flattenChange(change);
|
|
206
218
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
if (!extractor) {
|
|
210
|
-
// Unknown property - ignore
|
|
211
|
-
continue;
|
|
212
|
-
}
|
|
219
|
+
for (const [patternKey, matcher] of Object.entries(pattern)) {
|
|
220
|
+
if (RESERVED_KEYS.has(patternKey)) continue;
|
|
213
221
|
|
|
214
|
-
|
|
215
|
-
const actualValue = extractor(change);
|
|
222
|
+
const wildcardMatcher = compileWildcard(patternKey);
|
|
216
223
|
|
|
217
|
-
//
|
|
218
|
-
|
|
219
|
-
// - Array value: value must be in array
|
|
220
|
-
// - Missing properties (null) don't match
|
|
224
|
+
// Find all flat keys that match this wildcard pattern
|
|
225
|
+
const matchingKeys = Object.keys(flat).filter((k) => wildcardMatcher(k));
|
|
221
226
|
|
|
222
|
-
if (
|
|
227
|
+
if (matchingKeys.length === 0) {
|
|
228
|
+
// No flat keys match this wildcard → pattern key not satisfied
|
|
223
229
|
return false;
|
|
224
230
|
}
|
|
225
231
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
} else if (Array.isArray(value)) {
|
|
232
|
-
// Value must be in array
|
|
233
|
-
if (!value.includes(actualValue)) {
|
|
234
|
-
return false;
|
|
235
|
-
}
|
|
236
|
-
} else {
|
|
237
|
-
// Invalid value type - don't match
|
|
238
|
-
return false;
|
|
239
|
-
}
|
|
232
|
+
// At least one matching key must satisfy the value matcher
|
|
233
|
+
const anyMatch = matchingKeys.some((k) =>
|
|
234
|
+
matchValue(flat[k], matcher as ValueMatcher),
|
|
235
|
+
);
|
|
236
|
+
if (!anyMatch) return false;
|
|
240
237
|
}
|
|
241
238
|
|
|
242
|
-
// All
|
|
239
|
+
// All pattern keys satisfied
|
|
243
240
|
return true;
|
|
244
241
|
}
|
|
245
242
|
|
|
@@ -250,17 +247,59 @@ export function evaluatePattern(
|
|
|
250
247
|
* @returns A ChangeFilter function that evaluates the pattern
|
|
251
248
|
*
|
|
252
249
|
* @example
|
|
253
|
-
* ```
|
|
250
|
+
* ```
|
|
254
251
|
* const filter = compileFilterDSL({
|
|
255
252
|
* or: [
|
|
256
|
-
* {
|
|
257
|
-
* { schema: "public" }
|
|
253
|
+
* { objectType: "schema", operation: "create" },
|
|
254
|
+
* { "table/schema": "public" }
|
|
258
255
|
* ]
|
|
259
256
|
* });
|
|
260
257
|
* ```
|
|
261
258
|
*/
|
|
262
259
|
export function compileFilterDSL(dsl: FilterDSL): ChangeFilter {
|
|
260
|
+
validateRegexPatterns(dsl);
|
|
263
261
|
return (change: Change): boolean => {
|
|
264
262
|
return evaluatePattern(dsl, change);
|
|
265
263
|
};
|
|
266
264
|
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Walk the pattern tree and validate all regex patterns at compile time.
|
|
268
|
+
* Throws a descriptive error if any regex pattern is invalid.
|
|
269
|
+
*/
|
|
270
|
+
function validateRegexPatterns(pattern: FilterPattern): void {
|
|
271
|
+
if ("not" in pattern && pattern.not) {
|
|
272
|
+
validateRegexPatterns(pattern.not);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if ("and" in pattern && pattern.and) {
|
|
276
|
+
for (const p of pattern.and) validateRegexPatterns(p);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if ("or" in pattern && pattern.or) {
|
|
280
|
+
for (const p of pattern.or) validateRegexPatterns(p);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
for (const [key, value] of Object.entries(pattern)) {
|
|
285
|
+
if (RESERVED_KEYS.has(key)) continue;
|
|
286
|
+
if (
|
|
287
|
+
typeof value === "object" &&
|
|
288
|
+
value !== null &&
|
|
289
|
+
!Array.isArray(value) &&
|
|
290
|
+
"op" in value &&
|
|
291
|
+
value.op === "regex"
|
|
292
|
+
) {
|
|
293
|
+
const patterns = Array.isArray(value.value) ? value.value : [value.value];
|
|
294
|
+
for (const p of patterns) {
|
|
295
|
+
try {
|
|
296
|
+
new RegExp(p);
|
|
297
|
+
} catch (e) {
|
|
298
|
+
throw new Error(
|
|
299
|
+
`Invalid regex pattern "${p}" in filter DSL: ${(e as Error).message}`,
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|