@rebasepro/server-postgresql 0.7.0 → 0.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.
- package/build-errors.txt +37 -0
- package/dist/PostgresAdapter.d.ts +6 -0
- package/dist/PostgresBackendDriver.d.ts +118 -0
- package/dist/PostgresBootstrapper.d.ts +46 -0
- package/dist/auth/ensure-tables.d.ts +10 -0
- package/dist/auth/services.d.ts +251 -0
- package/dist/cli-errors.d.ts +29 -0
- package/dist/cli-helpers.d.ts +7 -0
- package/dist/cli.d.ts +1 -0
- package/dist/collections/PostgresCollectionRegistry.d.ts +47 -0
- package/dist/connection.d.ts +65 -0
- package/dist/data-transformer.d.ts +55 -0
- package/dist/databasePoolManager.d.ts +20 -0
- package/dist/history/HistoryService.d.ts +71 -0
- package/dist/history/ensure-history-table.d.ts +7 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.es.js +13560 -0
- package/dist/index.es.js.map +1 -0
- package/dist/interfaces.d.ts +18 -0
- package/dist/schema/auth-default-policies.d.ts +12 -0
- package/dist/schema/auth-schema.d.ts +2376 -0
- package/dist/schema/doctor-cli.d.ts +2 -0
- package/dist/schema/doctor.d.ts +52 -0
- package/dist/schema/generate-drizzle-schema-logic.d.ts +2 -0
- package/dist/schema/generate-drizzle-schema.d.ts +1 -0
- package/dist/schema/generate-postgres-ddl-logic.d.ts +5 -0
- package/dist/schema/generate-postgres-ddl.d.ts +1 -0
- package/dist/schema/introspect-db-inference.d.ts +5 -0
- package/dist/schema/introspect-db-logic.d.ts +118 -0
- package/dist/schema/introspect-db.d.ts +1 -0
- package/dist/schema/test-schema.d.ts +24 -0
- package/dist/services/BranchService.d.ts +47 -0
- package/dist/services/EntityFetchService.d.ts +214 -0
- package/dist/services/EntityPersistService.d.ts +40 -0
- package/dist/services/RelationService.d.ts +98 -0
- package/dist/services/entity-helpers.d.ts +38 -0
- package/dist/services/entityService.d.ts +110 -0
- package/dist/services/index.d.ts +4 -0
- package/dist/services/realtimeService.d.ts +220 -0
- package/dist/types.d.ts +3 -0
- package/dist/utils/drizzle-conditions.d.ts +138 -0
- package/dist/utils/pg-array-null-patch.d.ts +16 -0
- package/dist/utils/pg-error-utils.d.ts +65 -0
- package/dist/utils/table-classification.d.ts +8 -0
- package/dist/websocket.d.ts +11 -0
- package/package.json +17 -17
- package/src/PostgresBackendDriver.ts +135 -24
- package/src/cli.ts +73 -1
- package/src/collections/PostgresCollectionRegistry.ts +6 -6
- package/src/data-transformer.ts +2 -2
- package/src/schema/auth-default-policies.ts +13 -6
- package/src/schema/generate-drizzle-schema-logic.ts +16 -79
- package/src/schema/generate-postgres-ddl-logic.ts +14 -51
- package/src/services/realtimeService.ts +26 -2
- package/test/entity-callbacks-redaction.test.ts +86 -0
- package/test/postgresDataDriver.test.ts +2 -1
|
@@ -133,6 +133,7 @@ export class PostgresBackendDriver implements DataDriver {
|
|
|
133
133
|
if (!collection && !path) return {
|
|
134
134
|
collection: undefined,
|
|
135
135
|
callbacks: undefined,
|
|
136
|
+
globalCallbacks: undefined,
|
|
136
137
|
propertyCallbacks: undefined
|
|
137
138
|
};
|
|
138
139
|
const registryCollection = this.registry?.getCollectionByPath(path);
|
|
@@ -144,6 +145,7 @@ export class PostgresBackendDriver implements DataDriver {
|
|
|
144
145
|
: collection as EntityCollection<M>;
|
|
145
146
|
|
|
146
147
|
const callbacks = resolvedCollection?.callbacks;
|
|
148
|
+
const globalCallbacks = this.registry?.getGlobalCallbacks();
|
|
147
149
|
const properties = resolvedCollection?.properties;
|
|
148
150
|
let propertyCallbacks;
|
|
149
151
|
if (properties) {
|
|
@@ -152,6 +154,7 @@ export class PostgresBackendDriver implements DataDriver {
|
|
|
152
154
|
return {
|
|
153
155
|
collection: resolvedCollection,
|
|
154
156
|
callbacks,
|
|
157
|
+
globalCallbacks,
|
|
155
158
|
propertyCallbacks
|
|
156
159
|
};
|
|
157
160
|
}
|
|
@@ -184,13 +187,24 @@ export class PostgresBackendDriver implements DataDriver {
|
|
|
184
187
|
const {
|
|
185
188
|
collection: resolvedCollection,
|
|
186
189
|
callbacks,
|
|
190
|
+
globalCallbacks,
|
|
187
191
|
propertyCallbacks
|
|
188
192
|
} = this.resolveCollectionCallbacks(collection, path);
|
|
189
193
|
|
|
190
|
-
if (callbacks?.afterRead || propertyCallbacks?.afterRead) {
|
|
194
|
+
if (globalCallbacks?.afterRead || callbacks?.afterRead || propertyCallbacks?.afterRead) {
|
|
191
195
|
const contextForCallback = this.buildCallContext();
|
|
192
196
|
return Promise.all(entities.map(async (entity) => {
|
|
193
197
|
let fetched = entity;
|
|
198
|
+
// 1. Global callbacks first
|
|
199
|
+
if (globalCallbacks?.afterRead) {
|
|
200
|
+
fetched = await globalCallbacks.afterRead({
|
|
201
|
+
collection: resolvedCollection as unknown as EntityCollection,
|
|
202
|
+
path,
|
|
203
|
+
entity: fetched as unknown as Entity<Record<string, unknown>>,
|
|
204
|
+
context: contextForCallback
|
|
205
|
+
}) as unknown as Entity<M>;
|
|
206
|
+
}
|
|
207
|
+
// 2. Collection callbacks second
|
|
194
208
|
if (callbacks?.afterRead) {
|
|
195
209
|
fetched = await callbacks.afterRead({
|
|
196
210
|
collection: resolvedCollection as EntityCollection<M>,
|
|
@@ -199,13 +213,14 @@ export class PostgresBackendDriver implements DataDriver {
|
|
|
199
213
|
context: contextForCallback
|
|
200
214
|
}) ?? fetched;
|
|
201
215
|
}
|
|
216
|
+
// 3. Property callbacks third
|
|
202
217
|
if (propertyCallbacks?.afterRead) {
|
|
203
218
|
fetched = await propertyCallbacks.afterRead({
|
|
204
|
-
collection: resolvedCollection as EntityCollection
|
|
219
|
+
collection: resolvedCollection as unknown as EntityCollection,
|
|
205
220
|
path,
|
|
206
|
-
entity: fetched,
|
|
221
|
+
entity: fetched as unknown as Entity<Record<string, unknown>>,
|
|
207
222
|
context: contextForCallback
|
|
208
|
-
}) as Entity<M
|
|
223
|
+
}) as unknown as Entity<M>;
|
|
209
224
|
}
|
|
210
225
|
return fetched;
|
|
211
226
|
}));
|
|
@@ -293,26 +308,38 @@ export class PostgresBackendDriver implements DataDriver {
|
|
|
293
308
|
const {
|
|
294
309
|
collection: resolvedCollection,
|
|
295
310
|
callbacks,
|
|
311
|
+
globalCallbacks,
|
|
296
312
|
propertyCallbacks
|
|
297
313
|
} = this.resolveCollectionCallbacks(collection, path);
|
|
298
314
|
|
|
299
|
-
if (entity && (callbacks?.afterRead || propertyCallbacks?.afterRead)) {
|
|
315
|
+
if (entity && (globalCallbacks?.afterRead || callbacks?.afterRead || propertyCallbacks?.afterRead)) {
|
|
300
316
|
const contextForCallback = this.buildCallContext();
|
|
317
|
+
// 1. Global callbacks first
|
|
318
|
+
if (globalCallbacks?.afterRead) {
|
|
319
|
+
entity = await globalCallbacks.afterRead({
|
|
320
|
+
collection: resolvedCollection as unknown as EntityCollection,
|
|
321
|
+
path,
|
|
322
|
+
entity: entity as unknown as Entity<Record<string, unknown>>,
|
|
323
|
+
context: contextForCallback
|
|
324
|
+
}) as unknown as Entity<M>;
|
|
325
|
+
}
|
|
326
|
+
// 2. Collection callbacks second
|
|
301
327
|
if (callbacks?.afterRead) {
|
|
302
328
|
entity = await callbacks.afterRead({
|
|
303
329
|
collection: resolvedCollection as EntityCollection<M>,
|
|
304
330
|
path,
|
|
305
|
-
entity
|
|
331
|
+
entity: entity!,
|
|
306
332
|
context: contextForCallback
|
|
307
|
-
})
|
|
333
|
+
});
|
|
308
334
|
}
|
|
335
|
+
// 3. Property callbacks third
|
|
309
336
|
if (propertyCallbacks?.afterRead) {
|
|
310
337
|
entity = await propertyCallbacks.afterRead({
|
|
311
|
-
collection: resolvedCollection as EntityCollection
|
|
338
|
+
collection: resolvedCollection as unknown as EntityCollection,
|
|
312
339
|
path,
|
|
313
|
-
entity,
|
|
340
|
+
entity: entity as unknown as Entity<Record<string, unknown>>,
|
|
314
341
|
context: contextForCallback
|
|
315
|
-
}) as Entity<M
|
|
342
|
+
}) as unknown as Entity<M>;
|
|
316
343
|
}
|
|
317
344
|
}
|
|
318
345
|
|
|
@@ -375,6 +402,7 @@ export class PostgresBackendDriver implements DataDriver {
|
|
|
375
402
|
const {
|
|
376
403
|
collection: resolvedCollection,
|
|
377
404
|
callbacks,
|
|
405
|
+
globalCallbacks,
|
|
378
406
|
propertyCallbacks
|
|
379
407
|
} = this.resolveCollectionCallbacks(collection, path);
|
|
380
408
|
|
|
@@ -390,7 +418,22 @@ export class PostgresBackendDriver implements DataDriver {
|
|
|
390
418
|
}
|
|
391
419
|
}
|
|
392
420
|
|
|
393
|
-
if (callbacks?.beforeSave || propertyCallbacks?.beforeSave) {
|
|
421
|
+
if (globalCallbacks?.beforeSave || callbacks?.beforeSave || propertyCallbacks?.beforeSave) {
|
|
422
|
+
// 1. Global callbacks first
|
|
423
|
+
if (globalCallbacks?.beforeSave) {
|
|
424
|
+
const result = await globalCallbacks.beforeSave({
|
|
425
|
+
collection: resolvedCollection as unknown as EntityCollection,
|
|
426
|
+
path,
|
|
427
|
+
entityId,
|
|
428
|
+
values: updatedValues,
|
|
429
|
+
previousValues: previousValuesForHistory,
|
|
430
|
+
status,
|
|
431
|
+
context: contextForCallback
|
|
432
|
+
});
|
|
433
|
+
if (result) updatedValues = mergeDeep(updatedValues, result);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// 2. Collection callbacks second
|
|
394
437
|
if (callbacks?.beforeSave) {
|
|
395
438
|
const result = await callbacks.beforeSave({
|
|
396
439
|
collection: resolvedCollection as EntityCollection<M>,
|
|
@@ -404,9 +447,10 @@ export class PostgresBackendDriver implements DataDriver {
|
|
|
404
447
|
if (result) updatedValues = mergeDeep(updatedValues, result);
|
|
405
448
|
}
|
|
406
449
|
|
|
450
|
+
// 3. Property callbacks third
|
|
407
451
|
if (propertyCallbacks?.beforeSave) {
|
|
408
452
|
const result = await propertyCallbacks.beforeSave({
|
|
409
|
-
collection: resolvedCollection as EntityCollection
|
|
453
|
+
collection: resolvedCollection as unknown as EntityCollection,
|
|
410
454
|
path,
|
|
411
455
|
entityId,
|
|
412
456
|
values: updatedValues,
|
|
@@ -438,7 +482,17 @@ export class PostgresBackendDriver implements DataDriver {
|
|
|
438
482
|
resolvedCollection?.databaseId
|
|
439
483
|
);
|
|
440
484
|
|
|
441
|
-
if (savedEntity && (callbacks?.afterRead || propertyCallbacks?.afterRead)) {
|
|
485
|
+
if (savedEntity && (globalCallbacks?.afterRead || callbacks?.afterRead || propertyCallbacks?.afterRead)) {
|
|
486
|
+
// 1. Global callbacks first
|
|
487
|
+
if (globalCallbacks?.afterRead) {
|
|
488
|
+
savedEntity = await globalCallbacks.afterRead({
|
|
489
|
+
collection: resolvedCollection as unknown as EntityCollection,
|
|
490
|
+
path,
|
|
491
|
+
entity: savedEntity as unknown as Entity<Record<string, unknown>>,
|
|
492
|
+
context: contextForCallback
|
|
493
|
+
}) as unknown as Entity<M>;
|
|
494
|
+
}
|
|
495
|
+
// 2. Collection callbacks second
|
|
442
496
|
if (callbacks?.afterRead) {
|
|
443
497
|
savedEntity = await callbacks.afterRead({
|
|
444
498
|
collection: resolvedCollection as EntityCollection<M>,
|
|
@@ -447,17 +501,31 @@ export class PostgresBackendDriver implements DataDriver {
|
|
|
447
501
|
context: contextForCallback
|
|
448
502
|
}) ?? savedEntity;
|
|
449
503
|
}
|
|
504
|
+
// 3. Property callbacks third
|
|
450
505
|
if (propertyCallbacks?.afterRead) {
|
|
451
506
|
savedEntity = await propertyCallbacks.afterRead({
|
|
452
|
-
collection: resolvedCollection as EntityCollection
|
|
507
|
+
collection: resolvedCollection as unknown as EntityCollection,
|
|
453
508
|
path,
|
|
454
|
-
entity: savedEntity,
|
|
509
|
+
entity: savedEntity as unknown as Entity<Record<string, unknown>>,
|
|
455
510
|
context: contextForCallback
|
|
456
|
-
}) as Entity<M
|
|
511
|
+
}) as unknown as Entity<M>;
|
|
457
512
|
}
|
|
458
513
|
}
|
|
459
514
|
|
|
460
|
-
if (callbacks?.afterSave || propertyCallbacks?.afterSave) {
|
|
515
|
+
if (globalCallbacks?.afterSave || callbacks?.afterSave || propertyCallbacks?.afterSave) {
|
|
516
|
+
// 1. Global callbacks first
|
|
517
|
+
if (globalCallbacks?.afterSave) {
|
|
518
|
+
await globalCallbacks.afterSave({
|
|
519
|
+
collection: resolvedCollection as unknown as EntityCollection,
|
|
520
|
+
path,
|
|
521
|
+
entityId: savedEntity.id,
|
|
522
|
+
values: savedEntity.values,
|
|
523
|
+
previousValues: previousValuesForHistory,
|
|
524
|
+
status,
|
|
525
|
+
context: contextForCallback
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
// 2. Collection callbacks second
|
|
461
529
|
if (callbacks?.afterSave) {
|
|
462
530
|
await callbacks.afterSave({
|
|
463
531
|
collection: resolvedCollection as EntityCollection<M>,
|
|
@@ -469,9 +537,10 @@ export class PostgresBackendDriver implements DataDriver {
|
|
|
469
537
|
context: contextForCallback
|
|
470
538
|
});
|
|
471
539
|
}
|
|
540
|
+
// 3. Property callbacks third
|
|
472
541
|
if (propertyCallbacks?.afterSave) {
|
|
473
542
|
await propertyCallbacks.afterSave({
|
|
474
|
-
collection: resolvedCollection as EntityCollection
|
|
543
|
+
collection: resolvedCollection as unknown as EntityCollection,
|
|
475
544
|
path,
|
|
476
545
|
entityId: savedEntity.id,
|
|
477
546
|
values: savedEntity.values,
|
|
@@ -513,7 +582,20 @@ export class PostgresBackendDriver implements DataDriver {
|
|
|
513
582
|
|
|
514
583
|
return savedEntity;
|
|
515
584
|
} catch (error) {
|
|
516
|
-
if (callbacks?.afterSaveError || propertyCallbacks?.afterSaveError) {
|
|
585
|
+
if (globalCallbacks?.afterSaveError || callbacks?.afterSaveError || propertyCallbacks?.afterSaveError) {
|
|
586
|
+
// 1. Global callbacks first
|
|
587
|
+
if (globalCallbacks?.afterSaveError) {
|
|
588
|
+
await globalCallbacks.afterSaveError({
|
|
589
|
+
collection: resolvedCollection as unknown as EntityCollection,
|
|
590
|
+
path,
|
|
591
|
+
entityId: entityId || "unknown",
|
|
592
|
+
values: updatedValues,
|
|
593
|
+
previousValues: undefined,
|
|
594
|
+
status,
|
|
595
|
+
context: contextForCallback
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
// 2. Collection callbacks second
|
|
517
599
|
if (callbacks?.afterSaveError) {
|
|
518
600
|
await callbacks.afterSaveError({
|
|
519
601
|
collection: resolvedCollection as EntityCollection<M>,
|
|
@@ -525,9 +607,10 @@ export class PostgresBackendDriver implements DataDriver {
|
|
|
525
607
|
context: contextForCallback
|
|
526
608
|
});
|
|
527
609
|
}
|
|
610
|
+
// 3. Property callbacks third
|
|
528
611
|
if (propertyCallbacks?.afterSaveError) {
|
|
529
612
|
await propertyCallbacks.afterSaveError({
|
|
530
|
-
collection: resolvedCollection as EntityCollection
|
|
613
|
+
collection: resolvedCollection as unknown as EntityCollection,
|
|
531
614
|
path,
|
|
532
615
|
entityId: entityId || "unknown",
|
|
533
616
|
values: updatedValues,
|
|
@@ -550,13 +633,28 @@ export class PostgresBackendDriver implements DataDriver {
|
|
|
550
633
|
const {
|
|
551
634
|
collection: resolvedCollection,
|
|
552
635
|
callbacks,
|
|
636
|
+
globalCallbacks,
|
|
553
637
|
propertyCallbacks
|
|
554
638
|
} = this.resolveCollectionCallbacks(collection, entity.path);
|
|
555
639
|
|
|
556
640
|
const contextForCallback = this.buildCallContext();
|
|
557
641
|
|
|
558
|
-
if (callbacks?.beforeDelete || propertyCallbacks?.beforeDelete) {
|
|
642
|
+
if (globalCallbacks?.beforeDelete || callbacks?.beforeDelete || propertyCallbacks?.beforeDelete) {
|
|
559
643
|
let preventDefault = false;
|
|
644
|
+
// 1. Global callbacks first
|
|
645
|
+
if (globalCallbacks?.beforeDelete) {
|
|
646
|
+
const result = await globalCallbacks.beforeDelete({
|
|
647
|
+
collection: resolvedCollection as unknown as EntityCollection,
|
|
648
|
+
path: entity.path,
|
|
649
|
+
entityId: entity.id,
|
|
650
|
+
entity,
|
|
651
|
+
context: contextForCallback
|
|
652
|
+
});
|
|
653
|
+
if (result === false) {
|
|
654
|
+
preventDefault = true;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
// 2. Collection callbacks second
|
|
560
658
|
if (callbacks?.beforeDelete) {
|
|
561
659
|
const result = await callbacks.beforeDelete({
|
|
562
660
|
collection: resolvedCollection as EntityCollection<M>,
|
|
@@ -569,9 +667,10 @@ export class PostgresBackendDriver implements DataDriver {
|
|
|
569
667
|
preventDefault = true;
|
|
570
668
|
}
|
|
571
669
|
}
|
|
670
|
+
// 3. Property callbacks third
|
|
572
671
|
if (propertyCallbacks?.beforeDelete) {
|
|
573
672
|
const result = await propertyCallbacks.beforeDelete({
|
|
574
|
-
collection: resolvedCollection as EntityCollection
|
|
673
|
+
collection: resolvedCollection as unknown as EntityCollection,
|
|
575
674
|
path: entity.path,
|
|
576
675
|
entityId: entity.id,
|
|
577
676
|
entity,
|
|
@@ -592,7 +691,18 @@ export class PostgresBackendDriver implements DataDriver {
|
|
|
592
691
|
entity.databaseId || resolvedCollection?.databaseId
|
|
593
692
|
);
|
|
594
693
|
|
|
595
|
-
if (callbacks?.afterDelete || propertyCallbacks?.afterDelete) {
|
|
694
|
+
if (globalCallbacks?.afterDelete || callbacks?.afterDelete || propertyCallbacks?.afterDelete) {
|
|
695
|
+
// 1. Global callbacks first
|
|
696
|
+
if (globalCallbacks?.afterDelete) {
|
|
697
|
+
await globalCallbacks.afterDelete({
|
|
698
|
+
collection: resolvedCollection as unknown as EntityCollection,
|
|
699
|
+
path: entity.path,
|
|
700
|
+
entityId: entity.id,
|
|
701
|
+
entity,
|
|
702
|
+
context: contextForCallback
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
// 2. Collection callbacks second
|
|
596
706
|
if (callbacks?.afterDelete) {
|
|
597
707
|
await callbacks.afterDelete({
|
|
598
708
|
collection: resolvedCollection as EntityCollection<M>,
|
|
@@ -602,9 +712,10 @@ export class PostgresBackendDriver implements DataDriver {
|
|
|
602
712
|
context: contextForCallback
|
|
603
713
|
});
|
|
604
714
|
}
|
|
715
|
+
// 3. Property callbacks third
|
|
605
716
|
if (propertyCallbacks?.afterDelete) {
|
|
606
717
|
await propertyCallbacks.afterDelete({
|
|
607
|
-
collection: resolvedCollection as EntityCollection
|
|
718
|
+
collection: resolvedCollection as unknown as EntityCollection,
|
|
608
719
|
path: entity.path,
|
|
609
720
|
entityId: entity.id,
|
|
610
721
|
entity,
|
package/src/cli.ts
CHANGED
|
@@ -18,7 +18,36 @@ const __cliDirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
|
|
21
|
+
async function loadEnv(): Promise<void> {
|
|
22
|
+
try {
|
|
23
|
+
const dotenv = await import("dotenv");
|
|
24
|
+
const envPaths = [
|
|
25
|
+
process.env.DOTENV_CONFIG_PATH,
|
|
26
|
+
path.resolve(process.cwd(), ".env"),
|
|
27
|
+
path.resolve(process.cwd(), "../.env"),
|
|
28
|
+
path.resolve(process.cwd(), "../../.env")
|
|
29
|
+
].filter(Boolean) as string[];
|
|
30
|
+
|
|
31
|
+
for (const p of envPaths) {
|
|
32
|
+
if (fs.existsSync(p)) {
|
|
33
|
+
const parsed = dotenv.config({ path: p });
|
|
34
|
+
if (parsed.parsed) {
|
|
35
|
+
for (const [key, val] of Object.entries(parsed.parsed)) {
|
|
36
|
+
if (process.env[key] === undefined) {
|
|
37
|
+
process.env[key] = val;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
} catch {
|
|
45
|
+
// ignore
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
21
49
|
export async function runPluginCommand(args: string[]) {
|
|
50
|
+
await loadEnv();
|
|
22
51
|
const domain = args[0]; // "db" or "schema"
|
|
23
52
|
const subcommand = args[1];
|
|
24
53
|
|
|
@@ -126,16 +155,23 @@ async function dbCommand(subcommand: string, rawArgs: string[]): Promise<void> {
|
|
|
126
155
|
logger.info("");
|
|
127
156
|
logger.info(chalk.gray(" Step 2/3: Pushing schema to database with Atlas..."));
|
|
128
157
|
logger.info("");
|
|
158
|
+
const databaseUrl = process.env.DATABASE_URL;
|
|
159
|
+
if (databaseUrl) {
|
|
160
|
+
await ensureAuthSchemaAndFunctions(databaseUrl);
|
|
161
|
+
}
|
|
129
162
|
await runAtlas("schema", ["apply", "--to", "file://drizzle/schema.sql", "--auto-approve"], collectionsPath);
|
|
130
163
|
logger.info("");
|
|
131
164
|
|
|
132
|
-
const databaseUrl = process.env.DATABASE_URL;
|
|
133
165
|
if (databaseUrl) {
|
|
134
166
|
await applyPolicies(databaseUrl);
|
|
135
167
|
} else {
|
|
136
168
|
logger.warn(chalk.yellow(" ⚠️ DATABASE_URL not found in environment, skipping RLS policies application."));
|
|
137
169
|
}
|
|
138
170
|
} else if (subcommand === "migrate") {
|
|
171
|
+
const databaseUrl = process.env.DATABASE_URL;
|
|
172
|
+
if (databaseUrl) {
|
|
173
|
+
await ensureAuthSchemaAndFunctions(databaseUrl);
|
|
174
|
+
}
|
|
139
175
|
const extraArgs = argsList._.filter(arg => arg !== "migrate");
|
|
140
176
|
await runAtlas("migrate", ["apply", "--dir", "file://drizzle/migrations", ...extraArgs], collectionsPath);
|
|
141
177
|
}
|
|
@@ -146,6 +182,39 @@ async function dbCommand(subcommand: string, rawArgs: string[]): Promise<void> {
|
|
|
146
182
|
}
|
|
147
183
|
}
|
|
148
184
|
|
|
185
|
+
async function ensureAuthSchemaAndFunctions(databaseUrl: string): Promise<void> {
|
|
186
|
+
try {
|
|
187
|
+
const { Client } = await import("pg");
|
|
188
|
+
const client = new Client({ connectionString: databaseUrl });
|
|
189
|
+
await client.connect();
|
|
190
|
+
try {
|
|
191
|
+
await client.query(`
|
|
192
|
+
CREATE SCHEMA IF NOT EXISTS auth;
|
|
193
|
+
CREATE SCHEMA IF NOT EXISTS rebase;
|
|
194
|
+
|
|
195
|
+
CREATE OR REPLACE FUNCTION auth.uid() RETURNS text AS $$
|
|
196
|
+
SELECT NULLIF(current_setting('app.user_id', true), '');
|
|
197
|
+
$$ LANGUAGE sql STABLE;
|
|
198
|
+
|
|
199
|
+
CREATE OR REPLACE FUNCTION auth.jwt() RETURNS jsonb AS $$
|
|
200
|
+
SELECT COALESCE(
|
|
201
|
+
NULLIF(current_setting('app.jwt', true), ''),
|
|
202
|
+
'{}'
|
|
203
|
+
)::jsonb;
|
|
204
|
+
$$ LANGUAGE sql STABLE;
|
|
205
|
+
|
|
206
|
+
CREATE OR REPLACE FUNCTION auth.roles() RETURNS text AS $$
|
|
207
|
+
SELECT COALESCE(NULLIF(current_setting('app.user_roles', true), ''), '');
|
|
208
|
+
$$ LANGUAGE sql STABLE;
|
|
209
|
+
`);
|
|
210
|
+
} finally {
|
|
211
|
+
await client.end();
|
|
212
|
+
}
|
|
213
|
+
} catch (err) {
|
|
214
|
+
logger.warn(chalk.yellow(` ⚠️ Failed to bootstrap auth schema and helper functions: ${err instanceof Error ? err.message : String(err)}`));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
149
218
|
async function applyPolicies(databaseUrl: string): Promise<void> {
|
|
150
219
|
try {
|
|
151
220
|
const policiesPath = path.resolve(process.cwd(), "drizzle", "policies.sql");
|
|
@@ -426,6 +495,9 @@ async function runAtlas(domain: "schema" | "migrate", args: string[], collection
|
|
|
426
495
|
atlasArgs.push("--dev-url", devDatabaseUrl);
|
|
427
496
|
} else if (args.includes("apply") || args.includes("status")) {
|
|
428
497
|
atlasArgs.push("--url", databaseUrl, "--revisions-schema", "rebase");
|
|
498
|
+
if (args.includes("apply")) {
|
|
499
|
+
atlasArgs.push("--allow-dirty");
|
|
500
|
+
}
|
|
429
501
|
}
|
|
430
502
|
}
|
|
431
503
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CollectionRegistry } from "@rebasepro/common";
|
|
2
|
-
import { type
|
|
2
|
+
import { type EntityCollection, type Relation, getDataSourceCapabilities } from "@rebasepro/types";
|
|
3
3
|
import { PgEnum, PgTable } from "drizzle-orm/pg-core";
|
|
4
4
|
import { Relations } from "drizzle-orm";
|
|
5
5
|
import { CollectionRegistryInterface } from "../interfaces";
|
|
@@ -40,11 +40,11 @@ export class PostgresCollectionRegistry extends CollectionRegistry implements Co
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
|
-
* Finds collections assigned to a specific
|
|
43
|
+
* Finds collections assigned to a specific data source that do not have a registered table.
|
|
44
44
|
*/
|
|
45
|
-
getCollectionsWithoutTables(
|
|
45
|
+
getCollectionsWithoutTables(dataSourceKey = "(default)"): EntityCollection[] {
|
|
46
46
|
const collections = this.getCollections().filter(
|
|
47
|
-
c => c.
|
|
47
|
+
c => c.dataSource === dataSourceKey || (!c.dataSource && dataSourceKey === "(default)")
|
|
48
48
|
);
|
|
49
49
|
return collections.filter(c => !this.tables.has(getTableName(c)));
|
|
50
50
|
}
|
|
@@ -95,8 +95,8 @@ export class PostgresCollectionRegistry extends CollectionRegistry implements Co
|
|
|
95
95
|
*/
|
|
96
96
|
getRelationKeysForCollection(collectionPath: string): string[] {
|
|
97
97
|
const collection = this.getCollectionByPath(collectionPath);
|
|
98
|
-
if (!collection || !getDataSourceCapabilities(collection.
|
|
99
|
-
return
|
|
98
|
+
if (!collection || !getDataSourceCapabilities(collection.engine).supportsRelations || !collection.relations) return [];
|
|
99
|
+
return collection.relations!.map((r: Relation) => r.relationName || r.localKey || "").filter(Boolean);
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
}
|
package/src/data-transformer.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { eq, SQL } from "drizzle-orm";
|
|
2
2
|
import { AnyPgColumn } from "drizzle-orm/pg-core";
|
|
3
3
|
import { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
4
|
-
import {
|
|
4
|
+
import { EntityCollection, Properties, Property, Relation, RelationProperty, Vector, BinaryProperty } from "@rebasepro/types";
|
|
5
5
|
import { getTableName, resolveCollectionRelations, findRelation, createRelationRef, DEFAULT_ONE_OF_TYPE, DEFAULT_ONE_OF_VALUE } from "@rebasepro/common";
|
|
6
6
|
import { PostgresCollectionRegistry } from "./collections/PostgresCollectionRegistry";
|
|
7
7
|
import { DrizzleConditionBuilder } from "./utils/drizzle-conditions";
|
|
@@ -551,7 +551,7 @@ export function parsePropertyFromServer(value: unknown, property: Property, coll
|
|
|
551
551
|
relationDef = findRelation(resolvedRelations, propertyKey);
|
|
552
552
|
}
|
|
553
553
|
if (!relationDef) {
|
|
554
|
-
relationDef =
|
|
554
|
+
relationDef = collection.relations?.find((rel: Relation) => rel.relationName === (property as RelationProperty).relationName);
|
|
555
555
|
}
|
|
556
556
|
|
|
557
557
|
if (!relationDef) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EntityCollection, SecurityRule, SecurityOperation, AuthCollectionConfig, isPostgresCollection } from "@rebasepro/types";
|
|
1
|
+
import { EntityCollection, SecurityRule, SecurityOperation, AuthCollectionConfig, PolicyExpression, isPostgresCollection, policy } from "@rebasepro/types";
|
|
2
2
|
import { getTableName } from "@rebasepro/common";
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -37,7 +37,14 @@ import { getTableName } from "@rebasepro/common";
|
|
|
37
37
|
* control of an auth collection's write authorization, set
|
|
38
38
|
* `disableDefaultAuthPolicies: true` on the collection.
|
|
39
39
|
*/
|
|
40
|
-
|
|
40
|
+
// Expressed structurally (not as raw SQL) so the admin UI can evaluate it
|
|
41
|
+
// exactly — the framework's most security-critical policy must be reflected
|
|
42
|
+
// precisely, not left as an un-evaluable raw clause. Compiles to
|
|
43
|
+
// `auth.uid() IS NULL OR (string_to_array(auth.roles(), ',') && ARRAY['admin'])`.
|
|
44
|
+
const ADMIN_WRITE_EXPR: PolicyExpression = policy.or(
|
|
45
|
+
policy.not(policy.authenticated()),
|
|
46
|
+
policy.rolesOverlap(["admin"])
|
|
47
|
+
);
|
|
41
48
|
|
|
42
49
|
/** Write operations that must be admin-gated by default on auth collections. */
|
|
43
50
|
const DEFAULT_GUARDED_OPS: SecurityOperation[] = ["insert", "update", "delete"];
|
|
@@ -73,8 +80,8 @@ export function getEffectiveSecurityRules(collection: EntityCollection): Securit
|
|
|
73
80
|
name: `${tableName}_require_admin_write`,
|
|
74
81
|
mode: "restrictive",
|
|
75
82
|
operations: [...DEFAULT_GUARDED_OPS],
|
|
76
|
-
|
|
77
|
-
|
|
83
|
+
condition: ADMIN_WRITE_EXPR,
|
|
84
|
+
check: ADMIN_WRITE_EXPR
|
|
78
85
|
};
|
|
79
86
|
|
|
80
87
|
// Permissive grant: a restrictive policy alone denies everything, so this
|
|
@@ -82,8 +89,8 @@ export function getEffectiveSecurityRules(collection: EntityCollection): Securit
|
|
|
82
89
|
const allowAdminWrite: SecurityRule = {
|
|
83
90
|
name: `${tableName}_default_admin_write`,
|
|
84
91
|
operations: [...DEFAULT_GUARDED_OPS],
|
|
85
|
-
|
|
86
|
-
|
|
92
|
+
condition: ADMIN_WRITE_EXPR,
|
|
93
|
+
check: ADMIN_WRITE_EXPR
|
|
87
94
|
};
|
|
88
95
|
|
|
89
96
|
return [...explicit, allowAdminWrite, requireAdminGate];
|