@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.
Files changed (123) hide show
  1. package/dist/cli/commands/declarative-export.js +12 -17
  2. package/dist/cli/commands/plan.js +10 -13
  3. package/dist/cli/commands/sync.js +8 -12
  4. package/dist/cli/utils/integrations.d.ts +30 -6
  5. package/dist/cli/utils/integrations.js +98 -6
  6. package/dist/core/change-utils.d.ts +9 -0
  7. package/dist/core/change-utils.js +71 -0
  8. package/dist/core/change.types.d.ts +22 -0
  9. package/dist/core/change.types.js +37 -1
  10. package/dist/core/depend.js +25 -0
  11. package/dist/core/export/file-mapper.d.ts +2 -2
  12. package/dist/core/integrations/filter/dsl.d.ts +78 -74
  13. package/dist/core/integrations/filter/dsl.js +127 -79
  14. package/dist/core/integrations/filter/flatten.d.ts +51 -0
  15. package/dist/core/integrations/filter/flatten.js +116 -0
  16. package/dist/core/integrations/integration-dsl.d.ts +17 -1
  17. package/dist/core/integrations/merge.d.ts +20 -0
  18. package/dist/core/integrations/merge.js +60 -0
  19. package/dist/core/integrations/serialize/dsl.d.ts +7 -4
  20. package/dist/core/integrations/serialize/dsl.js +2 -2
  21. package/dist/core/integrations/supabase.js +23 -8
  22. package/dist/core/objects/aggregate/changes/aggregate.types.d.ts +1 -0
  23. package/dist/core/objects/base.change.d.ts +10 -0
  24. package/dist/core/objects/base.change.js +10 -0
  25. package/dist/core/objects/base.model.d.ts +4 -1
  26. package/dist/core/objects/base.model.js +5 -2
  27. package/dist/core/objects/collation/changes/collation.types.d.ts +1 -0
  28. package/dist/core/objects/domain/changes/domain.create.d.ts +1 -1
  29. package/dist/core/objects/domain/changes/domain.create.js +7 -1
  30. package/dist/core/objects/domain/changes/domain.types.d.ts +1 -0
  31. package/dist/core/objects/event-trigger/changes/event-trigger.types.d.ts +1 -0
  32. package/dist/core/objects/extension/changes/extension.types.d.ts +1 -0
  33. package/dist/core/objects/foreign-data-wrapper/foreign-data-wrapper/changes/foreign-data-wrapper.types.d.ts +1 -0
  34. package/dist/core/objects/foreign-data-wrapper/foreign-data-wrapper.types.d.ts +1 -0
  35. package/dist/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.types.d.ts +1 -0
  36. package/dist/core/objects/foreign-data-wrapper/server/changes/server.types.d.ts +1 -0
  37. package/dist/core/objects/foreign-data-wrapper/user-mapping/changes/user-mapping.types.d.ts +1 -0
  38. package/dist/core/objects/index/changes/index.types.d.ts +1 -0
  39. package/dist/core/objects/language/changes/language.types.d.ts +1 -0
  40. package/dist/core/objects/materialized-view/changes/materialized-view.types.d.ts +1 -0
  41. package/dist/core/objects/procedure/changes/procedure.types.d.ts +1 -0
  42. package/dist/core/objects/publication/changes/publication.types.d.ts +1 -0
  43. package/dist/core/objects/rls-policy/changes/rls-policy.types.d.ts +1 -0
  44. package/dist/core/objects/role/changes/role.types.d.ts +1 -0
  45. package/dist/core/objects/rule/changes/rule.types.d.ts +1 -0
  46. package/dist/core/objects/schema/changes/schema.types.d.ts +1 -0
  47. package/dist/core/objects/sequence/changes/sequence.types.d.ts +1 -0
  48. package/dist/core/objects/subscription/changes/subscription.types.d.ts +1 -0
  49. package/dist/core/objects/table/changes/table.types.d.ts +1 -0
  50. package/dist/core/objects/trigger/changes/trigger.types.d.ts +1 -0
  51. package/dist/core/objects/type/composite-type/changes/composite-type.types.d.ts +1 -0
  52. package/dist/core/objects/type/enum/changes/enum.types.d.ts +1 -0
  53. package/dist/core/objects/type/range/changes/range.types.d.ts +1 -0
  54. package/dist/core/objects/type/type.types.d.ts +1 -0
  55. package/dist/core/objects/view/changes/view.types.d.ts +1 -0
  56. package/dist/core/objects/view/view.diff.js +24 -13
  57. package/dist/core/postgres-config.d.ts +2 -2
  58. package/dist/core/sort/custom-constraints.js +1 -1
  59. package/dist/core/sort/logical-sort.js +3 -24
  60. package/package.json +5 -1
  61. package/src/cli/commands/declarative-export.ts +19 -27
  62. package/src/cli/commands/plan.ts +14 -20
  63. package/src/cli/commands/sync.ts +8 -15
  64. package/src/cli/utils/integrations.test.ts +210 -3
  65. package/src/cli/utils/integrations.ts +134 -6
  66. package/src/core/catalog.snapshot.test.ts +11 -2
  67. package/src/core/change-utils.test.ts +61 -0
  68. package/src/core/change-utils.ts +73 -0
  69. package/src/core/change.types.ts +50 -0
  70. package/src/core/depend.ts +25 -0
  71. package/src/core/export/file-mapper.ts +7 -2
  72. package/src/core/integrations/filter/dsl.test.ts +299 -60
  73. package/src/core/integrations/filter/dsl.ts +208 -169
  74. package/src/core/integrations/filter/flatten.test.ts +282 -0
  75. package/src/core/integrations/filter/flatten.ts +150 -0
  76. package/src/core/integrations/integration-dsl.ts +17 -1
  77. package/src/core/integrations/merge.test.ts +128 -0
  78. package/src/core/integrations/merge.ts +72 -0
  79. package/src/core/integrations/serialize/dsl.test.ts +6 -6
  80. package/src/core/integrations/serialize/dsl.ts +7 -4
  81. package/src/core/integrations/supabase.ts +23 -8
  82. package/src/core/objects/aggregate/changes/aggregate.types.ts +1 -0
  83. package/src/core/objects/base.change.ts +10 -0
  84. package/src/core/objects/base.model.test.ts +43 -0
  85. package/src/core/objects/base.model.ts +5 -2
  86. package/src/core/objects/collation/changes/collation.types.ts +1 -0
  87. package/src/core/objects/domain/changes/domain.create.ts +17 -1
  88. package/src/core/objects/domain/changes/domain.types.ts +1 -0
  89. package/src/core/objects/event-trigger/changes/event-trigger.types.ts +1 -0
  90. package/src/core/objects/extension/changes/extension.types.ts +1 -0
  91. package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/changes/foreign-data-wrapper.types.ts +1 -0
  92. package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper.types.ts +1 -0
  93. package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.types.ts +1 -0
  94. package/src/core/objects/foreign-data-wrapper/server/changes/server.types.ts +1 -0
  95. package/src/core/objects/foreign-data-wrapper/user-mapping/changes/user-mapping.types.ts +1 -0
  96. package/src/core/objects/index/changes/index.types.ts +1 -0
  97. package/src/core/objects/language/changes/language.types.ts +1 -0
  98. package/src/core/objects/materialized-view/changes/materialized-view.types.ts +1 -0
  99. package/src/core/objects/procedure/changes/procedure.types.ts +1 -0
  100. package/src/core/objects/publication/changes/publication.types.ts +1 -0
  101. package/src/core/objects/rls-policy/changes/rls-policy.types.ts +1 -0
  102. package/src/core/objects/role/changes/role.types.ts +1 -0
  103. package/src/core/objects/rule/changes/rule.types.ts +1 -0
  104. package/src/core/objects/schema/changes/schema.types.ts +1 -0
  105. package/src/core/objects/sequence/changes/sequence.types.ts +1 -0
  106. package/src/core/objects/subscription/changes/subscription.types.ts +1 -0
  107. package/src/core/objects/table/changes/table.types.ts +1 -0
  108. package/src/core/objects/trigger/changes/trigger.types.ts +1 -0
  109. package/src/core/objects/type/composite-type/changes/composite-type.types.ts +1 -0
  110. package/src/core/objects/type/enum/changes/enum.types.ts +1 -0
  111. package/src/core/objects/type/range/changes/range.types.ts +1 -0
  112. package/src/core/objects/type/type.types.ts +1 -0
  113. package/src/core/objects/view/changes/view.types.ts +1 -0
  114. package/src/core/objects/view/view.diff.test.ts +96 -0
  115. package/src/core/objects/view/view.diff.ts +30 -15
  116. package/src/core/postgres-config.ts +2 -2
  117. package/src/core/sort/custom-constraints.ts +1 -1
  118. package/src/core/sort/logical-sort.ts +3 -27
  119. package/src/typedoc.ts +248 -0
  120. package/dist/core/integrations/filter/extractors.d.ts +0 -12
  121. package/dist/core/integrations/filter/extractors.js +0 -178
  122. package/src/core/integrations/filter/extractors.test.ts +0 -244
  123. package/src/core/integrations/filter/extractors.ts +0 -187
