@getstrata/core 2.0.9 → 2.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/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # @getstrata/core changelog
2
2
 
3
+ ## 2.1.0
4
+
5
+ - refactor(starter): reuse shared config and base repositories - fix(database): await password casts before model writes - fix(database): preserve concrete model types in query results
6
+
3
7
  ## 2.0.9
4
8
 
5
9
  - fix(starter): remove demo credentials from public login defaults
@@ -1,19 +1,37 @@
1
+ declare const serviceTokenType: unique symbol;
2
+ /** Invariant service identity; its runtime value remains the existing string key. */
3
+ type ServiceToken<T> = string & {
4
+ readonly [serviceTokenType]: (value: T) => T;
5
+ };
6
+ /** Prevent a typed token from falling back to an unchecked legacy overload. */
7
+ type LegacyServiceKey = string & {
8
+ readonly [serviceTokenType]?: never;
9
+ };
10
+ declare function createServiceToken<T>(key: string): ServiceToken<T>;
1
11
  type ServiceFactory<T> = (container: ServiceContainer) => T;
2
12
  interface ServiceContainerLike {
3
13
  has(key: string): boolean;
4
- resolve<T>(key: string): T;
14
+ resolve<T>(key: ServiceToken<T>): T;
15
+ resolve<T>(key: LegacyServiceKey): T;
5
16
  }
6
17
  declare class ServiceContainer implements ServiceContainerLike {
7
18
  private readonly services;
8
19
  private readonly singletonFactories;
9
20
  private readonly bindings;
10
- set<T>(key: string, value: T): T;
11
- singleton<T>(key: string, factory: ServiceFactory<T>): void;
12
- bind<T>(key: string, factory: ServiceFactory<T>): void;
13
- get<T>(key: string): T;
14
- resolve<T>(key: string): T;
15
- make<T>(key: string): T;
16
- instance<T>(key: string, value: T): T;
21
+ set<T>(key: ServiceToken<T>, value: NoInfer<T>): T;
22
+ set<T>(key: LegacyServiceKey, value: T): T;
23
+ singleton<T>(key: ServiceToken<T>, factory: ServiceFactory<NoInfer<T>>): void;
24
+ singleton<T>(key: LegacyServiceKey, factory: ServiceFactory<T>): void;
25
+ bind<T>(key: ServiceToken<T>, factory: ServiceFactory<NoInfer<T>>): void;
26
+ bind<T>(key: LegacyServiceKey, factory: ServiceFactory<T>): void;
27
+ get<T>(key: ServiceToken<T>): T;
28
+ get<T>(key: LegacyServiceKey): T;
29
+ resolve<T>(key: ServiceToken<T>): T;
30
+ resolve<T>(key: LegacyServiceKey): T;
31
+ make<T>(key: ServiceToken<T>): T;
32
+ make<T>(key: LegacyServiceKey): T;
33
+ instance<T>(key: ServiceToken<T>, value: NoInfer<T>): T;
34
+ instance<T>(key: LegacyServiceKey, value: T): T;
17
35
  has(key: string): boolean;
18
36
  }
