Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
uasan committed Sep 10, 2023
1 parent c49713c commit ddccb56
Show file tree
Hide file tree
Showing 17 changed files with 249 additions and 21 deletions.
2 changes: 1 addition & 1 deletion bin/dev.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env node --watch --experimental-import-meta-resolve
#!/usr/bin/env node --watch

import { createWatchHost } from '../src/compiler/worker/watch.js';

Expand Down
2 changes: 1 addition & 1 deletion bin/migrate.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env node --experimental-import-meta-resolve
#!/usr/bin/env node

import { createBuilderHost } from '../src/compiler/worker/watch.js';

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
},
"imports": {
"#config": "./src/config.js",
"#utils/*": "./src/runtime/utils/*",
"#runtime/*": "./src/runtime/*",
"#compiler/*": "./src/compiler/*"
},
Expand Down
9 changes: 5 additions & 4 deletions src/compiler/worker/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export class BuilderHooks {
worker = {
filename: '',
instance: null,
options: { argv: process.argv.slice(2) },
close: () => {
if (this.worker.instance) {
this.worker.instance.once('exit', process.exit.bind(process));
Expand Down Expand Up @@ -60,10 +61,10 @@ export class BuilderHooks {
afterEmit() {
if (this.worker.filename) {
this.worker.instance?.terminate();
this.worker.instance = new Worker(this.worker.filename).on(
'error',
console.error
);
this.worker.instance = new Worker(
this.worker.filename,
this.worker.options
).on('error', console.error);
}
}

Expand Down
6 changes: 6 additions & 0 deletions src/runtime/migration/actions/help.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export function help() {
console.log(`These are common Migrate commands:
npm run migrate up Run up method for all new and updated migrations
npm run migrate down [filename] Run down method of [filename]
npm run migrate status Check status of migrations table`);
}
9 changes: 9 additions & 0 deletions src/runtime/migration/actions/status.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { reportStatusMigrate } from '../internals/report.js';

export function status(ctx, migrations) {
for (let index = 0; index < migrations.length; ) {
const migration = migrations[index];

reportStatusMigrate(migration, ++index);
}
}
56 changes: 56 additions & 0 deletions src/runtime/migration/actions/up.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { lockMigrate, unlockMigrate } from '../internals/connect.js';
import { reportUpMigrate } from '../internals/report.js';
import { saveMigrations } from '../internals/state.js';
import { STATUS_DONE, STATUS_NEW, STATUS_UPDATED } from '../constants.js';

async function upMigration(ctx, migrations) {
await lockMigrate(ctx);

for (let index = 0; index < migrations.length; ) {
const migration = migrations[index];

reportUpMigrate(migration, ++index);
await migration.up();
}

await saveMigrations(ctx, migrations);
await unlockMigrate(ctx);
}

export async function up(ctx, migrations) {
const upMigrations = [];
const wasDoneMigrations = [];
const afterCommitMigrations = [];

for (const migration of migrations)
switch (migration.status) {
case STATUS_NEW:
case STATUS_UPDATED: {
upMigrations.push(migration);

if (Object.hasOwn(migration, 'onAfterCommit'))
afterCommitMigrations.push(migration);

break;
}

case STATUS_DONE: {
if (Object.hasOwn(migration, 'onWasDone'))
wasDoneMigrations.push(migration);

break;
}
}

if (upMigrations.length) {
await ctx.startTransaction(upMigration, upMigrations);
}

for (const migration of afterCommitMigrations) {
await migration.onAfterCommit();
}

for (const migration of wasDoneMigrations) {
await migration.onWasDone();
}
}
28 changes: 25 additions & 3 deletions src/runtime/migration/app.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,29 @@
import { connect, disconnect } from './database.js';
import { argv } from 'node:process';

import { setMigrations } from './internals/state.js';
import { connect, disconnect } from './internals/connect.js';

import { up } from './actions/up.js';
import { help } from './actions/help.js';
import { status } from './actions/status.js';

const actions = {
up,
status,
};

export async function migrate(context, migrations) {
const ctx = await connect(context);
const action = actions[argv[2] ?? 'up'];

if (action) {
const ctx = await connect(context);

await disconnect(ctx);
try {
await action(ctx, await setMigrations(ctx, migrations), ...argv.slice(3));
} finally {
await disconnect(ctx);
}
} else {
help();
}
}
5 changes: 5 additions & 0 deletions src/runtime/migration/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const LOCK_ID = 0;

export const STATUS_NEW = 'new';
export const STATUS_UPDATED = 'updated';
export const STATUS_DONE = 'done';
10 changes: 9 additions & 1 deletion src/runtime/migration/context.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
import { Context } from '../context.js';
import { STATUS_NEW } from './constants.js';

export class MigrationContext extends Context {}
export class MigrationContext extends Context {
static hash = null;
static version = null;
static wasDone = false;

status = STATUS_NEW;
isBootStrap = true;
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import { green, yellow, red } from '../console/colors.js';
import { yellow, red } from '#utils/console.js';
import { ERRORS } from '@uah/postgres/src/constants.js';
import { LOCK_ID } from '../constants.js';

export const LOCK_ID = 0;
export const DEFAULT_DATABASE = 'postgres';
export const ERROR_DATABASE_NOT_EXIST = '3D000';

export async function createDatabase(ctx) {
const { options } = ctx.postgres;

await ctx.postgres.setOptions({ ...options, database: DEFAULT_DATABASE });
await ctx.postgres.reset({ ...options, database: DEFAULT_DATABASE });
await ctx.postgres.query(`CREATE DATABASE "${options.database}"`);
await ctx.postgres.setOptions(options);
await ctx.postgres.reset(options);
}

export async function dropDatabase(ctx) {
const { options } = ctx.postgres;

await ctx.postgres.setOptions({ ...options, database: DEFAULT_DATABASE });
await ctx.postgres.reset({ ...options, database: DEFAULT_DATABASE });
await ctx.postgres.query(`DROP DATABASE IF EXISTS "${options.database}"`);
}

Expand All @@ -28,8 +28,6 @@ export async function lockMigrate(ctx) {

await ctx.sql`SELECT pg_advisory_lock(${LOCK_ID}::bigint)`;
}

console.log(green(`Migrate: start lock ${LOCK_ID}`));
}

export async function unlockMigrate(ctx) {
Expand All @@ -42,11 +40,10 @@ export async function connect(context) {
try {
await ctx.postgres.connect();
} catch (error) {
if (error.code === ERROR_DATABASE_NOT_EXIST) await createDatabase(ctx);
if (error.code === ERRORS.DATABASE_NOT_EXIST) await createDatabase(ctx);
else throw error;
}

await lockMigrate(ctx);
return ctx;
}

Expand Down
28 changes: 28 additions & 0 deletions src/runtime/migration/internals/report.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { green, blue, red } from '#utils/console.js';

function report(migration, command, index) {
console.log(
green('Migrate: ') +
index +
' ' +
command +
' ' +
migration.constructor.path
);
}

export function reportUpMigrate(migration, index) {
report(migration, green('up'), index);
}

export function reportDownMigrate(migration, index) {
report(migration, red('down'), index);
}

export function reportStatusMigrate(migration, index) {
const status = migration.status.toUpperCase();

console.log(
index + ' ' + blue(status) + ' ' + green(migration.constructor.path)
);
}
67 changes: 67 additions & 0 deletions src/runtime/migration/internals/state.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { ERRORS } from '@uah/postgres/src/constants.js';
import { STATUS_DONE, STATUS_NEW, STATUS_UPDATED } from '../constants.js';

async function createTableMigrations(ctx) {
await ctx.sql`
CREATE TABLE public.migrations (
name text COLLATE "C" PRIMARY KEY,
hash bytea,
updated_at timestamptz not null default CURRENT_TIMESTAMP,
created_at timestamptz not null default CURRENT_TIMESTAMP
)`;
}

async function getStateMigrations(ctx, migrations) {
const names = migrations.map(({ path }) => path);
const hashes = migrations.map(({ hash }) => hash);

const query = ctx.sql`
SELECT json_object_agg(files.name, CASE
WHEN migrations.name IS NULL THEN ${STATUS_NEW}
WHEN migrations.hash IS DISTINCT FROM files.hash THEN ${STATUS_UPDATED}
ELSE ${STATUS_DONE}
END)
FROM unnest(${names}::text[], ${hashes}::bytea[]) AS files(name, hash)
LEFT JOIN public.migrations USING(name)`.asValue();

try {
return await query;
} catch (error) {
if (error.code === ERRORS.RELATION_NOT_EXIST) {
await createTableMigrations(ctx);
return await query;
}
throw error;
}
}

export async function setMigrations(ctx, migrations) {
const state = await getStateMigrations(ctx, migrations);

ctx.isBootStrap = Object.values(state).every(status => status === STATUS_NEW);

for (let i = 0; i < migrations.length; i++) {
const migration = migrations[i];
const status = state[migration.path];

migration.wasDone = status === STATUS_DONE;

migrations[i] = new migration();
migrations[i].status = status;
}

return migrations;
}

export async function saveMigrations(ctx, migrations) {
const names = migrations.map(m => m.constructor.path);
const hashes = migrations.map(m => m.constructor.hash);

await ctx.sql`
INSERT INTO public.migrations (name, hash)
SELECT name, hash
FROM unnest(${names}::text[], ${hashes}::bytea[]) AS files(name, hash)
ON CONFLICT (name) DO UPDATE SET
hash = EXCLUDED.hash,
updated_at = CURRENT_TIMESTAMP`;
}
4 changes: 4 additions & 0 deletions src/runtime/postgres/context.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { Pool } from '@uah/postgres/src/pool.js';
import { Client } from '@uah/postgres/src/client.js';

import { signal } from '../process.js';
import { startTransaction } from './transaction.js';

export function initPostgres({ prototype }, options) {
prototype.startTransaction = startTransaction;

prototype.postgres =
options.maxConnections > 1
? new Pool({ signal, ...options })
Expand Down
22 changes: 22 additions & 0 deletions src/runtime/postgres/transaction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {
TRANSACTION_ACTIVE,
TRANSACTION_INACTIVE,
} from '@uah/postgres/src/constants.js';

export async function startTransaction(action, payload) {
try {
await this.postgres.query('BEGIN');
const result = await action(this, payload);

if (this.postgres.state === TRANSACTION_ACTIVE) {
await this.postgres.query('COMMIT');
}

return result;
} catch (error) {
if (this.postgres.state !== TRANSACTION_INACTIVE) {
await this.postgres.query('ROLLBACK');
}
throw error;
}
}
2 changes: 1 addition & 1 deletion src/runtime/server/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {

import { signal } from '../process.js';
import { Router } from './router.js';
import { blue, green, red } from '../console/colors.js';
import { blue, green, red } from '#utils/console.js';

export const Server = {
url: '',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import process from 'node:process';

let FORCE_COLOR,
NODE_DISABLE_COLORS,
NO_COLOR,
Expand Down

0 comments on commit ddccb56

Please sign in to comment.