@getstrata/core 0.3.2 → 0.3.9
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/dist/core/auth/abilityChecker.d.ts +6 -0
- package/dist/core/auth/policy.d.ts +2 -2
- package/dist/core/database/bindConnection.d.ts +3 -0
- package/dist/core/database/migrations/runner.d.ts +19 -0
- package/dist/core/database/migrations/types.d.ts +14 -0
- package/dist/core/database/model.d.ts +28 -0
- package/dist/core/http/requireAbilityMiddleware.d.ts +2 -2
- package/dist/core/http/webFormRequest.d.ts +1 -0
- package/dist/core/validation/rules.d.ts +17 -0
- package/dist/framework/public-api.d.ts +8 -1
- package/dist/index.js +267 -5
- package/package.json +1 -1
|
@@ -9,7 +9,7 @@ declare class PolicyGate {
|
|
|
9
9
|
constructor();
|
|
10
10
|
private readonly policies;
|
|
11
11
|
register(resource: string, policy: Policy): void;
|
|
12
|
-
allows(resource: string, action:
|
|
13
|
-
authorize(resource: string, action:
|
|
12
|
+
allows(resource: string, action: string, user?: unknown, model?: unknown): boolean;
|
|
13
|
+
authorize(resource: string, action: string, user?: unknown, model?: unknown): void;
|
|
14
14
|
}
|
|
15
15
|
export { Policy, PolicyGate };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Migration, MigrationDatabase, MigrationStatus } from "./types";
|
|
2
|
+
type AppliedMigrationRow = {
|
|
3
|
+
name: string;
|
|
4
|
+
batch: number | string;
|
|
5
|
+
};
|
|
6
|
+
declare function ensureMigrationsTable(db: MigrationDatabase): Promise<void>;
|
|
7
|
+
declare function getAppliedMigrations(db: MigrationDatabase): Promise<AppliedMigrationRow[]>;
|
|
8
|
+
declare function loadMigrationsFromDirectory(directory: string): Promise<Migration[]>;
|
|
9
|
+
declare function getMigrationStatus(db: MigrationDatabase, migrations: Migration[]): Promise<MigrationStatus[]>;
|
|
10
|
+
declare function migrateDatabase(db: MigrationDatabase, migrations: Migration[], options?: {
|
|
11
|
+
onMigration?: (name: string) => void;
|
|
12
|
+
}): Promise<number>;
|
|
13
|
+
declare function rollbackDatabase(db: MigrationDatabase, migrations: Migration[], options?: {
|
|
14
|
+
onMigration?: (name: string) => void;
|
|
15
|
+
}): Promise<number>;
|
|
16
|
+
declare function freshDatabase(db: MigrationDatabase, migrations: Migration[], options?: {
|
|
17
|
+
onMigration?: (name: string) => void;
|
|
18
|
+
}): Promise<void>;
|
|
19
|
+
export { ensureMigrationsTable, freshDatabase, getAppliedMigrations, getMigrationStatus, loadMigrationsFromDirectory, migrateDatabase, rollbackDatabase, };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
interface MigrationDatabase {
|
|
2
|
+
unsafe<T = unknown>(query: string, params?: readonly unknown[]): Promise<T[]>;
|
|
3
|
+
}
|
|
4
|
+
interface Migration {
|
|
5
|
+
name: string;
|
|
6
|
+
up(db: MigrationDatabase): Promise<void>;
|
|
7
|
+
down(db: MigrationDatabase): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
type MigrationStatus = {
|
|
10
|
+
name: string;
|
|
11
|
+
status: "up" | "pending";
|
|
12
|
+
batch: number | null;
|
|
13
|
+
};
|
|
14
|
+
export type { Migration, MigrationDatabase, MigrationStatus };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type BaseRepository from "./baseRepository";
|
|
2
|
+
import type { BelongsToRelation, HasManyRelation } from "./relationships";
|
|
3
|
+
import type { RepositoryQuery } from "./repositoryQuery";
|
|
4
|
+
import type { QueryOptions, QueryWhere } from "./types";
|
|
5
|
+
interface ModelConstructor<TEntity extends object, PrimaryKey extends keyof TEntity & string, TModel extends Model<TEntity, PrimaryKey>> {
|
|
6
|
+
new (attributes: TEntity, repository: BaseRepository<TEntity, PrimaryKey>): TModel;
|
|
7
|
+
}
|
|
8
|
+
declare class Model<TEntity extends object, PrimaryKey extends keyof TEntity & string> {
|
|
9
|
+
protected readonly attributes: TEntity;
|
|
10
|
+
protected readonly repository: BaseRepository<TEntity, PrimaryKey>;
|
|
11
|
+
constructor(attributes: TEntity, repository: BaseRepository<TEntity, PrimaryKey>);
|
|
12
|
+
get<K extends keyof TEntity>(key: K): TEntity[K];
|
|
13
|
+
get id(): TEntity[PrimaryKey];
|
|
14
|
+
toObject(): TEntity;
|
|
15
|
+
protected primaryKey(): PrimaryKey;
|
|
16
|
+
static repository<TEntity extends object, PrimaryKey extends keyof TEntity & string, TModel extends Model<TEntity, PrimaryKey>>(this: ModelConstructor<TEntity, PrimaryKey, TModel>): BaseRepository<TEntity, PrimaryKey>;
|
|
17
|
+
static query<TEntity extends object, PrimaryKey extends keyof TEntity & string, TModel extends Model<TEntity, PrimaryKey>>(this: ModelConstructor<TEntity, PrimaryKey, TModel>): RepositoryQuery<TEntity, PrimaryKey>;
|
|
18
|
+
static find<TEntity extends object, PrimaryKey extends keyof TEntity & string, TModel extends Model<TEntity, PrimaryKey>>(this: ModelConstructor<TEntity, PrimaryKey, TModel>, id: TEntity[PrimaryKey]): Promise<TModel | null>;
|
|
19
|
+
static findOrFail<TEntity extends object, PrimaryKey extends keyof TEntity & string, TModel extends Model<TEntity, PrimaryKey>>(this: ModelConstructor<TEntity, PrimaryKey, TModel>, id: TEntity[PrimaryKey], errorFactory?: (id: TEntity[PrimaryKey]) => Error): Promise<TModel>;
|
|
20
|
+
static all<TEntity extends object, PrimaryKey extends keyof TEntity & string, TModel extends Model<TEntity, PrimaryKey>>(this: ModelConstructor<TEntity, PrimaryKey, TModel>, options?: QueryOptions<TEntity>): Promise<TModel[]>;
|
|
21
|
+
static firstWhere<TEntity extends object, PrimaryKey extends keyof TEntity & string, TModel extends Model<TEntity, PrimaryKey>>(this: ModelConstructor<TEntity, PrimaryKey, TModel>, where: QueryWhere<TEntity>, options?: Omit<QueryOptions<TEntity>, "where">): Promise<TModel | null>;
|
|
22
|
+
loadHasMany<TChild extends object, LocalKey extends keyof TEntity & string, ForeignKey extends keyof TChild & string, Alias extends string>(as: Alias, relation: HasManyRelation<TEntity, TChild, LocalKey, ForeignKey>, childRepository: BaseRepository<TChild, keyof TChild & string>, options?: Omit<QueryOptions<TChild>, "where">): Promise<this & Record<Alias, TChild[]>>;
|
|
23
|
+
loadBelongsTo<TParent extends object, ForeignKey extends keyof TEntity & string, OwnerKey extends keyof TParent & string, Alias extends string>(as: Alias, relation: BelongsToRelation<TEntity, TParent, ForeignKey, OwnerKey>, parentRepository: BaseRepository<TParent, OwnerKey>, options?: Omit<QueryOptions<TParent>, "where">): Promise<this & Record<Alias, TParent | undefined>>;
|
|
24
|
+
mergeAttributes(patch: Partial<TEntity>): this;
|
|
25
|
+
}
|
|
26
|
+
declare function registerModelRepository<TEntity extends object, PrimaryKey extends keyof TEntity & string, TModel extends Model<TEntity, PrimaryKey>>(model: ModelConstructor<TEntity, PrimaryKey, TModel>, repository: BaseRepository<TEntity, PrimaryKey>): ModelConstructor<TEntity, PrimaryKey, TModel> & typeof Model;
|
|
27
|
+
export type { ModelConstructor };
|
|
28
|
+
export { Model, registerModelRepository };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import type { AbilityChecker } from "../auth/abilityChecker";
|
|
2
2
|
import type { Middleware } from "./middleware";
|
|
3
|
-
declare function createRequireAbilityMiddleware(
|
|
3
|
+
declare function createRequireAbilityMiddleware(abilityChecker: AbilityChecker): (ability: string) => Middleware;
|
|
4
4
|
export { createRequireAbilityMiddleware };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
declare abstract class WebFormRequest<TOutput> {
|
|
2
2
|
authorize(_request: Request): boolean | Promise<boolean>;
|
|
3
3
|
protected abstract parse(payload: unknown): TOutput;
|
|
4
|
+
validatePayload(payload: unknown): TOutput;
|
|
4
5
|
validate(request: Request): Promise<TOutput>;
|
|
5
6
|
}
|
|
6
7
|
export { WebFormRequest };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
type ValidationRule = (field: string, value: unknown, payload: Record<string, unknown>) => string | undefined;
|
|
2
|
+
type ValidationSchema = Record<string, ValidationRule[]>;
|
|
3
|
+
declare function required(): ValidationRule;
|
|
4
|
+
declare function stringRule(): ValidationRule;
|
|
5
|
+
declare function minLength(minimum: number): ValidationRule;
|
|
6
|
+
declare function maxLength(maximum: number): ValidationRule;
|
|
7
|
+
declare function pattern(expression: RegExp): ValidationRule;
|
|
8
|
+
declare function enumRule<TValue extends string>(allowedValues: readonly TValue[]): ValidationRule;
|
|
9
|
+
declare function optional(): ValidationRule;
|
|
10
|
+
declare function integerRule(): ValidationRule;
|
|
11
|
+
declare function emailRule(): ValidationRule;
|
|
12
|
+
declare function confirmed(fieldName: string): ValidationRule;
|
|
13
|
+
declare function positiveIntegerRule(): ValidationRule;
|
|
14
|
+
declare function integerRange(minimum: number, maximum: number): ValidationRule;
|
|
15
|
+
declare function validateObject(payload: unknown, schema: ValidationSchema): Record<string, unknown>;
|
|
16
|
+
export type { ValidationRule, ValidationSchema };
|
|
17
|
+
export { confirmed, emailRule, enumRule, integerRange, integerRule, maxLength, minLength, optional, pattern, positiveIntegerRule, required, stringRule, validateObject, };
|
|
@@ -4,13 +4,18 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export type { ServiceProvider } from "../bootstrap/contracts";
|
|
6
6
|
export { ConfigStore, resolveService, ServiceContainer, } from "../bootstrap/contracts";
|
|
7
|
+
export type { AbilityChecker } from "../core/auth/abilityChecker";
|
|
7
8
|
export type { AuthUser } from "../core/auth/authContext";
|
|
8
9
|
export { currentAuthUser, runWithAuthUser } from "../core/auth/authContext";
|
|
9
|
-
export { Policy } from "../core/auth/policy";
|
|
10
|
+
export { Policy, PolicyGate } from "../core/auth/policy";
|
|
10
11
|
export { default as CacheRepository } from "../core/cache/repository";
|
|
11
12
|
export { CACHE_TAGS } from "../core/cache/tags";
|
|
12
13
|
export type { DatabaseConnection } from "../core/database/baseRepository";
|
|
13
14
|
export { default as BaseRepository } from "../core/database/baseRepository";
|
|
15
|
+
export { bindDatabaseConnection } from "../core/database/bindConnection";
|
|
16
|
+
export { freshDatabase, getMigrationStatus, loadMigrationsFromDirectory, migrateDatabase, rollbackDatabase, } from "../core/database/migrations/runner";
|
|
17
|
+
export type { Migration, MigrationDatabase, MigrationStatus, } from "../core/database/migrations/types";
|
|
18
|
+
export { Model, registerModelRepository } from "../core/database/model";
|
|
14
19
|
export type { BelongsToRelation, HasManyRelation } from "../core/database/relationships";
|
|
15
20
|
export { belongsTo, hasMany, indexBelongsToRelation, indexHasManyRelation, } from "../core/database/relationships";
|
|
16
21
|
export { RepositoryQuery } from "../core/database/repositoryQuery";
|
|
@@ -33,3 +38,5 @@ export type { Queue, QueuePriority } from "../core/queue";
|
|
|
33
38
|
export { AsyncQueue, createQueue, Job, SyncQueue } from "../core/queue";
|
|
34
39
|
export type { StorageDriver } from "../core/storage/storage";
|
|
35
40
|
export { LocalStorageDriver, StorageManager } from "../core/storage/storage";
|
|
41
|
+
export type { ValidationRule, ValidationSchema } from "../core/validation/rules";
|
|
42
|
+
export { emailRule, maxLength, minLength, required, stringRule, validateObject, } from "../core/validation/rules";
|
package/dist/index.js
CHANGED
|
@@ -293,6 +293,9 @@ function getDatabase() {
|
|
|
293
293
|
}
|
|
294
294
|
return connectionHolder.connection;
|
|
295
295
|
}
|
|
296
|
+
function resetDatabaseConnectionForTests(connection) {
|
|
297
|
+
connectionHolder.connection = connection;
|
|
298
|
+
}
|
|
296
299
|
var POOL_CONNECTION_METHODS = new Set(["begin", "close", "connect"]);
|
|
297
300
|
function resolveDatabase() {
|
|
298
301
|
return getActiveDatabaseConnection(getDatabase());
|
|
@@ -1024,6 +1027,170 @@ class BaseRepository {
|
|
|
1024
1027
|
}
|
|
1025
1028
|
}
|
|
1026
1029
|
var baseRepository_default = BaseRepository;
|
|
1030
|
+
// ../../src/core/database/bindConnection.ts
|
|
1031
|
+
function bindDatabaseConnection(connection) {
|
|
1032
|
+
resetDatabaseConnectionForTests(connection);
|
|
1033
|
+
}
|
|
1034
|
+
// ../../src/core/database/migrations/runner.ts
|
|
1035
|
+
import { readdir } from "fs/promises";
|
|
1036
|
+
import { join } from "path";
|
|
1037
|
+
import { pathToFileURL } from "url";
|
|
1038
|
+
var MIGRATIONS_TABLE = "framework_migrations";
|
|
1039
|
+
async function ensureMigrationsTable(db2) {
|
|
1040
|
+
await db2.unsafe(`
|
|
1041
|
+
CREATE TABLE IF NOT EXISTS ${MIGRATIONS_TABLE} (
|
|
1042
|
+
name TEXT PRIMARY KEY,
|
|
1043
|
+
batch INTEGER NOT NULL,
|
|
1044
|
+
run_on TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
1045
|
+
)
|
|
1046
|
+
`);
|
|
1047
|
+
}
|
|
1048
|
+
async function getAppliedMigrations(db2) {
|
|
1049
|
+
await ensureMigrationsTable(db2);
|
|
1050
|
+
return await db2.unsafe(`
|
|
1051
|
+
SELECT name, batch
|
|
1052
|
+
FROM ${MIGRATIONS_TABLE}
|
|
1053
|
+
ORDER BY batch ASC, name ASC
|
|
1054
|
+
`);
|
|
1055
|
+
}
|
|
1056
|
+
async function loadMigrationsFromDirectory(directory) {
|
|
1057
|
+
const entries = await readdir(directory);
|
|
1058
|
+
const migrationFiles = entries.filter((entry) => entry.endsWith(".ts") && entry !== "types.ts" && entry !== "index.ts" && entry !== "runner.ts").sort();
|
|
1059
|
+
const loadedMigrations = await Promise.all(migrationFiles.map(async (fileName) => {
|
|
1060
|
+
const moduleUrl = pathToFileURL(join(directory, fileName)).href;
|
|
1061
|
+
const module = await import(moduleUrl);
|
|
1062
|
+
return module.default;
|
|
1063
|
+
}));
|
|
1064
|
+
return loadedMigrations.filter((migration) => migration?.name !== undefined);
|
|
1065
|
+
}
|
|
1066
|
+
async function getMigrationStatus(db2, migrations) {
|
|
1067
|
+
const applied = await getAppliedMigrations(db2);
|
|
1068
|
+
const appliedByName = new Map(applied.map(({ name, batch }) => [name, Number(batch)]));
|
|
1069
|
+
return migrations.map(({ name }) => ({
|
|
1070
|
+
name,
|
|
1071
|
+
status: appliedByName.has(name) ? "up" : "pending",
|
|
1072
|
+
batch: appliedByName.get(name) ?? null
|
|
1073
|
+
}));
|
|
1074
|
+
}
|
|
1075
|
+
async function migrateDatabase(db2, migrations, options = {}) {
|
|
1076
|
+
const applied = await getAppliedMigrations(db2);
|
|
1077
|
+
const appliedNames = new Set(applied.map(({ name }) => name));
|
|
1078
|
+
const nextBatch = applied.reduce((currentMax, { batch }) => Math.max(currentMax, Number(batch)), 0) + 1;
|
|
1079
|
+
const pendingMigrations = migrations.filter(({ name }) => !appliedNames.has(name));
|
|
1080
|
+
for (const migration of pendingMigrations) {
|
|
1081
|
+
options.onMigration?.(migration.name);
|
|
1082
|
+
await migration.up(db2);
|
|
1083
|
+
await db2.unsafe(`INSERT INTO ${MIGRATIONS_TABLE} (name, batch) VALUES ($1, $2) ON CONFLICT (name) DO NOTHING`, [migration.name, nextBatch]);
|
|
1084
|
+
}
|
|
1085
|
+
return pendingMigrations.length;
|
|
1086
|
+
}
|
|
1087
|
+
async function rollbackDatabase(db2, migrations, options = {}) {
|
|
1088
|
+
const applied = await getAppliedMigrations(db2);
|
|
1089
|
+
if (applied.length === 0) {
|
|
1090
|
+
return 0;
|
|
1091
|
+
}
|
|
1092
|
+
const lastBatch = applied.reduce((currentMax, { batch }) => Math.max(currentMax, Number(batch)), 0);
|
|
1093
|
+
const migrationsToRollback = new Set(applied.filter(({ batch }) => Number(batch) === lastBatch).map(({ name }) => name));
|
|
1094
|
+
let rolledBack = 0;
|
|
1095
|
+
for (const migration of [...migrations].reverse()) {
|
|
1096
|
+
if (!migrationsToRollback.has(migration.name)) {
|
|
1097
|
+
continue;
|
|
1098
|
+
}
|
|
1099
|
+
options.onMigration?.(migration.name);
|
|
1100
|
+
await migration.down(db2);
|
|
1101
|
+
await db2.unsafe(`DELETE FROM ${MIGRATIONS_TABLE} WHERE name = $1`, [migration.name]);
|
|
1102
|
+
rolledBack += 1;
|
|
1103
|
+
}
|
|
1104
|
+
return rolledBack;
|
|
1105
|
+
}
|
|
1106
|
+
async function freshDatabase(db2, migrations, options = {}) {
|
|
1107
|
+
const applied = await getAppliedMigrations(db2);
|
|
1108
|
+
const appliedNames = new Set(applied.map(({ name }) => name));
|
|
1109
|
+
const appliedMigrations = migrations.filter(({ name }) => appliedNames.has(name));
|
|
1110
|
+
for (const migration of [...appliedMigrations].reverse()) {
|
|
1111
|
+
options.onMigration?.(migration.name);
|
|
1112
|
+
await migration.down(db2);
|
|
1113
|
+
}
|
|
1114
|
+
if (appliedMigrations.length > 0) {
|
|
1115
|
+
await db2.unsafe(`DELETE FROM ${MIGRATIONS_TABLE}`);
|
|
1116
|
+
}
|
|
1117
|
+
await migrateDatabase(db2, migrations, options);
|
|
1118
|
+
}
|
|
1119
|
+
// ../../src/core/database/model.ts
|
|
1120
|
+
var modelRepositories = new WeakMap;
|
|
1121
|
+
function resolveModelRepository(model) {
|
|
1122
|
+
const repository = modelRepositories.get(model);
|
|
1123
|
+
if (!repository) {
|
|
1124
|
+
throw new Error(`${model.name}.repository() is not implemented.`);
|
|
1125
|
+
}
|
|
1126
|
+
return repository;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
class Model {
|
|
1130
|
+
attributes;
|
|
1131
|
+
repository;
|
|
1132
|
+
constructor(attributes, repository) {
|
|
1133
|
+
this.attributes = attributes;
|
|
1134
|
+
this.repository = repository;
|
|
1135
|
+
}
|
|
1136
|
+
get(key) {
|
|
1137
|
+
return this.attributes[key];
|
|
1138
|
+
}
|
|
1139
|
+
get id() {
|
|
1140
|
+
return this.attributes[this.primaryKey()];
|
|
1141
|
+
}
|
|
1142
|
+
toObject() {
|
|
1143
|
+
return { ...this.attributes };
|
|
1144
|
+
}
|
|
1145
|
+
primaryKey() {
|
|
1146
|
+
throw new Error(`${this.constructor.name}.primaryKey() is not implemented.`);
|
|
1147
|
+
}
|
|
1148
|
+
static repository() {
|
|
1149
|
+
return resolveModelRepository(this);
|
|
1150
|
+
}
|
|
1151
|
+
static query() {
|
|
1152
|
+
return resolveModelRepository(this).query();
|
|
1153
|
+
}
|
|
1154
|
+
static async find(id) {
|
|
1155
|
+
const repository = resolveModelRepository(this);
|
|
1156
|
+
const record = await repository.findById(id);
|
|
1157
|
+
return record ? new this(record, repository) : null;
|
|
1158
|
+
}
|
|
1159
|
+
static async findOrFail(id, errorFactory) {
|
|
1160
|
+
const repository = resolveModelRepository(this);
|
|
1161
|
+
const record = await repository.findByIdOrThrow(id, errorFactory ?? ((value) => new Error(`Record ${String(value)} not found.`)));
|
|
1162
|
+
return new this(record, repository);
|
|
1163
|
+
}
|
|
1164
|
+
static async all(options = {}) {
|
|
1165
|
+
const repository = resolveModelRepository(this);
|
|
1166
|
+
const rows = await repository.findAll(options);
|
|
1167
|
+
return rows.map((row) => new this(row, repository));
|
|
1168
|
+
}
|
|
1169
|
+
static async firstWhere(where, options = {}) {
|
|
1170
|
+
const repository = resolveModelRepository(this);
|
|
1171
|
+
const rows = await repository.findAll({ ...options, where, limit: 1 });
|
|
1172
|
+
const record = rows[0];
|
|
1173
|
+
return record ? new this(record, repository) : null;
|
|
1174
|
+
}
|
|
1175
|
+
async loadHasMany(as, relation, childRepository, options = {}) {
|
|
1176
|
+
const grouped = await childRepository.withConnection(this.repository.getConnection()).loadHasManyForParents([this.attributes], relation, options);
|
|
1177
|
+
const loaded = grouped.get(this.attributes[relation.localKey]) ?? [];
|
|
1178
|
+
return Object.assign(this, { [as]: loaded });
|
|
1179
|
+
}
|
|
1180
|
+
async loadBelongsTo(as, relation, parentRepository, options = {}) {
|
|
1181
|
+
const grouped = await this.repository.loadBelongsToForParents([this.attributes], relation, parentRepository, options);
|
|
1182
|
+
const loaded = grouped.get(this.attributes[relation.foreignKey]);
|
|
1183
|
+
return Object.assign(this, { [as]: loaded });
|
|
1184
|
+
}
|
|
1185
|
+
mergeAttributes(patch) {
|
|
1186
|
+
Object.assign(this.attributes, patch);
|
|
1187
|
+
return this;
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
function registerModelRepository(model, repository) {
|
|
1191
|
+
modelRepositories.set(model, repository);
|
|
1192
|
+
return model;
|
|
1193
|
+
}
|
|
1027
1194
|
// ../../src/core/database/table.ts
|
|
1028
1195
|
function defineTable(definition) {
|
|
1029
1196
|
return definition;
|
|
@@ -1279,7 +1446,7 @@ function mailer() {
|
|
|
1279
1446
|
// ../../src/core/storage/storage.ts
|
|
1280
1447
|
var {S3Client } = globalThis.Bun;
|
|
1281
1448
|
import { mkdir, readFile, unlink, writeFile } from "fs/promises";
|
|
1282
|
-
import { dirname, join } from "path";
|
|
1449
|
+
import { dirname, join as join2 } from "path";
|
|
1283
1450
|
|
|
1284
1451
|
class LocalStorageDriver {
|
|
1285
1452
|
rootDirectory;
|
|
@@ -1287,7 +1454,7 @@ class LocalStorageDriver {
|
|
|
1287
1454
|
this.rootDirectory = rootDirectory;
|
|
1288
1455
|
}
|
|
1289
1456
|
resolvePath(path) {
|
|
1290
|
-
return
|
|
1457
|
+
return join2(this.rootDirectory, path.replace(/^\/+/, ""));
|
|
1291
1458
|
}
|
|
1292
1459
|
async put(path, contents) {
|
|
1293
1460
|
const absolutePath = this.resolvePath(path);
|
|
@@ -1434,9 +1601,9 @@ function isViewsEnabled() {
|
|
|
1434
1601
|
}
|
|
1435
1602
|
|
|
1436
1603
|
// ../../src/core/view/etaViewEngine.ts
|
|
1437
|
-
import { join as
|
|
1604
|
+
import { join as join3 } from "path";
|
|
1438
1605
|
import { Eta } from "eta";
|
|
1439
|
-
var DEFAULT_VIEWS_DIRECTORY =
|
|
1606
|
+
var DEFAULT_VIEWS_DIRECTORY = join3(process.cwd(), "resources/views");
|
|
1440
1607
|
// ../../src/core/view/htmlResponse.ts
|
|
1441
1608
|
function htmlResponse(html, init = {}) {
|
|
1442
1609
|
return new Response(html, {
|
|
@@ -2203,13 +2370,16 @@ class WebFormRequest {
|
|
|
2203
2370
|
authorize(_request) {
|
|
2204
2371
|
return true;
|
|
2205
2372
|
}
|
|
2373
|
+
validatePayload(payload) {
|
|
2374
|
+
return this.parse(payload);
|
|
2375
|
+
}
|
|
2206
2376
|
async validate(request) {
|
|
2207
2377
|
if (!await this.authorize(request)) {
|
|
2208
2378
|
throw new ForbiddenError;
|
|
2209
2379
|
}
|
|
2210
2380
|
const payload = requestPrefersJson(request) ? await parseJsonBody(request, (body) => body) : await parseFormBody(request);
|
|
2211
2381
|
try {
|
|
2212
|
-
return this.
|
|
2382
|
+
return this.validatePayload(payload);
|
|
2213
2383
|
} catch (error) {
|
|
2214
2384
|
if (error instanceof ValidationError) {
|
|
2215
2385
|
throw error;
|
|
@@ -2243,12 +2413,94 @@ class AsyncQueue {
|
|
|
2243
2413
|
function createQueue(driver) {
|
|
2244
2414
|
return driver === "async" ? new AsyncQueue : new SyncQueue;
|
|
2245
2415
|
}
|
|
2416
|
+
// ../../src/core/validation/rules.ts
|
|
2417
|
+
function required() {
|
|
2418
|
+
return (field, value) => {
|
|
2419
|
+
if (value === undefined || value === null || typeof value === "string" && value.trim() === "") {
|
|
2420
|
+
return `"${field}" is required.`;
|
|
2421
|
+
}
|
|
2422
|
+
return;
|
|
2423
|
+
};
|
|
2424
|
+
}
|
|
2425
|
+
function stringRule() {
|
|
2426
|
+
return (field, value) => {
|
|
2427
|
+
if (value === undefined || value === null) {
|
|
2428
|
+
return;
|
|
2429
|
+
}
|
|
2430
|
+
if (typeof value !== "string") {
|
|
2431
|
+
return `"${field}" must be a string.`;
|
|
2432
|
+
}
|
|
2433
|
+
return;
|
|
2434
|
+
};
|
|
2435
|
+
}
|
|
2436
|
+
function minLength(minimum) {
|
|
2437
|
+
return (field, value) => {
|
|
2438
|
+
if (typeof value !== "string") {
|
|
2439
|
+
return;
|
|
2440
|
+
}
|
|
2441
|
+
if (value.trim().length < minimum) {
|
|
2442
|
+
return `"${field}" must be at least ${minimum} characters.`;
|
|
2443
|
+
}
|
|
2444
|
+
return;
|
|
2445
|
+
};
|
|
2446
|
+
}
|
|
2447
|
+
function maxLength(maximum) {
|
|
2448
|
+
return (field, value) => {
|
|
2449
|
+
if (typeof value !== "string") {
|
|
2450
|
+
return;
|
|
2451
|
+
}
|
|
2452
|
+
if (value.trim().length > maximum) {
|
|
2453
|
+
return `"${field}" must be at most ${maximum} characters.`;
|
|
2454
|
+
}
|
|
2455
|
+
return;
|
|
2456
|
+
};
|
|
2457
|
+
}
|
|
2458
|
+
function emailRule() {
|
|
2459
|
+
return (field, value) => {
|
|
2460
|
+
if (typeof value !== "string") {
|
|
2461
|
+
return;
|
|
2462
|
+
}
|
|
2463
|
+
const normalized = value.trim();
|
|
2464
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(normalized)) {
|
|
2465
|
+
return `"${field}" must be a valid email address.`;
|
|
2466
|
+
}
|
|
2467
|
+
return;
|
|
2468
|
+
};
|
|
2469
|
+
}
|
|
2470
|
+
function validateObject(payload, schema) {
|
|
2471
|
+
if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
|
|
2472
|
+
throw new ValidationError("Request body must be a JSON object.");
|
|
2473
|
+
}
|
|
2474
|
+
const body = payload;
|
|
2475
|
+
const errors = {};
|
|
2476
|
+
const output = {};
|
|
2477
|
+
for (const [field, rules] of Object.entries(schema)) {
|
|
2478
|
+
const messages = rules.map((rule) => rule(field, body[field], body)).filter((message) => message !== undefined);
|
|
2479
|
+
if (messages.length > 0) {
|
|
2480
|
+
errors[field] = messages;
|
|
2481
|
+
continue;
|
|
2482
|
+
}
|
|
2483
|
+
if (field in body && body[field] !== undefined) {
|
|
2484
|
+
const rawValue = body[field];
|
|
2485
|
+
output[field] = typeof rawValue === "string" ? rawValue.trim() : rawValue;
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
if (Object.keys(errors).length > 0) {
|
|
2489
|
+
throw new ValidationError("The given data was invalid.", errors);
|
|
2490
|
+
}
|
|
2491
|
+
return output;
|
|
2492
|
+
}
|
|
2246
2493
|
export {
|
|
2247
2494
|
withErrorHandling,
|
|
2495
|
+
validateObject,
|
|
2496
|
+
stringRule,
|
|
2248
2497
|
storageFacade as storage,
|
|
2249
2498
|
securedBindRouteModel,
|
|
2250
2499
|
runWithAuthUser,
|
|
2500
|
+
rollbackDatabase,
|
|
2251
2501
|
resolveService,
|
|
2502
|
+
required,
|
|
2503
|
+
registerModelRepository,
|
|
2252
2504
|
queue,
|
|
2253
2505
|
prometheusRegistry,
|
|
2254
2506
|
policyGate,
|
|
@@ -2256,16 +2508,23 @@ export {
|
|
|
2256
2508
|
paginatedResponse,
|
|
2257
2509
|
normalizeMetricPath,
|
|
2258
2510
|
noContentResponse,
|
|
2511
|
+
minLength,
|
|
2512
|
+
migrateDatabase,
|
|
2513
|
+
maxLength,
|
|
2259
2514
|
mailer,
|
|
2260
2515
|
mail,
|
|
2261
2516
|
log,
|
|
2517
|
+
loadMigrationsFromDirectory,
|
|
2262
2518
|
jsonResponse,
|
|
2263
2519
|
isEtagEnabled,
|
|
2264
2520
|
indexHasManyRelation,
|
|
2265
2521
|
indexBelongsToRelation,
|
|
2266
2522
|
hasMany,
|
|
2523
|
+
getMigrationStatus,
|
|
2524
|
+
freshDatabase,
|
|
2267
2525
|
events,
|
|
2268
2526
|
etagFromResource,
|
|
2527
|
+
emailRule,
|
|
2269
2528
|
defineTable,
|
|
2270
2529
|
currentAuthUser,
|
|
2271
2530
|
createdResponse,
|
|
@@ -2273,6 +2532,7 @@ export {
|
|
|
2273
2532
|
createMetricsMiddleware,
|
|
2274
2533
|
config,
|
|
2275
2534
|
cache,
|
|
2535
|
+
bindDatabaseConnection,
|
|
2276
2536
|
belongsTo,
|
|
2277
2537
|
auth,
|
|
2278
2538
|
assertIfMatch,
|
|
@@ -2286,8 +2546,10 @@ export {
|
|
|
2286
2546
|
RepositoryQuery,
|
|
2287
2547
|
PrometheusRegistry,
|
|
2288
2548
|
PreconditionFailedError,
|
|
2549
|
+
PolicyGate,
|
|
2289
2550
|
Policy,
|
|
2290
2551
|
NotFoundError,
|
|
2552
|
+
Model,
|
|
2291
2553
|
Mailer,
|
|
2292
2554
|
LogMailDriver,
|
|
2293
2555
|
LocalStorageDriver,
|