19
37
  interface ConfigStoreLike {
@@ -28,5 +46,5 @@ declare class ConfigStore implements ConfigStoreLike {
28
46
  require<T>(key: string): T;
29
47
  has(key: string): boolean;
30
48
  }
31
- export type { ConfigStoreLike, ServiceContainerLike, ServiceFactory };
32
- export { ConfigStore, ServiceContainer };
49
+ export type { ConfigStoreLike, LegacyServiceKey, ServiceContainerLike, ServiceFactory, ServiceToken, };
50
+ export { ConfigStore, createServiceToken, ServiceContainer };
@@ -1,6 +1,6 @@
1
1
  import type { CacheLike } from "../../types/services";
2
2
  import type { StorageManager } from "../storage/storage";
3
- import type { ConfigStore, ConfigStoreLike, ServiceContainer, ServiceFactory } from "./container";
3
+ import type { ConfigStore, ConfigStoreLike, LegacyServiceKey, ServiceContainer, ServiceFactory, ServiceToken } from "./container";
4
4
  type AppRouteMap = Record<string, any>;
5
5
  type CachedJson = <T>(cacheKey: string, loader: () => Promise<T>, tags?: string[], request?: Request) => Promise<Response>;
6
6
  interface AppDependencies {
@@ -26,6 +26,7 @@ interface AppContext {
26
26
  }
27
27
  declare function getRequiredDependency<K extends keyof AppDependencies>(dependencies: Partial<AppDependencies>, key: K): AppDependencies[K];
28
28
  declare function assertAppDependenciesComplete(dependencies: MutableAppDependencies): asserts dependencies is AppDependencies;
29
- declare function resolveService<T>(dependencies: AppDependencies, token: string): T;
29
+ declare function resolveService<T>(dependencies: AppDependencies, token: ServiceToken<T>): T;
30
+ declare function resolveService<T>(dependencies: AppDependencies, token: LegacyServiceKey): T;
30
31
  export type { AppContext, AppDependencies, AppRouteMap, CachedJson, MutableAppDependencies, ProviderContext, ServiceFactory, ServiceProvider, };
31
32
  export { assertAppDependenciesComplete, getRequiredDependency, resolveService };
@@ -13,6 +13,14 @@ interface ModelConstructor<TEntity extends object, PrimaryKey extends keyof TEnt
13
13
  new (attributes: TEntity, repository: BaseRepository<TEntity, PrimaryKey>, exists?: boolean): TModel;
14
14
  }
15
15
  type AnyModel = Model<Record<string, unknown>, "id">;
16
+ type ModelAttributes<TModel> = TModel extends {
17
+ toObject(): infer TRecord extends object;
18
+ } ? TRecord : Record<string, unknown>;
19
+ type WithModelCount<TModel, TCount extends string> = {
20
+ toObject(): Omit<ModelAttributes<TModel>, TCount> & Record<TCount, unknown>;
21
+ get<K extends keyof (Omit<ModelAttributes<TModel>, TCount> & Record<TCount, unknown>)>(key: K): (Omit<ModelAttributes<TModel>, TCount> & Record<TCount, unknown>)[K];
22
+ } & TModel;
23
+ type ModelQueryResult<TModel, TCounts extends string> = [TCounts] extends [never] ? TModel : WithModelCount<TModel, TCounts>;
16
24
  type RelatedRef<TRelated extends object, RelatedKey extends keyof TRelated & string> = RelatedModelClass<TRelated, RelatedKey> | string | (() => RelatedModelClass<TRelated, RelatedKey>);
17
25
  type RelationNameInput = string | readonly string[];
18
26
  type ModelObserver = {
@@ -29,10 +37,14 @@ type ModelObserver = {
29
37
  /** Alias for `hasMany("Name")` when `Name` is not `constructor.name` or `$morphClass`. */
30
38
  declare function registerModelClass(name: string, model: object): void;
31
39
  declare function hydrateValue(value: unknown, cast: CastType): unknown;
40
+ declare function dehydrateValue(value: unknown, cast: "hashed"): Promise<unknown>;
41
+ declare function dehydrateValue(value: unknown, cast: Exclude<CastType, "hashed">): unknown;
32
42
  declare function dehydrateValue(value: unknown, cast: CastType): unknown;
33
43
  declare function filterMassAssignable(fillable: readonly string[] | undefined, guarded: readonly string[] | true | undefined, input: LoadedAttributes): LoadedAttributes;
34
- declare function applyCasts(values: LoadedAttributes, casts: ModelCasts, direction: "hydrate" | "dehydrate"): LoadedAttributes;
35
- declare class ModelQuery {
44
+ declare function applyCasts(values: LoadedAttributes, casts: ModelCasts, direction: "hydrate"): LoadedAttributes;
45
+ declare function applyCasts(values: LoadedAttributes, casts: ModelCasts, direction: "dehydrate"): Promise<LoadedAttributes>;
46
+ declare function applyCasts(values: LoadedAttributes, casts: ModelCasts, direction: "hydrate" | "dehydrate"): LoadedAttributes | Promise<LoadedAttributes>;
47
+ declare class ModelQuery<TModel extends object = AnyModel, TCounts extends string = never> {
36
48
  private readonly modelClass;
37
49
  readonly query: RepositoryQuery<Record<string, unknown>, "id">;
38
50
  private readonly eager;
@@ -55,7 +67,7 @@ declare class ModelQuery {
55
67
  page: number;
56
68
  perPage: number;
57
69
  }): Promise<{
58
- data: Array<Model<Record<string, unknown>, "id">>;
70
+ data: ModelQueryResult<TModel, TCounts>[];
59
71
  meta: Awaited<ReturnType<RepositoryQuery<Record<string, unknown>, "id">["paginate"]>>["meta"];
60
72
  }>;
61
73
  whereExists(sql: string, params?: readonly unknown[]): this;
@@ -73,17 +85,17 @@ declare class ModelQuery {
73
85
  withHasManyThrough(...args: Parameters<RepositoryQuery<Record<string, unknown>, "id">["withHasManyThrough"]>): this;
74
86
  withTrashed(): this;
75
87
  onlyTrashed(): this;
76
- get(): Promise<Array<Model<Record<string, unknown>, "id">>>;
88
+ get(): Promise<ModelQueryResult<TModel, TCounts>[]>;
77
89
  private hydrateRows;
78
- first(): Promise<Model<Record<string, unknown>, "id"> | null>;
90
+ first(): Promise<ModelQueryResult<TModel, TCounts> | null>;
79
91
  count(): Promise<number>;
80
92
  pluck(column: string): Promise<unknown[]>;
81
93
  pluck(column: string, keyBy: string): Promise<Map<unknown, unknown>>;
82
94
  value(column: string): Promise<unknown>;
83
- find(id: unknown): Promise<Model<Record<string, unknown>, "id"> | null>;
84
- findOrFail(id: unknown, errorFactory?: (id: unknown) => Error): Promise<Model<Record<string, unknown>, "id">>;
85
- then(onfulfilled?: ((value: Array<Model<Record<string, unknown>, "id">>) => unknown) | null, onrejected?: ((reason: unknown) => unknown) | null): Promise<unknown>;
86
- withCount(name: string, alias?: string): this;
95
+ find(id: unknown): Promise<ModelQueryResult<TModel, TCounts> | null>;
96
+ findOrFail(id: unknown, errorFactory?: (id: unknown) => Error): Promise<ModelQueryResult<TModel, TCounts>>;
97
+ then(onfulfilled?: ((value: ModelQueryResult<TModel, TCounts>[]) => unknown) | null, onrejected?: ((reason: unknown) => unknown) | null): Promise<unknown>;
98
+ withCount<TName extends string, TAlias extends string = `${TName}_count`>(name: TName, alias?: TAlias): ModelQuery<TModel, TCounts | TAlias>;
87
99
  private constrainExists;
88
100
  }
89
101
  declare class Model<TEntity extends object, PrimaryKey extends keyof TEntity & string> {
@@ -116,24 +128,40 @@ declare class Model<TEntity extends object, PrimaryKey extends keyof TEntity & s
116
128
  protected primaryKey(): PrimaryKey;
117
129
  protected static primaryKeyField(this: object): string;
118
130
  protected static hydrateAttributes<TEntity extends object>(this: object, attributes: TEntity): TEntity;
119
- protected static dehydrateAttributes(this: object, attributes: LoadedAttributes): LoadedAttributes;
131
+ protected static dehydrateAttributes(this: object, attributes: LoadedAttributes): LoadedAttributes | Promise<LoadedAttributes>;
120
132
  protected static fromRecord<TEntity extends object, PrimaryKey extends keyof TEntity & string>(this: object, record: TEntity, repository: BaseRepository<TEntity, PrimaryKey>, exists?: boolean): Model<TEntity, PrimaryKey>;
121
133
  static boot(): void;
122
134
  static observe(this: object, observer: ModelObserver): void;
123
135
  static addGlobalScope<TEntity extends object, PrimaryKey extends keyof TEntity & string>(this: object, _name: string, scope: GlobalScopeFn<TEntity, PrimaryKey>): void;
124
136
  static repository<TEntity extends object, PrimaryKey extends keyof TEntity & string>(this: object): BaseRepository<TEntity, PrimaryKey>;
125
- static query(this: object): ModelQuery;
126
- static newFromRecord(this: object, record: object, exists?: boolean): Model<Record<string, unknown>, "id">;
127
- static create(this: object, attributes: Record<string, unknown>, forced?: Record<string, unknown>): Promise<Model<Record<string, unknown>, "id">>;
128
- static with(this: object, ...relations: RelationNameInput[]): ModelQuery;
129
- static withTrashed(this: object): ModelQuery;
130
- static onlyTrashed(this: object): ModelQuery;
131
- static chunk(this: object, count: number, callback: (models: Array<Model<Record<string, unknown>, "id">>) => Promise<boolean | void>): Promise<void>;
132
- static cursorPaginate(this: object, options: {
137
+ static query<TModel extends object>(this: {
138
+ prototype: TModel;
139
+ }): ModelQuery<TModel>;
140
+ static newFromRecord<TModel extends object>(this: {
141
+ prototype: TModel;
142
+ }, record: object, exists?: boolean): TModel;
143
+ static create<TModel extends object>(this: {
144
+ prototype: TModel;
145
+ }, attributes: Record<string, unknown>, forced?: Record<string, unknown>): Promise<TModel>;
146
+ static with<TModel extends object>(this: {
147
+ prototype: TModel;
148
+ }, ...relations: RelationNameInput[]): ModelQuery<TModel>;
149
+ static withTrashed<TModel extends object>(this: {
150
+ prototype: TModel;
151
+ }): ModelQuery<TModel>;
152
+ static onlyTrashed<TModel extends object>(this: {
153
+ prototype: TModel;
154
+ }): ModelQuery<TModel>;
155
+ static chunk<TModel extends object>(this: {
156
+ prototype: TModel;
157
+ }, count: number, callback: (models: TModel[]) => Promise<boolean | void>): Promise<void>;
158
+ static cursorPaginate<TModel extends object>(this: {
159
+ prototype: TModel;
160
+ }, options: {
133
161
  perPage: number;
134
162
  cursor?: unknown;
135
163
  }): Promise<{
136
- data: Array<Model<Record<string, unknown>, "id">>;
164
+ data: TModel[];
137
165
  meta: {
138
166
  per_page: number;
139
167
  next_cursor: unknown;
@@ -141,22 +169,46 @@ declare class Model<TEntity extends object, PrimaryKey extends keyof TEntity & s
141
169
  has_more: boolean;
142
170
  };
143
171
  }>;
144
- static whereHas(this: object, name: string, constrain?: (query: AnyRelationQuery) => void): ModelQuery;
145
- static has(this: object, name: string): ModelQuery;
146
- static doesntHave(this: object, name: string): ModelQuery;
147
- static whereDoesntHave(this: object, name: string, constrain?: (query: AnyRelationQuery) => void): ModelQuery;
148
- static find(this: object, id: unknown): Promise<Model<Record<string, unknown>, "id"> | null>;
149
- static findOrFail(this: object, id: unknown, errorFactory?: (id: unknown) => Error): Promise<Model<Record<string, unknown>, "id">>;
150
- static all(this: object, options?: Omit<QueryOptions<object>, "where">): Promise<Array<Model<Record<string, unknown>, "id">>>;
151
- static where(this: object, where: QueryWhere<object>): ModelQuery;
172
+ static whereHas<TModel extends object>(this: {
173
+ prototype: TModel;
174
+ }, name: string, constrain?: (query: AnyRelationQuery) => void): ModelQuery<TModel>;
175
+ static has<TModel extends object>(this: {
176
+ prototype: TModel;
177
+ }, name: string): ModelQuery<TModel>;
178
+ static doesntHave<TModel extends object>(this: {
179
+ prototype: TModel;
180
+ }, name: string): ModelQuery<TModel>;
181
+ static whereDoesntHave<TModel extends object>(this: {
182
+ prototype: TModel;
183
+ }, name: string, constrain?: (query: AnyRelationQuery) => void): ModelQuery<TModel>;
184
+ static find<TModel extends object>(this: {
185
+ prototype: TModel;
186
+ }, id: unknown): Promise<TModel | null>;
187
+ static findOrFail<TModel extends object>(this: {
188
+ prototype: TModel;
189
+ }, id: unknown, errorFactory?: (id: unknown) => Error): Promise<TModel>;
190
+ static all<TModel extends object>(this: {
191
+ prototype: TModel;
192
+ }, options?: Omit<QueryOptions<object>, "where">): Promise<TModel[]>;
193
+ static where<TModel extends object>(this: {
194
+ prototype: TModel;
195
+ }, where: QueryWhere<object>): ModelQuery<TModel>;
152
196
  static count(this: object): Promise<number>;
153
197
  static pluck(this: object, column: string): Promise<unknown[]>;
154
198
  static pluck(this: object, column: string, keyBy: string): Promise<Map<unknown, unknown>>;
155
199
  static value(this: object, column: string): Promise<unknown>;
156
- static firstWhere(this: object, where: QueryWhere<object>, options?: Omit<QueryOptions<object>, "where">): Promise<Model<Record<string, unknown>, "id"> | null>;
157
- static firstOrNew(this: object, where: QueryWhere<object>, values?: Record<string, unknown>): Promise<Model<Record<string, unknown>, "id">>;
158
- static firstOrCreate(this: object, where: QueryWhere<object>, values?: Record<string, unknown>): Promise<Model<Record<string, unknown>, "id">>;
159
- static updateOrCreate(this: object, where: QueryWhere<object>, values?: Record<string, unknown>): Promise<Model<Record<string, unknown>, "id">>;
200
+ static firstWhere<TModel extends object>(this: {
201
+ prototype: TModel;
202
+ }, where: QueryWhere<object>, options?: Omit<QueryOptions<object>, "where">): Promise<TModel | null>;
203
+ static firstOrNew<TModel extends object>(this: {
204
+ prototype: TModel;
205
+ }, where: QueryWhere<object>, values?: Record<string, unknown>): Promise<TModel>;
206
+ static firstOrCreate<TModel extends object>(this: {
207
+ prototype: TModel;
208
+ }, where: QueryWhere<object>, values?: Record<string, unknown>): Promise<TModel>;
209
+ static updateOrCreate<TModel extends object>(this: {
210
+ prototype: TModel;
211
+ }, where: QueryWhere<object>, values?: Record<string, unknown>): Promise<TModel>;
160
212
  save(): Promise<this>;
161
213
  update(changes: Partial<TEntity>): Promise<this>;
162
214
  delete(): Promise<boolean>;
@@ -1,6 +1,6 @@
1
1
  type Middleware = (request: Request, next: () => Promise<Response>) => Promise<Response>;
2
- type RouteHandler = (request: Request) => Response | Promise<Response>;
3
- declare function composeMiddleware(...middleware: Middleware[]): (handler: RouteHandler) => RouteHandler;
2
+ type RouteHandler<TRequest extends Request = Request> = (request: TRequest) => Response | Promise<Response>;
3
+ declare function composeMiddleware(...middleware: Middleware[]): <TRequest extends Request = Request>(handler: RouteHandler<TRequest>) => RouteHandler<TRequest>;
4
4
  declare function requestIdMiddleware(request: Request, next: () => Promise<Response>): Promise<Response>;
5
5
  declare function wrapRouteHandler<T>(handler: T, middleware: Middleware[]): T;
6
6
  declare function applyMiddlewareToRoutes<T extends Record<string, unknown>>(routes: T, middleware: Middleware[]): T;
@@ -3,7 +3,9 @@ declare function jsonResponse(data: unknown, init?: ResponseInit): Response;
3
3
  declare function createdResponse(data: unknown, init?: ResponseInit): Response;
4
4
  declare function noContentResponse(): Response;
5
5
  declare function errorResponse(error: unknown): Response;
6
+ declare function withErrorHandling<THandler extends (request: Request) => Response | Promise<Response>>(handler: THandler): (...args: Parameters<THandler>) => Promise<Response>;
6
7
  declare function withErrorHandling<TArgs extends unknown[]>(handler: (...args: TArgs) => Response | Promise<Response>): (...args: TArgs) => Promise<Response>;
8
+ declare function withJsonErrorHandling<THandler extends (request: Request) => Response | Promise<Response>>(handler: THandler): (...args: Parameters<THandler>) => Promise<Response>;
7
9
  declare function withJsonErrorHandling<TArgs extends unknown[]>(handler: (...args: TArgs) => Response | Promise<Response>): (...args: TArgs) => Promise<Response>;
8
10
  declare function createJsonErrorMiddleware(): Middleware;
9
11
  declare function createWebErrorMiddleware(): Middleware;
@@ -1,3 +1,4 @@
1
- import { type Middleware, type RouteHandler } from "./middleware";
2
- declare function withMiddleware(...middleware: Middleware[]): (handler: RouteHandler) => RouteHandler;
1
+ import { type Middleware } from "./middleware";
2
+ /** Middleware consumes native Request; composition preserves the handler's request subtype. */
3
+ declare function withMiddleware(...middleware: Middleware[]): <TRequest extends Request = Request>(handler: import("./middleware").RouteHandler<TRequest>) => import("./middleware").RouteHandler<TRequest>;
3
4
  export { withMiddleware };
@@ -1,5 +1,11 @@
1
1
  // @bun
2
2
  // ../../src/core/contracts/container.ts
3
+ function createServiceToken(key) {
4
+ if (!key.trim())
5
+ throw new Error("Service token key must not be empty.");
6
+ return key;
7
+ }
8
+
3
9
  class ServiceContainer {
4
10
  services = new Map;
5
11
  singletonFactories = new Map;
@@ -71,5 +77,6 @@ class ConfigStore {
71
77
  }
72
78
  export {
73
79
  ConfigStore,
74
- ServiceContainer
80
+ ServiceContainer,
81
+ createServiceToken
75
82
  };
@@ -2,6 +2,17 @@
2
2
  // ../../src/core/database/model.ts
3
3
  import { ConflictError, NotFoundError } from "@getstrata/core/errors/http";
4
4
 
5
+ // ../../src/core/auth/password.ts
6
+ async function hashPassword(password) {
7
+ return await Bun.password.hash(password, {
8
+ algorithm: "bcrypt",
9
+ cost: 12
10
+ });
11
+ }
12
+ async function verifyPassword(password, passwordHash) {
13
+ return await Bun.password.verify(password, passwordHash);
14
+ }
15
+
5
16
  // ../../src/core/database/inflection.ts
6
17
  function singularize(word) {
7
18
  if (word.endsWith("ies") && word.length > 3) {
@@ -1624,12 +1635,14 @@ var PASSWORD_HASH_PATTERN = /^\$(?:2[aby]?|argon2(?:i|d|id)?)\$/;
1624
1635
  function isAlreadyHashed(value) {
1625
1636
  return PASSWORD_HASH_PATTERN.test(value);
1626
1637
  }
1627
- function hashCastValue(value) {
1638
+ async function hashCastValue(value) {
1639
+ if (value === null || value === undefined)
1640
+ return value;
1628
1641
  const plain = String(value);
1629
1642
  if (isAlreadyHashed(plain)) {
1630
1643
  return plain;
1631
1644
  }
1632
- return Bun.password.hashSync(plain, { algorithm: "bcrypt", cost: 12 });
1645
+ return await hashPassword(plain);
1633
1646
  }
1634
1647
  function hydrateValue(value, cast) {
1635
1648
  if (value === null || value === undefined) {
@@ -1654,6 +1667,8 @@ function hydrateValue(value, cast) {
1654
1667
  }
1655
1668
  }
1656
1669
  function dehydrateValue(value, cast) {
1670
+ if (cast === "hashed")
1671
+ return hashCastValue(value);
1657
1672
  if (value === null || value === undefined) {
1658
1673
  return value;
1659
1674
  }
@@ -1669,8 +1684,6 @@ function dehydrateValue(value, cast) {
1669
1684
  case "integer":
1670
1685
  case "int":
1671
1686
  return value === "" ? null : Number(value);
1672
- case "hashed":
1673
- return hashCastValue(value);
1674
1687
  default:
1675
1688
  return value;
1676
1689
  }
@@ -1691,17 +1704,25 @@ function filterMassAssignable(fillable, guarded, input) {
1691
1704
  return Object.fromEntries(Object.entries(input).filter(([key]) => !blocked.has(key)));
1692
1705
  }
1693
1706
  function applyCasts(values, casts, direction) {
1694
- if (Object.keys(casts).length === 0) {
1695
- return values;
1707
+ const entries = Object.entries(casts);
1708
+ if (entries.length === 0) {
1709
+ return direction === "hydrate" ? values : Promise.resolve(values);
1696
1710
  }
1697
1711
  const result = { ...values };
1698
- const castFn = direction === "hydrate" ? hydrateValue : dehydrateValue;
1699
- for (const [key, cast] of Object.entries(casts)) {
1700
- if (key in result && cast) {
1701
- result[key] = castFn(result[key], cast);
1712
+ if (direction === "hydrate") {
1713
+ for (const [key, cast] of entries) {
1714
+ if (key in result && cast)
1715
+ result[key] = hydrateValue(result[key], cast);
1702
1716
  }
1717
+ return result;
1703
1718
  }
1704
- return result;
1719
+ return (async () => {
1720
+ for (const [key, cast] of entries) {
1721
+ if (key in result && cast)
1722
+ result[key] = await dehydrateValue(result[key], cast);
1723
+ }
1724
+ return result;
1725
+ })();
1705
1726
  }
1706
1727
  function castPluckedValue(modelClass, column, value) {
1707
1728
  const cast = modelStatics(modelClass).$casts?.[column];
@@ -2096,7 +2117,7 @@ class Model {
2096
2117
  ...forced
2097
2118
  };
2098
2119
  const withTimestamps = applyTimestampsOnCreate(table.columns, assignable, timestamps);
2099
- const payload = statics.dehydrateAttributes(withTimestamps);
2120
+ const payload = await statics.dehydrateAttributes(withTimestamps);
2100
2121
  const pending = statics.newFromRecord({ ...payload }, false);
2101
2122
  if (await runObservers(pending, "saving") === false) {
2102
2123
  throw new Error(`${this.name}.create() was cancelled by an observer.`);
@@ -2237,7 +2258,7 @@ class Model {
2237
2258
  return this;
2238
2259
  }
2239
2260
  if (updating) {
2240
- const changes = applyTimestampsOnUpdate(table.columns, applyCasts(this.attributes, casts, "dehydrate"), timestamps);
2261
+ const changes = applyTimestampsOnUpdate(table.columns, await applyCasts(this.attributes, casts, "dehydrate"), timestamps);
2241
2262
  const record = await this.repository.updateByIdOrThrow(this.id, changes);
2242
2263
  this.attributes = ModelClass.hydrateAttributes(record);
2243
2264
  await runObservers(this, "updated");
@@ -2246,7 +2267,7 @@ class Model {
2246
2267
  }
2247
2268
  const assignable = filterMassAssignable(ModelClass.$fillable, ModelClass.$guarded, this.attributes);
2248
2269
  const withTimestamps = applyTimestampsOnCreate(table.columns, assignable, timestamps);
2249
- const payload = ModelClass.dehydrateAttributes(withTimestamps);
2270
+ const payload = await ModelClass.dehydrateAttributes(withTimestamps);
2250
2271
  const record = await this.repository.create(payload);
2251
2272
  this.attributes = ModelClass.hydrateAttributes(record);
2252
2273
  this._exists = true;
@@ -62,10 +62,7 @@ function composeMiddleware(...middleware) {
62
62
 
63
63
  // ../../src/core/http/routeMiddleware.ts
64
64
  function withMiddleware(...middleware) {
65
- const wrap = composeMiddleware(...middleware);
66
- return (handler) => {
67
- return wrap(handler);
68
- };
65
+ return composeMiddleware(...middleware);
69
66
  }
70
67
  export {
71
68
  withMiddleware
@@ -29,7 +29,7 @@ export { createTokenAbilityChecker } from "../core/auth/tokenAbilityChecker.ts";
29
29
  export { type CacheDriver, type CreateCacheStoreOptions, createCacheStore, } from "../core/cache/createCacheStore.ts";
30
30
  export { default as CacheRepository } from "../core/cache/repository.ts";
31
31
  export { CACHE_TAGS } from "../core/cache/tags.ts";
32
- export { ConfigStore, ServiceContainer } from "../core/contracts/container.ts";
32
+ export { ConfigStore, createServiceToken, ServiceContainer, type ServiceToken, } from "../core/contracts/container.ts";
33
33
  export type { ServiceProvider } from "../core/contracts/di.ts";
34
34
  export { resolveService } from "../core/contracts/di.ts";
35
35
  export type { DatabaseConnection } from "../core/database/baseRepository.ts";
package/dist/index.js CHANGED
@@ -1124,6 +1124,12 @@ function readBasicCredentials(request) {
1124
1124
  }
1125
1125
 
1126
1126
  // ../../src/core/auth/password.ts
1127
+ async function hashPassword(password) {
1128
+ return await Bun.password.hash(password, {
1129
+ algorithm: "bcrypt",
1130
+ cost: 12
1131
+ });
1132
+ }
1127
1133
  async function verifyPassword(password, passwordHash) {
1128
1134
  return await Bun.password.verify(password, passwordHash);
1129
1135
  }
@@ -3760,6 +3766,12 @@ var CACHE_TAGS = {
3760
3766
  offers: "offers"
3761
3767
  };
3762
3768
  // ../../src/core/contracts/container.ts
3769
+ function createServiceToken(key) {
3770
+ if (!key.trim())
3771
+ throw new Error("Service token key must not be empty.");
3772
+ return key;
3773
+ }
3774
+
3763
3775
  class ServiceContainer {
3764
3776
  services = new Map;
3765
3777
  singletonFactories = new Map;
@@ -6604,12 +6616,14 @@ var PASSWORD_HASH_PATTERN = /^\$(?:2[aby]?|argon2(?:i|d|id)?)\$/;
6604
6616
  function isAlreadyHashed(value) {
6605
6617
  return PASSWORD_HASH_PATTERN.test(value);
6606
6618
  }
6607
- function hashCastValue(value) {
6619
+ async function hashCastValue(value) {
6620
+ if (value === null || value === undefined)
6621
+ return value;
6608
6622
  const plain = String(value);
6609
6623
  if (isAlreadyHashed(plain)) {
6610
6624
  return plain;
6611
6625
  }
6612
- return Bun.password.hashSync(plain, { algorithm: "bcrypt", cost: 12 });
6626
+ return await hashPassword(plain);
6613
6627
  }
6614
6628
  function hydrateValue(value, cast) {
6615
6629
  if (value === null || value === undefined) {
@@ -6634,6 +6648,8 @@ function hydrateValue(value, cast) {
6634
6648
  }
6635
6649
  }
6636
6650
  function dehydrateValue(value, cast) {
6651
+ if (cast === "hashed")
6652
+ return hashCastValue(value);
6637
6653
  if (value === null || value === undefined) {
6638
6654
  return value;
6639
6655
  }
@@ -6649,8 +6665,6 @@ function dehydrateValue(value, cast) {
6649
6665
  case "integer":
6650
6666
  case "int":
6651
6667
  return value === "" ? null : Number(value);
6652
- case "hashed":
6653
- return hashCastValue(value);
6654
6668
  default:
6655
6669
  return value;
6656
6670
  }
@@ -6671,17 +6685,25 @@ function filterMassAssignable(fillable, guarded, input) {
6671
6685
  return Object.fromEntries(Object.entries(input).filter(([key]) => !blocked.has(key)));
6672
6686
  }
6673
6687
  function applyCasts(values, casts, direction) {
6674
- if (Object.keys(casts).length === 0) {
6675
- return values;
6688
+ const entries = Object.entries(casts);
6689
+ if (entries.length === 0) {
6690
+ return direction === "hydrate" ? values : Promise.resolve(values);
6676
6691
  }
6677
6692
  const result = { ...values };
6678
- const castFn = direction === "hydrate" ? hydrateValue : dehydrateValue;
6679
- for (const [key, cast] of Object.entries(casts)) {
6680
- if (key in result && cast) {
6681
- result[key] = castFn(result[key], cast);
6693
+ if (direction === "hydrate") {
6694
+ for (const [key, cast] of entries) {
6695
+ if (key in result && cast)
6696
+ result[key] = hydrateValue(result[key], cast);
6682
6697
  }
6698
+ return result;
6683
6699
  }
6684
- return result;
6700
+ return (async () => {
6701
+ for (const [key, cast] of entries) {
6702
+ if (key in result && cast)
6703
+ result[key] = await dehydrateValue(result[key], cast);
6704
+ }
6705
+ return result;
6706
+ })();
6685
6707
  }
6686
6708
  function castPluckedValue(modelClass, column, value) {
6687
6709
  const cast = modelStatics(modelClass).$casts?.[column];
@@ -7076,7 +7098,7 @@ class Model {
7076
7098
  ...forced
7077
7099
  };
7078
7100
  const withTimestamps = applyTimestampsOnCreate(table.columns, assignable, timestamps);
7079
- const payload = statics.dehydrateAttributes(withTimestamps);
7101
+ const payload = await statics.dehydrateAttributes(withTimestamps);
7080
7102
  const pending = statics.newFromRecord({ ...payload }, false);
7081
7103
  if (await runObservers(pending, "saving") === false) {
7082
7104
  throw new Error(`${this.name}.create() was cancelled by an observer.`);
@@ -7217,7 +7239,7 @@ class Model {
7217
7239
  return this;
7218
7240
  }
7219
7241
  if (updating) {
7220
- const changes = applyTimestampsOnUpdate(table.columns, applyCasts(this.attributes, casts, "dehydrate"), timestamps);
7242
+ const changes = applyTimestampsOnUpdate(table.columns, await applyCasts(this.attributes, casts, "dehydrate"), timestamps);
7221
7243
  const record = await this.repository.updateByIdOrThrow(this.id, changes);
7222
7244
  this.attributes = ModelClass.hydrateAttributes(record);
7223
7245
  await runObservers(this, "updated");
@@ -7226,7 +7248,7 @@ class Model {
7226
7248
  }
7227
7249
  const assignable = filterMassAssignable(ModelClass.$fillable, ModelClass.$guarded, this.attributes);
7228
7250
  const withTimestamps = applyTimestampsOnCreate(table.columns, assignable, timestamps);
7229
- const payload = ModelClass.dehydrateAttributes(withTimestamps);
7251
+ const payload = await ModelClass.dehydrateAttributes(withTimestamps);
7230
7252
  const record = await this.repository.create(payload);
7231
7253
  this.attributes = ModelClass.hydrateAttributes(record);
7232
7254
  this._exists = true;
@@ -10617,10 +10639,7 @@ function getRouteParams(request) {
10617
10639
  }
10618
10640
  // ../../src/core/http/routeMiddleware.ts
10619
10641
  function withMiddleware(...middleware) {
10620
- const wrap = composeMiddleware(...middleware);
10621
- return (handler) => {
10622
- return wrap(handler);
10623
- };
10642
+ return composeMiddleware(...middleware);
10624
10643
  }
10625
10644
  // ../../src/core/http/routeModelBinding.ts
10626
10645
  function bindRouteModel(param, resolver, handler) {
@@ -13179,6 +13198,7 @@ export {
13179
13198
  createScimAuthMiddleware,
13180
13199
  createScimThrottleMiddleware,
13181
13200
  createSecurityHeadersMiddleware,
13201
+ createServiceToken,
13182
13202
  createSqliteConnection,
13183
13203
  createStorageDriver,
13184
13204
  createTenantMiddleware,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getstrata/core",
3
- "version": "2.0.9",
3
+ "version": "2.1.0",
4
4
  "description": "Strata Bun framework public API",
5
5
  "type": "module",
6
6
  "license": "MIT",