arckode-framework 1.0.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 +546 -0
- package/adapters/__tests__/mysql.test.ts +283 -0
- package/adapters/jwt.ts +18 -0
- package/adapters/mysql.ts +98 -0
- package/adapters/postgres.ts +52 -0
- package/adapters/redis-cache.ts +64 -0
- package/adapters/sqlite.ts +73 -0
- package/adapters/vendor.d.ts +48 -0
- package/bin/arckode.js +7 -0
- package/cli/analyze.ts +506 -0
- package/cli/commands/db-migrate.ts +121 -0
- package/cli/commands/db-seed.ts +54 -0
- package/cli/commands/generate-api-client.ts +106 -0
- package/cli/commands/make-adapter.ts +132 -0
- package/cli/commands/make-auth.ts +297 -0
- package/cli/commands/make-frontend-module.ts +271 -0
- package/cli/commands/make-helper.ts +65 -0
- package/cli/commands/make-migration.ts +30 -0
- package/cli/commands/make-seed.ts +29 -0
- package/cli/generate.ts +132 -0
- package/cli/index.ts +604 -0
- package/cli/stubs/frontend-stub.ts +294 -0
- package/cli/stubs/fullstack-stub.ts +46 -0
- package/cli/stubs/module-stub.ts +469 -0
- package/kernel/__tests__/adapters.test.ts +101 -0
- package/kernel/__tests__/analyzer.test.ts +282 -0
- package/kernel/__tests__/framework.test.ts +617 -0
- package/kernel/__tests__/middlewares.test.ts +174 -0
- package/kernel/__tests__/static.test.ts +94 -0
- package/kernel/framework.ts +1851 -0
- package/kernel/middlewares.ts +179 -0
- package/kernel/static.ts +76 -0
- package/kernel/testing.ts +237 -0
- package/modules/events/index.ts +99 -0
- package/modules/mail/index.ts +51 -0
- package/modules/mail/smtp-adapter.ts +42 -0
- package/modules/queue/index.ts +78 -0
- package/modules/storage/index.ts +40 -0
- package/modules/storage/local-adapter.ts +41 -0
- package/modules/ws/__tests__/ws.test.ts +114 -0
- package/modules/ws/index.ts +136 -0
- package/package.json +99 -0
- package/skills/auth/SKILL.md +243 -0
- package/skills/cli/SKILL.md +258 -0
- package/skills/config/SKILL.md +253 -0
- package/skills/connectors/SKILL.md +259 -0
- package/skills/helpers/SKILL.md +206 -0
- package/skills/middlewares/SKILL.md +282 -0
- package/skills/orm/SKILL.md +260 -0
- package/skills/realtime/SKILL.md +307 -0
- package/skills/services/SKILL.md +206 -0
- package/skills/testing/SKILL.md +257 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// modules/queue/index.ts — Módulo de colas / jobs
|
|
2
|
+
// Procesa tareas en background. Adapter intercambiable (in-process, Redis, DB).
|
|
3
|
+
|
|
4
|
+
export interface Job {
|
|
5
|
+
id: string
|
|
6
|
+
name: string
|
|
7
|
+
data: unknown
|
|
8
|
+
attempts: number
|
|
9
|
+
maxAttempts: number
|
|
10
|
+
createdAt: string
|
|
11
|
+
error?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type JobHandler = (job: Job) => Promise<void>
|
|
15
|
+
|
|
16
|
+
export interface QueueAdapter {
|
|
17
|
+
push(name: string, data: unknown, opts?: { delay?: number; maxAttempts?: number }): Promise<string>
|
|
18
|
+
process(name: string, handler: JobHandler): void
|
|
19
|
+
onFailed?(name: string, job: Job, error: Error): void
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class QueueService {
|
|
23
|
+
private handlers = new Map<string, JobHandler>()
|
|
24
|
+
|
|
25
|
+
constructor(private adapter: QueueAdapter) {}
|
|
26
|
+
|
|
27
|
+
async dispatch(name: string, data: unknown, opts?: { delay?: number; maxAttempts?: number }): Promise<string> {
|
|
28
|
+
return this.adapter.push(name, data, opts)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
register(name: string, handler: JobHandler): void {
|
|
32
|
+
this.handlers.set(name, handler)
|
|
33
|
+
this.adapter.process(name, handler)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ─── Adapter en memoria (para desarrollo) ───
|
|
38
|
+
export class MemoryQueueAdapter implements QueueAdapter {
|
|
39
|
+
private handlers = new Map<string, JobHandler>()
|
|
40
|
+
private jobs: Job[] = []
|
|
41
|
+
|
|
42
|
+
async push(name: string, data: unknown, opts?: { delay?: number; maxAttempts?: number }): Promise<string> {
|
|
43
|
+
const job: Job = {
|
|
44
|
+
id: crypto.randomUUID(),
|
|
45
|
+
name,
|
|
46
|
+
data,
|
|
47
|
+
attempts: 0,
|
|
48
|
+
maxAttempts: opts?.maxAttempts ?? 3,
|
|
49
|
+
createdAt: new Date().toISOString(),
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const execute = async () => {
|
|
53
|
+
const handler = this.handlers.get(name)
|
|
54
|
+
if (!handler) return
|
|
55
|
+
try {
|
|
56
|
+
job.attempts++
|
|
57
|
+
await handler(job)
|
|
58
|
+
} catch (error) {
|
|
59
|
+
job.error = String(error)
|
|
60
|
+
if (job.attempts < job.maxAttempts) {
|
|
61
|
+
setTimeout(execute, 1000 * job.attempts)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (opts?.delay) {
|
|
67
|
+
setTimeout(execute, opts.delay)
|
|
68
|
+
} else {
|
|
69
|
+
process.nextTick(execute)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return job.id
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
process(name: string, handler: JobHandler): void {
|
|
76
|
+
this.handlers.set(name, handler)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// modules/storage/index.ts — Módulo de almacenamiento
|
|
2
|
+
// Sube y sirve archivos via adapter (local disk, S3, etc.)
|
|
3
|
+
|
|
4
|
+
export interface FileUpload {
|
|
5
|
+
fieldName: string
|
|
6
|
+
originalName: string
|
|
7
|
+
buffer: Buffer
|
|
8
|
+
mimeType: string
|
|
9
|
+
size: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface StoredFile {
|
|
13
|
+
url: string
|
|
14
|
+
path: string
|
|
15
|
+
originalName: string
|
|
16
|
+
mimeType: string
|
|
17
|
+
size: number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface StorageAdapter {
|
|
21
|
+
upload(file: FileUpload, directory?: string): Promise<StoredFile>
|
|
22
|
+
delete(path: string): Promise<void>
|
|
23
|
+
getUrl(path: string): string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class StorageService {
|
|
27
|
+
constructor(private adapter: StorageAdapter) {}
|
|
28
|
+
|
|
29
|
+
async upload(file: FileUpload, directory?: string): Promise<StoredFile> {
|
|
30
|
+
return this.adapter.upload(file, directory)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async delete(path: string): Promise<void> {
|
|
34
|
+
return this.adapter.delete(path)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
getUrl(path: string): string {
|
|
38
|
+
return this.adapter.getUrl(path)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// modules/storage/local-adapter.ts — Adapter de disco local
|
|
2
|
+
// Almacena archivos en el sistema de archivos local
|
|
3
|
+
|
|
4
|
+
import { mkdir, writeFile, unlink } from 'node:fs/promises'
|
|
5
|
+
import { join, extname } from 'node:path'
|
|
6
|
+
import type { StorageAdapter, FileUpload, StoredFile } from './index'
|
|
7
|
+
|
|
8
|
+
export class LocalStorageAdapter implements StorageAdapter {
|
|
9
|
+
constructor(
|
|
10
|
+
private basePath: string = './uploads',
|
|
11
|
+
private baseUrl: string = '/uploads',
|
|
12
|
+
) {}
|
|
13
|
+
|
|
14
|
+
async upload(file: FileUpload, directory?: string): Promise<StoredFile> {
|
|
15
|
+
const dir = directory ?? 'general'
|
|
16
|
+
const ext = extname(file.originalName)
|
|
17
|
+
const filename = `${Date.now()}-${Math.random().toString(36).slice(2)}${ext}`
|
|
18
|
+
const relativePath = `${dir}/${filename}`
|
|
19
|
+
const fullPath = join(this.basePath, relativePath)
|
|
20
|
+
|
|
21
|
+
await mkdir(join(this.basePath, dir), { recursive: true })
|
|
22
|
+
await writeFile(fullPath, file.buffer)
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
url: `${this.baseUrl}/${relativePath}`,
|
|
26
|
+
path: relativePath,
|
|
27
|
+
originalName: file.originalName,
|
|
28
|
+
mimeType: file.mimeType,
|
|
29
|
+
size: file.size,
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async delete(path: string): Promise<void> {
|
|
34
|
+
const fullPath = join(this.basePath, path)
|
|
35
|
+
await unlink(fullPath).catch(() => {})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
getUrl(path: string): string {
|
|
39
|
+
return `${this.baseUrl}/${path}`
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// modules/ws/__tests__/ws.test.ts — Tests del WebSocket server
|
|
2
|
+
// Prueba: handshake, parseFrame, sendFrame, broadcast, sendTo
|
|
3
|
+
|
|
4
|
+
import { describe, it, expect } from 'bun:test'
|
|
5
|
+
import { WSServer } from '../index'
|
|
6
|
+
import { createHash } from 'node:crypto'
|
|
7
|
+
|
|
8
|
+
// ─── Test de generateAccept ────────────────────────────
|
|
9
|
+
describe('WSServer - handshake', () => {
|
|
10
|
+
it('generateAccept produce output correcto', () => {
|
|
11
|
+
// El WebSocket server tiene método generateAccept
|
|
12
|
+
// Probamos que devuelve un string base64 válido
|
|
13
|
+
const ws = new WSServer() as any
|
|
14
|
+
const key = 'dGhlIHNhbXBsZSBub25jZQ=='
|
|
15
|
+
const accept = ws.generateAccept(key)
|
|
16
|
+
expect(accept).toBeTruthy()
|
|
17
|
+
expect(typeof accept).toBe('string')
|
|
18
|
+
expect(accept.length).toBeGreaterThan(0)
|
|
19
|
+
|
|
20
|
+
// Verificar que el hash es correcto según RFC 6455
|
|
21
|
+
const expected = createHash('sha1').update(key + '258EAFA5-E914-47DA-95CA-5AB5DC11B725').digest('base64')
|
|
22
|
+
expect(accept).toBe(expected)
|
|
23
|
+
})
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
// ─── Test de parseFrame ────────────────────────────────
|
|
27
|
+
describe('WSServer - parseFrame', () => {
|
|
28
|
+
it('parsea un frame de texto sin mask', () => {
|
|
29
|
+
const ws = new WSServer() as any
|
|
30
|
+
// Frame: FIN=1, opcode=1 (text), payload=Hola
|
|
31
|
+
const frame = Buffer.alloc(6)
|
|
32
|
+
frame[0] = 0x81 // FIN + texto
|
|
33
|
+
frame[1] = 4 // payload length
|
|
34
|
+
frame.write('Hola', 2) // payload
|
|
35
|
+
const result = ws.parseFrame(frame)
|
|
36
|
+
expect(result).toBe('Hola')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('parsea un frame con mask', () => {
|
|
40
|
+
const ws = new WSServer() as any
|
|
41
|
+
const payload = Buffer.from('Hello')
|
|
42
|
+
const mask = Buffer.from([0x01, 0x02, 0x03, 0x04])
|
|
43
|
+
const masked = Buffer.alloc(payload.length)
|
|
44
|
+
for (let i = 0; i < payload.length; i++) {
|
|
45
|
+
masked[i] = (payload[i] ?? 0) ^ (mask[i % 4] ?? 0)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const frame = Buffer.alloc(2 + 4 + payload.length)
|
|
49
|
+
frame[0] = 0x81
|
|
50
|
+
frame[1] = 0x80 | payload.length // MASK bit + length
|
|
51
|
+
mask.copy(frame, 2)
|
|
52
|
+
masked.copy(frame, 6)
|
|
53
|
+
|
|
54
|
+
const result = ws.parseFrame(frame)
|
|
55
|
+
expect(result).toBe('Hello')
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('devuelve null para frames no-texto', () => {
|
|
59
|
+
const ws = new WSServer() as any
|
|
60
|
+
// Frame: ping (opcode 0x09)
|
|
61
|
+
const frame = Buffer.alloc(2)
|
|
62
|
+
frame[0] = 0x89
|
|
63
|
+
frame[1] = 0
|
|
64
|
+
const result = ws.parseFrame(frame)
|
|
65
|
+
expect(result).toBeNull()
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
// ─── Test de sendFrame ────────────────────────────────
|
|
70
|
+
describe('WSServer - sendFrame', () => {
|
|
71
|
+
it('construye frame correctamente', () => {
|
|
72
|
+
const ws = new WSServer() as any
|
|
73
|
+
const chunks: Buffer[] = []
|
|
74
|
+
const mockSocket = { write: (b: Buffer) => chunks.push(b) }
|
|
75
|
+
|
|
76
|
+
ws.sendFrame(mockSocket, 'Hola')
|
|
77
|
+
expect(chunks).toHaveLength(1)
|
|
78
|
+
const frame = chunks[0]!
|
|
79
|
+
expect(frame[0]).toBe(0x81) // FIN + texto
|
|
80
|
+
expect(frame[1]).toBe(4) // longitud
|
|
81
|
+
expect(frame.slice(2).toString()).toBe('Hola')
|
|
82
|
+
})
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
// ─── Test de broadcast ────────────────────────────────
|
|
86
|
+
describe('WSServer - broadcast', () => {
|
|
87
|
+
it('envía a todos los clientes conectados', () => {
|
|
88
|
+
const ws = new WSServer() as any
|
|
89
|
+
const received: string[] = []
|
|
90
|
+
|
|
91
|
+
// Simular clientes conectados
|
|
92
|
+
ws.clients.set('c1', { id: 'c1', send: (d: string) => received.push(d), close: () => {} })
|
|
93
|
+
ws.clients.set('c2', { id: 'c2', send: (d: string) => received.push(d), close: () => {} })
|
|
94
|
+
|
|
95
|
+
ws.broadcast('test.event', { msg: 'hello' })
|
|
96
|
+
expect(received).toHaveLength(2)
|
|
97
|
+
expect(received[0]).toContain('test.event')
|
|
98
|
+
expect(received[1]).toContain('hello')
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
// ─── Test de sendTo ────────────────────────────────────
|
|
103
|
+
describe('WSServer - sendTo', () => {
|
|
104
|
+
it('envía a un cliente específico', () => {
|
|
105
|
+
const ws = new WSServer() as any
|
|
106
|
+
const received: string[] = []
|
|
107
|
+
|
|
108
|
+
ws.clients.set('target', { id: 'target', send: (d: string) => received.push(d), close: () => {} })
|
|
109
|
+
ws.clients.set('other', { id: 'other', send: (d: string) => received.push(d), close: () => {} })
|
|
110
|
+
|
|
111
|
+
ws.sendTo('target', 'private.event', { secret: true })
|
|
112
|
+
expect(received).toHaveLength(1)
|
|
113
|
+
})
|
|
114
|
+
})
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// modules/ws/index.ts — WebSocket server (tiempo real)
|
|
2
|
+
// Comunicación bidireccional entre servidor y clientes
|
|
3
|
+
// Uso: WS en backend, EventSource o WS nativo en frontend
|
|
4
|
+
|
|
5
|
+
import type { Server as HttpServer } from 'node:http'
|
|
6
|
+
import { createHash } from 'node:crypto'
|
|
7
|
+
|
|
8
|
+
export interface WSClient {
|
|
9
|
+
id: string
|
|
10
|
+
send(data: unknown): void
|
|
11
|
+
close(): void
|
|
12
|
+
onMessage(data: unknown): void
|
|
13
|
+
onClose(): void
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface WSAdapter {
|
|
17
|
+
broadcast(event: string, data: unknown): void
|
|
18
|
+
sendTo(clientId: string, event: string, data: unknown): void
|
|
19
|
+
getClients(): WSClient[]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ─── WebSocket nativo (sin dependencias externas) ───
|
|
23
|
+
// Usa el servidor HTTP de Node. Para producción usar `ws` o `uWebSockets`.
|
|
24
|
+
|
|
25
|
+
export class WSServer implements WSAdapter {
|
|
26
|
+
private clients = new Map<string, WSClient>()
|
|
27
|
+
|
|
28
|
+
attach(server: HttpServer): void {
|
|
29
|
+
server.on('upgrade', (req, socket, head) => {
|
|
30
|
+
// Implementación básica de WebSocket handshake
|
|
31
|
+
const key = req.headers['sec-websocket-key']
|
|
32
|
+
if (!key) { socket.destroy(); return }
|
|
33
|
+
|
|
34
|
+
const accept = this.generateAccept(key)
|
|
35
|
+
socket.write([
|
|
36
|
+
'HTTP/1.1 101 Switching Protocols',
|
|
37
|
+
'Upgrade: websocket',
|
|
38
|
+
'Connection: Upgrade',
|
|
39
|
+
`Sec-WebSocket-Accept: ${accept}`,
|
|
40
|
+
'',
|
|
41
|
+
'',
|
|
42
|
+
].join('\r\n'))
|
|
43
|
+
|
|
44
|
+
const clientId = crypto.randomUUID()
|
|
45
|
+
const client: WSClient = {
|
|
46
|
+
id: clientId,
|
|
47
|
+
send: (data) => this.sendFrame(socket, JSON.stringify(data)),
|
|
48
|
+
close: () => socket.end(),
|
|
49
|
+
onMessage: () => {},
|
|
50
|
+
onClose: () => {},
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
this.clients.set(clientId, client)
|
|
54
|
+
console.log(`[WS] Client connected: ${clientId}`)
|
|
55
|
+
|
|
56
|
+
socket.on('data', (buffer) => {
|
|
57
|
+
const message = this.parseFrame(buffer)
|
|
58
|
+
if (message) client.onMessage(message)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
socket.on('close', () => {
|
|
62
|
+
this.clients.delete(clientId)
|
|
63
|
+
client.onClose()
|
|
64
|
+
console.log(`[WS] Client disconnected: ${clientId}`)
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
broadcast(event: string, data: unknown): void {
|
|
70
|
+
const message = JSON.stringify({ event, data })
|
|
71
|
+
for (const client of this.clients.values()) {
|
|
72
|
+
try { client.send(message) } catch { /* ignore */ }
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
sendTo(clientId: string, event: string, data: unknown): void {
|
|
77
|
+
const client = this.clients.get(clientId)
|
|
78
|
+
if (client) {
|
|
79
|
+
client.send(JSON.stringify({ event, data }))
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
getClients(): WSClient[] {
|
|
84
|
+
return [...this.clients.values()]
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private generateAccept(key: string): string {
|
|
88
|
+
const GUID = '258EAFA5-E914-47DA-95CA-5AB5DC11B725'
|
|
89
|
+
return createHash('sha1').update(key + GUID).digest('base64')
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private sendFrame(socket: any, data: string): void {
|
|
93
|
+
const buffer = Buffer.from(data, 'utf-8')
|
|
94
|
+
const frame = Buffer.alloc(2 + buffer.length)
|
|
95
|
+
frame[0] = 0x81 // FIN + texto
|
|
96
|
+
frame[1] = buffer.length
|
|
97
|
+
buffer.copy(frame, 2)
|
|
98
|
+
socket.write(frame)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private parseFrame(buffer: Buffer): string | null {
|
|
102
|
+
if (buffer.length < 2) return null
|
|
103
|
+
const opcode = (buffer[0] ?? 0) & 0x0F
|
|
104
|
+
if (opcode !== 0x01) return null // solo texto
|
|
105
|
+
const masked = ((buffer[1] ?? 0) & 0x80) !== 0
|
|
106
|
+
let payloadLength = (buffer[1] ?? 0) & 0x7F
|
|
107
|
+
let offset = 2
|
|
108
|
+
|
|
109
|
+
if (payloadLength === 126) {
|
|
110
|
+
payloadLength = buffer.readUInt16BE(2)
|
|
111
|
+
offset = 4
|
|
112
|
+
} else if (payloadLength === 127) {
|
|
113
|
+
payloadLength = Number(buffer.readBigUInt64BE(2))
|
|
114
|
+
offset = 10
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (masked) {
|
|
118
|
+
const mask = buffer.slice(offset, offset + 4)
|
|
119
|
+
offset += 4
|
|
120
|
+
const payload = Buffer.alloc(payloadLength)
|
|
121
|
+
for (let i = 0; i < payloadLength; i++) {
|
|
122
|
+
payload[i] = (buffer[offset + i] ?? 0) ^ (mask[i % 4] ?? 0)
|
|
123
|
+
}
|
|
124
|
+
return payload.toString('utf-8')
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return buffer.slice(offset, offset + payloadLength).toString('utf-8')
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ─── Uso en composition root ───────────────────────────
|
|
132
|
+
// import { WSServer } from 'arckode-framework/modules/ws'
|
|
133
|
+
// const ws = new WSServer()
|
|
134
|
+
// ws.attach(http.server)
|
|
135
|
+
//
|
|
136
|
+
// ws.broadcast('pedido.nuevo', { id: '123' })
|
package/package.json
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "arckode-framework",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "AI-first TypeScript/Bun framework. Modular, SOLID, zero magic. The AI reads the composition root and knows everything.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./kernel/framework.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./kernel/framework.ts",
|
|
9
|
+
"./middlewares": "./kernel/middlewares.ts",
|
|
10
|
+
"./static": "./kernel/static.ts",
|
|
11
|
+
"./events": "./modules/events/index.ts",
|
|
12
|
+
"./ws": "./modules/ws/index.ts",
|
|
13
|
+
"./adapters/sqlite": "./adapters/sqlite.ts",
|
|
14
|
+
"./adapters/jwt": "./adapters/jwt.ts",
|
|
15
|
+
"./adapters/postgres": "./adapters/postgres.ts",
|
|
16
|
+
"./adapters/redis-cache": "./adapters/redis-cache.ts",
|
|
17
|
+
"./adapters/mysql": "./adapters/mysql.ts",
|
|
18
|
+
"./modules/mail": "./modules/mail/index.ts",
|
|
19
|
+
"./modules/storage": "./modules/storage/index.ts",
|
|
20
|
+
"./modules/queue": "./modules/queue/index.ts",
|
|
21
|
+
"./testing": "./kernel/testing.ts"
|
|
22
|
+
},
|
|
23
|
+
"bin": {
|
|
24
|
+
"arckode": "bin/arckode.js"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"bin/",
|
|
28
|
+
"kernel/",
|
|
29
|
+
"adapters/",
|
|
30
|
+
"modules/",
|
|
31
|
+
"cli/",
|
|
32
|
+
"skills/",
|
|
33
|
+
"README.md"
|
|
34
|
+
],
|
|
35
|
+
"engines": {
|
|
36
|
+
"bun": ">=1.0.0"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"arckode": "bun run cli/index.ts",
|
|
40
|
+
"test": "bun test",
|
|
41
|
+
"test:watch": "bun test --watch",
|
|
42
|
+
"test:coverage": "bun test --coverage",
|
|
43
|
+
"typecheck": "bunx tsc --noEmit",
|
|
44
|
+
"analyze:framework": "bun run cli/index.ts analyze"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"jsonwebtoken": "^9.0.0"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"better-sqlite3": ">=11.0.0",
|
|
51
|
+
"mysql2": ">=3.0.0",
|
|
52
|
+
"pg": ">=8.0.0",
|
|
53
|
+
"redis": ">=4.0.0",
|
|
54
|
+
"nodemailer": ">=6.0.0"
|
|
55
|
+
},
|
|
56
|
+
"peerDependenciesMeta": {
|
|
57
|
+
"better-sqlite3": {
|
|
58
|
+
"optional": true
|
|
59
|
+
},
|
|
60
|
+
"mysql2": {
|
|
61
|
+
"optional": true
|
|
62
|
+
},
|
|
63
|
+
"pg": {
|
|
64
|
+
"optional": true
|
|
65
|
+
},
|
|
66
|
+
"redis": {
|
|
67
|
+
"optional": true
|
|
68
|
+
},
|
|
69
|
+
"nodemailer": {
|
|
70
|
+
"optional": true
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"devDependencies": {
|
|
74
|
+
"@types/better-sqlite3": "^7.6.12",
|
|
75
|
+
"@types/jsonwebtoken": "^9.0.7",
|
|
76
|
+
"@types/nodemailer": "^6.4.0",
|
|
77
|
+
"@types/pg": "^8.11.0",
|
|
78
|
+
"bun-types": "^1.3.14",
|
|
79
|
+
"typescript": "^5.6.0"
|
|
80
|
+
},
|
|
81
|
+
"keywords": [
|
|
82
|
+
"framework",
|
|
83
|
+
"modular",
|
|
84
|
+
"SOLID",
|
|
85
|
+
"ai",
|
|
86
|
+
"bun",
|
|
87
|
+
"typescript",
|
|
88
|
+
"clean-architecture",
|
|
89
|
+
"composition-root",
|
|
90
|
+
"api",
|
|
91
|
+
"arckode"
|
|
92
|
+
],
|
|
93
|
+
"license": "MIT",
|
|
94
|
+
"repository": {
|
|
95
|
+
"type": "git",
|
|
96
|
+
"url": "git+https://gitlab.com/underworf/nexus-framework.git"
|
|
97
|
+
},
|
|
98
|
+
"homepage": "https://gitlab.com/underworf/nexus-framework#readme"
|
|
99
|
+
}
|