aiplang 2.11.3 → 2.11.4
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/bin/aiplang.js +8 -2
- package/package.json +7 -1
- package/server/server.js +201 -15
package/bin/aiplang.js
CHANGED
|
@@ -5,7 +5,7 @@ const fs = require('fs')
|
|
|
5
5
|
const path = require('path')
|
|
6
6
|
const http = require('http')
|
|
7
7
|
|
|
8
|
-
const VERSION = '2.11.
|
|
8
|
+
const VERSION = '2.11.4'
|
|
9
9
|
const RUNTIME_DIR = path.join(__dirname, '..', 'runtime')
|
|
10
10
|
const cmd = process.argv[2]
|
|
11
11
|
const args = process.argv.slice(3)
|
|
@@ -60,6 +60,12 @@ if (!cmd||cmd==='--help'||cmd==='-h') {
|
|
|
60
60
|
{{year}} current year
|
|
61
61
|
|
|
62
62
|
Customization:
|
|
63
|
+
# Bancos de dados suportados:
|
|
64
|
+
# ~db sqlite ./app.db (padrão — sem configuração)
|
|
65
|
+
# ~db pg $DATABASE_URL (PostgreSQL)
|
|
66
|
+
# ~db mysql $MYSQL_URL (MySQL / MariaDB)
|
|
67
|
+
# ~db mongodb $MONGODB_URL (MongoDB)
|
|
68
|
+
# ~db redis $REDIS_URL (Redis — cache/session)
|
|
63
69
|
~theme accent=#7c3aed radius=1.5rem font=Syne bg=#000 text=#fff
|
|
64
70
|
hero{...} animate:fade-up
|
|
65
71
|
row3{...} class:my-class animate:stagger
|
|
@@ -680,7 +686,7 @@ function generateTypes(app, srcFile) {
|
|
|
680
686
|
}
|
|
681
687
|
|
|
682
688
|
lines.push(`// ── aiplang version ──────────────────────────────────────────`)
|
|
683
|
-
lines.push(`export const AIPLANG_VERSION = '2.11.
|
|
689
|
+
lines.push(`export const AIPLANG_VERSION = '2.11.4'`)
|
|
684
690
|
lines.push(``)
|
|
685
691
|
return lines.join('\n')
|
|
686
692
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aiplang",
|
|
3
|
-
"version": "2.11.
|
|
3
|
+
"version": "2.11.4",
|
|
4
4
|
"description": "AI-first web language. One .aip file = complete app. Frontend + backend + database + auth.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"aiplang",
|
|
@@ -43,12 +43,18 @@
|
|
|
43
43
|
"@sqlite.org/sqlite-wasm": "^3.51.2-build8",
|
|
44
44
|
"bcryptjs": "^2.4.3",
|
|
45
45
|
"better-sqlite3": "^12.8.0",
|
|
46
|
+
"ioredis": "^5.3.2",
|
|
46
47
|
"jsonwebtoken": "^9.0.2",
|
|
48
|
+
"mongodb": "^6.5.0",
|
|
49
|
+
"mysql2": "^3.9.0",
|
|
47
50
|
"nodemailer": "^8.0.3",
|
|
48
51
|
"pg": "^8.11.0",
|
|
49
52
|
"sql.js": "^1.10.3",
|
|
50
53
|
"stripe": "^14.0.0",
|
|
51
54
|
"uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.44.0",
|
|
52
55
|
"ws": "^8.16.0"
|
|
56
|
+
},
|
|
57
|
+
"optionalDependencies": {
|
|
58
|
+
"better-sqlite3": "^9.4.0"
|
|
53
59
|
}
|
|
54
60
|
}
|
package/server/server.js
CHANGED
|
@@ -32,6 +32,12 @@ let SQL, DB_FILE, _db = null
|
|
|
32
32
|
let _pgPool = null // PostgreSQL connection pool
|
|
33
33
|
let _dbDriver = 'sqlite' // 'sqlite' | 'postgres'
|
|
34
34
|
let _useBetter = false // true when better-sqlite3 is available
|
|
35
|
+
let _mysqlPool = null // MySQL/MariaDB (mysql2)
|
|
36
|
+
let _mongoClient= null // MongoDB client
|
|
37
|
+
let _mongoDB = null // MongoDB database
|
|
38
|
+
let _redisClient= null // Redis (ioredis)
|
|
39
|
+
let _useMongo = false
|
|
40
|
+
let _useRedis = false
|
|
35
41
|
|
|
36
42
|
async function getDB(dbConfig = { driver: 'sqlite', dsn: ':memory:' }) {
|
|
37
43
|
if (_db || _pgPool) return _db || _pgPool
|
|
@@ -39,16 +45,72 @@ async function getDB(dbConfig = { driver: 'sqlite', dsn: ':memory:' }) {
|
|
|
39
45
|
const dsn = dbConfig.dsn || ':memory:'
|
|
40
46
|
_dbDriver = driver
|
|
41
47
|
|
|
42
|
-
|
|
48
|
+
// ── PostgreSQL ────────────────────────────────────────────────
|
|
49
|
+
if (driver === 'postgres' || dsn.startsWith('postgres')) {
|
|
43
50
|
try {
|
|
44
51
|
const { Pool } = require('pg')
|
|
45
52
|
_pgPool = new Pool({ connectionString: dsn, ssl: dsn.includes('ssl=true') ? { rejectUnauthorized: false } : false })
|
|
46
|
-
await _pgPool.query('SELECT 1')
|
|
53
|
+
await _pgPool.query('SELECT 1')
|
|
47
54
|
console.log('[aiplang] DB: PostgreSQL ✓')
|
|
48
55
|
return _pgPool
|
|
49
56
|
} catch (e) {
|
|
50
|
-
console.error('[aiplang] PostgreSQL
|
|
51
|
-
|
|
57
|
+
console.error('[aiplang] PostgreSQL falhou:', e.message)
|
|
58
|
+
_dbDriver = 'sqlite'
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ── MySQL / MariaDB ───────────────────────────────────────────
|
|
63
|
+
if (driver === 'mysql' || dsn.startsWith('mysql') || dsn.startsWith('mariadb')) {
|
|
64
|
+
try {
|
|
65
|
+
const mysql = require('mysql2/promise')
|
|
66
|
+
_mysqlPool = await mysql.createPool({
|
|
67
|
+
uri: dsn.startsWith('mariadb') ? dsn.replace('mariadb://', 'mysql://') : dsn,
|
|
68
|
+
waitForConnections: true,
|
|
69
|
+
connectionLimit: 10,
|
|
70
|
+
queueLimit: 0,
|
|
71
|
+
ssl: dsn.includes('ssl=true') ? { rejectUnauthorized: false } : undefined
|
|
72
|
+
})
|
|
73
|
+
await _mysqlPool.query('SELECT 1')
|
|
74
|
+
_dbDriver = 'mysql'
|
|
75
|
+
console.log('[aiplang] DB: MySQL/MariaDB ✓')
|
|
76
|
+
return _mysqlPool
|
|
77
|
+
} catch (e) {
|
|
78
|
+
console.error('[aiplang] MySQL falhou:', e.message)
|
|
79
|
+
_dbDriver = 'sqlite'
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ── MongoDB ───────────────────────────────────────────────────
|
|
84
|
+
if (driver === 'mongodb' || dsn.startsWith('mongodb')) {
|
|
85
|
+
try {
|
|
86
|
+
const { MongoClient } = require('mongodb')
|
|
87
|
+
_mongoClient = new MongoClient(dsn)
|
|
88
|
+
await _mongoClient.connect()
|
|
89
|
+
// Extrair nome do banco da URL
|
|
90
|
+
const dbName = dsn.split('/').pop()?.split('?')[0] || 'aiplang'
|
|
91
|
+
_mongoDB = _mongoClient.db(dbName)
|
|
92
|
+
_useMongo = true
|
|
93
|
+
_dbDriver = 'mongodb'
|
|
94
|
+
console.log('[aiplang] DB: MongoDB ✓ (' + dbName + ')')
|
|
95
|
+
return _mongoDB
|
|
96
|
+
} catch (e) {
|
|
97
|
+
console.error('[aiplang] MongoDB falhou:', e.message)
|
|
98
|
+
_dbDriver = 'sqlite'
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ── Redis ─────────────────────────────────────────────────────
|
|
103
|
+
if (driver === 'redis' || dsn.startsWith('redis')) {
|
|
104
|
+
try {
|
|
105
|
+
const Redis = require('ioredis')
|
|
106
|
+
_redisClient = new Redis(dsn, { lazyConnect: false, maxRetriesPerRequest: 3 })
|
|
107
|
+
await _redisClient.ping()
|
|
108
|
+
_useRedis = true
|
|
109
|
+
_dbDriver = 'redis'
|
|
110
|
+
console.log('[aiplang] DB: Redis ✓')
|
|
111
|
+
return _redisClient
|
|
112
|
+
} catch (e) {
|
|
113
|
+
console.error('[aiplang] Redis falhou:', e.message)
|
|
52
114
|
_dbDriver = 'sqlite'
|
|
53
115
|
}
|
|
54
116
|
}
|
|
@@ -89,7 +151,48 @@ function persistDB() {
|
|
|
89
151
|
}
|
|
90
152
|
let _dirty = false, _persistTimer = null
|
|
91
153
|
|
|
154
|
+
// ── MongoDB helpers ───────────────────────────────────────────────
|
|
155
|
+
function _sqlWhereToMongo(sql, params) {
|
|
156
|
+
// Converte WHERE simples: id = ? / email = ? → {id: val, email: val}
|
|
157
|
+
const filter = {}
|
|
158
|
+
const whereMatch = sql.match(/WHERE\s+(.+?)(?:ORDER|LIMIT|$)/is)
|
|
159
|
+
if (!whereMatch) return filter
|
|
160
|
+
const conditions = whereMatch[1].trim()
|
|
161
|
+
const parts = conditions.split(/\s+AND\s+/i)
|
|
162
|
+
let paramIdx = 0
|
|
163
|
+
for (const part of parts) {
|
|
164
|
+
const m = part.trim().match(/^(\w+)\s*=\s*\?$/)
|
|
165
|
+
if (m && paramIdx < params.length) {
|
|
166
|
+
filter[m[1]] = params[paramIdx++]
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return filter
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function _mongoToObj(doc) {
|
|
173
|
+
if (!doc) return null
|
|
174
|
+
const { _id, ...rest } = doc
|
|
175
|
+
return { id: _id?.toString() || rest.id, ...rest }
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ── MySQL: CREATE TABLE equivalente ──────────────────────────────
|
|
179
|
+
function _mysqlType(sqliteType) {
|
|
180
|
+
return {
|
|
181
|
+
'TEXT': 'TEXT',
|
|
182
|
+
'INTEGER': 'INT',
|
|
183
|
+
'REAL': 'DOUBLE',
|
|
184
|
+
'BLOB': 'BLOB'
|
|
185
|
+
}[sqliteType] || 'TEXT'
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
|
|
92
189
|
function dbRun(sql, params = []) {
|
|
190
|
+
if (_mysqlPool) {
|
|
191
|
+
// MySQL: async, mas dbRun é chamado sync em alguns contextos
|
|
192
|
+
// Enfileirar de forma segura
|
|
193
|
+
_mysqlPool.execute(sql, params).catch(e => console.debug('[aiplang:mysql]', e?.message))
|
|
194
|
+
return
|
|
195
|
+
}
|
|
93
196
|
if (_useBetter && !_pgPool) {
|
|
94
197
|
// better-sqlite3: synchronous, native — 30x faster than sql.js writes
|
|
95
198
|
_getStmt(sql).run(...params)
|
|
@@ -117,7 +220,8 @@ function convertPlaceholders(sql) {
|
|
|
117
220
|
}
|
|
118
221
|
|
|
119
222
|
async function dbRunAsync(sql, params = []) {
|
|
120
|
-
if (
|
|
223
|
+
if (_mysqlPool) return _mysqlPool.execute(sql, params)
|
|
224
|
+
if (_pgPool) return _pgPool.query(convertPlaceholders(sql), params)
|
|
121
225
|
dbRun(sql, params)
|
|
122
226
|
}
|
|
123
227
|
|
|
@@ -181,18 +285,18 @@ function _getStmt(sql) {
|
|
|
181
285
|
}
|
|
182
286
|
|
|
183
287
|
function dbAll(sql, params = [], _cacheKey = null, _cacheTables = null) {
|
|
184
|
-
if (_pgPool)
|
|
288
|
+
if (_pgPool) return [] // PostgreSQL usa dbAllAsync
|
|
289
|
+
if (_mysqlPool) return [] // MySQL usa dbAllAsync
|
|
290
|
+
if (_useMongo) return [] // MongoDB usa dbAllAsync
|
|
185
291
|
if (_cacheKey && !params.length) {
|
|
186
292
|
const cached = _cacheGet(_cacheKey)
|
|
187
293
|
if (cached !== null) return { __cached: true, __body: cached }
|
|
188
294
|
}
|
|
189
295
|
let rows
|
|
190
296
|
if (_useBetter) {
|
|
191
|
-
// better-sqlite3: synchronous, native, 7x faster
|
|
192
297
|
const stmt = _getStmt(sql)
|
|
193
298
|
rows = params.length ? stmt.all(...params) : stmt.all()
|
|
194
299
|
} else {
|
|
195
|
-
// sql.js fallback
|
|
196
300
|
const stmt = _db.prepare(sql)
|
|
197
301
|
stmt.bind(params); rows = []
|
|
198
302
|
while (stmt.step()) rows.push(stmt.getAsObject())
|
|
@@ -202,12 +306,33 @@ function dbAll(sql, params = [], _cacheKey = null, _cacheTables = null) {
|
|
|
202
306
|
return rows
|
|
203
307
|
}
|
|
204
308
|
|
|
205
|
-
async function dbAllAsync(sql, params = []) {
|
|
309
|
+
async function dbAllAsync(sql, params = [], _cacheKey = null, _cacheTables = null) {
|
|
310
|
+
// Cache check (universal)
|
|
311
|
+
if (_cacheKey && !params.length) {
|
|
312
|
+
const cached = _cacheGet(_cacheKey)
|
|
313
|
+
if (cached !== null) return { __cached: true, __body: cached }
|
|
314
|
+
}
|
|
315
|
+
let rows
|
|
206
316
|
if (_pgPool) {
|
|
207
317
|
const r = await _pgPool.query(convertPlaceholders(sql), params)
|
|
208
|
-
|
|
318
|
+
rows = r.rows
|
|
319
|
+
} else if (_mysqlPool) {
|
|
320
|
+
const [result] = await _mysqlPool.execute(sql, params)
|
|
321
|
+
rows = result
|
|
322
|
+
} else if (_useMongo) {
|
|
323
|
+
// MongoDB: extrair collection do SQL "SELECT * FROM table WHERE..."
|
|
324
|
+
const tableMatch = sql.match(/FROM\s+(\w+)/i)
|
|
325
|
+
const collection = tableMatch ? tableMatch[1] : null
|
|
326
|
+
if (!collection) return []
|
|
327
|
+
const filter = _sqlWhereToMongo(sql, params)
|
|
328
|
+
rows = await _mongoDB.collection(collection).find(filter).toArray()
|
|
329
|
+
} else {
|
|
330
|
+
rows = dbAll(sql, params)
|
|
331
|
+
}
|
|
332
|
+
if (Array.isArray(rows) && _cacheKey && !params.length) {
|
|
333
|
+
_cacheSet(_cacheKey, JSON.stringify(rows), _cacheTables)
|
|
209
334
|
}
|
|
210
|
-
return
|
|
335
|
+
return rows
|
|
211
336
|
}
|
|
212
337
|
|
|
213
338
|
function dbGet(sql, params = []) { return dbAll(sql, params)[0] || null }
|
|
@@ -460,9 +585,15 @@ function validateAip(source) {
|
|
|
460
585
|
}
|
|
461
586
|
|
|
462
587
|
function cacheSet(key, value, ttlMs = 60000) {
|
|
588
|
+
// Redis: persistir em redis além do cache em memória
|
|
589
|
+
if (_useRedis) {
|
|
590
|
+
try { _redisClient.set('aip:' + key, JSON.stringify(value), 'PX', ttlMs).catch(() => {}) } catch {}
|
|
591
|
+
}
|
|
463
592
|
_cache.set(key, { value, expires: Date.now() + ttlMs })
|
|
464
593
|
}
|
|
465
594
|
function cacheGet(key) {
|
|
595
|
+
// Redis: verificar redis se não tiver na memória
|
|
596
|
+
// (retorna null para busca assíncrona — o caller deve usar cacheGetAsync se precisar)
|
|
466
597
|
const item = _cache.get(key)
|
|
467
598
|
if (!item) return null
|
|
468
599
|
if (item.expires < Date.now()) { _cache.delete(key); return null }
|
|
@@ -621,6 +752,19 @@ class Model {
|
|
|
621
752
|
|
|
622
753
|
// ── Core queries ────────────────────────────────────────────────
|
|
623
754
|
all(opts = {}) {
|
|
755
|
+
// MongoDB
|
|
756
|
+
if (_useMongo) {
|
|
757
|
+
const filter = this.softDelete ? { deleted_at: { $exists: false } } : {}
|
|
758
|
+
const mongoOpts = {}
|
|
759
|
+
if (opts.limit) mongoOpts.limit = parseInt(opts.limit)
|
|
760
|
+
if (opts.offset) mongoOpts.skip = parseInt(opts.offset)
|
|
761
|
+
if (opts.order) {
|
|
762
|
+
const p = String(opts.order).trim().split(/\s+/)
|
|
763
|
+
mongoOpts.sort = { [p[0]]: p[1]?.toLowerCase()==='desc' ? -1 : 1 }
|
|
764
|
+
}
|
|
765
|
+
return _mongoDB.collection(this.tableName).find(filter, mongoOpts).toArray()
|
|
766
|
+
.then(docs => docs.map(d => { const { _id, ...rest } = d; return { id: _id?.toString(), ...rest } }))
|
|
767
|
+
}
|
|
624
768
|
let sql = `SELECT * FROM ${this.tableName}`
|
|
625
769
|
const params = [], conditions = []
|
|
626
770
|
if (this.softDelete) conditions.push('deleted_at IS NULL')
|
|
@@ -684,6 +828,11 @@ class Model {
|
|
|
684
828
|
if (!row.updated_at) row.updated_at = now()
|
|
685
829
|
}
|
|
686
830
|
const keys = Object.keys(row), vals = Object.values(row)
|
|
831
|
+
// MongoDB: usar insertOne
|
|
832
|
+
if (_useMongo) {
|
|
833
|
+
_mongoDB.collection(this.tableName).insertOne({ ...row }).catch(e => console.debug('[aiplang:mongo]', e?.message))
|
|
834
|
+
return row
|
|
835
|
+
}
|
|
687
836
|
dbRun(`INSERT INTO ${this.tableName} (${keys.join(',')}) VALUES (${keys.map(()=>'?').join(',')})`, vals)
|
|
688
837
|
emit(`${this.modelName}.created`, row)
|
|
689
838
|
return row
|
|
@@ -767,13 +916,39 @@ class Model {
|
|
|
767
916
|
// MIGRATION
|
|
768
917
|
// ═══════════════════════════════════════════════════════════════════
|
|
769
918
|
function migrateModels(models) {
|
|
919
|
+
// ── Seleção de mapa de tipos por banco ───────────────────────────
|
|
920
|
+
const _sqliteTypes = { uuid:'TEXT',int:'INTEGER',integer:'INTEGER',float:'REAL',bool:'INTEGER',timestamp:'TEXT',date:'TEXT',json:'TEXT',enum:'TEXT',text:'TEXT',email:'TEXT',url:'TEXT',phone:'TEXT' }
|
|
921
|
+
const _mysqlTypes = { uuid:'VARCHAR(36)',int:'INT',integer:'INT',float:'DOUBLE',bool:'TINYINT(1)',timestamp:'DATETIME',date:'DATE',json:'JSON',enum:'TEXT',text:'TEXT',email:'VARCHAR(255)',url:'TEXT',phone:'VARCHAR(20)' }
|
|
922
|
+
const _pgTypes = { uuid:'UUID',int:'INTEGER',integer:'INTEGER',float:'DOUBLE PRECISION',bool:'BOOLEAN',timestamp:'TIMESTAMPTZ',date:'DATE',json:'JSONB',enum:'TEXT',text:'TEXT',email:'TEXT',url:'TEXT',phone:'TEXT' }
|
|
923
|
+
const typeMap = _mysqlPool ? _mysqlTypes : (_pgPool ? _pgTypes : _sqliteTypes)
|
|
924
|
+
|
|
770
925
|
for (const model of models) {
|
|
771
926
|
const table = toTable(model.name)
|
|
927
|
+
|
|
928
|
+
// ── MongoDB: criar collection com índices ──────────────────────
|
|
929
|
+
if (_useMongo) {
|
|
930
|
+
_mongoDB.collection(table) // cria collection implicitamente
|
|
931
|
+
for (const f of model.fields) {
|
|
932
|
+
const colName = toCol(f.name)
|
|
933
|
+
if (f.modifiers.includes('unique')) {
|
|
934
|
+
_mongoDB.collection(table).createIndex({ [colName]: 1 }, { unique: true }).catch(() => {})
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
console.log(`[aiplang] ✓ ${table} (MongoDB)`)
|
|
938
|
+
continue
|
|
939
|
+
}
|
|
940
|
+
|
|
772
941
|
const cols = []
|
|
773
942
|
for (const f of model.fields) {
|
|
774
|
-
let sqlType =
|
|
943
|
+
let sqlType = typeMap[f.type] || 'TEXT'
|
|
775
944
|
let def = `${toCol(f.name)} ${sqlType}`
|
|
776
|
-
if (f.modifiers.includes('pk'))
|
|
945
|
+
if (f.modifiers.includes('pk')) {
|
|
946
|
+
if (_mysqlPool) def += ' PRIMARY KEY'
|
|
947
|
+
else def += ' PRIMARY KEY'
|
|
948
|
+
}
|
|
949
|
+
if (f.modifiers.includes('auto') && f.type !== 'uuid') {
|
|
950
|
+
if (_mysqlPool) def += ' AUTO_INCREMENT'
|
|
951
|
+
}
|
|
777
952
|
if (f.modifiers.includes('required')) def += ' NOT NULL'
|
|
778
953
|
if (f.modifiers.includes('unique')) def += ' UNIQUE'
|
|
779
954
|
if (f.default !== null) def += ` DEFAULT '${f.default}'`
|
|
@@ -905,7 +1080,18 @@ function parseApp(src) {
|
|
|
905
1080
|
}
|
|
906
1081
|
|
|
907
1082
|
function parseEnvLine(s) { const p=s.split(/\s+/); const ev={name:'',required:false,default:null}; for(const x of p){if(x==='required')ev.required=true;else if(x.includes('=')){const[k,v]=x.split('=');ev.name=k;ev.default=v}else ev.name=x}; return ev }
|
|
908
|
-
function parseDBLine(s) {
|
|
1083
|
+
function parseDBLine(s) {
|
|
1084
|
+
const p = s.split(/\s+/)
|
|
1085
|
+
let d = (p[0]||'sqlite').toLowerCase()
|
|
1086
|
+
// Normalizar aliases
|
|
1087
|
+
if (d==='pg'||d==='psql'||d==='postgresql') d='postgres'
|
|
1088
|
+
if (d==='mariadb') d='mysql'
|
|
1089
|
+
if (d==='mongo') d='mongodb'
|
|
1090
|
+
if (d==='redis'||d==='cache') d='redis'
|
|
1091
|
+
if (d==='sqlite3') d='sqlite'
|
|
1092
|
+
const dsn = p[1] || (d==='sqlite'?'./app.db':d==='redis'?'redis://localhost:6379':'')
|
|
1093
|
+
return { driver:d, dsn }
|
|
1094
|
+
}
|
|
909
1095
|
function parseAuthLine(s) { const p=s.split(/\s+/); const a={provider:'jwt',secret:p[1]||'$JWT_SECRET',expire:'7d',refresh:'30d'}; for(const x of p){if(x.startsWith('expire='))a.expire=x.slice(7);if(x.startsWith('refresh='))a.refresh=x.slice(8);if(x==='google')a.oauth=['google'];if(x==='github')a.oauth=[...(a.oauth||[]),'google']}; return a }
|
|
910
1096
|
function parseMailLine(s) { const parts=s.split(/\s+/); const m={driver:parts[0]||'smtp'}; for(const x of parts.slice(1)){const[k,v]=x.split('='); m[k]=v}; return m }
|
|
911
1097
|
function parseStripeLine(s) {
|
|
@@ -2246,7 +2432,7 @@ async function startServer(aipFile, port = 3000) {
|
|
|
2246
2432
|
})
|
|
2247
2433
|
|
|
2248
2434
|
srv.addRoute('GET', '/health', (req, res) => res.json(200, {
|
|
2249
|
-
status:'ok', version:'2.11.
|
|
2435
|
+
status:'ok', version:'2.11.4',
|
|
2250
2436
|
models: app.models.map(m=>m.name),
|
|
2251
2437
|
routes: app.apis.length, pages: app.pages.length,
|
|
2252
2438
|
admin: app.admin?.prefix || null,
|