@afora/core 0.0.7 → 0.0.8
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 +34 -6
- package/dist/core/application/start.d.ts +7 -0
- package/dist/core/application/types/application.interface.d.ts +36 -2
- package/dist/core/command/types/command.interface.d.ts +7 -0
- package/dist/core/entity/abstract-entity.d.ts +28 -1
- package/dist/core/entity/entity.d.ts +1 -1
- package/dist/core/entity/event.d.ts +13 -2
- package/dist/core/entity/exceptions.d.ts +8 -0
- package/dist/core/entity/types/entity.interface.d.ts +66 -2
- package/dist/core/entity/version.d.ts +13 -0
- package/dist/core/event-policy/event-policy.d.ts +25 -0
- package/dist/core/event-policy/exceptions.d.ts +11 -0
- package/dist/core/event-policy/publish.d.ts +17 -0
- package/dist/core/event-policy/publishers.d.ts +18 -0
- package/dist/core/event-policy/types/event-policy.interface.d.ts +72 -0
- package/dist/core/output/coerce.d.ts +9 -0
- package/dist/core/output/openapi-sink.d.ts +138 -38
- package/dist/core/output/status.d.ts +1 -1
- package/dist/core/parse/parse.d.ts +10 -0
- package/dist/core/parse/strip.d.ts +13 -0
- package/dist/core/repository/exceptions.d.ts +30 -0
- package/dist/core/repository/in-memory.d.ts +14 -1
- package/dist/core/repository/plan.d.ts +20 -0
- package/dist/core/repository/provider.d.ts +1 -1
- package/dist/core/repository/query.d.ts +9 -2
- package/dist/core/repository/repository.d.ts +40 -8
- package/dist/core/repository/state.d.ts +30 -2
- package/dist/core/repository/types/query.interface.d.ts +106 -5
- package/dist/core/repository/types/repository.interface.d.ts +106 -7
- package/dist/experimental.js +4 -4
- package/dist/{index-r78dsvtc.js → index-qxfkyd6k.js} +612 -612
- package/dist/{index-q6hggpzw.js → index-t5qwtyj5.js} +1807 -280
- package/dist/index.d.ts +22 -11
- package/dist/index.js +62 -14
- package/dist/std/blueprint-plugin.d.ts +6 -2
- package/dist/std/blueprint.d.ts +3 -0
- package/dist/std/entity.d.ts +2 -1
- package/dist/std/event-policies.d.ts +2 -0
- package/dist/std/properties/exceptions.d.ts +11 -0
- package/dist/std/properties/json.d.ts +54 -0
- package/dist/std/properties/time.d.ts +4 -0
- package/dist/std/use-cases/crud.d.ts +6 -4
- package/dist/std/use-cases/shared.d.ts +32 -4
- package/dist/std/use-cases/types.d.ts +100 -21
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +347 -2
- package/dist/testing/memory-publisher.d.ts +16 -0
- package/dist/testing/provider-contract.d.ts +14 -0
- package/package.json +3 -2
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import type { AnyBlueprint, AnyBlueprintList, AnyRecordClass, BlueprintField, IBlueprint, IBlueprintCollection, IBlueprintList, OptionalBlueprint, OptionalBlueprintList, OptionalRecord } from "../../core/blueprint/types/blueprint.interface.js";
|
|
1
|
+
import type { AnyBlueprint, AnyBlueprintList, AnyRecordClass, BlueprintField, BlueprintValue, IBlueprint, IBlueprintCollection, IBlueprintList, OptionalBlueprint, OptionalBlueprintList, OptionalRecord } from "../../core/blueprint/types/blueprint.interface.js";
|
|
2
2
|
import type { EntityIdentityKey } from "../../core/entity/types/entity.interface.js";
|
|
3
|
+
import type { EntityVersion } from "../../core/entity/version.js";
|
|
3
4
|
import type { OptionalProperty } from "../../core/property/types/optional-property.type.js";
|
|
4
5
|
import type { AnyPropertyConstructor } from "../../core/property/types/property-collection.type.js";
|
|
5
6
|
import type { AnyRepository, AnyRepositoryEntity, EntityInstanceOf } from "../../core/repository/types/repository.interface.js";
|
|
6
7
|
import type { IUseCasePlugin, UseCaseTarget } from "../../core/use-case/types/plugin.interface.js";
|
|
7
|
-
import type { AnyExceptionClass, StepsUseCaseConstructor } from "../../core/use-case/use-case.js";
|
|
8
|
+
import type { AnyExceptionClass, DependencyFunctions, StepsUseCaseConstructor, UseCaseCollection } from "../../core/use-case/use-case.js";
|
|
8
9
|
import type { Integer, StringProperty } from "../properties/primitives.js";
|
|
9
10
|
type EntityOf<Repository> = Repository extends {
|
|
10
11
|
readonly entity: infer Entity extends AnyRepositoryEntity;
|
|
@@ -88,6 +89,79 @@ export interface StdListOptions<Repository> extends StdUseCaseOptions, StdOwnerO
|
|
|
88
89
|
}[];
|
|
89
90
|
}
|
|
90
91
|
type None = Record<never, never>;
|
|
92
|
+
/** What `before` and `after` get of the call. */
|
|
93
|
+
export interface StdHookArgs<Input, Dependencies, Context> {
|
|
94
|
+
/** The input of the call, parsed; for `after`, the one `before` gave back. */
|
|
95
|
+
readonly input: Input;
|
|
96
|
+
/**
|
|
97
|
+
* The dependencies of the call, as `execute` resolved them (overrides
|
|
98
|
+
* included): `repository`, those the use case makes to load what the
|
|
99
|
+
* input names, and those of the option `dependencies`.
|
|
100
|
+
*/
|
|
101
|
+
readonly dependencies: Dependencies;
|
|
102
|
+
/** The context of the call: the id of the caller with `owner`, else `undefined`. */
|
|
103
|
+
readonly context: Context;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The options of a standard use case that writes: `before` runs before the
|
|
107
|
+
* write and may give back the input to write in place of the one given;
|
|
108
|
+
* `after` runs after it, within the transaction of the command; both get the
|
|
109
|
+
* dependencies of the use case and those of `dependencies`, and `throws` adds
|
|
110
|
+
* the exceptions they may throw to its errors (for `describe()` and statuses).
|
|
111
|
+
*/
|
|
112
|
+
export interface StdHookOptions<Input, Entity, Dependencies extends UseCaseCollection, Bound, Context, Loaded extends boolean> {
|
|
113
|
+
/** Repositories, use cases or environments the hooks use, by name, beside those the use case makes. */
|
|
114
|
+
readonly dependencies?: Dependencies;
|
|
115
|
+
/**
|
|
116
|
+
* Runs before the write (after the entity is loaded and its owner
|
|
117
|
+
* checked, for a use case over one entity, which gets it as `entity`);
|
|
118
|
+
* what it gives back, if anything, is the input the use case goes on with.
|
|
119
|
+
*/
|
|
120
|
+
readonly before?: (args: StdHookArgs<Input, Bound, Context> & (Loaded extends true ? {
|
|
121
|
+
readonly entity: Entity;
|
|
122
|
+
} : unknown)) => Input | void | Promise<Input | void>;
|
|
123
|
+
/** Runs after the write, with the entity written, before the response is given. */
|
|
124
|
+
readonly after?: (args: StdHookArgs<Input, Bound, Context> & {
|
|
125
|
+
readonly entity: Entity;
|
|
126
|
+
}) => unknown;
|
|
127
|
+
/** Exceptions (made by `exceptionOf`) `before` or `after` may throw: added to the errors of the use case. */
|
|
128
|
+
readonly throws?: readonly AnyExceptionClass[];
|
|
129
|
+
}
|
|
130
|
+
/** What `get`, `list` and `login` take in place of the hooks: none. */
|
|
131
|
+
export interface StdNoHookOptions {
|
|
132
|
+
readonly before?: never;
|
|
133
|
+
readonly after?: never;
|
|
134
|
+
readonly dependencies?: never;
|
|
135
|
+
readonly throws?: never;
|
|
136
|
+
}
|
|
137
|
+
type ContextValue<Repository, Options> = OwnerContext<Repository, Options> extends infer Context extends IBlueprintCollection ? Readonly<BlueprintValue<Context>> : undefined;
|
|
138
|
+
/** `Options` as `Hookable` infers them. */
|
|
139
|
+
type Shape<Omitted, Values, Owner> = {
|
|
140
|
+
readonly omit: readonly Omitted[];
|
|
141
|
+
readonly values: Values;
|
|
142
|
+
} & ([Owner] extends [undefined] ? unknown : {
|
|
143
|
+
readonly owner: Owner;
|
|
144
|
+
});
|
|
145
|
+
/** `StdHookOptions` for a use case over `Repository` that takes `Input`. */
|
|
146
|
+
type HookOptionsOf<Repository extends AnyRepository, Options, Input extends IBlueprintCollection, Dependencies extends UseCaseCollection, Loaded extends boolean> = StdHookOptions<BlueprintValue<Input>, EntityInstanceOf<EntityOf<Repository>>, Dependencies, DependencyFunctions<{
|
|
147
|
+
readonly repository: Repository;
|
|
148
|
+
} & Dependencies>, ContextValue<Repository, Options>, Loaded>;
|
|
149
|
+
/**
|
|
150
|
+
* The options of a use case that writes, as its maker reads them: those that
|
|
151
|
+
* type its input and context, inferred one by one (`Options` is inferred from
|
|
152
|
+
* the whole object only once the hooks are typed), and the hooks, typed by
|
|
153
|
+
* them and not inferred from. The types of `before` and `after` are only
|
|
154
|
+
* worked out for a call that gives them.
|
|
155
|
+
*/
|
|
156
|
+
interface Hookable<Repository extends AnyRepository, Input extends IBlueprintCollection, Omitted, Values, Owner, Dependencies extends UseCaseCollection, Loaded extends boolean> {
|
|
157
|
+
readonly omit?: readonly Omitted[];
|
|
158
|
+
readonly values?: Values;
|
|
159
|
+
readonly owner?: Owner;
|
|
160
|
+
readonly dependencies?: Dependencies;
|
|
161
|
+
readonly throws?: readonly AnyExceptionClass[];
|
|
162
|
+
readonly before?: NoInfer<HookOptionsOf<Repository, Shape<Omitted, Values, Owner>, Input, Dependencies, Loaded>["before"]>;
|
|
163
|
+
readonly after?: NoInfer<HookOptionsOf<Repository, Shape<Omitted, Values, Owner>, Input, Dependencies, Loaded>["after"]>;
|
|
164
|
+
}
|
|
91
165
|
type AsOf<Options> = Options extends {
|
|
92
166
|
readonly as: infer Key extends string;
|
|
93
167
|
} ? Key : "item";
|
|
@@ -111,9 +185,13 @@ type LeftOut<Options> = EntityIdentityKey | OwnerKey<Options> | (Options extends
|
|
|
111
185
|
} ? Key : never) | (Options extends {
|
|
112
186
|
readonly values: infer Values;
|
|
113
187
|
} ? keyof Values : never);
|
|
188
|
+
/** `"version"` for a versioned entity, whose `version` is part of its identity. */
|
|
189
|
+
type VersionKey<Repository> = Props<Repository> extends {
|
|
190
|
+
readonly version: typeof EntityVersion;
|
|
191
|
+
} ? "version" : never;
|
|
114
192
|
/** The input of `create`: the keys of the entity, an entity it holds by `<key>Id`. */
|
|
115
193
|
type CreateInput<Repository, Options> = {
|
|
116
|
-
readonly [Key in KeyOf<Repository> as Key extends LeftOut<Options> ? never : Props<Repository>[Key] extends AnyRepositoryEntity ? `${Key}Id` : HoldsEntity<Props<Repository>[Key]> extends true ? never : Key]: Props<Repository>[Key] extends AnyRepositoryEntity ? FieldAt<PropertiesOf<Props<Repository>[Key]>, "id"> : Props<Repository>[Key];
|
|
194
|
+
readonly [Key in KeyOf<Repository> as Key extends LeftOut<Options> | VersionKey<Repository> ? never : Props<Repository>[Key] extends AnyRepositoryEntity ? `${Key}Id` : HoldsEntity<Props<Repository>[Key]> extends true ? never : Key]: Props<Repository>[Key] extends AnyRepositoryEntity ? FieldAt<PropertiesOf<Props<Repository>[Key]>, "id"> : Props<Repository>[Key];
|
|
117
195
|
};
|
|
118
196
|
type ChangeKey<Repository> = EntityInstanceOf<EntityOf<Repository>> extends {
|
|
119
197
|
readonly change: infer Changes;
|
|
@@ -122,18 +200,18 @@ type ChangeKey<Repository> = EntityInstanceOf<EntityOf<Repository>> extends {
|
|
|
122
200
|
type UpdateInput<Repository> = {
|
|
123
201
|
readonly [Key in "id" | ChangeKey<Repository> as Key extends "id" ? Key : HoldsEntity<Props<Repository>[Key]> extends true ? never : Key]: Key extends "id" ? IdField<Repository> : Optional<Props<Repository>[Key]>;
|
|
124
202
|
};
|
|
125
|
-
type EntityUseCase<Repository extends AnyRepository, Options, Input extends IBlueprintCollection> = StepsUseCaseConstructor<Input, EntityResponse<Repository, Options>, {
|
|
203
|
+
type EntityUseCase<Repository extends AnyRepository, Options, Input extends IBlueprintCollection, Dependencies extends UseCaseCollection = None> = StepsUseCaseConstructor<Input, EntityResponse<Repository, Options>, {
|
|
126
204
|
readonly repository: Repository;
|
|
127
|
-
}, OwnerContext<Repository, Options>>;
|
|
205
|
+
} & Dependencies, OwnerContext<Repository, Options>>;
|
|
128
206
|
type ById<Repository> = {
|
|
129
207
|
readonly id: IdField<Repository>;
|
|
130
208
|
};
|
|
131
209
|
/** `create`: for a repository with the `create` action. */
|
|
132
|
-
export type CreateMaker = <Repository extends AnyRepository, const Options extends StdCreateOptions<Repository> = None>(repository: Repository & UseCaseTarget<Repository, "create">, options?: Options) => EntityUseCase<Repository, Options, CreateInput<Repository, Options
|
|
210
|
+
export type CreateMaker = <Repository extends AnyRepository, const Options extends StdCreateOptions<Repository> = None, const Omitted extends KeyOf<Repository> = never, const Values = None, const Owner = undefined, const Dependencies extends UseCaseCollection = None>(repository: Repository & UseCaseTarget<Repository, "create">, options?: Options & Hookable<Repository, CreateInput<Repository, Shape<Omitted, Values, Owner>>, Omitted, Values, Owner, Dependencies, false>) => EntityUseCase<Repository, Options, CreateInput<Repository, Options>, Dependencies>;
|
|
133
211
|
/** `get`: for a repository with `findById`. */
|
|
134
|
-
export type GetMaker = <Repository extends AnyRepository, const Options extends StdEntityOptions<Repository> = None>(repository: Repository & UseCaseTarget<Repository, "findById">, options?: Options) => EntityUseCase<Repository, Options, ById<Repository>>;
|
|
212
|
+
export type GetMaker = <Repository extends AnyRepository, const Options extends StdEntityOptions<Repository> & StdNoHookOptions = None>(repository: Repository & UseCaseTarget<Repository, "findById">, options?: Options) => EntityUseCase<Repository, Options, ById<Repository>>;
|
|
135
213
|
/** `list`: for a repository with `findPage`. */
|
|
136
|
-
export type ListMaker = <Repository extends AnyRepository, const Options extends StdListOptions<Repository> = None>(repository: Repository & UseCaseTarget<Repository, "findPage">, options?: Options) => StepsUseCaseConstructor<{
|
|
214
|
+
export type ListMaker = <Repository extends AnyRepository, const Options extends StdListOptions<Repository> & StdNoHookOptions = None>(repository: Repository & UseCaseTarget<Repository, "findPage">, options?: Options) => StepsUseCaseConstructor<{
|
|
137
215
|
readonly limit: OptionalProperty<typeof Integer>;
|
|
138
216
|
readonly offset: OptionalProperty<typeof Integer>;
|
|
139
217
|
}, {
|
|
@@ -145,41 +223,42 @@ export type ListMaker = <Repository extends AnyRepository, const Options extends
|
|
|
145
223
|
readonly repository: Repository;
|
|
146
224
|
}, OwnerContext<Repository, Options>>;
|
|
147
225
|
/** `update`: for a repository with `findById` and `update`. */
|
|
148
|
-
export type UpdateMaker = <Repository extends AnyRepository, const Options extends StdEntityOptions<Repository> = None>(repository: Repository & UseCaseTarget<Repository, "findById" | "update">, options?: Options) => EntityUseCase<Repository, Options, UpdateInput<Repository
|
|
226
|
+
export type UpdateMaker = <Repository extends AnyRepository, const Options extends StdEntityOptions<Repository> = None, const Owner = undefined, const Dependencies extends UseCaseCollection = None>(repository: Repository & UseCaseTarget<Repository, "findById" | "update">, options?: Options & Hookable<Repository, UpdateInput<Repository>, never, None, Owner, Dependencies, true>) => EntityUseCase<Repository, Options, UpdateInput<Repository>, Dependencies>;
|
|
149
227
|
/** `delete`: for a repository with `findById` and `delete`. */
|
|
150
|
-
export type DeleteMaker = <Repository extends AnyRepository, const Options extends StdEntityOptions<Repository> = None>(repository: Repository & UseCaseTarget<Repository, "findById" | "delete">, options?: Options) => EntityUseCase<Repository, Options, ById<Repository
|
|
228
|
+
export type DeleteMaker = <Repository extends AnyRepository, const Options extends StdEntityOptions<Repository> = None, const Owner = undefined, const Dependencies extends UseCaseCollection = None>(repository: Repository & UseCaseTarget<Repository, "findById" | "delete">, options?: Options & Hookable<Repository, ById<Repository>, never, None, Owner, Dependencies, true>) => EntityUseCase<Repository, Options, ById<Repository>, Dependencies>;
|
|
151
229
|
/** `restore`: for a repository with soft deletion and the `restore` action. */
|
|
152
230
|
export type RestoreMaker = <Repository extends AnyRepository, const Options extends StdUseCaseOptions & {
|
|
153
231
|
readonly as?: string;
|
|
154
|
-
} = None>(repository: Repository & UseCaseTarget<Repository, "restore">, options?: Options) => StepsUseCaseConstructor<ById<Repository>, EntityResponse<Repository, Options>, {
|
|
232
|
+
} = None, const Dependencies extends UseCaseCollection = None>(repository: Repository & UseCaseTarget<Repository, "restore">, options?: Options & Hookable<Repository, ById<Repository>, never, None, undefined, Dependencies, false>) => StepsUseCaseConstructor<ById<Repository>, EntityResponse<Repository, Options>, {
|
|
155
233
|
readonly repository: Repository;
|
|
156
|
-
}>;
|
|
234
|
+
} & Dependencies>;
|
|
157
235
|
/** `publish` and `unpublish`: for a publishable entity. */
|
|
158
|
-
export type PublishMaker = <Repository extends AnyRepository, const Options extends StdLifecycleOptions<Repository> = None>(repository: Repository & UseCaseTarget<Repository, "findById" | "update", {
|
|
236
|
+
export type PublishMaker = <Repository extends AnyRepository, const Options extends StdLifecycleOptions<Repository> = None, const Owner = undefined, const Dependencies extends UseCaseCollection = None>(repository: Repository & UseCaseTarget<Repository, "findById" | "update", {
|
|
159
237
|
publish(at?: string): unknown;
|
|
160
238
|
unpublish(): unknown;
|
|
161
|
-
}, "publishable">, options?: Options) => EntityUseCase<Repository, Options, ById<Repository
|
|
239
|
+
}, "publishable">, options?: Options & Hookable<Repository, ById<Repository>, never, None, Owner, Dependencies, true>) => EntityUseCase<Repository, Options, ById<Repository>, Dependencies>;
|
|
162
240
|
/** `archive` and `unarchive`: for an archivable entity. */
|
|
163
|
-
export type ArchiveMaker = <Repository extends AnyRepository, const Options extends StdLifecycleOptions<Repository> = None>(repository: Repository & UseCaseTarget<Repository, "findById" | "update", {
|
|
241
|
+
export type ArchiveMaker = <Repository extends AnyRepository, const Options extends StdLifecycleOptions<Repository> = None, const Owner = undefined, const Dependencies extends UseCaseCollection = None>(repository: Repository & UseCaseTarget<Repository, "findById" | "update", {
|
|
164
242
|
archive(at?: string): unknown;
|
|
165
243
|
readonly isArchived: boolean;
|
|
166
|
-
}, "archivable">, options?: Options) => EntityUseCase<Repository, Options, ById<Repository
|
|
244
|
+
}, "archivable">, options?: Options & Hookable<Repository, ById<Repository>, never, None, Owner, Dependencies, true>) => EntityUseCase<Repository, Options, ById<Repository>, Dependencies>;
|
|
167
245
|
/** `tag` and `untag`: for a taggable entity. */
|
|
168
|
-
export type TagMaker = <Repository extends AnyRepository, const Options extends StdEntityOptions<Repository> = None>(repository: Repository & UseCaseTarget<Repository, "findById" | "update", {
|
|
246
|
+
export type TagMaker = <Repository extends AnyRepository, const Options extends StdEntityOptions<Repository> = None, const Owner = undefined, const Dependencies extends UseCaseCollection = None>(repository: Repository & UseCaseTarget<Repository, "findById" | "update", {
|
|
169
247
|
tag(...tags: string[]): unknown;
|
|
170
248
|
untag(...tags: string[]): unknown;
|
|
171
|
-
}, "taggable">, options?: Options) => EntityUseCase<Repository, Options,
|
|
249
|
+
}, "taggable">, options?: Options & Hookable<Repository, TagInput<Repository>, never, None, Owner, Dependencies, true>) => EntityUseCase<Repository, Options, TagInput<Repository>, Dependencies>;
|
|
250
|
+
type TagInput<Repository> = ById<Repository> & {
|
|
172
251
|
readonly tags: IBlueprintList<typeof StringProperty>;
|
|
173
|
-
}
|
|
252
|
+
};
|
|
174
253
|
type Authenticatable = {
|
|
175
254
|
verifyPassword(plain: string): Promise<boolean>;
|
|
176
255
|
};
|
|
177
256
|
/** `register`: for an authenticatable entity, with `findByEmail` and `create`. */
|
|
178
|
-
export type RegisterMaker = <Repository extends AnyRepository, const Options extends StdCreateOptions<Repository> = None>(repository: Repository & UseCaseTarget<Repository, "findByEmail" | "create", Authenticatable, "authenticatable">, options?: Options) => EntityUseCase<Repository, Options, CreateInput<Repository, Options
|
|
257
|
+
export type RegisterMaker = <Repository extends AnyRepository, const Options extends StdCreateOptions<Repository> = None, const Omitted extends KeyOf<Repository> = never, const Values = None, const Owner = undefined, const Dependencies extends UseCaseCollection = None>(repository: Repository & UseCaseTarget<Repository, "findByEmail" | "create", Authenticatable, "authenticatable">, options?: Options & Hookable<Repository, CreateInput<Repository, Shape<Omitted, Values, Owner>>, Omitted, Values, Owner, Dependencies, false>) => EntityUseCase<Repository, Options, CreateInput<Repository, Options>, Dependencies>;
|
|
179
258
|
/** `login`: for an authenticatable entity, with `findByEmail`. */
|
|
180
259
|
export type LoginMaker = <Repository extends AnyRepository, const Options extends StdUseCaseOptions & {
|
|
181
260
|
readonly as?: string;
|
|
182
|
-
} = None>(repository: Repository & UseCaseTarget<Repository, "findByEmail", Authenticatable, "authenticatable">, options?: Options) => StepsUseCaseConstructor<{
|
|
261
|
+
} & StdNoHookOptions = None>(repository: Repository & UseCaseTarget<Repository, "findByEmail", Authenticatable, "authenticatable">, options?: Options) => StepsUseCaseConstructor<{
|
|
183
262
|
readonly email: FieldAt<Props<Repository>, "email">;
|
|
184
263
|
readonly password: typeof StringProperty;
|
|
185
264
|
}, EntityResponse<Repository, Options>, {
|
package/dist/testing/index.d.ts
CHANGED
package/dist/testing/index.js
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
|
-
import"../index-
|
|
1
|
+
import"../index-qxfkyd6k.js";
|
|
2
2
|
import {
|
|
3
3
|
RepositoryUniqueViolation2,
|
|
4
4
|
RepositoryEntityNotFound2,
|
|
5
|
+
RepositoryConcurrencyConflict2,
|
|
5
6
|
blueprintOf2,
|
|
6
7
|
entityOf2
|
|
7
|
-
} from "../index-
|
|
8
|
+
} from "../index-t5qwtyj5.js";
|
|
8
9
|
|
|
10
|
+
// src/testing/memory-publisher.ts
|
|
11
|
+
function memoryPublisher() {
|
|
12
|
+
const published = [];
|
|
13
|
+
return {
|
|
14
|
+
published,
|
|
15
|
+
publish(topic, event) {
|
|
16
|
+
published.push({ topic, event });
|
|
17
|
+
},
|
|
18
|
+
clear() {
|
|
19
|
+
published.length = 0;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
9
23
|
// src/testing/provider-contract.ts
|
|
10
24
|
function contextOf(name = "Accounts") {
|
|
11
25
|
class Account extends entityOf2(blueprintOf2((ctx) => ({ email: ctx.string, name: ctx.string }))) {
|
|
@@ -43,6 +57,11 @@ function providerContract(runner, makeProvider, options = {}) {
|
|
|
43
57
|
};
|
|
44
58
|
return { provider, context, seed };
|
|
45
59
|
}
|
|
60
|
+
const gated = (capability) => (name, body) => it(name, async () => {
|
|
61
|
+
const enabled = options[capability] ?? (await makeProvider()).capabilities?.[capability];
|
|
62
|
+
if (enabled === true)
|
|
63
|
+
await body();
|
|
64
|
+
});
|
|
46
65
|
describe("provider contract", () => {
|
|
47
66
|
describe("writes and reads", () => {
|
|
48
67
|
it("inserts and finds by id, empty provider counts zero", async () => {
|
|
@@ -468,6 +487,80 @@ function providerContract(runner, makeProvider, options = {}) {
|
|
|
468
487
|
})).toBe(5);
|
|
469
488
|
});
|
|
470
489
|
});
|
|
490
|
+
describe("indexes", () => {
|
|
491
|
+
const indexed = (name, body) => it(name, async () => {
|
|
492
|
+
const enabled = options.indexes ?? (await makeProvider()).capabilities?.indexes;
|
|
493
|
+
if (enabled === true)
|
|
494
|
+
await body();
|
|
495
|
+
});
|
|
496
|
+
const seeded = async () => {
|
|
497
|
+
const f = await fixture();
|
|
498
|
+
const context = {
|
|
499
|
+
...f.context,
|
|
500
|
+
indexes: {
|
|
501
|
+
byGroup: { where: ["group"], orderBy: "n" },
|
|
502
|
+
byName: { where: ["name"] }
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
const rows = [
|
|
506
|
+
row({ n: 3, group: 1, name: "Gil" }),
|
|
507
|
+
row({ group: 1, name: "Érico" }),
|
|
508
|
+
row({ n: 2, group: 2, name: "Ana" }),
|
|
509
|
+
row({ n: 2, group: 1, name: "Ana" }),
|
|
510
|
+
row({ n: 1, group: 1, name: "Bia" }),
|
|
511
|
+
row({ n: 4, group: 2, name: "Zoe" })
|
|
512
|
+
];
|
|
513
|
+
for (const data of rows)
|
|
514
|
+
await f.provider.insert(context, data);
|
|
515
|
+
await f.provider.update(context, {
|
|
516
|
+
...rows[4],
|
|
517
|
+
deletedAt: "2024-01-01T00:00:00.000Z"
|
|
518
|
+
});
|
|
519
|
+
return { ...f, context };
|
|
520
|
+
};
|
|
521
|
+
indexed("an indexed query gives what the scan gives", async () => {
|
|
522
|
+
const { provider, context } = await seeded();
|
|
523
|
+
const queries = [
|
|
524
|
+
["byGroup", { where: { group: 1 } }],
|
|
525
|
+
[
|
|
526
|
+
"byGroup",
|
|
527
|
+
{
|
|
528
|
+
where: { group: 1 },
|
|
529
|
+
orderBy: [{ key: "n" }, { key: "id" }]
|
|
530
|
+
}
|
|
531
|
+
],
|
|
532
|
+
[
|
|
533
|
+
"byGroup",
|
|
534
|
+
{
|
|
535
|
+
where: { group: 1, name: "Ana" },
|
|
536
|
+
orderBy: [{ key: "n", direction: "desc" }, { key: "id" }],
|
|
537
|
+
limit: 2,
|
|
538
|
+
offset: 1
|
|
539
|
+
}
|
|
540
|
+
],
|
|
541
|
+
[
|
|
542
|
+
"byGroup",
|
|
543
|
+
{
|
|
544
|
+
where: { group: 1 },
|
|
545
|
+
orderBy: [{ key: "n" }, { key: "id" }],
|
|
546
|
+
withDeleted: true
|
|
547
|
+
}
|
|
548
|
+
],
|
|
549
|
+
["byName", { where: { name: "Ana" }, orderBy: [{ key: "id" }] }]
|
|
550
|
+
];
|
|
551
|
+
for (const [index, query] of queries) {
|
|
552
|
+
expect(ids(await provider.findMany(context, { ...query, index }))).toEqual(ids(await provider.findMany(context, query)));
|
|
553
|
+
expect(await provider.count(context, {
|
|
554
|
+
...query,
|
|
555
|
+
index
|
|
556
|
+
})).toBe(await provider.count(context, query));
|
|
557
|
+
expect((await provider.findOne(context, {
|
|
558
|
+
...query,
|
|
559
|
+
index
|
|
560
|
+
}))?.id).toBe((await provider.findOne(context, query))?.id);
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
});
|
|
471
564
|
const withTransactions = async () => {
|
|
472
565
|
const enabled = options.transactions ?? (await makeProvider()).capabilities?.transactions;
|
|
473
566
|
return enabled === true;
|
|
@@ -592,8 +685,260 @@ function providerContract(runner, makeProvider, options = {}) {
|
|
|
592
685
|
expect(await provider.count(context)).toBe(0);
|
|
593
686
|
});
|
|
594
687
|
});
|
|
688
|
+
describe("versioning", () => {
|
|
689
|
+
const versioned = gated("versioning");
|
|
690
|
+
versioned("update writes with the expected version, and throws RepositoryConcurrencyConflict with a stale one", async () => {
|
|
691
|
+
const { provider, context, seed } = await fixture();
|
|
692
|
+
const data = row({ version: 1 });
|
|
693
|
+
await seed(data);
|
|
694
|
+
await provider.update(context, { ...data, name: "Bia", version: 2 }, { expectedVersion: 1 });
|
|
695
|
+
const stale = await failure(provider.update(context, { ...data, name: "Caio", version: 2 }, { expectedVersion: 1 }));
|
|
696
|
+
expect(stale instanceof RepositoryConcurrencyConflict2).toBe(true);
|
|
697
|
+
const { details } = stale;
|
|
698
|
+
expect([details.id, details.expected]).toEqual([data.id, 1]);
|
|
699
|
+
expect(await provider.findOne(context, { where: { id: data.id } })).toEqual({ ...data, name: "Bia", version: 2 });
|
|
700
|
+
});
|
|
701
|
+
versioned("two updates with the same expected version: exactly one succeeds", async () => {
|
|
702
|
+
const { provider, context, seed } = await fixture();
|
|
703
|
+
const data = row({ version: 1 });
|
|
704
|
+
await seed(data);
|
|
705
|
+
const results = await Promise.allSettled(["Bia", "Caio"].map((name) => provider.update(context, { ...data, name, version: 2 }, { expectedVersion: 1 })));
|
|
706
|
+
const rejected = results.filter((result) => result.status === "rejected");
|
|
707
|
+
expect(rejected.length).toBe(1);
|
|
708
|
+
expect(rejected[0]?.reason instanceof RepositoryConcurrencyConflict2).toBe(true);
|
|
709
|
+
const stored = await provider.findOne(context, {
|
|
710
|
+
where: { id: data.id }
|
|
711
|
+
});
|
|
712
|
+
expect(stored?.version).toBe(2);
|
|
713
|
+
});
|
|
714
|
+
versioned("update without an expected version ignores the stored one", async () => {
|
|
715
|
+
const { provider, context, seed } = await fixture();
|
|
716
|
+
const data = row({ version: 3 });
|
|
717
|
+
await seed(data);
|
|
718
|
+
await provider.update(context, { ...data, name: "Bia", version: 1 });
|
|
719
|
+
expect(await provider.findOne(context, { where: { id: data.id } })).toEqual({ ...data, name: "Bia", version: 1 });
|
|
720
|
+
});
|
|
721
|
+
versioned("an unknown id is still RepositoryEntityNotFound", async () => {
|
|
722
|
+
const { provider, context } = await fixture();
|
|
723
|
+
const error = await failure(provider.update(context, row({ version: 2 }), {
|
|
724
|
+
expectedVersion: 1
|
|
725
|
+
}));
|
|
726
|
+
expect(error instanceof RepositoryEntityNotFound2).toBe(true);
|
|
727
|
+
});
|
|
728
|
+
});
|
|
729
|
+
describe("atomic", () => {
|
|
730
|
+
const atomic = gated("atomic");
|
|
731
|
+
const increment = (provider, ...args) => provider.increment(...args);
|
|
732
|
+
atomic("increment adds the deltas, stores set and gives the row", async () => {
|
|
733
|
+
const { provider, context, seed } = await fixture();
|
|
734
|
+
const data = row({ visits: 2, age: 30 });
|
|
735
|
+
await seed(data);
|
|
736
|
+
const stored = await increment(provider, context, data.id, { visits: 3, age: -1, n: 5 }, { set: { name: "Bia" } });
|
|
737
|
+
const expected = { ...data, visits: 5, age: 29, n: 5, name: "Bia" };
|
|
738
|
+
expect(stored).toEqual(expected);
|
|
739
|
+
expect(await provider.findOne(context, { where: { id: data.id } })).toEqual(expected);
|
|
740
|
+
});
|
|
741
|
+
atomic("N concurrent increments sum to N", async () => {
|
|
742
|
+
const { provider, context, seed } = await fixture();
|
|
743
|
+
const data = row({ visits: 0 });
|
|
744
|
+
await seed(data);
|
|
745
|
+
await Promise.all(Array.from({ length: 25 }, () => increment(provider, context, data.id, { visits: 1 })));
|
|
746
|
+
const stored = await provider.findOne(context, {
|
|
747
|
+
where: { id: data.id }
|
|
748
|
+
});
|
|
749
|
+
expect(stored?.visits).toBe(25);
|
|
750
|
+
});
|
|
751
|
+
atomic("a missing id is RepositoryEntityNotFound without create, and inserted with it", async () => {
|
|
752
|
+
const { provider, context } = await fixture();
|
|
753
|
+
const data = row({ visits: 1 });
|
|
754
|
+
const missing = await failure(increment(provider, context, data.id, { visits: 1 }));
|
|
755
|
+
expect(missing instanceof RepositoryEntityNotFound2).toBe(true);
|
|
756
|
+
expect(await provider.count(context)).toBe(0);
|
|
757
|
+
const created = await increment(provider, context, data.id, { visits: 1 }, { create: data, set: { name: "Bia" } });
|
|
758
|
+
expect(created).toEqual(data);
|
|
759
|
+
const again = await increment(provider, context, data.id, { visits: 1 }, { create: data });
|
|
760
|
+
expect(again.visits).toBe(2);
|
|
761
|
+
expect(await provider.count(context)).toBe(1);
|
|
762
|
+
});
|
|
763
|
+
atomic("concurrent increments with create lose nothing", async () => {
|
|
764
|
+
const { provider, context } = await fixture();
|
|
765
|
+
const data = row({ visits: 1 });
|
|
766
|
+
await Promise.all(Array.from({ length: 10 }, () => increment(provider, context, data.id, { visits: 1 }, { create: data })));
|
|
767
|
+
const stored = await provider.findOne(context, {
|
|
768
|
+
where: { id: data.id }
|
|
769
|
+
});
|
|
770
|
+
expect(stored?.visits).toBe(10);
|
|
771
|
+
});
|
|
772
|
+
atomic("soft-deleted rows are not incremented", async () => {
|
|
773
|
+
const { provider, context, seed } = await fixture();
|
|
774
|
+
const data = row({ visits: 1, deletedAt: "2024-01-01T00:00:00.000Z" });
|
|
775
|
+
await seed(data);
|
|
776
|
+
const error = await failure(increment(provider, context, data.id, { visits: 1 }));
|
|
777
|
+
expect(error instanceof RepositoryEntityNotFound2).toBe(true);
|
|
778
|
+
const stored = await provider.findOne(context, {
|
|
779
|
+
where: { id: data.id },
|
|
780
|
+
withDeleted: true
|
|
781
|
+
});
|
|
782
|
+
expect(stored?.visits).toBe(1);
|
|
783
|
+
});
|
|
784
|
+
atomic("an increment of version makes an update with the old one conflict", async () => {
|
|
785
|
+
const { provider, context, seed } = await fixture();
|
|
786
|
+
if ((options.versioning ?? provider.capabilities?.versioning) !== true)
|
|
787
|
+
return;
|
|
788
|
+
const data = row({ visits: 0, version: 1 });
|
|
789
|
+
await seed(data);
|
|
790
|
+
const stored = await increment(provider, context, data.id, {
|
|
791
|
+
visits: 1,
|
|
792
|
+
version: 1
|
|
793
|
+
});
|
|
794
|
+
expect(stored.version).toBe(2);
|
|
795
|
+
const error = await failure(provider.update(context, { ...data, name: "Bia", version: 2 }, { expectedVersion: 1 }));
|
|
796
|
+
expect(error instanceof RepositoryConcurrencyConflict2).toBe(true);
|
|
797
|
+
expect((await provider.findOne(context, { where: { id: data.id } }))?.visits).toBe(1);
|
|
798
|
+
});
|
|
799
|
+
atomic("an expired row counts as absent: not found, or replaced by create", async () => {
|
|
800
|
+
const provider = await makeProvider();
|
|
801
|
+
if ((options.expiry ?? provider.capabilities?.expiry) !== true)
|
|
802
|
+
return;
|
|
803
|
+
const now = "2026-06-01T12:00:00.000Z";
|
|
804
|
+
const context = {
|
|
805
|
+
...contextOf(),
|
|
806
|
+
expiry: { key: "expiresAt" }
|
|
807
|
+
};
|
|
808
|
+
const window = row({
|
|
809
|
+
visits: 7,
|
|
810
|
+
expiresAt: "2026-06-01T11:00:00.000Z"
|
|
811
|
+
});
|
|
812
|
+
const live = row({
|
|
813
|
+
visits: 1,
|
|
814
|
+
expiresAt: "2026-06-01T13:00:00.000Z"
|
|
815
|
+
});
|
|
816
|
+
await provider.insert(context, window);
|
|
817
|
+
await provider.insert(context, live);
|
|
818
|
+
const missing = await failure(increment(provider, context, window.id, { visits: 1 }, {
|
|
819
|
+
now
|
|
820
|
+
}));
|
|
821
|
+
expect(missing instanceof RepositoryEntityNotFound2).toBe(true);
|
|
822
|
+
const fresh = {
|
|
823
|
+
id: window.id,
|
|
824
|
+
email: window.email,
|
|
825
|
+
name: "fresh",
|
|
826
|
+
visits: 1,
|
|
827
|
+
expiresAt: "2026-06-01T13:00:00.000Z"
|
|
828
|
+
};
|
|
829
|
+
const created = await increment(provider, context, window.id, { visits: 1 }, { create: fresh, set: { name: "incremented" }, now });
|
|
830
|
+
expect(created).toEqual(fresh);
|
|
831
|
+
const again = await increment(provider, context, window.id, { visits: 1 }, { create: fresh, set: { name: "incremented" }, now });
|
|
832
|
+
expect([again.visits, again.name]).toEqual([2, "incremented"]);
|
|
833
|
+
const counted = await increment(provider, context, live.id, { visits: 1 }, { create: { ...live, visits: 100 }, now });
|
|
834
|
+
expect(counted.visits).toBe(2);
|
|
835
|
+
});
|
|
836
|
+
});
|
|
837
|
+
describe("expiry", () => {
|
|
838
|
+
const expiring = gated("expiry");
|
|
839
|
+
const NOW = "2026-06-01T12:00:00.000Z";
|
|
840
|
+
const seeded = async () => {
|
|
841
|
+
const provider = await makeProvider();
|
|
842
|
+
const context = {
|
|
843
|
+
...contextOf(),
|
|
844
|
+
expiry: { key: "expiresAt" }
|
|
845
|
+
};
|
|
846
|
+
const [past, edge, never, future] = [
|
|
847
|
+
row({ n: 1, expiresAt: "2026-05-01T00:00:00.000Z" }),
|
|
848
|
+
row({ n: 2, expiresAt: NOW }),
|
|
849
|
+
row({ n: 3 }),
|
|
850
|
+
row({ n: 4, expiresAt: "2026-07-01T00:00:00.000Z" })
|
|
851
|
+
];
|
|
852
|
+
for (const data of [past, edge, never, future])
|
|
853
|
+
await provider.insert(context, data);
|
|
854
|
+
return { provider, context, past, edge, never, future };
|
|
855
|
+
};
|
|
856
|
+
expiring("reads leave out rows whose date is at or before now", async () => {
|
|
857
|
+
const { provider, context, past, edge, never, future } = await seeded();
|
|
858
|
+
const query = { now: NOW };
|
|
859
|
+
expect(ids(await provider.findMany(context, query))).toEqual([
|
|
860
|
+
never.id,
|
|
861
|
+
future.id
|
|
862
|
+
]);
|
|
863
|
+
expect(await provider.count(context, query)).toBe(2);
|
|
864
|
+
expect(await provider.findOne(context, {
|
|
865
|
+
...query,
|
|
866
|
+
where: { id: past.id }
|
|
867
|
+
})).toBe(undefined);
|
|
868
|
+
expect(await provider.findOne(context, {
|
|
869
|
+
...query,
|
|
870
|
+
where: { id: edge.id }
|
|
871
|
+
})).toBe(undefined);
|
|
872
|
+
expect((await provider.findOne(context, {
|
|
873
|
+
...query,
|
|
874
|
+
where: { id: future.id }
|
|
875
|
+
}))?.id).toBe(future.id);
|
|
876
|
+
expect(ids(await provider.findMany(context, {
|
|
877
|
+
...query,
|
|
878
|
+
where: { id: { in: [past.id, never.id] } }
|
|
879
|
+
}))).toEqual([never.id]);
|
|
880
|
+
});
|
|
881
|
+
expiring("a row without a date never expires", async () => {
|
|
882
|
+
const { provider, context, never } = await seeded();
|
|
883
|
+
const later = { now: "2999-01-01T00:00:00.000Z" };
|
|
884
|
+
expect(ids(await provider.findMany(context, later))).toEqual([
|
|
885
|
+
never.id
|
|
886
|
+
]);
|
|
887
|
+
expect(await provider.count(context, later)).toBe(1);
|
|
888
|
+
});
|
|
889
|
+
expiring("withExpired gives the expired rows too", async () => {
|
|
890
|
+
const { provider, context, past, edge, never, future } = await seeded();
|
|
891
|
+
const query = { now: NOW, withExpired: true };
|
|
892
|
+
expect(ids(await provider.findMany(context, query))).toEqual([
|
|
893
|
+
past.id,
|
|
894
|
+
edge.id,
|
|
895
|
+
never.id,
|
|
896
|
+
future.id
|
|
897
|
+
]);
|
|
898
|
+
expect(await provider.count(context, query)).toBe(4);
|
|
899
|
+
expect((await provider.findOne(context, {
|
|
900
|
+
...query,
|
|
901
|
+
where: { id: past.id }
|
|
902
|
+
}))?.id).toBe(past.id);
|
|
903
|
+
});
|
|
904
|
+
expiring("leaves expired rows out of a read after a position", async () => {
|
|
905
|
+
const enabled = options.keyset ?? (await makeProvider()).capabilities?.keyset;
|
|
906
|
+
if (enabled !== true)
|
|
907
|
+
return;
|
|
908
|
+
const { provider, context, past, edge, never, future } = await seeded();
|
|
909
|
+
const orderBy = [{ key: "n" }, { key: "id" }];
|
|
910
|
+
expect(ids(await provider.findMany(context, {
|
|
911
|
+
now: NOW,
|
|
912
|
+
orderBy,
|
|
913
|
+
after: [0, ""]
|
|
914
|
+
}))).toEqual([never.id, future.id]);
|
|
915
|
+
expect(ids(await provider.findMany(context, {
|
|
916
|
+
now: NOW,
|
|
917
|
+
orderBy,
|
|
918
|
+
after: [1, past.id],
|
|
919
|
+
withExpired: true
|
|
920
|
+
}))).toEqual([edge.id, never.id, future.id]);
|
|
921
|
+
});
|
|
922
|
+
expiring("unique keys still count expired rows", async () => {
|
|
923
|
+
const { provider, context, past } = await seeded();
|
|
924
|
+
const error = await failure(provider.insert(context, row({ email: past.email })));
|
|
925
|
+
expect(error instanceof RepositoryUniqueViolation2).toBe(true);
|
|
926
|
+
expect(error.details.key).toBe("email");
|
|
927
|
+
expect(await provider.count(context, { now: NOW, withExpired: true })).toBe(4);
|
|
928
|
+
});
|
|
929
|
+
expiring("an entity that does not expire ignores now", async () => {
|
|
930
|
+
const { provider, context, seed } = await fixture();
|
|
931
|
+
const data = row({ expiresAt: "2000-01-01T00:00:00.000Z" });
|
|
932
|
+
await seed(data);
|
|
933
|
+
expect(ids(await provider.findMany(context, { now: NOW }))).toEqual([
|
|
934
|
+
data.id
|
|
935
|
+
]);
|
|
936
|
+
expect(await provider.count(context, { now: NOW })).toBe(1);
|
|
937
|
+
});
|
|
938
|
+
});
|
|
595
939
|
});
|
|
596
940
|
}
|
|
597
941
|
export {
|
|
942
|
+
memoryPublisher,
|
|
598
943
|
providerContract
|
|
599
944
|
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { IEventPublisher, PublishedEvent } from "../core/event-policy/types/event-policy.interface.js";
|
|
2
|
+
/** A publisher that keeps what it is given, for tests. */
|
|
3
|
+
export interface MemoryPublisher extends IEventPublisher {
|
|
4
|
+
/** Each publish, in order. */
|
|
5
|
+
readonly published: readonly {
|
|
6
|
+
readonly topic: string;
|
|
7
|
+
readonly event: PublishedEvent;
|
|
8
|
+
}[];
|
|
9
|
+
/** Forgets what was published. */
|
|
10
|
+
clear(): void;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* A publisher for `applicationOf({ publishers })` in tests: it keeps each
|
|
14
|
+
* `{ topic, event }` it is given in `published`.
|
|
15
|
+
*/
|
|
16
|
+
export declare function memoryPublisher(): MemoryPublisher;
|
|
@@ -18,6 +18,20 @@ export interface ProviderContractOptions {
|
|
|
18
18
|
* to `provider.capabilities?.keyset`.
|
|
19
19
|
*/
|
|
20
20
|
readonly keyset?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Run the cases of declared indexes (`context.indexes`, `query.index`).
|
|
23
|
+
* Defaults to `provider.capabilities?.indexes`.
|
|
24
|
+
*/
|
|
25
|
+
readonly indexes?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Run the cases of an expected `version` on `update`. Defaults to
|
|
28
|
+
* `provider.capabilities?.versioning`.
|
|
29
|
+
*/
|
|
30
|
+
readonly versioning?: boolean;
|
|
31
|
+
/** Run the cases of `increment`. Defaults to `provider.capabilities?.atomic`. */
|
|
32
|
+
readonly atomic?: boolean;
|
|
33
|
+
/** Run the cases of expired records. Defaults to `provider.capabilities?.expiry`. */
|
|
34
|
+
readonly expiry?: boolean;
|
|
21
35
|
}
|
|
22
36
|
/**
|
|
23
37
|
* Registers the behavior every `IRepositoryProvider` v2 must have. Pass the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@afora/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"description": "Domain modeling on TypeBox: entities with invariants, repositories, use cases, sensitive data masking and layered exceptions, usable by any Standard Schema consumer.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"domain-driven-design",
|
|
@@ -84,8 +84,9 @@
|
|
|
84
84
|
"test:roundtrip": "bun run --cwd packages/codegen test:roundtrip",
|
|
85
85
|
"test:mcp": "bun test --cwd packages/mcp",
|
|
86
86
|
"test:hono": "bun test --cwd packages/hono",
|
|
87
|
+
"test:ws": "bun test --cwd packages/ws",
|
|
87
88
|
"test:drizzle": "bun test --cwd packages/drizzle",
|
|
88
|
-
"build:packages": "bun run build && bun run --cwd packages/codegen build && bun run --cwd packages/mcp build && bun run --cwd packages/hono build && bun run --cwd packages/drizzle build"
|
|
89
|
+
"build:packages": "bun run build && bun run --cwd packages/codegen build && bun run --cwd packages/mcp build && bun run --cwd packages/hono build && bun run --cwd packages/ws build && bun run --cwd packages/drizzle build"
|
|
89
90
|
},
|
|
90
91
|
"dependencies": {
|
|
91
92
|
"slugify": "^1.6.9"
|