@package-broker/adapter-node 0.5.1 → 0.7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@package-broker/adapter-node",
3
- "version": "0.5.1",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "keywords": [],
6
6
  "scripts": {
@@ -1,18 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Database Migration Script
5
- * Runs Drizzle migrations for SQLite database
4
+ * Database Migration Script (Self-contained)
5
+ * Runs SQL migrations for SQLite database
6
6
  *
7
7
  * Usage:
8
- * node scripts/migrate.js <db_path> [migrations_path]
8
+ * node scripts/migrate.cjs <db_path> [migrations_path]
9
9
  *
10
10
  * Example:
11
- * node scripts/migrate.js /data/database.sqlite
12
- * node scripts/migrate.js /data/database.sqlite /app/packages/main/migrations
11
+ * node scripts/migrate.cjs /data/database.sqlite
12
+ * node scripts/migrate.cjs /data/database.sqlite /app/packages/main/migrations
13
13
  */
14
14
 
15
- const { SqliteDriver } = require('../dist/drivers/sqlite-driver.cjs');
15
+ const Database = require('better-sqlite3');
16
+ const fs = require('fs');
16
17
  const path = require('path');
17
18
 
18
19
  const dbPath = process.argv[2];
@@ -20,23 +21,84 @@ const migrationsPath = process.argv[3] || 'packages/main/migrations';
20
21
 
21
22
  if (!dbPath) {
22
23
  console.error('❌ Error: Database path is required');
23
- console.error('Usage: node scripts/migrate.js <db_path> [migrations_path]');
24
+ console.error('Usage: node scripts/migrate.cjs <db_path> [migrations_path]');
24
25
  process.exit(1);
25
26
  }
26
27
 
27
- async function runMigrations() {
28
+ function runMigrations() {
28
29
  try {
29
30
  console.log(`Initializing database at: ${dbPath}`);
30
- const driver = new SqliteDriver(dbPath);
31
+
32
+ // Create SQLite connection
33
+ const sqlite = new Database(dbPath);
31
34
 
32
35
  const absoluteMigrationsPath = path.isAbsolute(migrationsPath)
33
36
  ? migrationsPath
34
37
  : path.join(process.cwd(), migrationsPath);
35
38
 
36
39
  console.log(`Running migrations from: ${absoluteMigrationsPath}`);
37
- await driver.runMigrations(absoluteMigrationsPath);
38
40
 
39
- console.log('✅ Database migrations completed successfully');
41
+ // Create migrations tracking table if it doesn't exist
42
+ sqlite.exec(`
43
+ CREATE TABLE IF NOT EXISTS __applied_migrations (
44
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
45
+ name TEXT UNIQUE NOT NULL,
46
+ applied_at TEXT DEFAULT CURRENT_TIMESTAMP
47
+ )
48
+ `);
49
+
50
+ // Get list of already applied migrations
51
+ const appliedMigrations = new Set(
52
+ sqlite.prepare('SELECT name FROM __applied_migrations').all().map(row => row.name)
53
+ );
54
+
55
+ // Get all SQL migration files sorted by name
56
+ const migrationFiles = fs.readdirSync(absoluteMigrationsPath)
57
+ .filter(file => file.endsWith('.sql'))
58
+ .sort();
59
+
60
+ if (migrationFiles.length === 0) {
61
+ console.log('No migration files found.');
62
+ sqlite.close();
63
+ process.exit(0);
64
+ }
65
+
66
+ let appliedCount = 0;
67
+
68
+ for (const file of migrationFiles) {
69
+ if (appliedMigrations.has(file)) {
70
+ console.log(` ⏭️ ${file} (already applied)`);
71
+ continue;
72
+ }
73
+
74
+ const filePath = path.join(absoluteMigrationsPath, file);
75
+ const sql = fs.readFileSync(filePath, 'utf-8');
76
+
77
+ console.log(` 📄 Applying: ${file}`);
78
+
79
+ try {
80
+ // Run migration in a transaction
81
+ sqlite.exec('BEGIN TRANSACTION');
82
+ sqlite.exec(sql);
83
+ sqlite.prepare('INSERT INTO __applied_migrations (name) VALUES (?)').run(file);
84
+ sqlite.exec('COMMIT');
85
+ appliedCount++;
86
+ } catch (err) {
87
+ sqlite.exec('ROLLBACK');
88
+ // Check if error is due to already existing table/column (safe to ignore)
89
+ if (err.message.includes('already exists') || err.message.includes('duplicate column')) {
90
+ console.log(` ⚠️ ${file}: ${err.message} (continuing)`);
91
+ sqlite.prepare('INSERT OR IGNORE INTO __applied_migrations (name) VALUES (?)').run(file);
92
+ } else {
93
+ throw err;
94
+ }
95
+ }
96
+ }
97
+
98
+ // Close connection
99
+ sqlite.close();
100
+
101
+ console.log(`\n✅ Database migrations completed (${appliedCount} applied)`);
40
102
  process.exit(0);
41
103
  } catch (error) {
42
104
  console.error('❌ Migration failed:', error.message);
@@ -31,15 +31,14 @@ export class SqliteDriver implements DatabaseDriver {
31
31
  }
32
32
 
33
33
  /**
34
- * Check if database is initialized by checking for Drizzle's migrations table
35
- * This is database-agnostic as Drizzle uses __drizzle_migrations for all databases
34
+ * Check if database is initialized by checking for the migrations tracking table
36
35
  */
37
36
  async isInitialized(): Promise<boolean> {
38
37
  const { sqlite } = this.connection;
39
38
  try {
40
- // Check for Drizzle's migration tracking table (database-agnostic approach)
39
+ // Check for migration tracking table created by migrate.cjs script
41
40
  const result = sqlite.prepare(
42
- "SELECT name FROM sqlite_master WHERE type='table' AND name='__drizzle_migrations'"
41
+ "SELECT name FROM sqlite_master WHERE type='table' AND name='__applied_migrations'"
43
42
  ).get();
44
43
  return !!result;
45
44
  } catch {
package/src/index.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { serve } from '@hono/node-server';
2
- import { Hono } from 'hono';
3
- import { createApp, type AppInstance, type DatabaseDriver } from '@package-broker/core';
2
+ import { createApp } from '@package-broker/core';
4
3
  import { config } from 'dotenv';
5
4
  import { SqliteDriver } from './drivers/sqlite-driver.js';
6
5
  import { FileSystemDriver } from './drivers/fs-driver.js';
@@ -9,7 +8,7 @@ import { MemoryCacheDriver, MemoryQueueDriver } from '@package-broker/core';
9
8
  import path from 'node:path';
10
9
  import { serveStatic } from '@hono/node-server/serve-static';
11
10
  import { readFile } from 'node:fs/promises';
12
- import type { Context, Next } from 'hono';
11
+ import type { Context } from 'hono';
13
12
 
14
13
  // Load environment variables
15
14
  config();
@@ -23,28 +22,13 @@ const STORAGE_PATH = process.env.STORAGE_PATH || './storage';
23
22
  const CACHE_DRIVER = process.env.CACHE_DRIVER || 'memory';
24
23
  const CACHE_URL = process.env.CACHE_URL || 'redis://localhost:6379';
25
24
  const QUEUE_DRIVER = process.env.QUEUE_DRIVER || 'memory';
26
- const MIGRATIONS_PATH = process.env.MIGRATIONS_PATH || 'packages/main/migrations';
27
-
28
- const DOCS_URL = 'https://package-broker.github.io/docs/installation/docker';
29
25
 
30
26
  console.log('Starting PACKAGE.broker Node Adapter...');
31
27
  console.log(`Configuration: DB=${DB_DRIVER}, STORAGE=${STORAGE_DRIVER}, CACHE=${CACHE_DRIVER}, QUEUE=${QUEUE_DRIVER}`);
32
28
 
33
- /**
34
- * Get the database not initialized error response
35
- * DRY: Single source of truth for error response structure
36
- */
37
- function getDatabaseNotInitializedError() {
38
- return {
39
- error: 'DATABASE_NOT_INITIALIZED',
40
- message: 'Database not initialized. Please run migrations.',
41
- docsUrl: DOCS_URL
42
- };
43
- }
44
-
45
29
  async function start() {
46
- // Initialize Database Driver (Port-Adapter pattern)
47
- let databaseDriver: DatabaseDriver;
30
+ // Initialize Database Driver
31
+ let databaseDriver;
48
32
 
49
33
  if (DB_DRIVER === 'sqlite') {
50
34
  console.log(`Initializing SQLite at ${DB_URL}`);
@@ -53,31 +37,31 @@ async function start() {
53
37
  throw new Error(`Unsupported DB_DRIVER: ${DB_DRIVER}`);
54
38
  }
55
39
 
56
- // Check if database needs initialization (database-agnostic check)
57
- const isDatabaseReady = await databaseDriver.isInitialized();
58
-
59
- if (!isDatabaseReady) {
40
+ // Check if database is initialized (logging only)
41
+ const isInitialized = await databaseDriver.isInitialized();
42
+ if (!isInitialized) {
60
43
  console.warn('');
61
44
  console.warn('⚠️ DATABASE NOT INITIALIZED');
62
- console.warn(' The database tables have not been created yet.');
63
- console.warn(' A warning page will be shown to users until migrations are run.');
64
- console.warn(` 📖 Documentation: ${DOCS_URL}`);
45
+ console.warn(' Run migration scripts to initialize the database:');
46
+ console.warn('');
47
+ console.warn(' docker exec <container> node packages/adapter-node/scripts/migrate.cjs /data/database.sqlite');
48
+ console.warn('');
49
+ console.warn(' 📖 See: https://package.broker/docs/getting-started/quickstart-docker');
65
50
  console.warn('');
66
- } else {
67
- console.log('Database initialized and ready.');
68
51
  }
69
52
 
70
53
  const database = databaseDriver.getConnection();
71
54
 
55
+ // Initialize Storage
72
56
  let storage;
73
57
  if (STORAGE_DRIVER === 'fs') {
74
58
  console.log(`Initializing FS Storage at ${STORAGE_PATH}`);
75
59
  storage = new FileSystemDriver(STORAGE_PATH);
76
60
  } else {
77
- // TODO: Add S3 support
78
61
  throw new Error(`Unsupported STORAGE_DRIVER: ${STORAGE_DRIVER}`);
79
62
  }
80
63
 
64
+ // Initialize Cache
81
65
  let cache;
82
66
  if (CACHE_DRIVER === 'redis') {
83
67
  console.log(`Initializing Redis Cache at ${CACHE_URL}`);
@@ -87,10 +71,11 @@ async function start() {
87
71
  cache = new MemoryCacheDriver();
88
72
  }
89
73
 
74
+ // Initialize Queue
90
75
  let queue;
91
76
  if (QUEUE_DRIVER === 'redis') {
92
77
  if (CACHE_DRIVER === 'redis') {
93
- queue = cache as any; // RedisDriver implements both
78
+ queue = cache as any;
94
79
  } else {
95
80
  console.log(`Initializing Redis Queue at ${CACHE_URL}`);
96
81
  queue = new RedisDriver(CACHE_URL);
@@ -100,65 +85,26 @@ async function start() {
100
85
  queue = new MemoryQueueDriver();
101
86
  }
102
87
 
103
- // If database is not ready, return error for API requests
104
- if (!isDatabaseReady) {
105
- const warningApp = new Hono();
106
- const errorResponse = getDatabaseNotInitializedError();
107
-
108
- // Health endpoint returns warning status (200 OK with error status in body for CI compatibility)
109
- warningApp.get('/health', (c) => {
110
- return c.json({
111
- status: 'error',
112
- ...errorResponse
113
- }, 200); // Return 200 for CI health checks, but include error status
114
- });
115
-
116
- // All API requests return JSON error (BEFORE static files)
117
- warningApp.all('/api/*', (c) => {
118
- return c.json(errorResponse, 503);
119
- });
120
-
121
- // Serve static files if PUBLIC_DIR is set (UI will display the error)
122
- if (process.env.PUBLIC_DIR) {
123
- warningApp.use('/*', serveStatic({ root: process.env.PUBLIC_DIR }));
124
- warningApp.get('*', async (c) => {
125
- try {
126
- return c.html(await readFile(path.join(process.env.PUBLIC_DIR!, 'index.html'), 'utf-8'));
127
- } catch (e) {
128
- return c.text('Not Found', 404);
129
- }
130
- });
131
- }
132
-
133
- console.log(`Server listening on port ${PORT} (WARNING: Database not initialized)`);
134
- serve({
135
- fetch: warningApp.fetch,
136
- port: PORT
137
- });
138
- return;
139
- }
140
-
141
- // Create App (API routes are registered inside createApp)
88
+ // Create App
142
89
  const app = createApp({
143
90
  database,
144
91
  storage,
145
92
  cache,
146
93
  });
147
94
 
148
- // Serve config.js dynamically (AFTER API routes)
95
+ // Serve config.js dynamically
149
96
  app.get('/config.js', (c: Context) => {
150
97
  return c.text(`window.env = { API_URL: "${process.env.API_URL || '/'}" };`, 200, {
151
98
  'Content-Type': 'application/javascript',
152
99
  });
153
100
  });
154
101
 
155
- // Static file serving and SPA fallback (MUST be AFTER API routes)
102
+ // Static file serving and SPA fallback
156
103
  if (process.env.PUBLIC_DIR) {
157
104
  console.log(`Serving static files from ${process.env.PUBLIC_DIR}`);
158
105
  app.use('/*', serveStatic({ root: process.env.PUBLIC_DIR }));
159
106
 
160
- // SPA Fallback for client-side routing
161
- // This catches any routes that weren't handled by API routes or static files
107
+ // SPA Fallback
162
108
  app.get('*', async (c: Context) => {
163
109
  try {
164
110
  return c.html(await readFile(path.join(process.env.PUBLIC_DIR!, 'index.html'), 'utf-8'));