@dismissible/nestjs-postgres-storage 0.0.2-canary.585db17.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/README.md +309 -0
- package/bin/dismissible-prisma.js +57 -0
- package/package.json +66 -0
- package/prisma/generated/prisma/browser.ts +24 -0
- package/prisma/generated/prisma/client.ts +44 -0
- package/prisma/generated/prisma/commonInputTypes.ts +208 -0
- package/prisma/generated/prisma/enums.ts +15 -0
- package/prisma/generated/prisma/internal/class.ts +190 -0
- package/prisma/generated/prisma/internal/prismaNamespace.ts +759 -0
- package/prisma/generated/prisma/internal/prismaNamespaceBrowser.ts +105 -0
- package/prisma/generated/prisma/models/DismissibleItem.ts +1134 -0
- package/prisma/generated/prisma/models.ts +12 -0
- package/prisma/migrations/20251219094936_init/migration.sql +12 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +23 -0
- package/prisma.config.mjs +33 -0
- package/src/index.ts +6 -0
- package/src/postgres-storage.adapter.spec.ts +160 -0
- package/src/postgres-storage.adapter.ts +90 -0
- package/src/postgres-storage.config.ts +11 -0
- package/src/postgres-storage.module.ts +79 -0
- package/src/prisma-config.spec.ts +93 -0
- package/src/prisma-config.ts +63 -0
- package/src/prisma.service.spec.ts +125 -0
- package/src/prisma.service.ts +44 -0
- package/src/schema-path.spec.ts +22 -0
- package/src/schema-path.ts +17 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
|
|
2
|
+
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
|
3
|
+
/* eslint-disable */
|
|
4
|
+
// biome-ignore-all lint: generated file
|
|
5
|
+
// @ts-nocheck
|
|
6
|
+
/*
|
|
7
|
+
* This is a barrel export file for all models and their related types.
|
|
8
|
+
*
|
|
9
|
+
* 🟢 You can import this file directly.
|
|
10
|
+
*/
|
|
11
|
+
export type * from './models/DismissibleItem'
|
|
12
|
+
export type * from './commonInputTypes'
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
-- CreateTable
|
|
2
|
+
CREATE TABLE "dismissible_items" (
|
|
3
|
+
"id" TEXT NOT NULL,
|
|
4
|
+
"user_id" TEXT NOT NULL,
|
|
5
|
+
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
6
|
+
"dismissed_at" TIMESTAMP(3),
|
|
7
|
+
|
|
8
|
+
CONSTRAINT "dismissible_items_pkey" PRIMARY KEY ("user_id","id")
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
-- CreateIndex
|
|
12
|
+
CREATE INDEX "dismissible_items_user_id_idx" ON "dismissible_items"("user_id");
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Prisma schema for Dismissible PostgreSQL storage
|
|
2
|
+
// https://pris.ly/d/prisma-schema
|
|
3
|
+
|
|
4
|
+
generator client {
|
|
5
|
+
provider = "prisma-client"
|
|
6
|
+
output = "./generated/prisma"
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
datasource db {
|
|
10
|
+
provider = "postgresql"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
model DismissibleItem {
|
|
14
|
+
id String
|
|
15
|
+
userId String @map("user_id")
|
|
16
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
17
|
+
dismissedAt DateTime? @map("dismissed_at")
|
|
18
|
+
|
|
19
|
+
@@id([userId, id])
|
|
20
|
+
@@index([userId])
|
|
21
|
+
@@map("dismissible_items")
|
|
22
|
+
}
|
|
23
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { fileURLToPath } from 'url';
|
|
2
|
+
import { dirname, join } from 'path';
|
|
3
|
+
import { defineConfig } from 'prisma/config';
|
|
4
|
+
|
|
5
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const prismaDir = join(__dirname, 'prisma');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Prisma configuration for @dismissible/nestjs-postgres-storage.
|
|
10
|
+
*
|
|
11
|
+
* This config is used by the dismissible-prisma CLI and can be used directly
|
|
12
|
+
* by consumers who need to extend or customize the configuration.
|
|
13
|
+
*
|
|
14
|
+
* For consumers who want to use the base config in their own prisma.config.mjs:
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```javascript
|
|
18
|
+
* import { defineConfig } from 'prisma/config';
|
|
19
|
+
* import { basePrismaConfig } from '@dismissible/nestjs-postgres-storage';
|
|
20
|
+
*
|
|
21
|
+
* export default defineConfig(basePrismaConfig);
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export default defineConfig({
|
|
25
|
+
schema: join(prismaDir, 'schema.prisma'),
|
|
26
|
+
migrations: {
|
|
27
|
+
path: join(prismaDir, 'migrations'),
|
|
28
|
+
},
|
|
29
|
+
datasource: {
|
|
30
|
+
url:
|
|
31
|
+
process.env.DATABASE_URL ?? process.env.DISMISSIBLE_POSTGRES_STORAGE_CONNECTION_STRING ?? '',
|
|
32
|
+
},
|
|
33
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { mock } from 'ts-jest-mocker';
|
|
2
|
+
import { PostgresStorageAdapter } from './postgres-storage.adapter';
|
|
3
|
+
import { PrismaService } from './prisma.service';
|
|
4
|
+
import { IDismissibleLogger } from '@dismissible/nestjs-logger';
|
|
5
|
+
import { DismissibleItemDto, DismissibleItemFactory } from '@dismissible/nestjs-dismissible-item';
|
|
6
|
+
|
|
7
|
+
describe('PostgresStorageAdapter', () => {
|
|
8
|
+
let adapter: PostgresStorageAdapter;
|
|
9
|
+
let mockPrismaService: PrismaService;
|
|
10
|
+
let mockLogger: jest.Mocked<IDismissibleLogger>;
|
|
11
|
+
let mockItemFactory: DismissibleItemFactory;
|
|
12
|
+
let mockDismissibleItem: {
|
|
13
|
+
findUnique: jest.Mock;
|
|
14
|
+
create: jest.Mock;
|
|
15
|
+
update: jest.Mock;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
mockLogger = mock<IDismissibleLogger>({ failIfMockNotProvided: false });
|
|
20
|
+
|
|
21
|
+
mockItemFactory = new DismissibleItemFactory();
|
|
22
|
+
|
|
23
|
+
mockDismissibleItem = {
|
|
24
|
+
findUnique: jest.fn(),
|
|
25
|
+
create: jest.fn(),
|
|
26
|
+
update: jest.fn(),
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
mockPrismaService = {
|
|
30
|
+
dismissibleItem: mockDismissibleItem,
|
|
31
|
+
} as unknown as PrismaService;
|
|
32
|
+
|
|
33
|
+
adapter = new PostgresStorageAdapter(mockPrismaService, mockLogger, mockItemFactory);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe('get', () => {
|
|
37
|
+
it('should return null when item is not found', async () => {
|
|
38
|
+
mockDismissibleItem.findUnique.mockResolvedValue(null);
|
|
39
|
+
|
|
40
|
+
const result = await adapter.get('user-123', 'item-456');
|
|
41
|
+
|
|
42
|
+
expect(result).toBeNull();
|
|
43
|
+
expect(mockDismissibleItem.findUnique).toHaveBeenCalledWith({
|
|
44
|
+
where: {
|
|
45
|
+
userId_id: {
|
|
46
|
+
userId: 'user-123',
|
|
47
|
+
id: 'item-456',
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
expect(mockLogger.debug).toHaveBeenCalledWith('PostgreSQL storage miss', {
|
|
52
|
+
userId: 'user-123',
|
|
53
|
+
itemId: 'item-456',
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should return the item when found', async () => {
|
|
58
|
+
const dbItem = {
|
|
59
|
+
id: 'item-456',
|
|
60
|
+
userId: 'user-123',
|
|
61
|
+
createdAt: new Date('2024-01-15T10:30:00.000Z'),
|
|
62
|
+
dismissedAt: null,
|
|
63
|
+
};
|
|
64
|
+
mockDismissibleItem.findUnique.mockResolvedValue(dbItem);
|
|
65
|
+
|
|
66
|
+
const result = await adapter.get('user-123', 'item-456');
|
|
67
|
+
|
|
68
|
+
expect(result).toEqual({
|
|
69
|
+
id: 'item-456',
|
|
70
|
+
userId: 'user-123',
|
|
71
|
+
createdAt: new Date('2024-01-15T10:30:00.000Z'),
|
|
72
|
+
dismissedAt: undefined,
|
|
73
|
+
});
|
|
74
|
+
expect(mockLogger.debug).toHaveBeenCalledWith('PostgreSQL storage hit', {
|
|
75
|
+
userId: 'user-123',
|
|
76
|
+
itemId: 'item-456',
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('create', () => {
|
|
82
|
+
it('should create a new item', async () => {
|
|
83
|
+
const item: DismissibleItemDto = {
|
|
84
|
+
id: 'item-456',
|
|
85
|
+
userId: 'user-123',
|
|
86
|
+
createdAt: new Date('2024-01-15T10:30:00.000Z'),
|
|
87
|
+
};
|
|
88
|
+
const dbItem = {
|
|
89
|
+
id: 'item-456',
|
|
90
|
+
userId: 'user-123',
|
|
91
|
+
createdAt: new Date('2024-01-15T10:30:00.000Z'),
|
|
92
|
+
dismissedAt: null,
|
|
93
|
+
};
|
|
94
|
+
mockDismissibleItem.create.mockResolvedValue(dbItem);
|
|
95
|
+
|
|
96
|
+
const result = await adapter.create(item);
|
|
97
|
+
|
|
98
|
+
expect(result).toEqual({
|
|
99
|
+
id: 'item-456',
|
|
100
|
+
userId: 'user-123',
|
|
101
|
+
createdAt: new Date('2024-01-15T10:30:00.000Z'),
|
|
102
|
+
dismissedAt: undefined,
|
|
103
|
+
});
|
|
104
|
+
expect(mockDismissibleItem.create).toHaveBeenCalledWith({
|
|
105
|
+
data: {
|
|
106
|
+
id: 'item-456',
|
|
107
|
+
userId: 'user-123',
|
|
108
|
+
createdAt: new Date('2024-01-15T10:30:00.000Z'),
|
|
109
|
+
dismissedAt: null,
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
expect(mockLogger.debug).toHaveBeenCalledWith('PostgreSQL storage create', {
|
|
113
|
+
userId: 'user-123',
|
|
114
|
+
itemId: 'item-456',
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('update', () => {
|
|
120
|
+
it('should update an existing item', async () => {
|
|
121
|
+
const item: DismissibleItemDto = {
|
|
122
|
+
id: 'item-456',
|
|
123
|
+
userId: 'user-123',
|
|
124
|
+
createdAt: new Date('2024-01-15T10:30:00.000Z'),
|
|
125
|
+
dismissedAt: new Date('2024-01-15T12:00:00.000Z'),
|
|
126
|
+
};
|
|
127
|
+
const dbItem = {
|
|
128
|
+
id: 'item-456',
|
|
129
|
+
userId: 'user-123',
|
|
130
|
+
createdAt: new Date('2024-01-15T10:30:00.000Z'),
|
|
131
|
+
dismissedAt: new Date('2024-01-15T12:00:00.000Z'),
|
|
132
|
+
};
|
|
133
|
+
mockDismissibleItem.update.mockResolvedValue(dbItem);
|
|
134
|
+
|
|
135
|
+
const result = await adapter.update(item);
|
|
136
|
+
|
|
137
|
+
expect(result).toEqual({
|
|
138
|
+
id: 'item-456',
|
|
139
|
+
userId: 'user-123',
|
|
140
|
+
createdAt: new Date('2024-01-15T10:30:00.000Z'),
|
|
141
|
+
dismissedAt: new Date('2024-01-15T12:00:00.000Z'),
|
|
142
|
+
});
|
|
143
|
+
expect(mockDismissibleItem.update).toHaveBeenCalledWith({
|
|
144
|
+
where: {
|
|
145
|
+
userId_id: {
|
|
146
|
+
userId: 'user-123',
|
|
147
|
+
id: 'item-456',
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
data: {
|
|
151
|
+
dismissedAt: new Date('2024-01-15T12:00:00.000Z'),
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
expect(mockLogger.debug).toHaveBeenCalledWith('PostgreSQL storage update', {
|
|
155
|
+
userId: 'user-123',
|
|
156
|
+
itemId: 'item-456',
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Injectable, Inject } from '@nestjs/common';
|
|
2
|
+
import { DISMISSIBLE_LOGGER, IDismissibleLogger } from '@dismissible/nestjs-logger';
|
|
3
|
+
import { IDismissibleStorage } from '@dismissible/nestjs-storage';
|
|
4
|
+
import { DismissibleItemDto, DismissibleItemFactory } from '@dismissible/nestjs-dismissible-item';
|
|
5
|
+
import { PrismaService } from './prisma.service';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* PostgreSQL storage adapter for dismissible items using Prisma.
|
|
9
|
+
* Implements IDismissibleStorage for persistent database storage.
|
|
10
|
+
*/
|
|
11
|
+
@Injectable()
|
|
12
|
+
export class PostgresStorageAdapter implements IDismissibleStorage {
|
|
13
|
+
constructor(
|
|
14
|
+
private readonly prisma: PrismaService,
|
|
15
|
+
@Inject(DISMISSIBLE_LOGGER) private readonly logger: IDismissibleLogger,
|
|
16
|
+
private readonly itemFactory: DismissibleItemFactory,
|
|
17
|
+
) {}
|
|
18
|
+
|
|
19
|
+
async get(userId: string, itemId: string): Promise<DismissibleItemDto | null> {
|
|
20
|
+
this.logger.debug('PostgreSQL storage get', { userId, itemId });
|
|
21
|
+
|
|
22
|
+
const item = await this.prisma.dismissibleItem.findUnique({
|
|
23
|
+
where: {
|
|
24
|
+
userId_id: {
|
|
25
|
+
userId,
|
|
26
|
+
id: itemId,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if (!item) {
|
|
32
|
+
this.logger.debug('PostgreSQL storage miss', { userId, itemId });
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
this.logger.debug('PostgreSQL storage hit', { userId, itemId });
|
|
37
|
+
|
|
38
|
+
return this.mapToDto(item);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async create(item: DismissibleItemDto): Promise<DismissibleItemDto> {
|
|
42
|
+
this.logger.debug('PostgreSQL storage create', { userId: item.userId, itemId: item.id });
|
|
43
|
+
|
|
44
|
+
const created = await this.prisma.dismissibleItem.create({
|
|
45
|
+
data: {
|
|
46
|
+
id: item.id,
|
|
47
|
+
userId: item.userId,
|
|
48
|
+
createdAt: item.createdAt,
|
|
49
|
+
dismissedAt: item.dismissedAt ?? null,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return this.mapToDto(created);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async update(item: DismissibleItemDto): Promise<DismissibleItemDto> {
|
|
57
|
+
this.logger.debug('PostgreSQL storage update', { userId: item.userId, itemId: item.id });
|
|
58
|
+
|
|
59
|
+
const updated = await this.prisma.dismissibleItem.update({
|
|
60
|
+
where: {
|
|
61
|
+
userId_id: {
|
|
62
|
+
userId: item.userId,
|
|
63
|
+
id: item.id,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
data: {
|
|
67
|
+
dismissedAt: item.dismissedAt ?? null,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return this.mapToDto(updated);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Map a Prisma model to a DismissibleItemDto.
|
|
76
|
+
*/
|
|
77
|
+
private mapToDto(item: {
|
|
78
|
+
id: string;
|
|
79
|
+
userId: string;
|
|
80
|
+
createdAt: Date;
|
|
81
|
+
dismissedAt: Date | null;
|
|
82
|
+
}): DismissibleItemDto {
|
|
83
|
+
return this.itemFactory.create({
|
|
84
|
+
id: item.id,
|
|
85
|
+
userId: item.userId,
|
|
86
|
+
createdAt: item.createdAt,
|
|
87
|
+
dismissedAt: item.dismissedAt ?? undefined,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { IsString } from 'class-validator';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Injection token for the PostgresStorage configuration.
|
|
5
|
+
*/
|
|
6
|
+
export const DISMISSIBLE_POSTGRES_STORAGE_CONFIG = Symbol('DISMISSIBLE_POSTGRES_STORAGE_CONFIG');
|
|
7
|
+
|
|
8
|
+
export class PostgresStorageConfig {
|
|
9
|
+
@IsString()
|
|
10
|
+
public readonly connectionString!: string;
|
|
11
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { DynamicModule, Module, ModuleMetadata } from '@nestjs/common';
|
|
2
|
+
import { DISMISSIBLE_STORAGE_ADAPTER } from '@dismissible/nestjs-storage';
|
|
3
|
+
import { PostgresStorageAdapter } from './postgres-storage.adapter';
|
|
4
|
+
import { PrismaService } from './prisma.service';
|
|
5
|
+
import { DismissibleItemModule } from '@dismissible/nestjs-dismissible-item';
|
|
6
|
+
import { DISMISSIBLE_LOGGER, IDismissibleLogger } from '@dismissible/nestjs-logger';
|
|
7
|
+
import {
|
|
8
|
+
PostgresStorageConfig,
|
|
9
|
+
DISMISSIBLE_POSTGRES_STORAGE_CONFIG,
|
|
10
|
+
} from './postgres-storage.config';
|
|
11
|
+
|
|
12
|
+
export interface PostgresStorageModuleOptions {
|
|
13
|
+
connectionString: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface PostgresStorageModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
|
|
17
|
+
inject?: any[];
|
|
18
|
+
useFactory: (
|
|
19
|
+
...args: any[]
|
|
20
|
+
) => PostgresStorageModuleOptions | Promise<PostgresStorageModuleOptions>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@Module({})
|
|
24
|
+
export class PostgresStorageModule {
|
|
25
|
+
static forRoot(options: PostgresStorageModuleOptions): DynamicModule {
|
|
26
|
+
return {
|
|
27
|
+
module: PostgresStorageModule,
|
|
28
|
+
imports: [DismissibleItemModule],
|
|
29
|
+
providers: [
|
|
30
|
+
{
|
|
31
|
+
provide: DISMISSIBLE_POSTGRES_STORAGE_CONFIG,
|
|
32
|
+
useValue: {
|
|
33
|
+
connectionString: options.connectionString,
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
provide: PrismaService,
|
|
38
|
+
useFactory(config: PostgresStorageConfig, logger: IDismissibleLogger) {
|
|
39
|
+
return new PrismaService(config, logger);
|
|
40
|
+
},
|
|
41
|
+
inject: [DISMISSIBLE_POSTGRES_STORAGE_CONFIG, DISMISSIBLE_LOGGER],
|
|
42
|
+
},
|
|
43
|
+
PostgresStorageAdapter,
|
|
44
|
+
{
|
|
45
|
+
provide: DISMISSIBLE_STORAGE_ADAPTER,
|
|
46
|
+
useExisting: PostgresStorageAdapter,
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
exports: [DISMISSIBLE_STORAGE_ADAPTER, PrismaService],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
static forRootAsync(options: PostgresStorageModuleAsyncOptions): DynamicModule {
|
|
54
|
+
return {
|
|
55
|
+
module: PostgresStorageModule,
|
|
56
|
+
imports: [...(options.imports || []), DismissibleItemModule],
|
|
57
|
+
providers: [
|
|
58
|
+
{
|
|
59
|
+
provide: DISMISSIBLE_POSTGRES_STORAGE_CONFIG,
|
|
60
|
+
useFactory: options.useFactory,
|
|
61
|
+
inject: options.inject || [],
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
provide: PrismaService,
|
|
65
|
+
useFactory(config: PostgresStorageConfig, logger: IDismissibleLogger) {
|
|
66
|
+
return new PrismaService(config, logger);
|
|
67
|
+
},
|
|
68
|
+
inject: [DISMISSIBLE_POSTGRES_STORAGE_CONFIG, DISMISSIBLE_LOGGER],
|
|
69
|
+
},
|
|
70
|
+
PostgresStorageAdapter,
|
|
71
|
+
{
|
|
72
|
+
provide: DISMISSIBLE_STORAGE_ADAPTER,
|
|
73
|
+
useExisting: PostgresStorageAdapter,
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
exports: [DISMISSIBLE_STORAGE_ADAPTER, PrismaService],
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { join } from 'path';
|
|
2
|
+
|
|
3
|
+
describe('prisma-config', () => {
|
|
4
|
+
const originalEnv = process.env;
|
|
5
|
+
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
jest.resetModules();
|
|
8
|
+
process.env = { ...originalEnv };
|
|
9
|
+
delete process.env.DATABASE_URL;
|
|
10
|
+
delete process.env.DISMISSIBLE_POSTGRES_STORAGE_CONNECTION_STRING;
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
afterAll(() => {
|
|
14
|
+
process.env = originalEnv;
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('createPrismaConfig', () => {
|
|
18
|
+
it('should return config with schema path relative to package', async () => {
|
|
19
|
+
const { createPrismaConfig } = await import('./prisma-config');
|
|
20
|
+
const config = createPrismaConfig();
|
|
21
|
+
|
|
22
|
+
expect(config.schema).toContain('schema.prisma');
|
|
23
|
+
expect(config.schema).toMatch(/postgres-storage.*prisma.*schema\.prisma$/);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should return config with migrations path relative to package', async () => {
|
|
27
|
+
const { createPrismaConfig } = await import('./prisma-config');
|
|
28
|
+
const config = createPrismaConfig();
|
|
29
|
+
|
|
30
|
+
expect(config.migrations.path).toContain('migrations');
|
|
31
|
+
expect(config.migrations.path).toMatch(/postgres-storage.*prisma.*migrations$/);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should use DATABASE_URL when set', async () => {
|
|
35
|
+
process.env.DATABASE_URL = 'postgres://test:test@localhost/test';
|
|
36
|
+
|
|
37
|
+
const { createPrismaConfig } = await import('./prisma-config');
|
|
38
|
+
const config = createPrismaConfig();
|
|
39
|
+
|
|
40
|
+
expect(config.datasource.url).toBe('postgres://test:test@localhost/test');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should fall back to DISMISSIBLE_POSTGRES_STORAGE_CONNECTION_STRING when DATABASE_URL is not set', async () => {
|
|
44
|
+
process.env.DISMISSIBLE_POSTGRES_STORAGE_CONNECTION_STRING =
|
|
45
|
+
'postgres://fallback:fallback@localhost/fallback';
|
|
46
|
+
|
|
47
|
+
const { createPrismaConfig } = await import('./prisma-config');
|
|
48
|
+
const config = createPrismaConfig();
|
|
49
|
+
|
|
50
|
+
expect(config.datasource.url).toBe('postgres://fallback:fallback@localhost/fallback');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should prefer DATABASE_URL over DISMISSIBLE_POSTGRES_STORAGE_CONNECTION_STRING', async () => {
|
|
54
|
+
process.env.DATABASE_URL = 'postgres://primary:primary@localhost/primary';
|
|
55
|
+
process.env.DISMISSIBLE_POSTGRES_STORAGE_CONNECTION_STRING =
|
|
56
|
+
'postgres://fallback:fallback@localhost/fallback';
|
|
57
|
+
|
|
58
|
+
const { createPrismaConfig } = await import('./prisma-config');
|
|
59
|
+
const config = createPrismaConfig();
|
|
60
|
+
|
|
61
|
+
expect(config.datasource.url).toBe('postgres://primary:primary@localhost/primary');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should return empty string when no database URL is set', async () => {
|
|
65
|
+
const { createPrismaConfig } = await import('./prisma-config');
|
|
66
|
+
const config = createPrismaConfig();
|
|
67
|
+
|
|
68
|
+
expect(config.datasource.url).toBe('');
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('basePrismaConfig', () => {
|
|
73
|
+
it('should be exported and have the expected structure', async () => {
|
|
74
|
+
const { basePrismaConfig } = await import('./prisma-config');
|
|
75
|
+
|
|
76
|
+
expect(basePrismaConfig).toBeDefined();
|
|
77
|
+
expect(basePrismaConfig).toHaveProperty('schema');
|
|
78
|
+
expect(basePrismaConfig).toHaveProperty('migrations');
|
|
79
|
+
expect(basePrismaConfig).toHaveProperty('datasource');
|
|
80
|
+
expect(basePrismaConfig.migrations).toHaveProperty('path');
|
|
81
|
+
expect(basePrismaConfig.datasource).toHaveProperty('url');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should have schema and migrations paths pointing to the same prisma directory', async () => {
|
|
85
|
+
const { basePrismaConfig } = await import('./prisma-config');
|
|
86
|
+
|
|
87
|
+
const schemaDir = join(basePrismaConfig.schema, '..');
|
|
88
|
+
const migrationsDir = join(basePrismaConfig.migrations.path, '..');
|
|
89
|
+
|
|
90
|
+
expect(schemaDir).toBe(migrationsDir);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { join } from 'path';
|
|
2
|
+
import { getPrismaSchemaPath } from './schema-path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates a Prisma configuration object with paths resolved relative to
|
|
6
|
+
* the @dismissible/nestjs-postgres-storage package.
|
|
7
|
+
*
|
|
8
|
+
* @returns Prisma configuration object suitable for use with defineConfig()
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* // prisma.config.mjs
|
|
13
|
+
* import { defineConfig } from 'prisma/config';
|
|
14
|
+
* import { basePrismaConfig } from '@dismissible/nestjs-postgres-storage';
|
|
15
|
+
*
|
|
16
|
+
* export default defineConfig(basePrismaConfig);
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export function createPrismaConfig() {
|
|
20
|
+
const prismaDir = join(getPrismaSchemaPath(), '..');
|
|
21
|
+
return {
|
|
22
|
+
schema: join(prismaDir, 'schema.prisma'),
|
|
23
|
+
migrations: {
|
|
24
|
+
path: join(prismaDir, 'migrations'),
|
|
25
|
+
},
|
|
26
|
+
datasource: {
|
|
27
|
+
url:
|
|
28
|
+
process.env.DATABASE_URL ??
|
|
29
|
+
process.env.DISMISSIBLE_POSTGRES_STORAGE_CONNECTION_STRING ??
|
|
30
|
+
'',
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Base Prisma configuration for @dismissible/nestjs-postgres-storage.
|
|
37
|
+
*
|
|
38
|
+
* Use this with Prisma's defineConfig() to create your prisma.config.mjs:
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```javascript
|
|
42
|
+
* // prisma.config.mjs
|
|
43
|
+
* import { defineConfig } from 'prisma/config';
|
|
44
|
+
* import { basePrismaConfig } from '@dismissible/nestjs-postgres-storage';
|
|
45
|
+
*
|
|
46
|
+
* export default defineConfig(basePrismaConfig);
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* @example Extending the config
|
|
50
|
+
* ```javascript
|
|
51
|
+
* import { defineConfig } from 'prisma/config';
|
|
52
|
+
* import { basePrismaConfig } from '@dismissible/nestjs-postgres-storage';
|
|
53
|
+
*
|
|
54
|
+
* export default defineConfig({
|
|
55
|
+
* ...basePrismaConfig,
|
|
56
|
+
* migrations: {
|
|
57
|
+
* ...basePrismaConfig.migrations,
|
|
58
|
+
* seed: 'tsx prisma/seed.ts',
|
|
59
|
+
* },
|
|
60
|
+
* });
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export const basePrismaConfig = createPrismaConfig();
|