@stravigor/core 0.1.0 → 0.2.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 +1 -1
- package/src/auth/auth.ts +2 -1
- package/src/broadcast/broadcast_manager.ts +18 -5
- package/src/broadcast/client.ts +10 -4
- package/src/cache/cache_manager.ts +6 -2
- package/src/cache/http_cache.ts +1 -5
- package/src/core/container.ts +2 -6
- package/src/database/database.ts +11 -7
- package/src/database/migration/runner.ts +3 -1
- package/src/database/query_builder.ts +553 -60
- package/src/encryption/encryption_manager.ts +7 -1
- package/src/exceptions/errors.ts +1 -5
- package/src/exceptions/http_exception.ts +4 -1
- package/src/generators/api_generator.ts +8 -1
- package/src/generators/doc_generator.ts +33 -28
- package/src/generators/model_generator.ts +3 -1
- package/src/generators/test_generator.ts +81 -91
- package/src/i18n/helpers.ts +5 -1
- package/src/i18n/i18n_manager.ts +3 -1
- package/src/i18n/middleware.ts +2 -8
- package/src/mail/helpers.ts +1 -1
- package/src/mail/index.ts +4 -0
- package/src/mail/mail_manager.ts +20 -1
- package/src/mail/transports/alibaba_transport.ts +88 -0
- package/src/mail/transports/mailgun_transport.ts +74 -0
- package/src/mail/transports/resend_transport.ts +3 -4
- package/src/mail/transports/sendgrid_transport.ts +12 -9
- package/src/mail/transports/smtp_transport.ts +5 -5
- package/src/mail/types.ts +19 -1
- package/src/notification/channels/discord_channel.ts +6 -1
- package/src/notification/channels/webhook_channel.ts +8 -3
- package/src/notification/helpers.ts +7 -7
- package/src/notification/notification_manager.ts +7 -6
- package/src/orm/base_model.ts +4 -2
- package/src/queue/queue.ts +3 -1
- package/src/scheduler/cron.ts +12 -6
- package/src/scheduler/schedule.ts +17 -8
- package/src/session/session_manager.ts +3 -1
- package/src/storage/storage_manager.ts +3 -1
- package/src/view/compiler.ts +1 -3
- package/src/view/islands/island_builder.ts +4 -4
- package/src/view/islands/vue_plugin.ts +11 -15
- package/src/view/tokenizer.ts +11 -1
package/package.json
CHANGED
package/src/auth/auth.ts
CHANGED
|
@@ -53,7 +53,8 @@ export default class Auth {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
static get db(): Database {
|
|
56
|
-
if (!Auth._db)
|
|
56
|
+
if (!Auth._db)
|
|
57
|
+
throw new ConfigurationError('Auth not configured. Resolve Auth through the container first.')
|
|
57
58
|
return Auth._db
|
|
58
59
|
}
|
|
59
60
|
|
|
@@ -59,8 +59,14 @@ interface ClientConnection {
|
|
|
59
59
|
function parsePattern(pattern: string): { regex: RegExp; paramNames: string[] } {
|
|
60
60
|
const paramNames: string[] = []
|
|
61
61
|
const regexStr = pattern
|
|
62
|
-
.replace(/\/\*(\w+)/, (_, name) => {
|
|
63
|
-
|
|
62
|
+
.replace(/\/\*(\w+)/, (_, name) => {
|
|
63
|
+
paramNames.push(name)
|
|
64
|
+
return '/(.+)'
|
|
65
|
+
})
|
|
66
|
+
.replace(/:(\w+)/g, (_, name) => {
|
|
67
|
+
paramNames.push(name)
|
|
68
|
+
return '([^/]+)'
|
|
69
|
+
})
|
|
64
70
|
return { regex: new RegExp(`^${regexStr}$`), paramNames }
|
|
65
71
|
}
|
|
66
72
|
|
|
@@ -183,7 +189,9 @@ export default class BroadcastManager {
|
|
|
183
189
|
BroadcastManager._pingTimer = setInterval(() => {
|
|
184
190
|
const ping = JSON.stringify({ t: 'ping' })
|
|
185
191
|
for (const client of BroadcastManager._clients.values()) {
|
|
186
|
-
try {
|
|
192
|
+
try {
|
|
193
|
+
client.ws.send(ping)
|
|
194
|
+
} catch {}
|
|
187
195
|
}
|
|
188
196
|
}, pingInterval)
|
|
189
197
|
}
|
|
@@ -293,7 +301,10 @@ export default class BroadcastManager {
|
|
|
293
301
|
return ctx
|
|
294
302
|
}
|
|
295
303
|
|
|
296
|
-
private static async handleSubscribe(
|
|
304
|
+
private static async handleSubscribe(
|
|
305
|
+
client: ClientConnection,
|
|
306
|
+
channelName: string
|
|
307
|
+
): Promise<void> {
|
|
297
308
|
if (!channelName) return
|
|
298
309
|
|
|
299
310
|
// Already subscribed
|
|
@@ -389,7 +400,9 @@ export default class BroadcastManager {
|
|
|
389
400
|
if (clientId === excludeClientId) continue
|
|
390
401
|
const client = BroadcastManager._clients.get(clientId)
|
|
391
402
|
if (client) {
|
|
392
|
-
try {
|
|
403
|
+
try {
|
|
404
|
+
client.ws.send(msg)
|
|
405
|
+
} catch {}
|
|
393
406
|
}
|
|
394
407
|
}
|
|
395
408
|
}
|
package/src/broadcast/client.ts
CHANGED
|
@@ -74,7 +74,9 @@ export class Subscription {
|
|
|
74
74
|
this.listeners.set(event, set)
|
|
75
75
|
}
|
|
76
76
|
set.add(callback)
|
|
77
|
-
return () => {
|
|
77
|
+
return () => {
|
|
78
|
+
set!.delete(callback)
|
|
79
|
+
}
|
|
78
80
|
}
|
|
79
81
|
|
|
80
82
|
/**
|
|
@@ -158,7 +160,7 @@ export class Broadcast {
|
|
|
158
160
|
|
|
159
161
|
const sub = new Subscription(
|
|
160
162
|
channel,
|
|
161
|
-
|
|
163
|
+
msg => this.send(msg),
|
|
162
164
|
() => this.subscriptions.delete(channel)
|
|
163
165
|
)
|
|
164
166
|
|
|
@@ -190,7 +192,9 @@ export class Broadcast {
|
|
|
190
192
|
this.listeners.set(event, set)
|
|
191
193
|
}
|
|
192
194
|
set.add(callback)
|
|
193
|
-
return () => {
|
|
195
|
+
return () => {
|
|
196
|
+
set!.delete(callback)
|
|
197
|
+
}
|
|
194
198
|
}
|
|
195
199
|
|
|
196
200
|
/** Close the connection permanently (no reconnection). */
|
|
@@ -206,7 +210,9 @@ export class Broadcast {
|
|
|
206
210
|
// ---------------------------------------------------------------------------
|
|
207
211
|
|
|
208
212
|
private autoUrl(): string {
|
|
213
|
+
// @ts-ignore browser-only API
|
|
209
214
|
const proto = location.protocol === 'https:' ? 'wss:' : 'ws:'
|
|
215
|
+
// @ts-ignore browser-only API
|
|
210
216
|
return `${proto}//${location.host}/_broadcast`
|
|
211
217
|
}
|
|
212
218
|
|
|
@@ -219,7 +225,7 @@ export class Broadcast {
|
|
|
219
225
|
this.emit('connected')
|
|
220
226
|
}
|
|
221
227
|
|
|
222
|
-
this.ws.onmessage =
|
|
228
|
+
this.ws.onmessage = event => {
|
|
223
229
|
try {
|
|
224
230
|
const msg = JSON.parse(event.data)
|
|
225
231
|
this.handleMessage(msg)
|
|
@@ -34,13 +34,17 @@ export default class CacheManager {
|
|
|
34
34
|
if (driver === 'memory') {
|
|
35
35
|
CacheManager._store = new MemoryCacheStore()
|
|
36
36
|
} else {
|
|
37
|
-
throw new ConfigurationError(
|
|
37
|
+
throw new ConfigurationError(
|
|
38
|
+
`Unknown cache driver: ${driver}. Use CacheManager.useStore() for custom drivers.`
|
|
39
|
+
)
|
|
38
40
|
}
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
static get store(): CacheStore {
|
|
42
44
|
if (!CacheManager._store) {
|
|
43
|
-
throw new ConfigurationError(
|
|
45
|
+
throw new ConfigurationError(
|
|
46
|
+
'CacheManager not configured. Resolve it through the container first.'
|
|
47
|
+
)
|
|
44
48
|
}
|
|
45
49
|
return CacheManager._store
|
|
46
50
|
}
|
package/src/cache/http_cache.ts
CHANGED
|
@@ -97,11 +97,7 @@ export function httpCache(options: HttpCacheOptions = {}): Middleware {
|
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
function buildCacheControl(
|
|
101
|
-
directives: CacheDirective[],
|
|
102
|
-
maxAge: number,
|
|
103
|
-
sMaxAge?: number
|
|
104
|
-
): string {
|
|
100
|
+
function buildCacheControl(directives: CacheDirective[], maxAge: number, sMaxAge?: number): string {
|
|
105
101
|
const parts = [...directives]
|
|
106
102
|
if (maxAge > 0) parts.push(`max-age=${maxAge}` as CacheDirective)
|
|
107
103
|
if (sMaxAge != null) parts.push(`s-maxage=${sMaxAge}` as CacheDirective)
|
package/src/core/container.ts
CHANGED
|
@@ -47,9 +47,7 @@ export default class Container {
|
|
|
47
47
|
register<T>(name: string, factory: Factory<T> | Constructor<T>): this
|
|
48
48
|
register<T>(nameOrCtor: string | Constructor<T>, factory?: Factory<T> | Constructor<T>): this {
|
|
49
49
|
if (typeof nameOrCtor === 'function') {
|
|
50
|
-
const resolved = factory
|
|
51
|
-
? this.toFactory(factory)
|
|
52
|
-
: this.classToFactory(nameOrCtor)
|
|
50
|
+
const resolved = factory ? this.toFactory(factory) : this.classToFactory(nameOrCtor)
|
|
53
51
|
this.factories.set(nameOrCtor, { factory: resolved, singleton: false })
|
|
54
52
|
} else {
|
|
55
53
|
this.factories.set(nameOrCtor, { factory: this.toFactory(factory!), singleton: false })
|
|
@@ -65,9 +63,7 @@ export default class Container {
|
|
|
65
63
|
singleton<T>(name: string, factory: Factory<T> | Constructor<T>): this
|
|
66
64
|
singleton<T>(nameOrCtor: string | Constructor<T>, factory?: Factory<T> | Constructor<T>): this {
|
|
67
65
|
if (typeof nameOrCtor === 'function') {
|
|
68
|
-
const resolved = factory
|
|
69
|
-
? this.toFactory(factory)
|
|
70
|
-
: this.classToFactory(nameOrCtor)
|
|
66
|
+
const resolved = factory ? this.toFactory(factory) : this.classToFactory(nameOrCtor)
|
|
71
67
|
this.factories.set(nameOrCtor, { factory: resolved, singleton: true })
|
|
72
68
|
} else {
|
|
73
69
|
this.factories.set(nameOrCtor, { factory: this.toFactory(factory!), singleton: true })
|
package/src/database/database.ts
CHANGED
|
@@ -2,12 +2,14 @@ import { SQL } from 'bun'
|
|
|
2
2
|
import Configuration from '../config/configuration.ts'
|
|
3
3
|
import { inject } from '../core/inject.ts'
|
|
4
4
|
import { ConfigurationError } from '../exceptions/errors.ts'
|
|
5
|
+
import { env } from '../helpers/env.ts'
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Database connection wrapper backed by {@link SQL Bun.sql}.
|
|
8
9
|
*
|
|
9
10
|
* Reads connection credentials from the `database.*` configuration keys
|
|
10
|
-
* (loaded from `config/database.ts` / `.env`).
|
|
11
|
+
* (loaded from `config/database.ts` / `.env`). Falls back to reading
|
|
12
|
+
* `DB_*` environment variables directly when no config file is present.
|
|
11
13
|
*
|
|
12
14
|
* Register as a singleton in the DI container so a single connection pool
|
|
13
15
|
* is shared across the application.
|
|
@@ -25,11 +27,11 @@ export default class Database {
|
|
|
25
27
|
|
|
26
28
|
constructor(protected config: Configuration) {
|
|
27
29
|
this.connection = new SQL({
|
|
28
|
-
hostname: config.get('database.host', '127.0.0.1'),
|
|
29
|
-
port: config.get('database.port', 5432),
|
|
30
|
-
username: config.get('database.username', 'postgres'),
|
|
31
|
-
password: config.get('database.password', ''),
|
|
32
|
-
database: config.get('database.database', 'strav'),
|
|
30
|
+
hostname: config.get('database.host') ?? env('DB_HOST', '127.0.0.1'),
|
|
31
|
+
port: config.get('database.port') ?? env.int('DB_PORT', 5432),
|
|
32
|
+
username: config.get('database.username') ?? env('DB_USER', 'postgres'),
|
|
33
|
+
password: config.get('database.password') ?? env('DB_PASSWORD', ''),
|
|
34
|
+
database: config.get('database.database') ?? env('DB_DATABASE', 'strav'),
|
|
33
35
|
})
|
|
34
36
|
Database._connection = this.connection
|
|
35
37
|
}
|
|
@@ -42,7 +44,9 @@ export default class Database {
|
|
|
42
44
|
/** The global SQL connection, available after DI bootstrap. */
|
|
43
45
|
static get raw(): SQL {
|
|
44
46
|
if (!Database._connection) {
|
|
45
|
-
throw new ConfigurationError(
|
|
47
|
+
throw new ConfigurationError(
|
|
48
|
+
'Database not configured. Resolve Database through the container first.'
|
|
49
|
+
)
|
|
46
50
|
}
|
|
47
51
|
return Database._connection
|
|
48
52
|
}
|
|
@@ -88,7 +88,9 @@ export default class MigrationRunner {
|
|
|
88
88
|
])
|
|
89
89
|
})
|
|
90
90
|
} catch (err) {
|
|
91
|
-
throw new DatabaseError(
|
|
91
|
+
throw new DatabaseError(
|
|
92
|
+
`Migration ${version} failed: ${err instanceof Error ? err.message : err}`
|
|
93
|
+
)
|
|
92
94
|
}
|
|
93
95
|
}
|
|
94
96
|
|