@@ -6,15 +6,21 @@ const tableCreate = {
6
6
  objectType: "table",
7
7
  operation: "create",
8
8
  scope: "object",
9
- table: { schema: "public", name: "t" },
9
+ table: {
10
+ schema: "public",
11
+ name: "t",
12
+ owner: "postgres",
13
+ is_partition: false,
14
+ },
10
15
  requires: ["schema:public"],
16
+ creates: ["table:public.t"],
11
17
  } as unknown as Change;
12
18
 
13
19
  const viewAlter = {
14
20
  objectType: "view",
15
21
  operation: "alter",
16
22
  scope: "comment",
17
- view: { schema: "private", name: "v" },
23
+ view: { schema: "private", name: "v", owner: "admin" },
18
24
  requires: ["schema:private", "type:auth.users"],
19
25
  } as unknown as Change;
20
26
 
@@ -26,14 +32,23 @@ const roleDrop = {
26
32
  requires: [],
27
33
  } as unknown as Change;
28
34
 
35
+ const membershipChange = {
36
+ objectType: "role",
37
+ operation: "create",
38
+ scope: "membership",
39
+ member: "app_user",
40
+ role: { name: "admin_group" },
41
+ requires: [],
42
+ } as unknown as Change;
43
+
29
44
  describe("evaluatePattern", () => {
30
- describe("core properties", () => {
31
- test("type match", () => {
32
- expect(evaluatePattern({ type: "table" }, tableCreate)).toBe(true);
45
+ describe("bare key matching (top-level properties)", () => {
46
+ test("objectType match", () => {
47
+ expect(evaluatePattern({ objectType: "table" }, tableCreate)).toBe(true);
33
48
  });
34
49
 
35
- test("type mismatch", () => {
36
- expect(evaluatePattern({ type: "view" }, tableCreate)).toBe(false);
50
+ test("objectType mismatch", () => {
51
+ expect(evaluatePattern({ objectType: "view" }, tableCreate)).toBe(false);
37
52
  });
38
53
 
39
54
  test("operation match", () => {
@@ -52,12 +67,18 @@ describe("evaluatePattern", () => {
52
67
  expect(evaluatePattern({ scope: "comment" }, tableCreate)).toBe(false);
53
68
  });
54
69
 
55
- test("multiple core properties AND together", () => {
70
+ test("multiple bare keys AND together", () => {
56
71
  expect(
57
- evaluatePattern({ type: "table", operation: "create" }, tableCreate),
72
+ evaluatePattern(
73
+ { objectType: "table", operation: "create" },
74
+ tableCreate,
75
+ ),
58
76
  ).toBe(true);
59
77
  expect(
60
- evaluatePattern({ type: "table", operation: "drop" }, tableCreate),
78
+ evaluatePattern(
79
+ { objectType: "table", operation: "drop" },
80
+ tableCreate,
81
+ ),
61
82
  ).toBe(false);
62
83
  });
63
84
 
@@ -67,124 +88,257 @@ describe("evaluatePattern", () => {
67
88
  });
68
89
  });
69
90
 
70
- describe("composition patterns", () => {
71
- test("not negates a pattern", () => {
72
- expect(evaluatePattern({ not: { type: "table" } }, tableCreate)).toBe(
91
+ describe("path key matching (model sub-object properties)", () => {
92
+ test("exact path match", () => {
93
+ expect(evaluatePattern({ "table/schema": "public" }, tableCreate)).toBe(
94
+ true,
95
+ );
96
+ expect(evaluatePattern({ "table/schema": "private" }, tableCreate)).toBe(
73
97
  false,
74
98
  );
75
- expect(evaluatePattern({ not: { type: "view" } }, tableCreate)).toBe(
99
+ });
100
+
101
+ test("path with array value checks inclusion", () => {
102
+ expect(
103
+ evaluatePattern({ "table/schema": ["public", "private"] }, tableCreate),
104
+ ).toBe(true);
105
+ expect(
106
+ evaluatePattern({ "table/schema": ["private", "auth"] }, tableCreate),
107
+ ).toBe(false);
108
+ });
109
+
110
+ test("path not found returns false", () => {
111
+ expect(evaluatePattern({ "table/schema": "public" }, roleDrop)).toBe(
112
+ false,
113
+ );
114
+ });
115
+ });
116
+
117
+ describe("wildcard pattern matching", () => {
118
+ test("*/schema matches any objectType's schema", () => {
119
+ expect(evaluatePattern({ "*/schema": "public" }, tableCreate)).toBe(true);
120
+ expect(evaluatePattern({ "*/schema": "private" }, viewAlter)).toBe(true);
121
+ expect(evaluatePattern({ "*/schema": "public" }, viewAlter)).toBe(false);
122
+ });
123
+
124
+ test("*/owner matches across object types", () => {
125
+ expect(evaluatePattern({ "*/owner": "postgres" }, tableCreate)).toBe(
76
126
  true,
77
127
  );
128
+ expect(evaluatePattern({ "*/owner": "admin" }, viewAlter)).toBe(true);
78
129
  });
79
130
 
80
- test("and requires all to match", () => {
131
+ test("*/schema does not match objectTypes without schema", () => {
132
+ expect(evaluatePattern({ "*/schema": "anything" }, roleDrop)).toBe(false);
133
+ });
134
+
135
+ test("*/name matches role/name", () => {
136
+ expect(evaluatePattern({ "*/name": "admin" }, roleDrop)).toBe(true);
137
+ });
138
+
139
+ test("wildcard with array value", () => {
81
140
  expect(
82
- evaluatePattern(
83
- { and: [{ type: "table" }, { operation: "create" }] },
84
- tableCreate,
85
- ),
141
+ evaluatePattern({ "*/schema": ["public", "private"] }, tableCreate),
86
142
  ).toBe(true);
143
+ });
144
+ });
145
+
146
+ describe("boolean matching", () => {
147
+ test("matches boolean value", () => {
87
148
  expect(
88
- evaluatePattern(
89
- { and: [{ type: "table" }, { operation: "drop" }] },
90
- tableCreate,
91
- ),
92
- ).toBe(false);
149
+ evaluatePattern({ "table/is_partition": false }, tableCreate),
150
+ ).toBe(true);
151
+ expect(evaluatePattern({ "table/is_partition": true }, tableCreate)).toBe(
152
+ false,
153
+ );
93
154
  });
155
+ });
94
156
 
95
- test("or requires any to match", () => {
157
+ describe("regex matching", () => {
158
+ test("regex on string value", () => {
96
159
  expect(
97
160
  evaluatePattern(
98
- { or: [{ type: "table" }, { type: "view" }] },
161
+ { "table/name": { op: "regex", value: "^t" } },
99
162
  tableCreate,
100
163
  ),
101
164
  ).toBe(true);
102
165
  expect(
103
166
  evaluatePattern(
104
- { or: [{ type: "role" }, { type: "view" }] },
167
+ { "table/name": { op: "regex", value: "^z" } },
105
168
  tableCreate,
106
169
  ),
107
170
  ).toBe(false);
108
171
  });
109
172
 
110
- test("nested composition", () => {
173
+ test("regex with array of patterns", () => {
111
174
  expect(
112
175
  evaluatePattern(
113
- { not: { or: [{ type: "role" }, { type: "view" }] } },
176
+ { "table/name": { op: "regex", value: ["^z", "^t"] } },
114
177
  tableCreate,
115
178
  ),
116
179
  ).toBe(true);
117
180
  });
118
- });
119
181
 
120
- describe("requiresMatching", () => {
121
- test("prefix match on requires array", () => {
182
+ test("regex on array value (requires)", () => {
122
183
  expect(
123
- evaluatePattern({ requiresMatching: ["schema:public"] }, tableCreate),
184
+ evaluatePattern(
185
+ { requires: { op: "regex", value: "^schema:" } },
186
+ tableCreate,
187
+ ),
124
188
  ).toBe(true);
125
189
  expect(
126
- evaluatePattern({ requiresMatching: ["schema:"] }, tableCreate),
190
+ evaluatePattern(
191
+ { requires: { op: "regex", value: "^type:auth\\." } },
192
+ viewAlter,
193
+ ),
127
194
  ).toBe(true);
128
- });
129
-
130
- test("no match when prefix absent", () => {
131
195
  expect(
132
- evaluatePattern({ requiresMatching: ["type:auth."] }, tableCreate),
196
+ evaluatePattern(
197
+ { requires: { op: "regex", value: "^type:auth\\." } },
198
+ tableCreate,
199
+ ),
133
200
  ).toBe(false);
134
201
  });
202
+ });
203
+
204
+ describe("array value matching (requires/creates)", () => {
205
+ test("string match against array checks any element", () => {
206
+ expect(evaluatePattern({ requires: "schema:public" }, tableCreate)).toBe(
207
+ true,
208
+ );
209
+ expect(evaluatePattern({ requires: "schema:private" }, tableCreate)).toBe(
210
+ false,
211
+ );
212
+ });
135
213
 
136
- test("matches when any prefix matches any requires entry", () => {
214
+ test("array match against array checks intersection", () => {
137
215
  expect(
138
- evaluatePattern({ requiresMatching: ["type:auth."] }, viewAlter),
216
+ evaluatePattern(
217
+ { requires: ["schema:public", "schema:other"] },
218
+ tableCreate,
219
+ ),
139
220
  ).toBe(true);
140
221
  });
141
222
 
142
- test("no match when requires is empty", () => {
143
- expect(evaluatePattern({ requiresMatching: ["schema:"] }, roleDrop)).toBe(
223
+ test("no match on empty requires", () => {
224
+ expect(evaluatePattern({ requires: "schema:public" }, roleDrop)).toBe(
225
+ false,
226
+ );
227
+ });
228
+ });
229
+
230
+ describe("member/grantee matching", () => {
231
+ test("member match", () => {
232
+ expect(evaluatePattern({ member: "app_user" }, membershipChange)).toBe(
233
+ true,
234
+ );
235
+ expect(evaluatePattern({ member: "other_user" }, membershipChange)).toBe(
144
236
  false,
145
237
  );
146
238
  });
239
+
240
+ test("member not present returns false", () => {
241
+ expect(evaluatePattern({ member: "app_user" }, tableCreate)).toBe(false);
242
+ });
147
243
  });
148
244
 
149
- describe("extracted properties", () => {
150
- test("schema as string exact match", () => {
151
- expect(evaluatePattern({ schema: "public" }, tableCreate)).toBe(true);
152
- expect(evaluatePattern({ schema: "private" }, tableCreate)).toBe(false);
245
+ describe("composition patterns", () => {
246
+ test("not negates a pattern", () => {
247
+ expect(
248
+ evaluatePattern({ not: { objectType: "table" } }, tableCreate),
249
+ ).toBe(false);
250
+ expect(
251
+ evaluatePattern({ not: { objectType: "view" } }, tableCreate),
252
+ ).toBe(true);
153
253
  });
154
254
 
155
- test("schema as array checks inclusion", () => {
255
+ test("and requires all to match", () => {
156
256
  expect(
157
- evaluatePattern({ schema: ["public", "private"] }, tableCreate),
257
+ evaluatePattern(
258
+ { and: [{ objectType: "table" }, { operation: "create" }] },
259
+ tableCreate,
260
+ ),
158
261
  ).toBe(true);
159
262
  expect(
160
- evaluatePattern({ schema: ["private", "auth"] }, tableCreate),
263
+ evaluatePattern(
264
+ { and: [{ objectType: "table" }, { operation: "drop" }] },
265
+ tableCreate,
266
+ ),
161
267
  ).toBe(false);
162
268
  });
163
269
 
164
- test("null extracted value returns false", () => {
165
- expect(evaluatePattern({ schema: "public" }, roleDrop)).toBe(false);
270
+ test("or requires any to match", () => {
271
+ expect(
272
+ evaluatePattern(
273
+ { or: [{ objectType: "table" }, { objectType: "view" }] },
274
+ tableCreate,
275
+ ),
276
+ ).toBe(true);
277
+ expect(
278
+ evaluatePattern(
279
+ { or: [{ objectType: "role" }, { objectType: "view" }] },
280
+ tableCreate,
281
+ ),
282
+ ).toBe(false);
283
+ });
284
+
285
+ test("nested composition", () => {
286
+ expect(
287
+ evaluatePattern(
288
+ { not: { or: [{ objectType: "role" }, { objectType: "view" }] } },
289
+ tableCreate,
290
+ ),
291
+ ).toBe(true);
292
+ });
293
+
294
+ test("composition with wildcard patterns", () => {
295
+ expect(
296
+ evaluatePattern(
297
+ { not: { "*/schema": ["auth", "extensions"] } },
298
+ tableCreate,
299
+ ),
300
+ ).toBe(true);
166
301
  });
302
+ });
167
303
 
168
- test("unknown property key is ignored", () => {
169
- const pattern = { unknownKey: "value" } as Record<string, unknown>;
304
+ describe("combined path + bare keys", () => {
305
+ test("objectType and path key AND together", () => {
170
306
  expect(
171
307
  evaluatePattern(
172
- pattern as Parameters<typeof evaluatePattern>[0],
308
+ { objectType: "table", "table/is_partition": false },
173
309
  tableCreate,
174
310
  ),
175
311
  ).toBe(true);
312
+ expect(
313
+ evaluatePattern(
314
+ { objectType: "table", "table/is_partition": true },
315
+ tableCreate,
316
+ ),
317
+ ).toBe(false);
176
318
  });
319
+ });
177
320
 
178
- test("cascade property is ignored and does not affect match", () => {
321
+ describe("cascade property", () => {
322
+ test("cascade is ignored and does not affect match", () => {
179
323
  expect(
180
- evaluatePattern({ type: "table", cascade: true }, tableCreate),
324
+ evaluatePattern(
325
+ { objectType: "table", cascade: true } as Parameters<
326
+ typeof evaluatePattern
327
+ >[0],
328
+ tableCreate,
329
+ ),
181
330
  ).toBe(true);
182
331
  expect(
183
- evaluatePattern({ type: "table", cascade: false }, tableCreate),
332
+ evaluatePattern(
333
+ { objectType: "table", cascade: false } as Parameters<
334
+ typeof evaluatePattern
335
+ >[0],
336
+ tableCreate,
337
+ ),
184
338
  ).toBe(true);
185
339
  expect(
186
340
  evaluatePattern(
187
- { not: { schema: "auth" }, cascade: true },
341
+ { not: { "*/schema": "auth" }, cascade: true },
188
342
  tableCreate,
189
343
  ),
190
344
  ).toBe(true);
@@ -194,7 +348,7 @@ describe("evaluatePattern", () => {
194
348
 
195
349
  describe("compileFilterDSL", () => {
196
350
  test("returns a function that evaluates the pattern", () => {
197
- const filter = compileFilterDSL({ type: "table" });
351
+ const filter = compileFilterDSL({ objectType: "table" });
198
352
  expect(typeof filter).toBe("function");
199
353
  expect(filter(tableCreate)).toBe(true);
200
354
  expect(filter(roleDrop)).toBe(false);
@@ -202,10 +356,95 @@ describe("compileFilterDSL", () => {
202
356
 
203
357
  test("works with composition patterns", () => {
204
358
  const filter = compileFilterDSL({
205
- or: [{ type: "table" }, { type: "role" }],
359
+ or: [{ objectType: "table" }, { objectType: "role" }],
206
360
  });
207
361
  expect(filter(tableCreate)).toBe(true);
208
362
  expect(filter(roleDrop)).toBe(true);
209
363
  expect(filter(viewAlter)).toBe(false);
210
364
  });
365
+
366
+ test("works with wildcard-based patterns", () => {
367
+ const filter = compileFilterDSL({
368
+ "*/schema": "public",
369
+ });
370
+ expect(filter(tableCreate)).toBe(true);
371
+ expect(filter(viewAlter)).toBe(false);
372
+ });
373
+
374
+ test("throws on invalid regex pattern", () => {
375
+ expect(() =>
376
+ compileFilterDSL({
377
+ "table/name": { op: "regex", value: "[invalid" },
378
+ }),
379
+ ).toThrow(/Invalid regex pattern "\[invalid" in filter DSL/);
380
+ });
381
+
382
+ test("throws on invalid regex in array of patterns", () => {
383
+ expect(() =>
384
+ compileFilterDSL({
385
+ "table/name": { op: "regex", value: ["^valid$", "(unclosed"] },
386
+ }),
387
+ ).toThrow(/Invalid regex pattern "\(unclosed" in filter DSL/);
388
+ });
389
+
390
+ test("throws on invalid regex nested in composition", () => {
391
+ expect(() =>
392
+ compileFilterDSL({
393
+ or: [
394
+ { objectType: "table" },
395
+ { "table/name": { op: "regex", value: "**bad" } },
396
+ ],
397
+ }),
398
+ ).toThrow(/Invalid regex pattern "\*\*bad" in filter DSL/);
399
+ });
400
+ });
401
+
402
+ describe("glob pattern features", () => {
403
+ const tableCreate = {
404
+ objectType: "table",
405
+ operation: "create",
406
+ scope: "object",
407
+ table: {
408
+ schema: "public",
409
+ name: "t",
410
+ owner: "postgres",
411
+ is_partition: false,
412
+ },
413
+ requires: ["schema:public"],
414
+ creates: ["table:public.t"],
415
+ } as unknown as Change;
416
+
417
+ const viewAlter = {
418
+ objectType: "view",
419
+ operation: "alter",
420
+ scope: "comment",
421
+ view: { schema: "private", name: "v", owner: "admin" },
422
+ requires: ["schema:private", "type:auth.users"],
423
+ } as unknown as Change;
424
+
425
+ const roleDrop = {
426
+ objectType: "role",
427
+ operation: "drop",
428
+ scope: "object",
429
+ role: { name: "admin" },
430
+ requires: [],
431
+ } as unknown as Change;
432
+
433
+ test("brace expansion in path pattern keys", () => {
434
+ const filter = compileFilterDSL({ "{table,view}/schema": "public" });
435
+ expect(filter(tableCreate)).toBe(true);
436
+ expect(filter(viewAlter)).toBe(false); // private, not public
437
+ expect(filter(roleDrop)).toBe(false); // no matching key
438
+ });
439
+
440
+ test("partial wildcard in field names", () => {
441
+ const filter = compileFilterDSL({ "table/is_*": false });
442
+ expect(filter(tableCreate)).toBe(true); // is_partition = false
443
+ });
444
+
445
+ test("extglob negation in pattern keys", () => {
446
+ const filter = compileFilterDSL({ "!(role)/schema": "public" });
447
+ expect(filter(tableCreate)).toBe(true); // table/schema = public
448
+ expect(filter(roleDrop)).toBe(false); // role excluded by negation
449
+ });
211
450
  });