supabase-test 0.0.1
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/LICENSE +23 -0
- package/README.md +557 -0
- package/admin.d.ts +26 -0
- package/admin.js +182 -0
- package/connect.d.ts +19 -0
- package/connect.js +83 -0
- package/dist/README.md +557 -0
- package/dist/package.json +72 -0
- package/esm/admin.js +178 -0
- package/esm/connect.js +78 -0
- package/esm/index.js +6 -0
- package/esm/manager.js +136 -0
- package/esm/roles.js +32 -0
- package/esm/seed/adapters.js +23 -0
- package/esm/seed/csv.js +44 -0
- package/esm/seed/index.js +16 -0
- package/esm/seed/json.js +18 -0
- package/esm/seed/launchql.js +19 -0
- package/esm/seed/sqitch.js +17 -0
- package/esm/seed/types.js +1 -0
- package/esm/stream.js +43 -0
- package/esm/test-client.js +150 -0
- package/index.d.ts +6 -0
- package/index.js +22 -0
- package/manager.d.ts +25 -0
- package/manager.js +140 -0
- package/package.json +72 -0
- package/roles.d.ts +17 -0
- package/roles.js +38 -0
- package/seed/adapters.d.ts +4 -0
- package/seed/adapters.js +28 -0
- package/seed/csv.d.ts +9 -0
- package/seed/csv.js +49 -0
- package/seed/index.d.ts +16 -0
- package/seed/index.js +33 -0
- package/seed/json.d.ts +6 -0
- package/seed/json.js +21 -0
- package/seed/launchql.d.ts +2 -0
- package/seed/launchql.js +22 -0
- package/seed/sqitch.d.ts +2 -0
- package/seed/sqitch.js +20 -0
- package/seed/types.d.ts +13 -0
- package/seed/types.js +2 -0
- package/stream.d.ts +2 -0
- package/stream.js +46 -0
- package/test-client.d.ts +49 -0
- package/test-client.js +154 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { Client } from 'pg';
|
|
2
|
+
import { getRoleName } from './roles';
|
|
3
|
+
export class PgTestClient {
|
|
4
|
+
config;
|
|
5
|
+
client;
|
|
6
|
+
opts;
|
|
7
|
+
ctxStmts = '';
|
|
8
|
+
contextSettings = {};
|
|
9
|
+
_ended = false;
|
|
10
|
+
connectPromise = null;
|
|
11
|
+
constructor(config, opts = {}) {
|
|
12
|
+
this.opts = opts;
|
|
13
|
+
this.config = config;
|
|
14
|
+
this.client = new Client({
|
|
15
|
+
host: this.config.host,
|
|
16
|
+
port: this.config.port,
|
|
17
|
+
database: this.config.database,
|
|
18
|
+
user: this.config.user,
|
|
19
|
+
password: this.config.password
|
|
20
|
+
});
|
|
21
|
+
if (!opts.deferConnect) {
|
|
22
|
+
this.connectPromise = this.client.connect();
|
|
23
|
+
if (opts.trackConnect)
|
|
24
|
+
opts.trackConnect(this.connectPromise);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
async ensureConnected() {
|
|
28
|
+
if (this.connectPromise) {
|
|
29
|
+
try {
|
|
30
|
+
await this.connectPromise;
|
|
31
|
+
}
|
|
32
|
+
catch { }
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async close() {
|
|
36
|
+
if (!this._ended) {
|
|
37
|
+
this._ended = true;
|
|
38
|
+
await this.ensureConnected();
|
|
39
|
+
this.client.end();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async begin() {
|
|
43
|
+
await this.client.query('BEGIN;');
|
|
44
|
+
}
|
|
45
|
+
async savepoint(name = 'lqlsavepoint') {
|
|
46
|
+
await this.client.query(`SAVEPOINT "${name}";`);
|
|
47
|
+
}
|
|
48
|
+
async rollback(name = 'lqlsavepoint') {
|
|
49
|
+
await this.client.query(`ROLLBACK TO SAVEPOINT "${name}";`);
|
|
50
|
+
}
|
|
51
|
+
async commit() {
|
|
52
|
+
await this.client.query('COMMIT;');
|
|
53
|
+
}
|
|
54
|
+
async beforeEach() {
|
|
55
|
+
await this.begin();
|
|
56
|
+
await this.savepoint();
|
|
57
|
+
}
|
|
58
|
+
async afterEach() {
|
|
59
|
+
await this.rollback();
|
|
60
|
+
await this.commit();
|
|
61
|
+
}
|
|
62
|
+
setContext(ctx) {
|
|
63
|
+
Object.assign(this.contextSettings, ctx);
|
|
64
|
+
this.ctxStmts = Object.entries(this.contextSettings)
|
|
65
|
+
.map(([key, val]) => val === null
|
|
66
|
+
? `SELECT set_config('${key}', NULL, true);`
|
|
67
|
+
: `SELECT set_config('${key}', '${val}', true);`)
|
|
68
|
+
.join('\n');
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Set authentication context for the current session.
|
|
72
|
+
* Configures role and user ID using cascading defaults from options → opts.auth → RoleMapping.
|
|
73
|
+
*/
|
|
74
|
+
auth(options = {}) {
|
|
75
|
+
const role = options.role ?? this.opts.auth?.role ?? getRoleName('authenticated', this.opts);
|
|
76
|
+
const userIdKey = options.userIdKey ?? this.opts.auth?.userIdKey ?? 'jwt.claims.user_id';
|
|
77
|
+
const userId = options.userId ?? this.opts.auth?.userId ?? null;
|
|
78
|
+
this.setContext({
|
|
79
|
+
role,
|
|
80
|
+
[userIdKey]: userId !== null ? String(userId) : null
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Commit current transaction to make data visible to other connections, then start fresh transaction.
|
|
85
|
+
* Maintains test isolation by creating a savepoint and reapplying session context.
|
|
86
|
+
*/
|
|
87
|
+
async publish() {
|
|
88
|
+
await this.commit(); // make data visible to other sessions
|
|
89
|
+
await this.begin(); // fresh tx
|
|
90
|
+
await this.savepoint(); // keep rollback harness
|
|
91
|
+
await this.ctxQuery(); // reapply all setContext()
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Clear all session context variables and reset to default anonymous role.
|
|
95
|
+
*/
|
|
96
|
+
clearContext() {
|
|
97
|
+
const defaultRole = getRoleName('anonymous', this.opts);
|
|
98
|
+
const nulledSettings = {};
|
|
99
|
+
Object.keys(this.contextSettings).forEach(key => {
|
|
100
|
+
nulledSettings[key] = null;
|
|
101
|
+
});
|
|
102
|
+
nulledSettings.role = defaultRole;
|
|
103
|
+
this.ctxStmts = Object.entries(nulledSettings)
|
|
104
|
+
.map(([key, val]) => val === null
|
|
105
|
+
? `SELECT set_config('${key}', NULL, true);`
|
|
106
|
+
: `SELECT set_config('${key}', '${val}', true);`)
|
|
107
|
+
.join('\n');
|
|
108
|
+
this.contextSettings = { role: defaultRole };
|
|
109
|
+
}
|
|
110
|
+
async any(query, values) {
|
|
111
|
+
const result = await this.query(query, values);
|
|
112
|
+
return result.rows;
|
|
113
|
+
}
|
|
114
|
+
async one(query, values) {
|
|
115
|
+
const rows = await this.any(query, values);
|
|
116
|
+
if (rows.length !== 1) {
|
|
117
|
+
throw new Error('Expected exactly one result');
|
|
118
|
+
}
|
|
119
|
+
return rows[0];
|
|
120
|
+
}
|
|
121
|
+
async oneOrNone(query, values) {
|
|
122
|
+
const rows = await this.any(query, values);
|
|
123
|
+
return rows[0] || null;
|
|
124
|
+
}
|
|
125
|
+
async many(query, values) {
|
|
126
|
+
const rows = await this.any(query, values);
|
|
127
|
+
if (rows.length === 0)
|
|
128
|
+
throw new Error('Expected many rows, got none');
|
|
129
|
+
return rows;
|
|
130
|
+
}
|
|
131
|
+
async manyOrNone(query, values) {
|
|
132
|
+
return this.any(query, values);
|
|
133
|
+
}
|
|
134
|
+
async none(query, values) {
|
|
135
|
+
await this.query(query, values);
|
|
136
|
+
}
|
|
137
|
+
async result(query, values) {
|
|
138
|
+
return this.query(query, values);
|
|
139
|
+
}
|
|
140
|
+
async query(query, values) {
|
|
141
|
+
await this.ctxQuery();
|
|
142
|
+
const result = await this.client.query(query, values);
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
async ctxQuery() {
|
|
146
|
+
if (this.ctxStmts) {
|
|
147
|
+
await this.client.query(this.ctxStmts);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
package/index.d.ts
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./admin"), exports);
|
|
18
|
+
__exportStar(require("./connect"), exports);
|
|
19
|
+
__exportStar(require("./manager"), exports);
|
|
20
|
+
__exportStar(require("./roles"), exports);
|
|
21
|
+
__exportStar(require("./seed"), exports);
|
|
22
|
+
__exportStar(require("./test-client"), exports);
|
package/manager.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Pool } from 'pg';
|
|
2
|
+
import { PgConfig } from 'pg-env';
|
|
3
|
+
import { PgTestClient, PgTestClientOpts } from './test-client';
|
|
4
|
+
export declare class PgTestConnector {
|
|
5
|
+
private static instance;
|
|
6
|
+
private readonly clients;
|
|
7
|
+
private readonly pgPools;
|
|
8
|
+
private readonly seenDbConfigs;
|
|
9
|
+
private readonly pendingConnects;
|
|
10
|
+
private verbose;
|
|
11
|
+
private shuttingDown;
|
|
12
|
+
private constructor();
|
|
13
|
+
static getInstance(verbose?: boolean): PgTestConnector;
|
|
14
|
+
private poolKey;
|
|
15
|
+
private dbKey;
|
|
16
|
+
beginTeardown(): void;
|
|
17
|
+
private registerConnect;
|
|
18
|
+
private awaitPendingConnects;
|
|
19
|
+
getPool(config: PgConfig): Pool;
|
|
20
|
+
getClient(config: PgConfig, opts?: Partial<PgTestClientOpts>): PgTestClient;
|
|
21
|
+
closeAll(): Promise<void>;
|
|
22
|
+
close(): void;
|
|
23
|
+
drop(config: PgConfig): void;
|
|
24
|
+
kill(client: PgTestClient): Promise<void>;
|
|
25
|
+
}
|
package/manager.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PgTestConnector = void 0;
|
|
4
|
+
const logger_1 = require("@launchql/logger");
|
|
5
|
+
const pg_1 = require("pg");
|
|
6
|
+
const pg_env_1 = require("pg-env");
|
|
7
|
+
const admin_1 = require("./admin");
|
|
8
|
+
const test_client_1 = require("./test-client");
|
|
9
|
+
const log = new logger_1.Logger('test-connector');
|
|
10
|
+
const SYS_EVENTS = ['SIGTERM'];
|
|
11
|
+
const end = (pool) => {
|
|
12
|
+
try {
|
|
13
|
+
if (pool.ended || pool.ending) {
|
|
14
|
+
log.warn('⚠️ pg pool already ended or ending');
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
pool.end();
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
log.error('❌ pg pool termination error:', err);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
class PgTestConnector {
|
|
24
|
+
static instance;
|
|
25
|
+
clients = new Set();
|
|
26
|
+
pgPools = new Map();
|
|
27
|
+
seenDbConfigs = new Map();
|
|
28
|
+
pendingConnects = new Set();
|
|
29
|
+
verbose = false;
|
|
30
|
+
shuttingDown = false;
|
|
31
|
+
constructor(verbose = false) {
|
|
32
|
+
this.verbose = verbose;
|
|
33
|
+
SYS_EVENTS.forEach((event) => {
|
|
34
|
+
process.on(event, () => {
|
|
35
|
+
log.info(`⏹ Received ${event}, closing all connections...`);
|
|
36
|
+
this.closeAll();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
static getInstance(verbose = false) {
|
|
41
|
+
if (!PgTestConnector.instance) {
|
|
42
|
+
PgTestConnector.instance = new PgTestConnector(verbose);
|
|
43
|
+
}
|
|
44
|
+
return PgTestConnector.instance;
|
|
45
|
+
}
|
|
46
|
+
poolKey(config) {
|
|
47
|
+
return `${config.user}@${config.host}:${config.port}/${config.database}`;
|
|
48
|
+
}
|
|
49
|
+
dbKey(config) {
|
|
50
|
+
return `${config.host}:${config.port}/${config.database}`;
|
|
51
|
+
}
|
|
52
|
+
beginTeardown() {
|
|
53
|
+
this.shuttingDown = true;
|
|
54
|
+
}
|
|
55
|
+
registerConnect(p) {
|
|
56
|
+
this.pendingConnects.add(p);
|
|
57
|
+
p.finally(() => this.pendingConnects.delete(p));
|
|
58
|
+
}
|
|
59
|
+
async awaitPendingConnects() {
|
|
60
|
+
const arr = Array.from(this.pendingConnects);
|
|
61
|
+
if (arr.length) {
|
|
62
|
+
await Promise.allSettled(arr);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
getPool(config) {
|
|
66
|
+
const key = this.poolKey(config);
|
|
67
|
+
if (!this.pgPools.has(key)) {
|
|
68
|
+
const pool = new pg_1.Pool(config);
|
|
69
|
+
this.pgPools.set(key, pool);
|
|
70
|
+
log.info(`📘 Created new pg pool: ${key}`);
|
|
71
|
+
}
|
|
72
|
+
return this.pgPools.get(key);
|
|
73
|
+
}
|
|
74
|
+
getClient(config, opts = {}) {
|
|
75
|
+
if (this.shuttingDown) {
|
|
76
|
+
throw new Error('PgTestConnector is shutting down; no new clients allowed');
|
|
77
|
+
}
|
|
78
|
+
const client = new test_client_1.PgTestClient(config, {
|
|
79
|
+
trackConnect: (p) => this.registerConnect(p),
|
|
80
|
+
...opts
|
|
81
|
+
});
|
|
82
|
+
this.clients.add(client);
|
|
83
|
+
const key = this.dbKey(config);
|
|
84
|
+
this.seenDbConfigs.set(key, config);
|
|
85
|
+
log.info(`🔌 New PgTestClient connected to ${config.database}`);
|
|
86
|
+
return client;
|
|
87
|
+
}
|
|
88
|
+
async closeAll() {
|
|
89
|
+
this.beginTeardown();
|
|
90
|
+
await this.awaitPendingConnects();
|
|
91
|
+
log.info('🧹 Closing all PgTestClients...');
|
|
92
|
+
await Promise.all(Array.from(this.clients).map(async (client) => {
|
|
93
|
+
try {
|
|
94
|
+
await client.close();
|
|
95
|
+
log.success(`✅ Closed client for ${client.config.database}`);
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
log.error(`❌ Error closing PgTestClient for ${client.config.database}:`, err);
|
|
99
|
+
}
|
|
100
|
+
}));
|
|
101
|
+
this.clients.clear();
|
|
102
|
+
log.info('🧯 Disposing pg pools...');
|
|
103
|
+
for (const [key, pool] of this.pgPools.entries()) {
|
|
104
|
+
log.debug(`🧯 Disposing pg pool [${key}]`);
|
|
105
|
+
end(pool);
|
|
106
|
+
}
|
|
107
|
+
this.pgPools.clear();
|
|
108
|
+
log.info('🗑️ Dropping seen databases...');
|
|
109
|
+
await Promise.all(Array.from(this.seenDbConfigs.values()).map(async (config) => {
|
|
110
|
+
try {
|
|
111
|
+
const rootPg = (0, pg_env_1.getPgEnvOptions)();
|
|
112
|
+
const admin = new admin_1.DbAdmin({ ...config, user: rootPg.user, password: rootPg.password }, this.verbose);
|
|
113
|
+
admin.drop();
|
|
114
|
+
log.warn(`🧨 Dropped database: ${config.database}`);
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
log.error(`❌ Failed to drop database ${config.database}:`, err);
|
|
118
|
+
}
|
|
119
|
+
}));
|
|
120
|
+
this.seenDbConfigs.clear();
|
|
121
|
+
log.success('✅ All PgTestClients closed, pools disposed, databases dropped.');
|
|
122
|
+
this.pendingConnects.clear();
|
|
123
|
+
this.shuttingDown = false;
|
|
124
|
+
}
|
|
125
|
+
close() {
|
|
126
|
+
this.closeAll();
|
|
127
|
+
}
|
|
128
|
+
drop(config) {
|
|
129
|
+
const key = this.dbKey(config);
|
|
130
|
+
const admin = new admin_1.DbAdmin(config, this.verbose);
|
|
131
|
+
admin.drop();
|
|
132
|
+
log.warn(`🧨 Dropped database: ${config.database}`);
|
|
133
|
+
this.seenDbConfigs.delete(key);
|
|
134
|
+
}
|
|
135
|
+
async kill(client) {
|
|
136
|
+
await client.close();
|
|
137
|
+
this.drop(client.config);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
exports.PgTestConnector = PgTestConnector;
|
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "supabase-test",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"author": "Dan Lynch <pyramation@gmail.com>",
|
|
5
|
+
"description": "supabase-test offers isolated, role-aware, and rollback-friendly PostgreSQL environments for integration tests — giving developers realistic test coverage without external state pollution",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"module": "esm/index.js",
|
|
8
|
+
"types": "index.d.ts",
|
|
9
|
+
"homepage": "https://github.com/launchql/launchql",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public",
|
|
13
|
+
"directory": "dist"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/launchql/launchql"
|
|
18
|
+
},
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/launchql/launchql/issues"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"postgres",
|
|
24
|
+
"postgresql",
|
|
25
|
+
"testing",
|
|
26
|
+
"integration-tests",
|
|
27
|
+
"database-testing",
|
|
28
|
+
"pg",
|
|
29
|
+
"rls",
|
|
30
|
+
"role-based-access",
|
|
31
|
+
"test-database",
|
|
32
|
+
"test-runner",
|
|
33
|
+
"jest",
|
|
34
|
+
"mocha",
|
|
35
|
+
"sqitch",
|
|
36
|
+
"launchql",
|
|
37
|
+
"graphile",
|
|
38
|
+
"typeorm",
|
|
39
|
+
"knex",
|
|
40
|
+
"seed",
|
|
41
|
+
"fixtures",
|
|
42
|
+
"transactions",
|
|
43
|
+
"rollback",
|
|
44
|
+
"node-postgres",
|
|
45
|
+
"pg-pool",
|
|
46
|
+
"pg-client"
|
|
47
|
+
],
|
|
48
|
+
"scripts": {
|
|
49
|
+
"copy": "copyfiles -f ../../LICENSE README.md package.json dist",
|
|
50
|
+
"clean": "rimraf dist/**",
|
|
51
|
+
"prepare": "npm run build",
|
|
52
|
+
"build": "npm run clean; tsc; tsc -p tsconfig.esm.json; npm run copy",
|
|
53
|
+
"build:dev": "npm run clean; tsc --declarationMap; tsc -p tsconfig.esm.json; npm run copy",
|
|
54
|
+
"lint": "eslint . --fix",
|
|
55
|
+
"test": "jest",
|
|
56
|
+
"test:watch": "jest --watch"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@types/pg": "^8.15.2",
|
|
60
|
+
"@types/pg-copy-streams": "^1.2.5"
|
|
61
|
+
},
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"@launchql/core": "^2.11.1",
|
|
64
|
+
"@launchql/env": "^2.4.1",
|
|
65
|
+
"@launchql/server-utils": "^2.4.1",
|
|
66
|
+
"@launchql/types": "^2.6.0",
|
|
67
|
+
"pg": "^8.16.0",
|
|
68
|
+
"pg-cache": "^1.3.1",
|
|
69
|
+
"pg-copy-streams": "^6.0.6",
|
|
70
|
+
"pg-env": "^1.1.0"
|
|
71
|
+
}
|
|
72
|
+
}
|
package/roles.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { PgTestConnectionOptions, RoleMapping } from '@launchql/types';
|
|
2
|
+
/**
|
|
3
|
+
* Default role mapping configuration
|
|
4
|
+
*/
|
|
5
|
+
export declare const DEFAULT_ROLE_MAPPING: Required<RoleMapping>;
|
|
6
|
+
/**
|
|
7
|
+
* Get resolved role mapping with defaults
|
|
8
|
+
*/
|
|
9
|
+
export declare const getRoleMapping: (options?: PgTestConnectionOptions) => Required<RoleMapping>;
|
|
10
|
+
/**
|
|
11
|
+
* Get role name by key with fallback to default mapping
|
|
12
|
+
*/
|
|
13
|
+
export declare const getRoleName: (roleKey: keyof Omit<RoleMapping, "default">, options?: PgTestConnectionOptions) => string;
|
|
14
|
+
/**
|
|
15
|
+
* Get default role name
|
|
16
|
+
*/
|
|
17
|
+
export declare const getDefaultRole: (options?: PgTestConnectionOptions) => string;
|
package/roles.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getDefaultRole = exports.getRoleName = exports.getRoleMapping = exports.DEFAULT_ROLE_MAPPING = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Default role mapping configuration
|
|
6
|
+
*/
|
|
7
|
+
exports.DEFAULT_ROLE_MAPPING = {
|
|
8
|
+
anonymous: 'anonymous',
|
|
9
|
+
authenticated: 'authenticated',
|
|
10
|
+
administrator: 'administrator',
|
|
11
|
+
default: 'anonymous'
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Get resolved role mapping with defaults
|
|
15
|
+
*/
|
|
16
|
+
const getRoleMapping = (options) => {
|
|
17
|
+
return {
|
|
18
|
+
...exports.DEFAULT_ROLE_MAPPING,
|
|
19
|
+
...(options?.roles || {})
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
exports.getRoleMapping = getRoleMapping;
|
|
23
|
+
/**
|
|
24
|
+
* Get role name by key with fallback to default mapping
|
|
25
|
+
*/
|
|
26
|
+
const getRoleName = (roleKey, options) => {
|
|
27
|
+
const mapping = (0, exports.getRoleMapping)(options);
|
|
28
|
+
return mapping[roleKey];
|
|
29
|
+
};
|
|
30
|
+
exports.getRoleName = getRoleName;
|
|
31
|
+
/**
|
|
32
|
+
* Get default role name
|
|
33
|
+
*/
|
|
34
|
+
const getDefaultRole = (options) => {
|
|
35
|
+
const mapping = (0, exports.getRoleMapping)(options);
|
|
36
|
+
return mapping.default;
|
|
37
|
+
};
|
|
38
|
+
exports.getDefaultRole = getDefaultRole;
|
package/seed/adapters.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sqlfile = sqlfile;
|
|
4
|
+
exports.fn = fn;
|
|
5
|
+
exports.compose = compose;
|
|
6
|
+
function sqlfile(files) {
|
|
7
|
+
return {
|
|
8
|
+
seed(ctx) {
|
|
9
|
+
for (const file of files) {
|
|
10
|
+
ctx.admin.loadSql(file, ctx.config.database);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function fn(fn) {
|
|
16
|
+
return {
|
|
17
|
+
seed: fn
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function compose(adapters) {
|
|
21
|
+
return {
|
|
22
|
+
async seed(ctx) {
|
|
23
|
+
for (const adapter of adapters) {
|
|
24
|
+
await adapter.seed(ctx);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
package/seed/csv.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { PgTestClient } from '../test-client';
|
|
2
|
+
import { SeedAdapter } from './types';
|
|
3
|
+
interface CsvSeedMap {
|
|
4
|
+
[tableName: string]: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function csv(tables: CsvSeedMap): SeedAdapter;
|
|
7
|
+
export declare function copyCsvIntoTable(pg: PgTestClient, table: string, filePath: string): Promise<void>;
|
|
8
|
+
export declare function exportTableToCsv(pg: PgTestClient, table: string, filePath: string): Promise<void>;
|
|
9
|
+
export {};
|
package/seed/csv.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.csv = csv;
|
|
4
|
+
exports.copyCsvIntoTable = copyCsvIntoTable;
|
|
5
|
+
exports.exportTableToCsv = exportTableToCsv;
|
|
6
|
+
const promises_1 = require("node:stream/promises");
|
|
7
|
+
const logger_1 = require("@launchql/logger");
|
|
8
|
+
const fs_1 = require("fs");
|
|
9
|
+
const pg_copy_streams_1 = require("pg-copy-streams");
|
|
10
|
+
const log = new logger_1.Logger('csv');
|
|
11
|
+
function csv(tables) {
|
|
12
|
+
return {
|
|
13
|
+
async seed(ctx) {
|
|
14
|
+
for (const [table, filePath] of Object.entries(tables)) {
|
|
15
|
+
if (!(0, fs_1.existsSync)(filePath)) {
|
|
16
|
+
throw new Error(`CSV file not found: ${filePath}`);
|
|
17
|
+
}
|
|
18
|
+
log.info(`📥 Seeding "${table}" from ${filePath}`);
|
|
19
|
+
await copyCsvIntoTable(ctx.pg, table, filePath);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
async function copyCsvIntoTable(pg, table, filePath) {
|
|
25
|
+
const client = pg.client;
|
|
26
|
+
const stream = client.query((0, pg_copy_streams_1.from)(`COPY ${table} FROM STDIN WITH CSV HEADER`));
|
|
27
|
+
const source = (0, fs_1.createReadStream)(filePath);
|
|
28
|
+
try {
|
|
29
|
+
await (0, promises_1.pipeline)(source, stream);
|
|
30
|
+
log.success(`✅ Successfully seeded "${table}"`);
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
log.error(`❌ COPY failed for "${table}": ${err.message}`);
|
|
34
|
+
throw err;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async function exportTableToCsv(pg, table, filePath) {
|
|
38
|
+
const client = pg.client;
|
|
39
|
+
const stream = client.query((0, pg_copy_streams_1.to)(`COPY ${table} TO STDOUT WITH CSV HEADER`));
|
|
40
|
+
const target = (0, fs_1.createWriteStream)(filePath);
|
|
41
|
+
try {
|
|
42
|
+
await (0, promises_1.pipeline)(stream, target);
|
|
43
|
+
log.success(`✅ Exported "${table}" to ${filePath}`);
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
log.error(`❌ Failed to export "${table}": ${err.message}`);
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
49
|
+
}
|
package/seed/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { compose, fn, sqlfile } from './adapters';
|
|
2
|
+
import { csv } from './csv';
|
|
3
|
+
import { json } from './json';
|
|
4
|
+
import { launchql } from './launchql';
|
|
5
|
+
import { sqitch } from './sqitch';
|
|
6
|
+
export * from './csv';
|
|
7
|
+
export * from './types';
|
|
8
|
+
export declare const seed: {
|
|
9
|
+
launchql: typeof launchql;
|
|
10
|
+
sqitch: typeof sqitch;
|
|
11
|
+
json: typeof json;
|
|
12
|
+
csv: typeof csv;
|
|
13
|
+
compose: typeof compose;
|
|
14
|
+
fn: typeof fn;
|
|
15
|
+
sqlfile: typeof sqlfile;
|
|
16
|
+
};
|
package/seed/index.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.seed = void 0;
|
|
18
|
+
const adapters_1 = require("./adapters");
|
|
19
|
+
const csv_1 = require("./csv");
|
|
20
|
+
const json_1 = require("./json");
|
|
21
|
+
const launchql_1 = require("./launchql");
|
|
22
|
+
const sqitch_1 = require("./sqitch");
|
|
23
|
+
__exportStar(require("./csv"), exports);
|
|
24
|
+
__exportStar(require("./types"), exports);
|
|
25
|
+
exports.seed = {
|
|
26
|
+
launchql: launchql_1.launchql,
|
|
27
|
+
sqitch: sqitch_1.sqitch,
|
|
28
|
+
json: json_1.json,
|
|
29
|
+
csv: csv_1.csv,
|
|
30
|
+
compose: adapters_1.compose,
|
|
31
|
+
fn: adapters_1.fn,
|
|
32
|
+
sqlfile: adapters_1.sqlfile
|
|
33
|
+
};
|
package/seed/json.d.ts
ADDED
package/seed/json.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.json = json;
|
|
4
|
+
function json(data) {
|
|
5
|
+
return {
|
|
6
|
+
async seed(ctx) {
|
|
7
|
+
const { pg } = ctx;
|
|
8
|
+
for (const [table, rows] of Object.entries(data)) {
|
|
9
|
+
if (!Array.isArray(rows) || rows.length === 0)
|
|
10
|
+
continue;
|
|
11
|
+
const columns = Object.keys(rows[0]);
|
|
12
|
+
const placeholders = columns.map((_, i) => `$${i + 1}`).join(', ');
|
|
13
|
+
const sql = `INSERT INTO ${table} (${columns.join(', ')}) VALUES (${placeholders})`;
|
|
14
|
+
for (const row of rows) {
|
|
15
|
+
const values = columns.map((c) => row[c]);
|
|
16
|
+
await pg.query(sql, values);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}
|
package/seed/launchql.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.launchql = launchql;
|
|
4
|
+
const core_1 = require("@launchql/core");
|
|
5
|
+
const env_1 = require("@launchql/env");
|
|
6
|
+
function launchql(cwd, cache = false) {
|
|
7
|
+
return {
|
|
8
|
+
async seed(ctx) {
|
|
9
|
+
const proj = new core_1.LaunchQLPackage(cwd ?? ctx.connect.cwd);
|
|
10
|
+
if (!proj.isInModule())
|
|
11
|
+
return;
|
|
12
|
+
await proj.deploy((0, env_1.getEnvOptions)({
|
|
13
|
+
pg: ctx.config,
|
|
14
|
+
deployment: {
|
|
15
|
+
fast: true,
|
|
16
|
+
usePlan: true,
|
|
17
|
+
cache
|
|
18
|
+
}
|
|
19
|
+
}), proj.getModuleName());
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|