@stravigor/core 0.1.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 +45 -0
- package/package.json +83 -0
- package/src/auth/access_token.ts +122 -0
- package/src/auth/auth.ts +86 -0
- package/src/auth/index.ts +7 -0
- package/src/auth/middleware/authenticate.ts +64 -0
- package/src/auth/middleware/csrf.ts +62 -0
- package/src/auth/middleware/guest.ts +46 -0
- package/src/broadcast/broadcast_manager.ts +411 -0
- package/src/broadcast/client.ts +302 -0
- package/src/broadcast/index.ts +58 -0
- package/src/cache/cache_manager.ts +56 -0
- package/src/cache/cache_store.ts +31 -0
- package/src/cache/helpers.ts +74 -0
- package/src/cache/http_cache.ts +109 -0
- package/src/cache/index.ts +6 -0
- package/src/cache/memory_store.ts +63 -0
- package/src/cli/bootstrap.ts +37 -0
- package/src/cli/commands/generate_api.ts +74 -0
- package/src/cli/commands/generate_key.ts +46 -0
- package/src/cli/commands/generate_models.ts +48 -0
- package/src/cli/commands/migration_compare.ts +152 -0
- package/src/cli/commands/migration_fresh.ts +123 -0
- package/src/cli/commands/migration_generate.ts +79 -0
- package/src/cli/commands/migration_rollback.ts +53 -0
- package/src/cli/commands/migration_run.ts +44 -0
- package/src/cli/commands/queue_flush.ts +35 -0
- package/src/cli/commands/queue_retry.ts +34 -0
- package/src/cli/commands/queue_work.ts +40 -0
- package/src/cli/commands/scheduler_work.ts +45 -0
- package/src/cli/strav.ts +33 -0
- package/src/config/configuration.ts +105 -0
- package/src/config/loaders/base_loader.ts +69 -0
- package/src/config/loaders/env_loader.ts +112 -0
- package/src/config/loaders/typescript_loader.ts +56 -0
- package/src/config/types.ts +8 -0
- package/src/core/application.ts +4 -0
- package/src/core/container.ts +117 -0
- package/src/core/index.ts +3 -0
- package/src/core/inject.ts +39 -0
- package/src/database/database.ts +54 -0
- package/src/database/index.ts +30 -0
- package/src/database/introspector.ts +446 -0
- package/src/database/migration/differ.ts +308 -0
- package/src/database/migration/file_generator.ts +125 -0
- package/src/database/migration/index.ts +18 -0
- package/src/database/migration/runner.ts +133 -0
- package/src/database/migration/sql_generator.ts +378 -0
- package/src/database/migration/tracker.ts +76 -0
- package/src/database/migration/types.ts +189 -0
- package/src/database/query_builder.ts +474 -0
- package/src/encryption/encryption_manager.ts +209 -0
- package/src/encryption/helpers.ts +158 -0
- package/src/encryption/index.ts +3 -0
- package/src/encryption/types.ts +6 -0
- package/src/events/emitter.ts +101 -0
- package/src/events/index.ts +2 -0
- package/src/exceptions/errors.ts +75 -0
- package/src/exceptions/exception_handler.ts +126 -0
- package/src/exceptions/helpers.ts +25 -0
- package/src/exceptions/http_exception.ts +129 -0
- package/src/exceptions/index.ts +23 -0
- package/src/exceptions/strav_error.ts +11 -0
- package/src/generators/api_generator.ts +972 -0
- package/src/generators/config.ts +87 -0
- package/src/generators/doc_generator.ts +974 -0
- package/src/generators/index.ts +11 -0
- package/src/generators/model_generator.ts +586 -0
- package/src/generators/route_generator.ts +188 -0
- package/src/generators/test_generator.ts +1666 -0
- package/src/helpers/crypto.ts +4 -0
- package/src/helpers/env.ts +50 -0
- package/src/helpers/identity.ts +12 -0
- package/src/helpers/index.ts +4 -0
- package/src/helpers/strings.ts +67 -0
- package/src/http/context.ts +215 -0
- package/src/http/cookie.ts +59 -0
- package/src/http/cors.ts +163 -0
- package/src/http/index.ts +16 -0
- package/src/http/middleware.ts +39 -0
- package/src/http/rate_limit.ts +173 -0
- package/src/http/router.ts +556 -0
- package/src/http/server.ts +79 -0
- package/src/i18n/defaults/en/validation.json +20 -0
- package/src/i18n/helpers.ts +72 -0
- package/src/i18n/i18n_manager.ts +155 -0
- package/src/i18n/index.ts +4 -0
- package/src/i18n/middleware.ts +90 -0
- package/src/i18n/translator.ts +96 -0
- package/src/i18n/types.ts +17 -0
- package/src/logger/index.ts +6 -0
- package/src/logger/logger.ts +100 -0
- package/src/logger/request_logger.ts +19 -0
- package/src/logger/sinks/console_sink.ts +24 -0
- package/src/logger/sinks/file_sink.ts +24 -0
- package/src/logger/sinks/sink.ts +36 -0
- package/src/mail/css_inliner.ts +79 -0
- package/src/mail/helpers.ts +212 -0
- package/src/mail/index.ts +19 -0
- package/src/mail/mail_manager.ts +92 -0
- package/src/mail/transports/log_transport.ts +69 -0
- package/src/mail/transports/resend_transport.ts +59 -0
- package/src/mail/transports/sendgrid_transport.ts +77 -0
- package/src/mail/transports/smtp_transport.ts +48 -0
- package/src/mail/types.ts +80 -0
- package/src/notification/base_notification.ts +67 -0
- package/src/notification/channels/database_channel.ts +30 -0
- package/src/notification/channels/discord_channel.ts +43 -0
- package/src/notification/channels/email_channel.ts +37 -0
- package/src/notification/channels/webhook_channel.ts +45 -0
- package/src/notification/helpers.ts +214 -0
- package/src/notification/index.ts +20 -0
- package/src/notification/notification_manager.ts +126 -0
- package/src/notification/types.ts +122 -0
- package/src/orm/base_model.ts +351 -0
- package/src/orm/decorators.ts +127 -0
- package/src/orm/index.ts +4 -0
- package/src/policy/authorize.ts +44 -0
- package/src/policy/index.ts +3 -0
- package/src/policy/policy_result.ts +13 -0
- package/src/queue/index.ts +11 -0
- package/src/queue/queue.ts +338 -0
- package/src/queue/worker.ts +197 -0
- package/src/scheduler/cron.ts +140 -0
- package/src/scheduler/index.ts +7 -0
- package/src/scheduler/runner.ts +116 -0
- package/src/scheduler/schedule.ts +183 -0
- package/src/scheduler/scheduler.ts +47 -0
- package/src/schema/database_representation.ts +122 -0
- package/src/schema/define_association.ts +60 -0
- package/src/schema/define_schema.ts +46 -0
- package/src/schema/field_builder.ts +155 -0
- package/src/schema/field_definition.ts +66 -0
- package/src/schema/index.ts +21 -0
- package/src/schema/naming.ts +19 -0
- package/src/schema/postgres.ts +109 -0
- package/src/schema/registry.ts +157 -0
- package/src/schema/representation_builder.ts +479 -0
- package/src/schema/type_builder.ts +107 -0
- package/src/schema/types.ts +35 -0
- package/src/session/index.ts +4 -0
- package/src/session/middleware.ts +46 -0
- package/src/session/session.ts +308 -0
- package/src/session/session_manager.ts +81 -0
- package/src/storage/index.ts +13 -0
- package/src/storage/local_driver.ts +46 -0
- package/src/storage/s3_driver.ts +51 -0
- package/src/storage/storage.ts +43 -0
- package/src/storage/storage_manager.ts +59 -0
- package/src/storage/types.ts +42 -0
- package/src/storage/upload.ts +91 -0
- package/src/validation/index.ts +18 -0
- package/src/validation/rules.ts +170 -0
- package/src/validation/validate.ts +41 -0
- package/src/view/cache.ts +47 -0
- package/src/view/client/islands.ts +50 -0
- package/src/view/compiler.ts +185 -0
- package/src/view/engine.ts +139 -0
- package/src/view/escape.ts +14 -0
- package/src/view/index.ts +13 -0
- package/src/view/islands/island_builder.ts +161 -0
- package/src/view/islands/vue_plugin.ts +140 -0
- package/src/view/middleware/static.ts +35 -0
- package/src/view/tokenizer.ts +172 -0
- package/tsconfig.json +4 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import type { PostgreSQLType } from './postgres.ts'
|
|
2
|
+
import type { FieldDefinition, FieldValidator } from './field_definition.ts'
|
|
3
|
+
|
|
4
|
+
/** Initial options that the type builder can pass at construction time. */
|
|
5
|
+
interface FieldBuilderOptions {
|
|
6
|
+
length?: number
|
|
7
|
+
precision?: number
|
|
8
|
+
scale?: number
|
|
9
|
+
references?: string
|
|
10
|
+
enumValues?: string[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Fluent builder for a single field definition.
|
|
15
|
+
*
|
|
16
|
+
* Created by the `t` type builder; collects modifiers, validators,
|
|
17
|
+
* and type parameters, then exposes the result as a {@link FieldDefinition}.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* t.varchar(255).email().unique().required()
|
|
21
|
+
*/
|
|
22
|
+
export default class FieldBuilder {
|
|
23
|
+
private def: FieldDefinition
|
|
24
|
+
|
|
25
|
+
constructor(pgType: PostgreSQLType, options?: FieldBuilderOptions) {
|
|
26
|
+
this.def = {
|
|
27
|
+
pgType,
|
|
28
|
+
required: false,
|
|
29
|
+
nullable: false,
|
|
30
|
+
unique: false,
|
|
31
|
+
primaryKey: false,
|
|
32
|
+
index: false,
|
|
33
|
+
sensitive: false,
|
|
34
|
+
isArray: false,
|
|
35
|
+
arrayDimensions: 1,
|
|
36
|
+
length: options?.length,
|
|
37
|
+
precision: options?.precision,
|
|
38
|
+
scale: options?.scale,
|
|
39
|
+
references: options?.references,
|
|
40
|
+
enumValues: options?.enumValues,
|
|
41
|
+
validators: [],
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// === Modifiers ===
|
|
46
|
+
|
|
47
|
+
/** Mark the field as NOT NULL. */
|
|
48
|
+
required(): this {
|
|
49
|
+
this.def.required = true
|
|
50
|
+
return this
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Mark the field as explicitly nullable. */
|
|
54
|
+
nullable(): this {
|
|
55
|
+
this.def.nullable = true
|
|
56
|
+
return this
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Add a UNIQUE constraint. */
|
|
60
|
+
unique(): this {
|
|
61
|
+
this.def.unique = true
|
|
62
|
+
return this
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Set the column's DEFAULT value. */
|
|
66
|
+
default(value: unknown): this {
|
|
67
|
+
this.def.defaultValue = value
|
|
68
|
+
return this
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Mark the field as a primary key. */
|
|
72
|
+
primaryKey(): this {
|
|
73
|
+
this.def.primaryKey = true
|
|
74
|
+
return this
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Add a database index on this field. */
|
|
78
|
+
index(): this {
|
|
79
|
+
this.def.index = true
|
|
80
|
+
return this
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Mark the field as containing sensitive data. */
|
|
84
|
+
sensitive(): this {
|
|
85
|
+
this.def.sensitive = true
|
|
86
|
+
return this
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Convert this field to a PostgreSQL array type. */
|
|
90
|
+
array(dimensions: number = 1): this {
|
|
91
|
+
this.def.isArray = true
|
|
92
|
+
this.def.arrayDimensions = dimensions
|
|
93
|
+
return this
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// === Validators ===
|
|
97
|
+
|
|
98
|
+
/** Minimum value/length validator. */
|
|
99
|
+
min(value: number): this {
|
|
100
|
+
this.def.validators.push({ type: 'min', params: { value } })
|
|
101
|
+
return this
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Maximum value/length validator. */
|
|
105
|
+
max(value: number): this {
|
|
106
|
+
this.def.validators.push({ type: 'max', params: { value } })
|
|
107
|
+
return this
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Email format validator. */
|
|
111
|
+
email(): this {
|
|
112
|
+
this.def.validators.push({ type: 'email' })
|
|
113
|
+
return this
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** URL format validator. */
|
|
117
|
+
url(): this {
|
|
118
|
+
this.def.validators.push({ type: 'url' })
|
|
119
|
+
return this
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Regex pattern validator. */
|
|
123
|
+
regex(pattern: string | RegExp): this {
|
|
124
|
+
const source = pattern instanceof RegExp ? pattern.source : pattern
|
|
125
|
+
this.def.validators.push({ type: 'regex', params: { pattern: source } })
|
|
126
|
+
return this
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** String length validator (exact or range). */
|
|
130
|
+
length(min: number, max?: number): this {
|
|
131
|
+
this.def.validators.push({ type: 'length', params: { min, max } })
|
|
132
|
+
return this
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Numeric precision. Also sets the type parameter for SQL generation. */
|
|
136
|
+
precision(p: number): this {
|
|
137
|
+
this.def.precision = p
|
|
138
|
+
this.def.validators.push({ type: 'precision', params: { value: p } })
|
|
139
|
+
return this
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Numeric scale. Also sets the type parameter for SQL generation. */
|
|
143
|
+
scale(s: number): this {
|
|
144
|
+
this.def.scale = s
|
|
145
|
+
this.def.validators.push({ type: 'scale', params: { value: s } })
|
|
146
|
+
return this
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// === Accessors ===
|
|
150
|
+
|
|
151
|
+
/** Return the finalized field definition. */
|
|
152
|
+
toDefinition(): FieldDefinition {
|
|
153
|
+
return { ...this.def, validators: [...this.def.validators] }
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { PostgreSQLType } from './postgres.ts'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A single validation constraint.
|
|
5
|
+
* Code generators read these to produce runtime validation rules.
|
|
6
|
+
*/
|
|
7
|
+
export interface FieldValidator {
|
|
8
|
+
type: 'min' | 'max' | 'email' | 'url' | 'regex' | 'length' | 'precision' | 'scale'
|
|
9
|
+
params?: Record<string, unknown>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Serializable description of a single schema field.
|
|
14
|
+
*
|
|
15
|
+
* Produced by the {@link FieldBuilder} fluent chain, consumed by
|
|
16
|
+
* code generators, migration engines, and validators.
|
|
17
|
+
*/
|
|
18
|
+
export interface FieldDefinition {
|
|
19
|
+
/** The resolved PostgreSQL column type (e.g. 'varchar', 'integer', 'jsonb'). */
|
|
20
|
+
pgType: PostgreSQLType
|
|
21
|
+
|
|
22
|
+
/** Whether the field is NOT NULL. */
|
|
23
|
+
required: boolean
|
|
24
|
+
|
|
25
|
+
/** Whether the field explicitly allows NULL. */
|
|
26
|
+
nullable: boolean
|
|
27
|
+
|
|
28
|
+
/** Whether the field has a UNIQUE constraint. */
|
|
29
|
+
unique: boolean
|
|
30
|
+
|
|
31
|
+
/** Default value expression (literal or SQL expression string). */
|
|
32
|
+
defaultValue?: unknown
|
|
33
|
+
|
|
34
|
+
/** Whether the field is a primary key. */
|
|
35
|
+
primaryKey: boolean
|
|
36
|
+
|
|
37
|
+
/** Whether the field should have a database index. */
|
|
38
|
+
index: boolean
|
|
39
|
+
|
|
40
|
+
/** Whether the field contains sensitive data (excluded from logs/serialization). */
|
|
41
|
+
sensitive: boolean
|
|
42
|
+
|
|
43
|
+
/** Whether the field is an array. If true, pgType describes the element type. */
|
|
44
|
+
isArray: boolean
|
|
45
|
+
|
|
46
|
+
/** Array dimensions (1 = one-dimensional, 2 = two-dimensional, etc.). */
|
|
47
|
+
arrayDimensions: number
|
|
48
|
+
|
|
49
|
+
/** Maximum length for varchar/char, or bit length for bit types. */
|
|
50
|
+
length?: number
|
|
51
|
+
|
|
52
|
+
/** Precision for decimal/numeric types. */
|
|
53
|
+
precision?: number
|
|
54
|
+
|
|
55
|
+
/** Scale for decimal/numeric types. */
|
|
56
|
+
scale?: number
|
|
57
|
+
|
|
58
|
+
/** For reference fields: the name of the referenced table/schema. */
|
|
59
|
+
references?: string
|
|
60
|
+
|
|
61
|
+
/** For enum fields: the list of allowed string values. */
|
|
62
|
+
enumValues?: string[]
|
|
63
|
+
|
|
64
|
+
/** Validation constraints collected from the fluent chain. */
|
|
65
|
+
validators: FieldValidator[]
|
|
66
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export { default as defineSchema } from './define_schema.ts'
|
|
2
|
+
export { default as defineAssociation } from './define_association.ts'
|
|
3
|
+
export { default as t } from './type_builder.ts'
|
|
4
|
+
export { default as FieldBuilder } from './field_builder.ts'
|
|
5
|
+
export { default as SchemaRegistry } from './registry.ts'
|
|
6
|
+
export { default as RepresentationBuilder } from './representation_builder.ts'
|
|
7
|
+
export { toSnakeCase, serialToIntegerType } from './naming.ts'
|
|
8
|
+
export type { FieldDefinition, FieldValidator } from './field_definition.ts'
|
|
9
|
+
export { Archetype } from './types.ts'
|
|
10
|
+
export type { SchemaDefinition, SchemaInput } from './types.ts'
|
|
11
|
+
export type {
|
|
12
|
+
DatabaseRepresentation,
|
|
13
|
+
TableDefinition,
|
|
14
|
+
ColumnDefinition,
|
|
15
|
+
EnumDefinition,
|
|
16
|
+
ForeignKeyConstraint,
|
|
17
|
+
PrimaryKeyConstraint,
|
|
18
|
+
UniqueConstraint,
|
|
19
|
+
IndexDefinition,
|
|
20
|
+
DefaultValue,
|
|
21
|
+
} from './database_representation.ts'
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { PostgreSQLType } from './postgres.ts'
|
|
2
|
+
|
|
3
|
+
export { toSnakeCase } from '../helpers/strings.ts'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Map a serial/bigserial/smallserial pgType to the corresponding integer type
|
|
7
|
+
* used for foreign key columns. All other types pass through unchanged.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* serialToIntegerType('serial') // 'integer'
|
|
11
|
+
* serialToIntegerType('bigserial') // 'bigint'
|
|
12
|
+
* serialToIntegerType('uuid') // 'uuid'
|
|
13
|
+
*/
|
|
14
|
+
export function serialToIntegerType(pgType: PostgreSQLType): PostgreSQLType {
|
|
15
|
+
if (pgType === 'serial') return 'integer'
|
|
16
|
+
if (pgType === 'bigserial') return 'bigint'
|
|
17
|
+
if (pgType === 'smallserial') return 'smallint'
|
|
18
|
+
return pgType
|
|
19
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// Complete PostgreSQL type system
|
|
2
|
+
export type PostgreSQLType =
|
|
3
|
+
| PostgreSQLNumericTypes
|
|
4
|
+
| PostgreSQLMonetaryTypes
|
|
5
|
+
| PostgreSQLCharacterTypes
|
|
6
|
+
| PostgreSQLBinaryTypes
|
|
7
|
+
| PostgreSQLDateTimeTypes
|
|
8
|
+
| PostgreSQLBooleanTypes
|
|
9
|
+
| PostgreSQLUUIDTypes
|
|
10
|
+
| PostgreSQLNetworkTypes
|
|
11
|
+
| PostgreSQLBitStringTypes
|
|
12
|
+
| PostgreSQLTextSearchTypes
|
|
13
|
+
| PostgreSQLJSONTypes
|
|
14
|
+
| PostgreSQLGeometricTypes
|
|
15
|
+
| PostgreSQLRangeTypes
|
|
16
|
+
| PostgreSQLXMLTypes
|
|
17
|
+
| PostgreSQLArrayType
|
|
18
|
+
| PostgreSQLCustomType
|
|
19
|
+
|
|
20
|
+
/** Integer, arbitrary precision, floating-point, and auto-incrementing types. */
|
|
21
|
+
export type PostgreSQLNumericTypes =
|
|
22
|
+
| 'smallint'
|
|
23
|
+
| 'integer'
|
|
24
|
+
| 'bigint'
|
|
25
|
+
| 'decimal'
|
|
26
|
+
| 'numeric'
|
|
27
|
+
| 'real'
|
|
28
|
+
| 'double_precision'
|
|
29
|
+
| 'smallserial'
|
|
30
|
+
| 'serial'
|
|
31
|
+
| 'bigserial'
|
|
32
|
+
|
|
33
|
+
/** Currency amounts with locale-aware formatting. */
|
|
34
|
+
export type PostgreSQLMonetaryTypes = 'money'
|
|
35
|
+
|
|
36
|
+
/** Fixed-length, variable-length, and unlimited text. */
|
|
37
|
+
export type PostgreSQLCharacterTypes =
|
|
38
|
+
| 'character_varying'
|
|
39
|
+
| 'varchar'
|
|
40
|
+
| 'character'
|
|
41
|
+
| 'char'
|
|
42
|
+
| 'text'
|
|
43
|
+
|
|
44
|
+
/** Raw binary data (byte array). */
|
|
45
|
+
export type PostgreSQLBinaryTypes = 'bytea'
|
|
46
|
+
|
|
47
|
+
/** Dates, times, timestamps, and intervals. */
|
|
48
|
+
export type PostgreSQLDateTimeTypes =
|
|
49
|
+
| 'timestamp'
|
|
50
|
+
| 'timestamptz'
|
|
51
|
+
| 'date'
|
|
52
|
+
| 'time'
|
|
53
|
+
| 'timetz'
|
|
54
|
+
| 'interval'
|
|
55
|
+
|
|
56
|
+
/** True/false logical value. */
|
|
57
|
+
export type PostgreSQLBooleanTypes = 'boolean'
|
|
58
|
+
|
|
59
|
+
/** RFC 4122 universally unique identifiers. */
|
|
60
|
+
export type PostgreSQLUUIDTypes = 'uuid'
|
|
61
|
+
|
|
62
|
+
/** IPv4/IPv6 addresses, CIDR blocks, and MAC addresses. */
|
|
63
|
+
export type PostgreSQLNetworkTypes = 'inet' | 'cidr' | 'macaddr' | 'macaddr8'
|
|
64
|
+
|
|
65
|
+
/** Fixed-length and variable-length bit strings. */
|
|
66
|
+
export type PostgreSQLBitStringTypes = 'bit' | 'bit_varying'
|
|
67
|
+
|
|
68
|
+
/** Full-text search document and query representations. */
|
|
69
|
+
export type PostgreSQLTextSearchTypes = 'tsvector' | 'tsquery'
|
|
70
|
+
|
|
71
|
+
/** JSON data stored as text or decomposed binary. */
|
|
72
|
+
export type PostgreSQLJSONTypes = 'json' | 'jsonb'
|
|
73
|
+
|
|
74
|
+
/** XML document data. */
|
|
75
|
+
export type PostgreSQLXMLTypes = 'xml'
|
|
76
|
+
|
|
77
|
+
/** 2D geometric primitives: points, lines, segments, boxes, paths, polygons, and circles. */
|
|
78
|
+
export type PostgreSQLGeometricTypes =
|
|
79
|
+
| 'point'
|
|
80
|
+
| 'line'
|
|
81
|
+
| 'lseg'
|
|
82
|
+
| 'box'
|
|
83
|
+
| 'path'
|
|
84
|
+
| 'polygon'
|
|
85
|
+
| 'circle'
|
|
86
|
+
|
|
87
|
+
/** Ranges over integer, numeric, timestamp, and date values. */
|
|
88
|
+
export type PostgreSQLRangeTypes =
|
|
89
|
+
| 'int4range'
|
|
90
|
+
| 'int8range'
|
|
91
|
+
| 'numrange'
|
|
92
|
+
| 'tsrange'
|
|
93
|
+
| 'tstzrange'
|
|
94
|
+
| 'daterange'
|
|
95
|
+
|
|
96
|
+
/** Typed multi-dimensional array of any non-array PostgreSQL type. */
|
|
97
|
+
export interface PostgreSQLArrayType {
|
|
98
|
+
type: 'array'
|
|
99
|
+
element: Exclude<PostgreSQLType, PostgreSQLArrayType>
|
|
100
|
+
dimensions?: number
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** User-defined type, enum, or domain. */
|
|
104
|
+
export interface PostgreSQLCustomType {
|
|
105
|
+
type: 'custom'
|
|
106
|
+
name: string // Custom type name or enum name
|
|
107
|
+
definition?: string // For inline enum definitions
|
|
108
|
+
values?: string[] // For enum types - the actual values array
|
|
109
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { readdirSync } from 'node:fs'
|
|
2
|
+
import { join, resolve } from 'node:path'
|
|
3
|
+
import type { SchemaDefinition } from './types.ts'
|
|
4
|
+
import type { DatabaseRepresentation } from './database_representation.ts'
|
|
5
|
+
import RepresentationBuilder from './representation_builder.ts'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Discovers, stores, validates, and provides dependency-ordered
|
|
9
|
+
* access to all schema definitions in the application.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* const registry = new SchemaRegistry()
|
|
13
|
+
* await registry.discover('database/schemas')
|
|
14
|
+
* registry.validate()
|
|
15
|
+
* const ordered = registry.resolve() // topologically sorted
|
|
16
|
+
*/
|
|
17
|
+
export default class SchemaRegistry {
|
|
18
|
+
private schemas = new Map<string, SchemaDefinition>()
|
|
19
|
+
|
|
20
|
+
/** Register a single schema definition. */
|
|
21
|
+
register(schema: SchemaDefinition): this {
|
|
22
|
+
if (this.schemas.has(schema.name)) {
|
|
23
|
+
throw new Error(`Schema "${schema.name}" is already registered`)
|
|
24
|
+
}
|
|
25
|
+
this.schemas.set(schema.name, schema)
|
|
26
|
+
return this
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Retrieve a schema by name. */
|
|
30
|
+
get(name: string): SchemaDefinition | undefined {
|
|
31
|
+
return this.schemas.get(name)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Check whether a schema with the given name exists. */
|
|
35
|
+
has(name: string): boolean {
|
|
36
|
+
return this.schemas.has(name)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Return all registered schemas as an array. */
|
|
40
|
+
all(): SchemaDefinition[] {
|
|
41
|
+
return Array.from(this.schemas.values())
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Scan a directory for schema files and register all discovered schemas.
|
|
46
|
+
* Each `.ts` file must default-export a {@link SchemaDefinition}.
|
|
47
|
+
*/
|
|
48
|
+
async discover(schemasPath: string): Promise<void> {
|
|
49
|
+
const absPath = resolve(schemasPath)
|
|
50
|
+
let files: string[]
|
|
51
|
+
try {
|
|
52
|
+
files = readdirSync(absPath)
|
|
53
|
+
} catch {
|
|
54
|
+
throw new Error(`Schemas directory not found: ${schemasPath}`)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
for (const file of files) {
|
|
58
|
+
if (!file.endsWith('.ts')) continue
|
|
59
|
+
const filePath = join(absPath, file)
|
|
60
|
+
const mod = await import(filePath)
|
|
61
|
+
const schema: SchemaDefinition = mod.default
|
|
62
|
+
if (!schema || !schema.name) {
|
|
63
|
+
throw new Error(`Schema file "${file}" does not export a valid SchemaDefinition`)
|
|
64
|
+
}
|
|
65
|
+
this.register(schema)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Validate all schemas: check that every reference and parent
|
|
71
|
+
* points to a registered schema.
|
|
72
|
+
*/
|
|
73
|
+
validate(): void {
|
|
74
|
+
for (const schema of this.schemas.values()) {
|
|
75
|
+
if (schema.parent && !this.schemas.has(schema.parent)) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`Schema "${schema.name}" references parent "${schema.parent}" which is not registered`
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (schema.associates) {
|
|
82
|
+
for (const assoc of schema.associates) {
|
|
83
|
+
if (!this.schemas.has(assoc)) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`Schema "${schema.name}" associates with "${assoc}" which is not registered`
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
for (const [fieldName, fieldDef] of Object.entries(schema.fields)) {
|
|
92
|
+
if (fieldDef.references && !this.schemas.has(fieldDef.references)) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
`Schema "${schema.name}" field "${fieldName}" references "${fieldDef.references}" which is not registered`
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Return schemas in dependency order (topological sort).
|
|
103
|
+
* Schemas with no dependencies come first.
|
|
104
|
+
* Throws if a circular dependency is detected.
|
|
105
|
+
*/
|
|
106
|
+
resolve(): SchemaDefinition[] {
|
|
107
|
+
const visited = new Set<string>()
|
|
108
|
+
const visiting = new Set<string>()
|
|
109
|
+
const sorted: SchemaDefinition[] = []
|
|
110
|
+
|
|
111
|
+
const visit = (name: string, path: string[]) => {
|
|
112
|
+
if (visited.has(name)) return
|
|
113
|
+
if (visiting.has(name)) {
|
|
114
|
+
throw new Error(`Circular dependency detected: ${[...path, name].join(' -> ')}`)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
visiting.add(name)
|
|
118
|
+
const schema = this.schemas.get(name)!
|
|
119
|
+
for (const dep of this.getDependencies(schema)) {
|
|
120
|
+
visit(dep, [...path, name])
|
|
121
|
+
}
|
|
122
|
+
visiting.delete(name)
|
|
123
|
+
visited.add(name)
|
|
124
|
+
sorted.push(schema)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
for (const name of this.schemas.keys()) {
|
|
128
|
+
visit(name, [])
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return sorted
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Generate the {@link DatabaseRepresentation} from all registered schemas.
|
|
136
|
+
* Must be called after {@link validate} to ensure all references are resolvable.
|
|
137
|
+
*/
|
|
138
|
+
buildRepresentation(): DatabaseRepresentation {
|
|
139
|
+
const ordered = this.resolve()
|
|
140
|
+
return new RepresentationBuilder(ordered).build()
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Collect all schema names that the given schema depends on. */
|
|
144
|
+
private getDependencies(schema: SchemaDefinition): string[] {
|
|
145
|
+
const deps = new Set<string>()
|
|
146
|
+
|
|
147
|
+
if (schema.parent) deps.add(schema.parent)
|
|
148
|
+
if (schema.associates) {
|
|
149
|
+
for (const assoc of schema.associates) deps.add(assoc)
|
|
150
|
+
}
|
|
151
|
+
for (const fieldDef of Object.values(schema.fields)) {
|
|
152
|
+
if (fieldDef.references) deps.add(fieldDef.references)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return Array.from(deps)
|
|
156
|
+
}
|
|
157
|
+
}
|