@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
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
hkdfSync,
|
|
3
|
+
createCipheriv,
|
|
4
|
+
createDecipheriv,
|
|
5
|
+
createHmac,
|
|
6
|
+
timingSafeEqual,
|
|
7
|
+
} from 'node:crypto'
|
|
2
8
|
import { inject } from '../core/inject.ts'
|
|
3
9
|
import Configuration from '../config/configuration.ts'
|
|
4
10
|
import type { EncryptionConfig } from './types.ts'
|
package/src/exceptions/errors.ts
CHANGED
|
@@ -21,11 +21,7 @@ export class ModelNotFoundError extends StravError {
|
|
|
21
21
|
public readonly model: string,
|
|
22
22
|
public readonly id?: unknown
|
|
23
23
|
) {
|
|
24
|
-
super(
|
|
25
|
-
id !== undefined
|
|
26
|
-
? `${model} with ID ${id} not found`
|
|
27
|
-
: `${model} not found`
|
|
28
|
-
)
|
|
24
|
+
super(id !== undefined ? `${model} with ID ${id} not found` : `${model} not found`)
|
|
29
25
|
}
|
|
30
26
|
}
|
|
31
27
|
|
|
@@ -111,7 +111,10 @@ export class ValidationError extends HttpException {
|
|
|
111
111
|
* throw new RateLimitError(60) // retry after 60 seconds
|
|
112
112
|
*/
|
|
113
113
|
export class RateLimitError extends HttpException {
|
|
114
|
-
constructor(
|
|
114
|
+
constructor(
|
|
115
|
+
public readonly retryAfter?: number,
|
|
116
|
+
message?: string
|
|
117
|
+
) {
|
|
115
118
|
super(429, message)
|
|
116
119
|
}
|
|
117
120
|
}
|
|
@@ -34,7 +34,14 @@ const ARCHETYPE_POLICY: Record<Archetype, string[]> = {
|
|
|
34
34
|
[Archetype.Entity]: ['canList', 'canView', 'canCreate', 'canUpdate', 'canDelete'],
|
|
35
35
|
[Archetype.Attribute]: ['canList', 'canView', 'canCreate', 'canUpdate', 'canDelete'],
|
|
36
36
|
[Archetype.Reference]: ['canList', 'canView', 'canCreate', 'canUpdate', 'canDelete'],
|
|
37
|
-
[Archetype.Contribution]: [
|
|
37
|
+
[Archetype.Contribution]: [
|
|
38
|
+
'canList',
|
|
39
|
+
'canView',
|
|
40
|
+
'canCreate',
|
|
41
|
+
'canUpdate',
|
|
42
|
+
'canDelete',
|
|
43
|
+
'canModerate',
|
|
44
|
+
],
|
|
38
45
|
[Archetype.Component]: ['canList', 'canView', 'canUpdate'],
|
|
39
46
|
[Archetype.Event]: ['canList', 'canView', 'canAppend'],
|
|
40
47
|
[Archetype.Configuration]: ['canView', 'canUpsert', 'canReset'],
|
|
@@ -41,14 +41,12 @@ const DEPENDENT_ARCHETYPES: Set<Archetype> = new Set([
|
|
|
41
41
|
const SYSTEM_COLUMNS = new Set(['id', 'created_at', 'updated_at', 'deleted_at'])
|
|
42
42
|
|
|
43
43
|
const ARCHETYPE_DESCRIPTIONS: Record<Archetype, string> = {
|
|
44
|
-
[Archetype.Entity]:
|
|
45
|
-
'A standalone entity with full CRUD operations and soft delete support.',
|
|
44
|
+
[Archetype.Entity]: 'A standalone entity with full CRUD operations and soft delete support.',
|
|
46
45
|
[Archetype.Contribution]:
|
|
47
46
|
'A user-contributed resource under a parent entity. Full CRUD with soft delete.',
|
|
48
47
|
[Archetype.Reference]:
|
|
49
48
|
'A lookup/reference table with full CRUD. No soft delete — records are permanently removed.',
|
|
50
|
-
[Archetype.Attribute]:
|
|
51
|
-
'A dependent attribute of a parent entity. Full CRUD with soft delete.',
|
|
49
|
+
[Archetype.Attribute]: 'A dependent attribute of a parent entity. Full CRUD with soft delete.',
|
|
52
50
|
[Archetype.Component]:
|
|
53
51
|
'A tightly-coupled component of a parent entity. Can be listed, viewed, and updated — but not independently created or deleted.',
|
|
54
52
|
[Archetype.Event]:
|
|
@@ -222,19 +220,29 @@ ${content}
|
|
|
222
220
|
|
|
223
221
|
// Introduction & Auth
|
|
224
222
|
lines.push(' <div class="mb-6">')
|
|
225
|
-
lines.push(
|
|
226
|
-
|
|
227
|
-
|
|
223
|
+
lines.push(
|
|
224
|
+
' <p class="px-3 text-[0.6875rem] font-semibold uppercase tracking-wider text-zinc-500 mb-2">Getting Started</p>'
|
|
225
|
+
)
|
|
226
|
+
lines.push(
|
|
227
|
+
' <a href="#introduction" class="sidebar-link block px-3 py-1.5 text-sm text-zinc-400 rounded-md transition-colors">Introduction</a>'
|
|
228
|
+
)
|
|
229
|
+
lines.push(
|
|
230
|
+
' <a href="#authentication" class="sidebar-link block px-3 py-1.5 text-sm text-zinc-400 rounded-md transition-colors">Authentication</a>'
|
|
231
|
+
)
|
|
228
232
|
lines.push(' </div>')
|
|
229
233
|
|
|
230
234
|
// Resources
|
|
231
235
|
lines.push(' <div>')
|
|
232
|
-
lines.push(
|
|
236
|
+
lines.push(
|
|
237
|
+
' <p class="px-3 text-[0.6875rem] font-semibold uppercase tracking-wider text-zinc-500 mb-2">Resources</p>'
|
|
238
|
+
)
|
|
233
239
|
|
|
234
240
|
const emitSchema = (schema: SchemaDefinition): void => {
|
|
235
241
|
const anchor = toSnakeCase(schema.name)
|
|
236
242
|
const label = this.displayName(schema.name)
|
|
237
|
-
lines.push(
|
|
243
|
+
lines.push(
|
|
244
|
+
` <a href="#${anchor}" class="sidebar-link block px-3 py-1.5 text-sm text-zinc-400 rounded-md transition-colors">${label}</a>`
|
|
245
|
+
)
|
|
238
246
|
|
|
239
247
|
const children = childrenOf.get(schema.name)
|
|
240
248
|
if (children?.length) {
|
|
@@ -341,10 +349,7 @@ Content-Type: application/json</code></pre>
|
|
|
341
349
|
// Per-resource section
|
|
342
350
|
// ---------------------------------------------------------------------------
|
|
343
351
|
|
|
344
|
-
private buildResourceSection(
|
|
345
|
-
schema: SchemaDefinition,
|
|
346
|
-
table: TableDefinition
|
|
347
|
-
): string {
|
|
352
|
+
private buildResourceSection(schema: SchemaDefinition, table: TableDefinition): string {
|
|
348
353
|
const anchor = toSnakeCase(schema.name)
|
|
349
354
|
const label = this.displayName(schema.name)
|
|
350
355
|
const actions = ARCHETYPE_CONTROLLER[schema.archetype] ?? []
|
|
@@ -352,9 +357,7 @@ Content-Type: application/json</code></pre>
|
|
|
352
357
|
const description = ARCHETYPE_DESCRIPTIONS[schema.archetype]
|
|
353
358
|
|
|
354
359
|
const fieldsTable = this.buildFieldsTable(schema, table)
|
|
355
|
-
const endpoints = actions
|
|
356
|
-
.map(action => this.buildEndpoint(action, schema, table))
|
|
357
|
-
.join('\n')
|
|
360
|
+
const endpoints = actions.map(action => this.buildEndpoint(action, schema, table)).join('\n')
|
|
358
361
|
|
|
359
362
|
return ` <section id="${anchor}" class="mb-20">
|
|
360
363
|
<div class="flex items-center gap-3">
|
|
@@ -434,11 +437,7 @@ ${rows.join('\n')}
|
|
|
434
437
|
// Per-endpoint block
|
|
435
438
|
// ---------------------------------------------------------------------------
|
|
436
439
|
|
|
437
|
-
private buildEndpoint(
|
|
438
|
-
action: string,
|
|
439
|
-
schema: SchemaDefinition,
|
|
440
|
-
table: TableDefinition
|
|
441
|
-
): string {
|
|
440
|
+
private buildEndpoint(action: string, schema: SchemaDefinition, table: TableDefinition): string {
|
|
442
441
|
const { method, pathPattern, description } = this.actionMeta(action, schema)
|
|
443
442
|
const methodColor = this.methodBadgeColor(method)
|
|
444
443
|
const anchor = `${toSnakeCase(schema.name)}-${action}`
|
|
@@ -738,10 +737,7 @@ ${rows.join('\n')}
|
|
|
738
737
|
}
|
|
739
738
|
|
|
740
739
|
const statusCode = action === 'store' ? 201 : 200
|
|
741
|
-
const body =
|
|
742
|
-
action === 'index'
|
|
743
|
-
? JSON.stringify([obj], null, 2)
|
|
744
|
-
: JSON.stringify(obj, null, 2)
|
|
740
|
+
const body = action === 'index' ? JSON.stringify([obj], null, 2) : JSON.stringify(obj, null, 2)
|
|
745
741
|
|
|
746
742
|
return { statusCode, responseBody: body }
|
|
747
743
|
}
|
|
@@ -769,7 +765,9 @@ ${rows.join('\n')}
|
|
|
769
765
|
// ---------------------------------------------------------------------------
|
|
770
766
|
|
|
771
767
|
private displayName(name: string): string {
|
|
772
|
-
return toPascalCase(name)
|
|
768
|
+
return toPascalCase(name)
|
|
769
|
+
.replace(/([A-Z])/g, ' $1')
|
|
770
|
+
.trim()
|
|
773
771
|
}
|
|
774
772
|
|
|
775
773
|
private displayNamePlural(name: string): string {
|
|
@@ -915,9 +913,16 @@ ${rows.join('\n')}
|
|
|
915
913
|
default: {
|
|
916
914
|
const snake = toSnakeCase(fieldName)
|
|
917
915
|
if (snake === 'email' || snake.endsWith('_email')) return 'user@example.com'
|
|
918
|
-
if (
|
|
916
|
+
if (
|
|
917
|
+
snake === 'url' ||
|
|
918
|
+
snake.endsWith('_url') ||
|
|
919
|
+
snake === 'website' ||
|
|
920
|
+
snake === 'homepage'
|
|
921
|
+
)
|
|
919
922
|
return 'https://example.com'
|
|
920
|
-
const label = toPascalCase(fieldName)
|
|
923
|
+
const label = toPascalCase(fieldName)
|
|
924
|
+
.replace(/([A-Z])/g, ' $1')
|
|
925
|
+
.trim()
|
|
921
926
|
return `Sample ${label}`
|
|
922
927
|
}
|
|
923
928
|
}
|
|
@@ -272,7 +272,9 @@ export default class ModelGenerator {
|
|
|
272
272
|
if (needsReferenceImport) decoratorImports.push('reference')
|
|
273
273
|
if (needsAssociateImport) decoratorImports.push('associate')
|
|
274
274
|
if (decoratorImports.length > 0) {
|
|
275
|
-
importLines.push(
|
|
275
|
+
importLines.push(
|
|
276
|
+
`import { ${decoratorImports.join(', ')} } from '@stravigor/core/orm/decorators'`
|
|
277
|
+
)
|
|
276
278
|
}
|
|
277
279
|
|
|
278
280
|
for (const [entity, enumNames] of enumImports) {
|