pg-dump-parser 1.6.0 → 1.8.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.
@@ -0,0 +1,372 @@
1
+ import { parsePgDump } from './parsePgDump';
2
+ import {
3
+ groupAndSortSchemaObjects,
4
+ sortSchemaObjects,
5
+ sortSchemaObjectsByScope,
6
+ } from './sortSchemaObjects';
7
+ import multiline from 'multiline-ts';
8
+ import { describe, expect, test } from 'vitest';
9
+
10
+ describe('sortSchemaObjects', () => {
11
+ test('sorts schema objects by type order', () => {
12
+ const dump = multiline`
13
+ --
14
+ -- Name: foo; Type: TABLE; Schema: public; Owner: postgres
15
+ --
16
+
17
+ CREATE TABLE public.foo (
18
+ id integer NOT NULL,
19
+ name text NOT NULL
20
+ );
21
+
22
+
23
+ --
24
+ -- Name: bar; Type: INDEX; Schema: public; Owner: postgres
25
+ --
26
+
27
+ CREATE INDEX bar ON public.foo USING btree (name);
28
+
29
+
30
+ --
31
+ -- Name: baz; Type: EXTENSION; Schema: -; Owner: -
32
+ --
33
+
34
+ CREATE EXTENSION IF NOT EXISTS baz;
35
+
36
+
37
+ --
38
+ -- Name: qux; Type: CONSTRAINT; Schema: public; Owner: postgres
39
+ --
40
+
41
+ ALTER TABLE ONLY public.foo
42
+ ADD CONSTRAINT qux PRIMARY KEY (id);
43
+ `;
44
+
45
+ const schemaObjects = parsePgDump(dump);
46
+ const sorted = sortSchemaObjects(schemaObjects);
47
+
48
+ // Extension should come first
49
+ expect(sorted[0].header).toMatchObject({ Name: 'baz', Type: 'EXTENSION' });
50
+ // Then table
51
+ expect(sorted[1].header).toMatchObject({ Name: 'foo', Type: 'TABLE' });
52
+ // Then constraint
53
+ expect(sorted[2].header).toMatchObject({ Name: 'qux', Type: 'CONSTRAINT' });
54
+ // Finally index
55
+ expect(sorted[3].header).toMatchObject({ Name: 'bar', Type: 'INDEX' });
56
+ });
57
+
58
+ test('sorts constraints by type (PRIMARY, UNIQUE, FOREIGN, CHECK)', () => {
59
+ const dump = multiline`
60
+ --
61
+ -- Name: foo foo_check; Type: CONSTRAINT; Schema: public; Owner: postgres
62
+ --
63
+
64
+ ALTER TABLE ONLY public.foo
65
+ ADD CONSTRAINT foo_check CHECK (id > 0);
66
+
67
+
68
+ --
69
+ -- Name: foo foo_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
70
+ --
71
+
72
+ ALTER TABLE ONLY public.foo
73
+ ADD CONSTRAINT foo_fkey FOREIGN KEY (bar_id) REFERENCES public.bar(id);
74
+
75
+
76
+ --
77
+ -- Name: foo foo_unique; Type: CONSTRAINT; Schema: public; Owner: postgres
78
+ --
79
+
80
+ ALTER TABLE ONLY public.foo
81
+ ADD CONSTRAINT foo_unique UNIQUE (name);
82
+
83
+
84
+ --
85
+ -- Name: foo foo_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
86
+ --
87
+
88
+ ALTER TABLE ONLY public.foo
89
+ ADD CONSTRAINT foo_pkey PRIMARY KEY (id);
90
+ `;
91
+
92
+ const schemaObjects = parsePgDump(dump);
93
+ const sorted = sortSchemaObjects(schemaObjects);
94
+
95
+ // Primary key should come first
96
+ expect(sorted[0].sql).toContain('PRIMARY KEY');
97
+ // Then unique
98
+ expect(sorted[1].sql).toContain('UNIQUE');
99
+ // Then check
100
+ expect(sorted[2].sql).toContain('CHECK');
101
+ // Finally foreign key
102
+ expect(sorted[3].sql).toContain('FOREIGN KEY');
103
+ });
104
+
105
+ test('sorts indexes alphabetically within the same table', () => {
106
+ const dump = multiline`
107
+ --
108
+ -- Name: foo; Type: TABLE; Schema: public; Owner: postgres
109
+ --
110
+
111
+ CREATE TABLE public.foo (
112
+ id integer NOT NULL,
113
+ name text NOT NULL,
114
+ email text
115
+ );
116
+
117
+
118
+ --
119
+ -- Name: foo_name_idx; Type: INDEX; Schema: public; Owner: postgres
120
+ --
121
+
122
+ CREATE INDEX foo_name_idx ON public.foo USING btree (name);
123
+
124
+
125
+ --
126
+ -- Name: foo_email_idx; Type: INDEX; Schema: public; Owner: postgres
127
+ --
128
+
129
+ CREATE INDEX foo_email_idx ON public.foo USING btree (email);
130
+
131
+
132
+ --
133
+ -- Name: foo_id_idx; Type: INDEX; Schema: public; Owner: postgres
134
+ --
135
+
136
+ CREATE INDEX foo_id_idx ON public.foo USING btree (id);
137
+ `;
138
+
139
+ const schemaObjects = parsePgDump(dump);
140
+ const sorted = sortSchemaObjects(schemaObjects);
141
+
142
+ const indexes = sorted.filter(
143
+ (object) => 'Type' in object.header && object.header.Type === 'INDEX',
144
+ );
145
+
146
+ expect(indexes[0].header).toMatchObject({ Name: 'foo_email_idx' });
147
+ expect(indexes[1].header).toMatchObject({ Name: 'foo_id_idx' });
148
+ expect(indexes[2].header).toMatchObject({ Name: 'foo_name_idx' });
149
+ });
150
+
151
+ test('sorts comments by type and target', () => {
152
+ const dump = multiline`
153
+ --
154
+ -- Name: COLUMN foo.name; Type: COMMENT; Schema: public; Owner: postgres
155
+ --
156
+
157
+ COMMENT ON COLUMN public.foo.name IS 'Name column';
158
+
159
+
160
+ --
161
+ -- Name: TABLE foo; Type: COMMENT; Schema: public; Owner: postgres
162
+ --
163
+
164
+ COMMENT ON TABLE public.foo IS 'Foo table';
165
+
166
+
167
+ --
168
+ -- Name: COLUMN foo.id; Type: COMMENT; Schema: public; Owner: postgres
169
+ --
170
+
171
+ COMMENT ON COLUMN public.foo.id IS 'ID column';
172
+
173
+
174
+ --
175
+ -- Name: EXTENSION pgcrypto; Type: COMMENT; Schema: -; Owner: -
176
+ --
177
+
178
+ COMMENT ON EXTENSION pgcrypto IS 'Crypto extension';
179
+ `;
180
+
181
+ const schemaObjects = parsePgDump(dump);
182
+ const sorted = sortSchemaObjects(schemaObjects);
183
+
184
+ const comments = sorted.filter(
185
+ (object) => 'Type' in object.header && object.header.Type === 'COMMENT',
186
+ );
187
+
188
+ // Extension comment first
189
+ expect(comments[0].header).toMatchObject({ Name: 'EXTENSION pgcrypto' });
190
+ // Then table comment
191
+ expect(comments[1].header).toMatchObject({ Name: 'TABLE foo' });
192
+ // Then column comments sorted by name
193
+ expect(comments[2].header).toMatchObject({ Name: 'COLUMN foo.id' });
194
+ expect(comments[3].header).toMatchObject({ Name: 'COLUMN foo.name' });
195
+ });
196
+
197
+ test('groups and sorts schema objects by scope', () => {
198
+ const dump = multiline`
199
+ --
200
+ -- Name: foo; Type: TABLE; Schema: public; Owner: postgres
201
+ --
202
+
203
+ CREATE TABLE public.foo (
204
+ id integer NOT NULL,
205
+ name text NOT NULL
206
+ );
207
+
208
+
209
+ --
210
+ -- Name: foo_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
211
+ --
212
+
213
+ ALTER TABLE ONLY public.foo
214
+ ADD CONSTRAINT foo_pkey PRIMARY KEY (id);
215
+
216
+
217
+ --
218
+ -- Name: foo_name_idx; Type: INDEX; Schema: public; Owner: postgres
219
+ --
220
+
221
+ CREATE INDEX foo_name_idx ON public.foo USING btree (name);
222
+
223
+
224
+ --
225
+ -- Name: bar; Type: TABLE; Schema: public; Owner: postgres
226
+ --
227
+
228
+ CREATE TABLE public.bar (
229
+ id integer NOT NULL,
230
+ foo_id integer
231
+ );
232
+
233
+
234
+ --
235
+ -- Name: bar_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
236
+ --
237
+
238
+ ALTER TABLE ONLY public.bar
239
+ ADD CONSTRAINT bar_pkey PRIMARY KEY (id);
240
+
241
+
242
+ --
243
+ -- Name: bar_foo_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
244
+ --
245
+
246
+ ALTER TABLE ONLY public.bar
247
+ ADD CONSTRAINT bar_foo_id_fkey FOREIGN KEY (foo_id) REFERENCES public.foo(id);
248
+ `;
249
+
250
+ const schemaObjects = parsePgDump(dump);
251
+ const grouped = groupAndSortSchemaObjects(schemaObjects);
252
+
253
+ // Should have two groups for the two tables
254
+ const tableGroups = Array.from(grouped.keys()).filter((key) =>
255
+ key.includes('TABLE:'),
256
+ );
257
+ expect(tableGroups).toHaveLength(2);
258
+
259
+ // Each group should be sorted internally
260
+ for (const [key, objects] of grouped.entries()) {
261
+ if (key.includes('TABLE:public:foo')) {
262
+ // foo table group should have table, constraint, and index
263
+ expect(objects).toHaveLength(3);
264
+ expect(objects[0].header).toMatchObject({ Type: 'TABLE' });
265
+ expect(objects[1].header).toMatchObject({ Type: 'CONSTRAINT' });
266
+ expect(objects[2].header).toMatchObject({ Type: 'INDEX' });
267
+ } else if (key.includes('TABLE:public:bar')) {
268
+ // bar table group should have table and two constraints
269
+ expect(objects).toHaveLength(3);
270
+ expect(objects[0].header).toMatchObject({ Type: 'TABLE' });
271
+ expect(objects[1].header).toMatchObject({ Type: 'CONSTRAINT' });
272
+ expect(objects[2].header).toMatchObject({ Type: 'FK CONSTRAINT' });
273
+ }
274
+ }
275
+ });
276
+
277
+ test('sortSchemaObjectsByScope returns flat sorted array', () => {
278
+ const dump = multiline`
279
+ --
280
+ -- Name: foo; Type: TABLE; Schema: public; Owner: postgres
281
+ --
282
+
283
+ CREATE TABLE public.foo (
284
+ id integer NOT NULL
285
+ );
286
+
287
+
288
+ --
289
+ -- Name: foo_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
290
+ --
291
+
292
+ ALTER TABLE ONLY public.foo
293
+ ADD CONSTRAINT foo_pkey PRIMARY KEY (id);
294
+
295
+
296
+ --
297
+ -- Name: bar; Type: TABLE; Schema: public; Owner: postgres
298
+ --
299
+
300
+ CREATE TABLE public.bar (
301
+ id integer NOT NULL
302
+ );
303
+
304
+
305
+ --
306
+ -- Name: bar_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
307
+ --
308
+
309
+ ALTER TABLE ONLY public.bar
310
+ ADD CONSTRAINT bar_pkey PRIMARY KEY (id);
311
+ `;
312
+
313
+ const schemaObjects = parsePgDump(dump);
314
+ const sorted = sortSchemaObjectsByScope(schemaObjects);
315
+
316
+ expect(sorted).toHaveLength(4);
317
+
318
+ // Objects should be grouped by table
319
+ // First bar table and its constraint
320
+ expect(sorted[0].header).toMatchObject({ Name: 'bar', Type: 'TABLE' });
321
+ expect(sorted[1].header).toMatchObject({
322
+ Name: 'bar_pkey',
323
+ Type: 'CONSTRAINT',
324
+ });
325
+
326
+ // Then foo table and its constraint
327
+ expect(sorted[2].header).toMatchObject({ Name: 'foo', Type: 'TABLE' });
328
+ expect(sorted[3].header).toMatchObject({
329
+ Name: 'foo_pkey',
330
+ Type: 'CONSTRAINT',
331
+ });
332
+ });
333
+
334
+ test('handles schemas correctly in sorting', () => {
335
+ const dump = multiline`
336
+ --
337
+ -- Name: foo; Type: TABLE; Schema: custom; Owner: postgres
338
+ --
339
+
340
+ CREATE TABLE custom.foo (
341
+ id integer NOT NULL
342
+ );
343
+
344
+
345
+ --
346
+ -- Name: foo; Type: TABLE; Schema: public; Owner: postgres
347
+ --
348
+
349
+ CREATE TABLE public.foo (
350
+ id integer NOT NULL
351
+ );
352
+
353
+
354
+ --
355
+ -- Name: bar; Type: TABLE; Schema: -; Owner: postgres
356
+ --
357
+
358
+ CREATE TABLE bar (
359
+ id integer NOT NULL
360
+ );
361
+ `;
362
+
363
+ const schemaObjects = parsePgDump(dump);
364
+ const sorted = sortSchemaObjects(schemaObjects);
365
+
366
+ // Tables with null schema should come first
367
+ expect(sorted[0].header).toMatchObject({ Name: 'bar', Schema: null });
368
+ // Then sorted by schema name
369
+ expect(sorted[1].header).toMatchObject({ Name: 'foo', Schema: 'custom' });
370
+ expect(sorted[2].header).toMatchObject({ Name: 'foo', Schema: 'public' });
371
+ });
372
+ });
@@ -0,0 +1,323 @@
1
+ import { type AttributedHeader, type SchemaObject } from './parsePgDump';
2
+ import { scopeSchemaObject } from './scopeSchemaObject';
3
+
4
+ /**
5
+ * Sort order for different schema object types
6
+ */
7
+ const TYPE_SORT_ORDER: Record<string, number> = {
8
+ // ACLs and other
9
+ ACL: 18,
10
+
11
+ AGGREGATE: 6,
12
+ CAST: 22,
13
+
14
+ // Comments
15
+ COMMENT: 17,
16
+ // Constraints (sorted by type)
17
+ CONSTRAINT: 11,
18
+ // Table modifications and defaults
19
+ DEFAULT: 10,
20
+
21
+ 'DEFAULT ACL': 19,
22
+ // Extensions first
23
+ EXTENSION: 1,
24
+ 'FK CONSTRAINT': 12,
25
+
26
+ // Functions and procedures
27
+ FUNCTION: 4,
28
+
29
+ // Indexes
30
+ INDEX: 13,
31
+ 'MATERIALIZED VIEW': 15,
32
+
33
+ PROCEDURE: 5,
34
+
35
+ // Publications and casts
36
+ PUBLICATION: 21,
37
+ // Types and schemas
38
+ SCHEMA: 2,
39
+
40
+ SEQUENCE: 8,
41
+
42
+ 'SEQUENCE OWNED BY': 9,
43
+
44
+ // Tables and sequences
45
+ TABLE: 7,
46
+ // Table attachments
47
+ 'TABLE ATTACH': 20,
48
+
49
+ 'TEXT SEARCH CONFIGURATION': 24,
50
+
51
+ // Text search
52
+ 'TEXT SEARCH DICTIONARY': 23,
53
+ // Triggers
54
+ TRIGGER: 16,
55
+
56
+ TYPE: 3,
57
+ // Views
58
+ VIEW: 14,
59
+ };
60
+
61
+ /**
62
+ * Get the sort key for a constraint based on its type
63
+ */
64
+ const getConstraintSortKey = (sql: string): string => {
65
+ const upperSql = sql.toUpperCase();
66
+
67
+ if (upperSql.includes('PRIMARY KEY')) {
68
+ return '1_PRIMARY';
69
+ } else if (upperSql.includes('UNIQUE')) {
70
+ return '2_UNIQUE';
71
+ } else if (upperSql.includes('FOREIGN KEY')) {
72
+ return '3_FOREIGN';
73
+ } else if (upperSql.includes('CHECK')) {
74
+ return '4_CHECK';
75
+ } else if (upperSql.includes('EXCLUDE')) {
76
+ return '5_EXCLUDE';
77
+ }
78
+
79
+ return '9_OTHER';
80
+ };
81
+
82
+ /**
83
+ * Compare two schema objects for sorting
84
+ */
85
+ const compareSchemaObjects = (
86
+ a: SchemaObject,
87
+ b: SchemaObject,
88
+ schemaObjects: SchemaObject[],
89
+ ): number => {
90
+ // Handle non-attributed headers (like database dump headers)
91
+ if (!('Type' in a.header) && !('Type' in b.header)) {
92
+ return 0;
93
+ }
94
+
95
+ if (!('Type' in a.header)) {
96
+ return -1;
97
+ }
98
+
99
+ if (!('Type' in b.header)) {
100
+ return 1;
101
+ }
102
+
103
+ const aHeader = a.header as AttributedHeader;
104
+ const bHeader = b.header as AttributedHeader;
105
+
106
+ // First, sort by type order
107
+ const aTypeOrder = TYPE_SORT_ORDER[aHeader.Type] ?? 999;
108
+ const bTypeOrder = TYPE_SORT_ORDER[bHeader.Type] ?? 999;
109
+
110
+ if (aTypeOrder !== bTypeOrder) {
111
+ return aTypeOrder - bTypeOrder;
112
+ }
113
+
114
+ // For the same type, apply specific sorting rules
115
+
116
+ // Sort by schema first (null schemas come first)
117
+ const aSchema = aHeader.Schema ?? '';
118
+ const bSchema = bHeader.Schema ?? '';
119
+ if (aSchema !== bSchema) {
120
+ if (aSchema === '') {
121
+ return -1;
122
+ }
123
+
124
+ if (bSchema === '') {
125
+ return 1;
126
+ }
127
+
128
+ return aSchema.localeCompare(bSchema);
129
+ }
130
+
131
+ // Special handling for constraints
132
+ if (aHeader.Type === 'CONSTRAINT' || aHeader.Type === 'FK CONSTRAINT') {
133
+ // Extract table name from constraint name (format: "table constraint_name")
134
+ const aTableName = aHeader.Name.split(' ')[0];
135
+ const bTableName = bHeader.Name.split(' ')[0];
136
+
137
+ if (aTableName !== bTableName) {
138
+ return aTableName.localeCompare(bTableName);
139
+ }
140
+
141
+ // Within the same table, sort by constraint type
142
+ const aConstraintKey = getConstraintSortKey(a.sql);
143
+ const bConstraintKey = getConstraintSortKey(b.sql);
144
+
145
+ if (aConstraintKey !== bConstraintKey) {
146
+ return aConstraintKey.localeCompare(bConstraintKey);
147
+ }
148
+ }
149
+
150
+ // Special handling for indexes
151
+ if (aHeader.Type === 'INDEX') {
152
+ // Group indexes by their target table
153
+ const aScopeObject = scopeSchemaObject(schemaObjects, a);
154
+ const bScopeObject = scopeSchemaObject(schemaObjects, b);
155
+
156
+ if (aScopeObject && bScopeObject) {
157
+ const aTableName = aScopeObject.name;
158
+ const bTableName = bScopeObject.name;
159
+
160
+ if (aTableName !== bTableName) {
161
+ return aTableName.localeCompare(bTableName);
162
+ }
163
+ }
164
+ }
165
+
166
+ // Special handling for comments - sort by what they comment on
167
+ if (aHeader.Type === 'COMMENT') {
168
+ const aTarget = aHeader.Name;
169
+ const bTarget = bHeader.Name;
170
+
171
+ // Extract the type of comment (TABLE, COLUMN, etc.)
172
+ const aCommentType = aTarget.split(' ')[0];
173
+ const bCommentType = bTarget.split(' ')[0];
174
+
175
+ if (aCommentType !== bCommentType) {
176
+ // Define order for comment types
177
+ const commentTypeOrder: Record<string, number> = {
178
+ AGGREGATE: 6,
179
+ COLUMN: 8,
180
+ EXTENSION: 1,
181
+ FUNCTION: 4,
182
+ INDEX: 10,
183
+ MATERIALIZED: 12,
184
+ PROCEDURE: 5,
185
+ SCHEMA: 2,
186
+ SEQUENCE: 9,
187
+ TABLE: 7,
188
+ TYPE: 3,
189
+ VIEW: 11,
190
+ };
191
+
192
+ const aOrder = commentTypeOrder[aCommentType] ?? 999;
193
+ const bOrder = commentTypeOrder[bCommentType] ?? 999;
194
+
195
+ if (aOrder !== bOrder) {
196
+ return aOrder - bOrder;
197
+ }
198
+ }
199
+
200
+ // For COLUMN comments, sort by table name then column position
201
+ if (aCommentType === 'COLUMN') {
202
+ const aMatch = aTarget.match(/COLUMN\s+(\S+\.\S+)\.(.+)/u);
203
+ const bMatch = bTarget.match(/COLUMN\s+(\S+\.\S+)\.(.+)/u);
204
+
205
+ if (aMatch && bMatch) {
206
+ const aTable = aMatch[1];
207
+ const bTable = bMatch[1];
208
+
209
+ if (aTable !== bTable) {
210
+ return aTable.localeCompare(bTable);
211
+ }
212
+
213
+ // Same table, sort by column name
214
+ const aColumn = aMatch[2];
215
+ const bColumn = bMatch[2];
216
+
217
+ return aColumn.localeCompare(bColumn);
218
+ }
219
+ }
220
+ }
221
+
222
+ // Finally, sort by name
223
+ return aHeader.Name.localeCompare(bHeader.Name);
224
+ };
225
+
226
+ /**
227
+ * Groups schema objects by their scope (table, view, etc.) and sorts them
228
+ */
229
+ export const groupAndSortSchemaObjects = (
230
+ schemaObjects: SchemaObject[],
231
+ ): Map<string, SchemaObject[]> => {
232
+ const grouped = new Map<string, SchemaObject[]>();
233
+
234
+ // First, group objects by their scope
235
+ for (const schemaObject of schemaObjects) {
236
+ const scope = scopeSchemaObject(schemaObjects, schemaObject);
237
+
238
+ let key: string;
239
+ if (scope) {
240
+ // Create a unique key for each scope
241
+ key = `${scope.type}:${scope.schema ?? 'null'}:${scope.name}`;
242
+ } else if ('Type' in schemaObject.header) {
243
+ // For objects without a scope, group by type
244
+ const header = schemaObject.header as AttributedHeader;
245
+ key = `_UNSCOPED:${header.Type}:${header.Schema ?? 'null'}:${header.Name}`;
246
+ } else {
247
+ // Title headers
248
+ key = '_TITLE';
249
+ }
250
+
251
+ if (!grouped.has(key)) {
252
+ grouped.set(key, []);
253
+ }
254
+
255
+ const groupArray = grouped.get(key);
256
+ if (groupArray) {
257
+ groupArray.push(schemaObject);
258
+ }
259
+ }
260
+
261
+ // Sort objects within each group
262
+ for (const objects of grouped.values()) {
263
+ objects.sort((a, b) => compareSchemaObjects(a, b, schemaObjects));
264
+ }
265
+
266
+ // Sort the groups themselves
267
+ const sortedGrouped = new Map(
268
+ // eslint-disable-next-line unicorn/no-array-sort
269
+ [...grouped.entries()].sort(([keyA], [keyB]) => {
270
+ // Title headers first
271
+ if (keyA === '_TITLE') {
272
+ return -1;
273
+ }
274
+
275
+ if (keyB === '_TITLE') {
276
+ return 1;
277
+ }
278
+
279
+ // Then unscoped objects
280
+ if (keyA.startsWith('_UNSCOPED:') && !keyB.startsWith('_UNSCOPED:')) {
281
+ return -1;
282
+ }
283
+
284
+ if (!keyA.startsWith('_UNSCOPED:') && keyB.startsWith('_UNSCOPED:')) {
285
+ return 1;
286
+ }
287
+
288
+ // Sort by type, schema, then name
289
+ return keyA.localeCompare(keyB);
290
+ }),
291
+ );
292
+
293
+ return sortedGrouped;
294
+ };
295
+
296
+ /**
297
+ * Sorts an array of schema objects
298
+ */
299
+ export const sortSchemaObjects = (
300
+ schemaObjects: SchemaObject[],
301
+ ): SchemaObject[] => {
302
+ const sorted = [...schemaObjects];
303
+
304
+ sorted.sort((a, b) => compareSchemaObjects(a, b, schemaObjects));
305
+
306
+ return sorted;
307
+ };
308
+
309
+ /**
310
+ * Sorts schema objects while preserving their grouping by scope
311
+ */
312
+ export const sortSchemaObjectsByScope = (
313
+ schemaObjects: SchemaObject[],
314
+ ): SchemaObject[] => {
315
+ const grouped = groupAndSortSchemaObjects(schemaObjects);
316
+ const result: SchemaObject[] = [];
317
+
318
+ for (const objects of grouped.values()) {
319
+ result.push(...objects);
320
+ }
321
+
322
+ return result;
323
+ };