@pgpmjs/export 0.1.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,1170 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.normalizeOutdir = exports.preparePackage = exports.makeReplacer = exports.installMissingModules = exports.detectMissingModules = exports.META_TABLE_CONFIG = exports.META_TABLE_ORDER = exports.META_COMMON_FOOTER = exports.META_COMMON_HEADER = exports.SERVICE_REQUIRED_EXTENSIONS = exports.DB_REQUIRED_EXTENSIONS = void 0;
7
+ const fs_1 = require("fs");
8
+ const glob_1 = require("glob");
9
+ const komoji_1 = require("komoji");
10
+ const path_1 = __importDefault(require("path"));
11
+ const core_1 = require("@pgpmjs/core");
12
+ // =============================================================================
13
+ // Shared constants
14
+ // =============================================================================
15
+ /**
16
+ * Required extensions for database schema exports.
17
+ * Includes native PostgreSQL extensions and pgpm modules.
18
+ */
19
+ exports.DB_REQUIRED_EXTENSIONS = [
20
+ 'plpgsql',
21
+ 'uuid-ossp',
22
+ 'citext',
23
+ 'pgcrypto',
24
+ 'btree_gin',
25
+ 'btree_gist',
26
+ 'pg_textsearch',
27
+ 'pg_trgm',
28
+ 'postgis',
29
+ 'hstore',
30
+ 'vector',
31
+ 'metaschema-schema',
32
+ 'pgpm-inflection',
33
+ 'pgpm-uuid',
34
+ 'pgpm-utils',
35
+ 'pgpm-database-jobs',
36
+ 'pgpm-jwt-claims',
37
+ 'pgpm-stamps',
38
+ 'pgpm-base32',
39
+ 'pgpm-totp',
40
+ 'pgpm-types'
41
+ ];
42
+ /**
43
+ * Map PostgreSQL data types to FieldType values.
44
+ * Uses udt_name from information_schema which gives the base type name.
45
+ */
46
+ const mapPgTypeToFieldType = (udtName) => {
47
+ switch (udtName) {
48
+ case 'uuid':
49
+ return 'uuid';
50
+ case '_uuid':
51
+ return 'uuid[]';
52
+ case 'text':
53
+ case 'varchar':
54
+ case 'bpchar':
55
+ case 'name':
56
+ return 'text';
57
+ case '_text':
58
+ case '_varchar':
59
+ return 'text[]';
60
+ case 'bool':
61
+ return 'boolean';
62
+ case 'jsonb':
63
+ case 'json':
64
+ return 'jsonb';
65
+ case '_jsonb':
66
+ return 'jsonb[]';
67
+ case 'int4':
68
+ case 'int8':
69
+ case 'int2':
70
+ case 'numeric':
71
+ return 'int';
72
+ case 'interval':
73
+ return 'interval';
74
+ case 'timestamptz':
75
+ case 'timestamp':
76
+ return 'timestamptz';
77
+ default:
78
+ return 'text';
79
+ }
80
+ };
81
+ /**
82
+ * Required extensions for service/meta exports.
83
+ * Includes native PostgreSQL extensions and pgpm modules for metadata management.
84
+ */
85
+ exports.SERVICE_REQUIRED_EXTENSIONS = [
86
+ 'plpgsql',
87
+ 'metaschema-schema',
88
+ 'metaschema-modules',
89
+ 'services'
90
+ ];
91
+ /**
92
+ * Common SQL header for meta export files.
93
+ * Sets session_replication_role and grants necessary permissions.
94
+ */
95
+ exports.META_COMMON_HEADER = `SET session_replication_role TO replica;
96
+ -- using replica in case we are deploying triggers to metaschema_public
97
+
98
+ -- unaccent, postgis affected and require grants
99
+ GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public to public;
100
+
101
+ DO $LQLMIGRATION$
102
+ DECLARE
103
+ BEGIN
104
+
105
+ EXECUTE format('GRANT CONNECT ON DATABASE %I TO %I', current_database(), 'app_user');
106
+ EXECUTE format('GRANT CONNECT ON DATABASE %I TO %I', current_database(), 'app_admin');
107
+
108
+ END;
109
+ $LQLMIGRATION$;`;
110
+ /**
111
+ * Common SQL footer for meta export files.
112
+ */
113
+ exports.META_COMMON_FOOTER = `
114
+ SET session_replication_role TO DEFAULT;`;
115
+ /**
116
+ * Ordered list of meta tables for export.
117
+ * Tables are processed in this order to satisfy foreign key dependencies.
118
+ */
119
+ exports.META_TABLE_ORDER = [
120
+ 'database',
121
+ 'schema',
122
+ 'table',
123
+ 'field',
124
+ 'policy',
125
+ 'index',
126
+ 'trigger',
127
+ 'trigger_function',
128
+ 'rls_function',
129
+ 'foreign_key_constraint',
130
+ 'primary_key_constraint',
131
+ 'unique_constraint',
132
+ 'check_constraint',
133
+ 'full_text_search',
134
+ 'schema_grant',
135
+ 'table_grant',
136
+ 'default_privilege',
137
+ 'domains',
138
+ 'sites',
139
+ 'apis',
140
+ 'apps',
141
+ 'site_modules',
142
+ 'site_themes',
143
+ 'site_metadata',
144
+ 'api_modules',
145
+ 'api_extensions',
146
+ 'api_schemas',
147
+ 'rls_module',
148
+ 'user_auth_module',
149
+ 'memberships_module',
150
+ 'permissions_module',
151
+ 'limits_module',
152
+ 'levels_module',
153
+ 'users_module',
154
+ 'hierarchy_module',
155
+ 'membership_types_module',
156
+ 'invites_module',
157
+ 'emails_module',
158
+ 'sessions_module',
159
+ 'secrets_module',
160
+ 'profiles_module',
161
+ 'encrypted_secrets_module',
162
+ 'connected_accounts_module',
163
+ 'phone_numbers_module',
164
+ 'crypto_addresses_module',
165
+ 'crypto_auth_module',
166
+ 'field_module',
167
+ 'table_module',
168
+ 'secure_table_provision',
169
+ 'uuid_module',
170
+ 'default_ids_module',
171
+ 'denormalized_table_field',
172
+ 'table_template_module'
173
+ ];
174
+ /**
175
+ * Shared metadata table configuration.
176
+ *
177
+ * This is the **superset** of fields needed by both the SQL export flow
178
+ * (export-meta.ts) and the GraphQL export flow (export-graphql-meta.ts).
179
+ * Each flow dynamically filters to only the fields that actually exist:
180
+ * - SQL flow: uses buildDynamicFields() to intersect with information_schema
181
+ * - GraphQL flow: filters to fields present in the returned data
182
+ *
183
+ * Adding a field here that doesn't exist in a particular environment is safe.
184
+ */
185
+ exports.META_TABLE_CONFIG = {
186
+ // =============================================================================
187
+ // metaschema_public tables
188
+ // =============================================================================
189
+ database: {
190
+ schema: 'metaschema_public',
191
+ table: 'database',
192
+ fields: {
193
+ id: 'uuid',
194
+ owner_id: 'uuid',
195
+ name: 'text',
196
+ hash: 'uuid'
197
+ }
198
+ },
199
+ database_extension: {
200
+ schema: 'metaschema_public',
201
+ table: 'database_extension',
202
+ fields: {
203
+ id: 'uuid',
204
+ database_id: 'uuid',
205
+ name: 'text',
206
+ schema_id: 'uuid'
207
+ }
208
+ },
209
+ schema: {
210
+ schema: 'metaschema_public',
211
+ table: 'schema',
212
+ fields: {
213
+ id: 'uuid',
214
+ database_id: 'uuid',
215
+ name: 'text',
216
+ schema_name: 'text',
217
+ description: 'text',
218
+ is_public: 'boolean'
219
+ }
220
+ },
221
+ table: {
222
+ schema: 'metaschema_public',
223
+ table: 'table',
224
+ fields: {
225
+ id: 'uuid',
226
+ database_id: 'uuid',
227
+ schema_id: 'uuid',
228
+ name: 'text',
229
+ description: 'text'
230
+ }
231
+ },
232
+ field: {
233
+ schema: 'metaschema_public',
234
+ table: 'field',
235
+ // Use ON CONFLICT DO NOTHING to handle the unique constraint (databases_field_uniq_names_idx)
236
+ // which normalizes UUID field names by stripping suffixes like _id, _uuid, etc.
237
+ // This causes collisions when tables have both 'foo' (text) and 'foo_id' (uuid) columns.
238
+ conflictDoNothing: true,
239
+ fields: {
240
+ id: 'uuid',
241
+ database_id: 'uuid',
242
+ table_id: 'uuid',
243
+ name: 'text',
244
+ type: 'text',
245
+ description: 'text'
246
+ }
247
+ },
248
+ policy: {
249
+ schema: 'metaschema_public',
250
+ table: 'policy',
251
+ fields: {
252
+ id: 'uuid',
253
+ database_id: 'uuid',
254
+ table_id: 'uuid',
255
+ name: 'text',
256
+ grantee_name: 'text',
257
+ privilege: 'text',
258
+ permissive: 'boolean',
259
+ disabled: 'boolean',
260
+ policy_type: 'text',
261
+ data: 'jsonb'
262
+ }
263
+ },
264
+ index: {
265
+ schema: 'metaschema_public',
266
+ table: 'index',
267
+ fields: {
268
+ id: 'uuid',
269
+ database_id: 'uuid',
270
+ table_id: 'uuid',
271
+ name: 'text',
272
+ field_ids: 'uuid[]',
273
+ include_field_ids: 'uuid[]',
274
+ access_method: 'text',
275
+ index_params: 'jsonb',
276
+ where_clause: 'jsonb',
277
+ is_unique: 'boolean'
278
+ }
279
+ },
280
+ trigger: {
281
+ schema: 'metaschema_public',
282
+ table: 'trigger',
283
+ fields: {
284
+ id: 'uuid',
285
+ database_id: 'uuid',
286
+ table_id: 'uuid',
287
+ name: 'text',
288
+ event: 'text',
289
+ function_name: 'text'
290
+ }
291
+ },
292
+ trigger_function: {
293
+ schema: 'metaschema_public',
294
+ table: 'trigger_function',
295
+ fields: {
296
+ id: 'uuid',
297
+ database_id: 'uuid',
298
+ name: 'text',
299
+ code: 'text'
300
+ }
301
+ },
302
+ rls_function: {
303
+ schema: 'metaschema_public',
304
+ table: 'rls_function',
305
+ fields: {
306
+ id: 'uuid',
307
+ database_id: 'uuid',
308
+ table_id: 'uuid',
309
+ name: 'text',
310
+ label: 'text',
311
+ description: 'text',
312
+ data: 'jsonb',
313
+ inline: 'boolean',
314
+ security: 'int'
315
+ }
316
+ },
317
+ foreign_key_constraint: {
318
+ schema: 'metaschema_public',
319
+ table: 'foreign_key_constraint',
320
+ fields: {
321
+ id: 'uuid',
322
+ database_id: 'uuid',
323
+ table_id: 'uuid',
324
+ name: 'text',
325
+ description: 'text',
326
+ smart_tags: 'jsonb',
327
+ type: 'text',
328
+ field_ids: 'uuid[]',
329
+ ref_table_id: 'uuid',
330
+ ref_field_ids: 'uuid[]',
331
+ delete_action: 'text',
332
+ update_action: 'text'
333
+ }
334
+ },
335
+ primary_key_constraint: {
336
+ schema: 'metaschema_public',
337
+ table: 'primary_key_constraint',
338
+ fields: {
339
+ id: 'uuid',
340
+ database_id: 'uuid',
341
+ table_id: 'uuid',
342
+ name: 'text',
343
+ type: 'text',
344
+ field_ids: 'uuid[]'
345
+ }
346
+ },
347
+ unique_constraint: {
348
+ schema: 'metaschema_public',
349
+ table: 'unique_constraint',
350
+ fields: {
351
+ id: 'uuid',
352
+ database_id: 'uuid',
353
+ table_id: 'uuid',
354
+ name: 'text',
355
+ description: 'text',
356
+ smart_tags: 'jsonb',
357
+ type: 'text',
358
+ field_ids: 'uuid[]'
359
+ }
360
+ },
361
+ check_constraint: {
362
+ schema: 'metaschema_public',
363
+ table: 'check_constraint',
364
+ fields: {
365
+ id: 'uuid',
366
+ database_id: 'uuid',
367
+ table_id: 'uuid',
368
+ name: 'text',
369
+ type: 'text',
370
+ field_ids: 'uuid[]',
371
+ expr: 'jsonb'
372
+ }
373
+ },
374
+ full_text_search: {
375
+ schema: 'metaschema_public',
376
+ table: 'full_text_search',
377
+ fields: {
378
+ id: 'uuid',
379
+ database_id: 'uuid',
380
+ table_id: 'uuid',
381
+ field_id: 'uuid',
382
+ field_ids: 'uuid[]',
383
+ weights: 'text[]',
384
+ langs: 'text[]'
385
+ }
386
+ },
387
+ schema_grant: {
388
+ schema: 'metaschema_public',
389
+ table: 'schema_grant',
390
+ fields: {
391
+ id: 'uuid',
392
+ database_id: 'uuid',
393
+ schema_id: 'uuid',
394
+ grantee_name: 'text'
395
+ }
396
+ },
397
+ table_grant: {
398
+ schema: 'metaschema_public',
399
+ table: 'table_grant',
400
+ fields: {
401
+ id: 'uuid',
402
+ database_id: 'uuid',
403
+ table_id: 'uuid',
404
+ privilege: 'text',
405
+ grantee_name: 'text',
406
+ field_ids: 'uuid[]',
407
+ is_grant: 'boolean'
408
+ }
409
+ },
410
+ default_privilege: {
411
+ schema: 'metaschema_public',
412
+ table: 'default_privilege',
413
+ fields: {
414
+ id: 'uuid',
415
+ database_id: 'uuid',
416
+ schema_id: 'uuid',
417
+ object_type: 'text',
418
+ privilege: 'text',
419
+ grantee_name: 'text',
420
+ is_grant: 'boolean'
421
+ }
422
+ },
423
+ // =============================================================================
424
+ // services_public tables
425
+ // =============================================================================
426
+ domains: {
427
+ schema: 'services_public',
428
+ table: 'domains',
429
+ fields: {
430
+ id: 'uuid',
431
+ database_id: 'uuid',
432
+ site_id: 'uuid',
433
+ api_id: 'uuid',
434
+ domain: 'text',
435
+ subdomain: 'text'
436
+ }
437
+ },
438
+ sites: {
439
+ schema: 'services_public',
440
+ table: 'sites',
441
+ fields: {
442
+ id: 'uuid',
443
+ database_id: 'uuid',
444
+ title: 'text',
445
+ description: 'text',
446
+ og_image: 'image',
447
+ favicon: 'upload',
448
+ apple_touch_icon: 'image',
449
+ logo: 'image'
450
+ }
451
+ },
452
+ apis: {
453
+ schema: 'services_public',
454
+ table: 'apis',
455
+ fields: {
456
+ id: 'uuid',
457
+ database_id: 'uuid',
458
+ name: 'text',
459
+ is_public: 'boolean',
460
+ role_name: 'text',
461
+ anon_role: 'text'
462
+ }
463
+ },
464
+ apps: {
465
+ schema: 'services_public',
466
+ table: 'apps',
467
+ fields: {
468
+ id: 'uuid',
469
+ database_id: 'uuid',
470
+ site_id: 'uuid',
471
+ name: 'text',
472
+ app_image: 'image',
473
+ app_store_link: 'url',
474
+ app_store_id: 'text',
475
+ app_id_prefix: 'text',
476
+ play_store_link: 'url'
477
+ }
478
+ },
479
+ site_modules: {
480
+ schema: 'services_public',
481
+ table: 'site_modules',
482
+ fields: {
483
+ id: 'uuid',
484
+ database_id: 'uuid',
485
+ site_id: 'uuid',
486
+ name: 'text',
487
+ data: 'jsonb'
488
+ }
489
+ },
490
+ site_themes: {
491
+ schema: 'services_public',
492
+ table: 'site_themes',
493
+ fields: {
494
+ id: 'uuid',
495
+ database_id: 'uuid',
496
+ site_id: 'uuid',
497
+ theme: 'jsonb'
498
+ }
499
+ },
500
+ site_metadata: {
501
+ schema: 'services_public',
502
+ table: 'site_metadata',
503
+ fields: {
504
+ id: 'uuid',
505
+ database_id: 'uuid',
506
+ site_id: 'uuid',
507
+ title: 'text',
508
+ description: 'text',
509
+ og_image: 'image'
510
+ }
511
+ },
512
+ api_modules: {
513
+ schema: 'services_public',
514
+ table: 'api_modules',
515
+ fields: {
516
+ id: 'uuid',
517
+ database_id: 'uuid',
518
+ api_id: 'uuid',
519
+ name: 'text',
520
+ data: 'jsonb'
521
+ }
522
+ },
523
+ api_extensions: {
524
+ schema: 'services_public',
525
+ table: 'api_extensions',
526
+ fields: {
527
+ id: 'uuid',
528
+ database_id: 'uuid',
529
+ api_id: 'uuid',
530
+ name: 'text'
531
+ }
532
+ },
533
+ api_schemas: {
534
+ schema: 'services_public',
535
+ table: 'api_schemas',
536
+ fields: {
537
+ id: 'uuid',
538
+ database_id: 'uuid',
539
+ schema_id: 'uuid',
540
+ api_id: 'uuid'
541
+ }
542
+ },
543
+ // =============================================================================
544
+ // metaschema_modules_public tables
545
+ // =============================================================================
546
+ rls_module: {
547
+ schema: 'metaschema_modules_public',
548
+ table: 'rls_module',
549
+ fields: {
550
+ id: 'uuid',
551
+ database_id: 'uuid',
552
+ schema_id: 'uuid',
553
+ private_schema_id: 'uuid',
554
+ session_credentials_table_id: 'uuid',
555
+ sessions_table_id: 'uuid',
556
+ users_table_id: 'uuid',
557
+ authenticate: 'text',
558
+ authenticate_strict: 'text',
559
+ current_role: 'text',
560
+ current_role_id: 'text'
561
+ }
562
+ },
563
+ user_auth_module: {
564
+ schema: 'metaschema_modules_public',
565
+ table: 'user_auth_module',
566
+ fields: {
567
+ id: 'uuid',
568
+ database_id: 'uuid',
569
+ schema_id: 'uuid',
570
+ emails_table_id: 'uuid',
571
+ users_table_id: 'uuid',
572
+ secrets_table_id: 'uuid',
573
+ encrypted_table_id: 'uuid',
574
+ sessions_table_id: 'uuid',
575
+ session_credentials_table_id: 'uuid',
576
+ audits_table_id: 'uuid',
577
+ audits_table_name: 'text',
578
+ sign_in_function: 'text',
579
+ sign_up_function: 'text',
580
+ sign_out_function: 'text',
581
+ sign_in_one_time_token_function: 'text',
582
+ one_time_token_function: 'text',
583
+ extend_token_expires: 'text',
584
+ send_account_deletion_email_function: 'text',
585
+ delete_account_function: 'text',
586
+ set_password_function: 'text',
587
+ reset_password_function: 'text',
588
+ forgot_password_function: 'text',
589
+ send_verification_email_function: 'text',
590
+ verify_email_function: 'text',
591
+ verify_password_function: 'text',
592
+ check_password_function: 'text'
593
+ }
594
+ },
595
+ memberships_module: {
596
+ schema: 'metaschema_modules_public',
597
+ table: 'memberships_module',
598
+ fields: {
599
+ id: 'uuid',
600
+ database_id: 'uuid',
601
+ schema_id: 'uuid',
602
+ private_schema_id: 'uuid',
603
+ memberships_table_id: 'uuid',
604
+ memberships_table_name: 'text',
605
+ members_table_id: 'uuid',
606
+ members_table_name: 'text',
607
+ membership_defaults_table_id: 'uuid',
608
+ membership_defaults_table_name: 'text',
609
+ grants_table_id: 'uuid',
610
+ grants_table_name: 'text',
611
+ actor_table_id: 'uuid',
612
+ limits_table_id: 'uuid',
613
+ default_limits_table_id: 'uuid',
614
+ permissions_table_id: 'uuid',
615
+ default_permissions_table_id: 'uuid',
616
+ sprt_table_id: 'uuid',
617
+ admin_grants_table_id: 'uuid',
618
+ admin_grants_table_name: 'text',
619
+ owner_grants_table_id: 'uuid',
620
+ owner_grants_table_name: 'text',
621
+ membership_type: 'int',
622
+ entity_table_id: 'uuid',
623
+ entity_table_owner_id: 'uuid',
624
+ prefix: 'text',
625
+ actor_mask_check: 'text',
626
+ actor_perm_check: 'text',
627
+ entity_ids_by_mask: 'text',
628
+ entity_ids_by_perm: 'text',
629
+ entity_ids_function: 'text'
630
+ }
631
+ },
632
+ permissions_module: {
633
+ schema: 'metaschema_modules_public',
634
+ table: 'permissions_module',
635
+ fields: {
636
+ id: 'uuid',
637
+ database_id: 'uuid',
638
+ schema_id: 'uuid',
639
+ private_schema_id: 'uuid',
640
+ table_id: 'uuid',
641
+ table_name: 'text',
642
+ default_table_id: 'uuid',
643
+ default_table_name: 'text',
644
+ bitlen: 'int',
645
+ membership_type: 'int',
646
+ entity_table_id: 'uuid',
647
+ actor_table_id: 'uuid',
648
+ prefix: 'text',
649
+ get_padded_mask: 'text',
650
+ get_mask: 'text',
651
+ get_by_mask: 'text',
652
+ get_mask_by_name: 'text'
653
+ }
654
+ },
655
+ limits_module: {
656
+ schema: 'metaschema_modules_public',
657
+ table: 'limits_module',
658
+ fields: {
659
+ id: 'uuid',
660
+ database_id: 'uuid',
661
+ schema_id: 'uuid',
662
+ private_schema_id: 'uuid',
663
+ table_id: 'uuid',
664
+ table_name: 'text',
665
+ default_table_id: 'uuid',
666
+ default_table_name: 'text',
667
+ limit_increment_function: 'text',
668
+ limit_decrement_function: 'text',
669
+ limit_increment_trigger: 'text',
670
+ limit_decrement_trigger: 'text',
671
+ limit_update_trigger: 'text',
672
+ limit_check_function: 'text',
673
+ prefix: 'text',
674
+ membership_type: 'int',
675
+ entity_table_id: 'uuid',
676
+ actor_table_id: 'uuid'
677
+ }
678
+ },
679
+ levels_module: {
680
+ schema: 'metaschema_modules_public',
681
+ table: 'levels_module',
682
+ fields: {
683
+ id: 'uuid',
684
+ database_id: 'uuid',
685
+ schema_id: 'uuid',
686
+ private_schema_id: 'uuid',
687
+ steps_table_id: 'uuid',
688
+ steps_table_name: 'text',
689
+ achievements_table_id: 'uuid',
690
+ achievements_table_name: 'text',
691
+ levels_table_id: 'uuid',
692
+ levels_table_name: 'text',
693
+ level_requirements_table_id: 'uuid',
694
+ level_requirements_table_name: 'text',
695
+ completed_step: 'text',
696
+ incompleted_step: 'text',
697
+ tg_achievement: 'text',
698
+ tg_achievement_toggle: 'text',
699
+ tg_achievement_toggle_boolean: 'text',
700
+ tg_achievement_boolean: 'text',
701
+ upsert_achievement: 'text',
702
+ tg_update_achievements: 'text',
703
+ steps_required: 'text',
704
+ level_achieved: 'text',
705
+ prefix: 'text',
706
+ membership_type: 'int',
707
+ entity_table_id: 'uuid',
708
+ actor_table_id: 'uuid'
709
+ }
710
+ },
711
+ users_module: {
712
+ schema: 'metaschema_modules_public',
713
+ table: 'users_module',
714
+ fields: {
715
+ id: 'uuid',
716
+ database_id: 'uuid',
717
+ schema_id: 'uuid',
718
+ table_id: 'uuid',
719
+ table_name: 'text',
720
+ type_table_id: 'uuid',
721
+ type_table_name: 'text'
722
+ }
723
+ },
724
+ hierarchy_module: {
725
+ schema: 'metaschema_modules_public',
726
+ table: 'hierarchy_module',
727
+ fields: {
728
+ id: 'uuid',
729
+ database_id: 'uuid',
730
+ schema_id: 'uuid',
731
+ private_schema_id: 'uuid',
732
+ chart_edges_table_id: 'uuid',
733
+ chart_edges_table_name: 'text',
734
+ hierarchy_sprt_table_id: 'uuid',
735
+ hierarchy_sprt_table_name: 'text',
736
+ chart_edge_grants_table_id: 'uuid',
737
+ chart_edge_grants_table_name: 'text',
738
+ entity_table_id: 'uuid',
739
+ users_table_id: 'uuid',
740
+ prefix: 'text',
741
+ private_schema_name: 'text',
742
+ sprt_table_name: 'text',
743
+ rebuild_hierarchy_function: 'text',
744
+ get_subordinates_function: 'text',
745
+ get_managers_function: 'text',
746
+ is_manager_of_function: 'text'
747
+ }
748
+ },
749
+ membership_types_module: {
750
+ schema: 'metaschema_modules_public',
751
+ table: 'membership_types_module',
752
+ fields: {
753
+ id: 'uuid',
754
+ database_id: 'uuid',
755
+ schema_id: 'uuid',
756
+ table_id: 'uuid',
757
+ table_name: 'text'
758
+ }
759
+ },
760
+ invites_module: {
761
+ schema: 'metaschema_modules_public',
762
+ table: 'invites_module',
763
+ fields: {
764
+ id: 'uuid',
765
+ database_id: 'uuid',
766
+ schema_id: 'uuid',
767
+ private_schema_id: 'uuid',
768
+ emails_table_id: 'uuid',
769
+ users_table_id: 'uuid',
770
+ invites_table_id: 'uuid',
771
+ claimed_invites_table_id: 'uuid',
772
+ invites_table_name: 'text',
773
+ claimed_invites_table_name: 'text',
774
+ submit_invite_code_function: 'text',
775
+ prefix: 'text',
776
+ membership_type: 'int',
777
+ entity_table_id: 'uuid'
778
+ }
779
+ },
780
+ emails_module: {
781
+ schema: 'metaschema_modules_public',
782
+ table: 'emails_module',
783
+ fields: {
784
+ id: 'uuid',
785
+ database_id: 'uuid',
786
+ schema_id: 'uuid',
787
+ private_schema_id: 'uuid',
788
+ table_id: 'uuid',
789
+ owner_table_id: 'uuid',
790
+ table_name: 'text'
791
+ }
792
+ },
793
+ sessions_module: {
794
+ schema: 'metaschema_modules_public',
795
+ table: 'sessions_module',
796
+ fields: {
797
+ id: 'uuid',
798
+ database_id: 'uuid',
799
+ schema_id: 'uuid',
800
+ sessions_table_id: 'uuid',
801
+ session_credentials_table_id: 'uuid',
802
+ auth_settings_table_id: 'uuid',
803
+ users_table_id: 'uuid',
804
+ sessions_default_expiration: 'interval',
805
+ sessions_table: 'text',
806
+ session_credentials_table: 'text',
807
+ auth_settings_table: 'text'
808
+ }
809
+ },
810
+ secrets_module: {
811
+ schema: 'metaschema_modules_public',
812
+ table: 'secrets_module',
813
+ fields: {
814
+ id: 'uuid',
815
+ database_id: 'uuid',
816
+ schema_id: 'uuid',
817
+ table_id: 'uuid',
818
+ table_name: 'text'
819
+ }
820
+ },
821
+ profiles_module: {
822
+ schema: 'metaschema_modules_public',
823
+ table: 'profiles_module',
824
+ fields: {
825
+ id: 'uuid',
826
+ database_id: 'uuid',
827
+ schema_id: 'uuid',
828
+ private_schema_id: 'uuid',
829
+ table_id: 'uuid',
830
+ table_name: 'text',
831
+ profile_permissions_table_id: 'uuid',
832
+ profile_permissions_table_name: 'text',
833
+ profile_grants_table_id: 'uuid',
834
+ profile_grants_table_name: 'text',
835
+ profile_definition_grants_table_id: 'uuid',
836
+ profile_definition_grants_table_name: 'text',
837
+ membership_type: 'int',
838
+ entity_table_id: 'uuid',
839
+ actor_table_id: 'uuid',
840
+ permissions_table_id: 'uuid',
841
+ memberships_table_id: 'uuid',
842
+ prefix: 'text'
843
+ }
844
+ },
845
+ encrypted_secrets_module: {
846
+ schema: 'metaschema_modules_public',
847
+ table: 'encrypted_secrets_module',
848
+ fields: {
849
+ id: 'uuid',
850
+ database_id: 'uuid',
851
+ schema_id: 'uuid',
852
+ table_id: 'uuid',
853
+ table_name: 'text'
854
+ }
855
+ },
856
+ connected_accounts_module: {
857
+ schema: 'metaschema_modules_public',
858
+ table: 'connected_accounts_module',
859
+ fields: {
860
+ id: 'uuid',
861
+ database_id: 'uuid',
862
+ schema_id: 'uuid',
863
+ private_schema_id: 'uuid',
864
+ table_id: 'uuid',
865
+ owner_table_id: 'uuid',
866
+ table_name: 'text'
867
+ }
868
+ },
869
+ phone_numbers_module: {
870
+ schema: 'metaschema_modules_public',
871
+ table: 'phone_numbers_module',
872
+ fields: {
873
+ id: 'uuid',
874
+ database_id: 'uuid',
875
+ schema_id: 'uuid',
876
+ private_schema_id: 'uuid',
877
+ table_id: 'uuid',
878
+ owner_table_id: 'uuid',
879
+ table_name: 'text'
880
+ }
881
+ },
882
+ crypto_addresses_module: {
883
+ schema: 'metaschema_modules_public',
884
+ table: 'crypto_addresses_module',
885
+ fields: {
886
+ id: 'uuid',
887
+ database_id: 'uuid',
888
+ schema_id: 'uuid',
889
+ private_schema_id: 'uuid',
890
+ table_id: 'uuid',
891
+ owner_table_id: 'uuid',
892
+ table_name: 'text',
893
+ crypto_network: 'text'
894
+ }
895
+ },
896
+ crypto_auth_module: {
897
+ schema: 'metaschema_modules_public',
898
+ table: 'crypto_auth_module',
899
+ fields: {
900
+ id: 'uuid',
901
+ database_id: 'uuid',
902
+ schema_id: 'uuid',
903
+ users_table_id: 'uuid',
904
+ sessions_table_id: 'uuid',
905
+ session_credentials_table_id: 'uuid',
906
+ secrets_table_id: 'uuid',
907
+ addresses_table_id: 'uuid',
908
+ user_field: 'text',
909
+ crypto_network: 'text',
910
+ sign_in_request_challenge: 'text',
911
+ sign_in_record_failure: 'text',
912
+ sign_up_with_key: 'text',
913
+ sign_in_with_challenge: 'text'
914
+ }
915
+ },
916
+ field_module: {
917
+ schema: 'metaschema_modules_public',
918
+ table: 'field_module',
919
+ fields: {
920
+ id: 'uuid',
921
+ database_id: 'uuid',
922
+ private_schema_id: 'uuid',
923
+ table_id: 'uuid',
924
+ field_id: 'uuid',
925
+ node_type: 'text',
926
+ data: 'jsonb',
927
+ triggers: 'text[]',
928
+ functions: 'text[]'
929
+ }
930
+ },
931
+ table_module: {
932
+ schema: 'metaschema_modules_public',
933
+ table: 'table_module',
934
+ fields: {
935
+ id: 'uuid',
936
+ database_id: 'uuid',
937
+ private_schema_id: 'uuid',
938
+ table_id: 'uuid',
939
+ node_type: 'text',
940
+ data: 'jsonb',
941
+ fields: 'uuid[]'
942
+ }
943
+ },
944
+ table_template_module: {
945
+ schema: 'metaschema_modules_public',
946
+ table: 'table_template_module',
947
+ fields: {
948
+ id: 'uuid',
949
+ database_id: 'uuid',
950
+ schema_id: 'uuid',
951
+ private_schema_id: 'uuid',
952
+ table_id: 'uuid',
953
+ owner_table_id: 'uuid',
954
+ table_name: 'text',
955
+ node_type: 'text',
956
+ data: 'jsonb'
957
+ }
958
+ },
959
+ secure_table_provision: {
960
+ schema: 'metaschema_modules_public',
961
+ table: 'secure_table_provision',
962
+ fields: {
963
+ id: 'uuid',
964
+ database_id: 'uuid',
965
+ schema_id: 'uuid',
966
+ table_id: 'uuid',
967
+ table_name: 'text',
968
+ node_type: 'text',
969
+ use_rls: 'boolean',
970
+ node_data: 'jsonb',
971
+ grant_roles: 'text[]',
972
+ fields: 'jsonb[]',
973
+ grant_privileges: 'jsonb[]',
974
+ policy_type: 'text',
975
+ policy_privileges: 'text[]',
976
+ policy_role: 'text',
977
+ policy_permissive: 'boolean',
978
+ policy_data: 'jsonb',
979
+ out_fields: 'uuid[]'
980
+ }
981
+ },
982
+ uuid_module: {
983
+ schema: 'metaschema_modules_public',
984
+ table: 'uuid_module',
985
+ fields: {
986
+ id: 'uuid',
987
+ database_id: 'uuid',
988
+ schema_id: 'uuid',
989
+ uuid_function: 'text',
990
+ uuid_seed: 'text'
991
+ }
992
+ },
993
+ default_ids_module: {
994
+ schema: 'metaschema_modules_public',
995
+ table: 'default_ids_module',
996
+ fields: {
997
+ id: 'uuid',
998
+ database_id: 'uuid'
999
+ }
1000
+ },
1001
+ denormalized_table_field: {
1002
+ schema: 'metaschema_modules_public',
1003
+ table: 'denormalized_table_field',
1004
+ fields: {
1005
+ id: 'uuid',
1006
+ database_id: 'uuid',
1007
+ table_id: 'uuid',
1008
+ field_id: 'uuid',
1009
+ set_ids: 'uuid[]',
1010
+ ref_table_id: 'uuid',
1011
+ ref_field_id: 'uuid',
1012
+ ref_ids: 'uuid[]',
1013
+ use_updates: 'boolean',
1014
+ update_defaults: 'boolean',
1015
+ func_name: 'text',
1016
+ func_order: 'int'
1017
+ }
1018
+ }
1019
+ };
1020
+ // =============================================================================
1021
+ // Shared functions
1022
+ // =============================================================================
1023
+ /**
1024
+ * Checks which pgpm modules from the extensions list are missing from the workspace
1025
+ * and prompts the user if they want to install them.
1026
+ *
1027
+ * This function only does detection and prompting - it does NOT install.
1028
+ * Use installMissingModules() after the module is created to do the actual installation.
1029
+ *
1030
+ * @param project - The PgpmPackage instance (only needs workspace context)
1031
+ * @param extensions - List of extension names (control file names)
1032
+ * @param prompter - Optional prompter for interactive confirmation
1033
+ * @returns Object with missing modules and whether user wants to install them
1034
+ */
1035
+ const detectMissingModules = async (project, extensions, prompter, argv) => {
1036
+ // Use workspace-level check - doesn't require being inside a module
1037
+ const installed = project.getWorkspaceInstalledModules();
1038
+ const missingModules = (0, core_1.getMissingInstallableModules)(extensions, installed);
1039
+ if (missingModules.length === 0) {
1040
+ return { missingModules: [], shouldInstall: false };
1041
+ }
1042
+ const missingNames = missingModules.map(m => m.npmName);
1043
+ console.log(`\nMissing pgpm modules detected: ${missingNames.join(', ')}`);
1044
+ if (prompter) {
1045
+ const { install } = await prompter.prompt(argv || {}, [
1046
+ {
1047
+ type: 'confirm',
1048
+ name: 'install',
1049
+ message: `Install missing modules (${missingNames.join(', ')})?`,
1050
+ default: true
1051
+ }
1052
+ ]);
1053
+ return { missingModules, shouldInstall: install };
1054
+ }
1055
+ return { missingModules, shouldInstall: false };
1056
+ };
1057
+ exports.detectMissingModules = detectMissingModules;
1058
+ /**
1059
+ * Installs missing modules into a specific module directory.
1060
+ * Must be called after the module has been created.
1061
+ *
1062
+ * @param moduleDir - The directory of the module to install into
1063
+ * @param missingModules - Array of missing modules to install
1064
+ */
1065
+ const installMissingModules = async (moduleDir, missingModules) => {
1066
+ if (missingModules.length === 0) {
1067
+ return;
1068
+ }
1069
+ const missingNames = missingModules.map(m => m.npmName);
1070
+ console.log('Installing missing modules...');
1071
+ // Create a new PgpmPackage instance pointing to the module directory
1072
+ const moduleProject = new core_1.PgpmPackage(moduleDir);
1073
+ await moduleProject.installModules(...missingNames);
1074
+ console.log('Modules installed successfully.');
1075
+ };
1076
+ exports.installMissingModules = installMissingModules;
1077
+ /**
1078
+ * Generates a function for replacing schema names and extension names in strings.
1079
+ */
1080
+ const makeReplacer = ({ schemas, name, schemaPrefix }) => {
1081
+ const replacements = ['constructive-extension-name', name];
1082
+ const prefix = schemaPrefix || name;
1083
+ const schemaReplacers = schemas.map((schema) => [
1084
+ schema.schema_name,
1085
+ (0, komoji_1.toSnakeCase)(`${prefix}_${schema.name}`)
1086
+ ]);
1087
+ const replace = [...schemaReplacers, replacements].map(([from, to]) => [new RegExp(from, 'g'), to]);
1088
+ const replacer = (str, n = 0) => {
1089
+ if (!str)
1090
+ return '';
1091
+ if (replace[n] && replace[n].length === 2) {
1092
+ return replacer(str.replace(replace[n][0], replace[n][1]), n + 1);
1093
+ }
1094
+ return str;
1095
+ };
1096
+ return { replacer, replace };
1097
+ };
1098
+ exports.makeReplacer = makeReplacer;
1099
+ /**
1100
+ * Creates a PGPM package directory or resets the deploy/revert/verify directories if it exists.
1101
+ * If the module already exists and a prompter is provided, prompts the user for confirmation.
1102
+ *
1103
+ * @returns The absolute path to the created/prepared module directory
1104
+ */
1105
+ const preparePackage = async ({ project, author, outdir, name, description, extensions, prompter, repoName, username }) => {
1106
+ const curDir = process.cwd();
1107
+ const pgpmDir = path_1.default.resolve(path_1.default.join(outdir, name));
1108
+ (0, fs_1.mkdirSync)(pgpmDir, { recursive: true });
1109
+ process.chdir(pgpmDir);
1110
+ try {
1111
+ const plan = (0, glob_1.sync)(path_1.default.join(pgpmDir, 'pgpm.plan'));
1112
+ if (!plan.length) {
1113
+ const { fullName, email } = (0, core_1.parseAuthor)(author);
1114
+ // Always run non-interactively — all answers are pre-filled
1115
+ const effectiveUsername = username || fullName || 'export';
1116
+ await project.initModule({
1117
+ name,
1118
+ description,
1119
+ author,
1120
+ extensions,
1121
+ // Use outputDir to create module directly in the specified location
1122
+ outputDir: outdir,
1123
+ noTty: true,
1124
+ prompter,
1125
+ answers: {
1126
+ moduleName: name,
1127
+ moduleDesc: description,
1128
+ access: 'restricted',
1129
+ license: 'CLOSED',
1130
+ fullName,
1131
+ ...(email && { email }),
1132
+ // Use provided values or sensible defaults
1133
+ repoName: repoName || name,
1134
+ username: effectiveUsername
1135
+ }
1136
+ });
1137
+ }
1138
+ else {
1139
+ if (prompter) {
1140
+ const { overwrite } = await prompter.prompt({}, [
1141
+ {
1142
+ type: 'confirm',
1143
+ name: 'overwrite',
1144
+ message: `Module "${name}" already exists at ${pgpmDir}. Overwrite deploy/revert/verify directories?`,
1145
+ default: true,
1146
+ useDefault: true
1147
+ }
1148
+ ]);
1149
+ if (!overwrite) {
1150
+ throw new Error(`Export cancelled: Module "${name}" already exists.`);
1151
+ }
1152
+ }
1153
+ (0, fs_1.rmSync)(path_1.default.resolve(pgpmDir, 'deploy'), { recursive: true, force: true });
1154
+ (0, fs_1.rmSync)(path_1.default.resolve(pgpmDir, 'revert'), { recursive: true, force: true });
1155
+ (0, fs_1.rmSync)(path_1.default.resolve(pgpmDir, 'verify'), { recursive: true, force: true });
1156
+ }
1157
+ }
1158
+ finally {
1159
+ process.chdir(curDir);
1160
+ }
1161
+ return pgpmDir;
1162
+ };
1163
+ exports.preparePackage = preparePackage;
1164
+ /**
1165
+ * Normalizes an output directory path to ensure it ends with a path separator.
1166
+ */
1167
+ const normalizeOutdir = (outdir) => {
1168
+ return outdir.endsWith(path_1.default.sep) ? outdir : outdir + path_1.default.sep;
1169
+ };
1170
+ exports.normalizeOutdir = normalizeOutdir;