@afora/core 0.0.9 → 0.0.12
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 +62 -189
- package/dist/adapters.js +31 -55
- package/dist/core/application/application.d.ts +30 -0
- package/dist/core/application/application.js +1291 -0
- package/dist/core/application/exceptions.js +85 -0
- package/dist/core/application/migrate.js +67 -0
- package/dist/core/application/providers.js +108 -0
- package/dist/core/application/registry.js +23 -0
- package/dist/core/application/schema.js +398 -0
- package/dist/core/application/start.js +162 -0
- package/dist/core/application/types/application.interface.d.ts +23 -3
- package/dist/core/auth/jwt.js +200 -0
- package/dist/core/auth/types/jwt.interface.d.ts +10 -2
- package/dist/core/blueprint/blueprint-plugin.js +7 -0
- package/dist/core/blueprint/blueprint.js +146 -0
- package/dist/core/blueprint/callable.js +13 -0
- package/dist/core/blueprint/exceptions.js +20 -0
- package/dist/core/blueprint/is-blueprint.js +4 -0
- package/dist/core/blueprint/list.js +19 -0
- package/dist/core/blueprint/optional.js +69 -0
- package/dist/core/blueprint/record-field.js +14 -0
- package/dist/core/blueprint/slice.js +127 -0
- package/dist/core/blueprint/types/blueprint.interface.d.ts +16 -0
- package/dist/core/blueprint/variant.js +72 -0
- package/dist/core/command/command.js +428 -0
- package/dist/core/command/exceptions.js +30 -0
- package/dist/core/command/types/command.interface.d.ts +10 -1
- package/dist/core/entity/abstract-entity.d.ts +19 -0
- package/dist/core/entity/abstract-entity.js +181 -0
- package/dist/core/entity/entity.js +209 -0
- package/dist/core/entity/event.d.ts +39 -0
- package/dist/core/entity/event.js +80 -0
- package/dist/core/entity/exceptions.js +27 -0
- package/dist/core/entity/trust.js +7 -0
- package/dist/core/entity/version.js +13 -0
- package/dist/core/environment/environment.d.ts +26 -3
- package/dist/core/environment/environment.js +71 -0
- package/dist/core/event-policy/event-policy.js +76 -0
- package/dist/core/event-policy/exceptions.js +12 -0
- package/dist/core/event-policy/publish.js +98 -0
- package/dist/core/event-policy/publishers.js +18 -0
- package/dist/core/event-policy/types/event-policy.interface.d.ts +8 -2
- package/dist/core/exception/enums/exception-layers.js +15 -0
- package/dist/core/exception/exception.d.ts +15 -0
- package/dist/core/exception/exception.js +128 -0
- package/dist/core/exception/exported.js +49 -0
- package/dist/core/exception/guard.d.ts +20 -1
- package/dist/core/exception/guard.js +16 -0
- package/dist/core/exception/types/exception.interface.d.ts +13 -0
- package/dist/core/json.js +19 -0
- package/dist/core/logger/exceptions.js +7 -0
- package/dist/core/logger/logger.d.ts +14 -1
- package/dist/core/logger/logger.js +178 -0
- package/dist/core/openapi/exceptions.js +7 -0
- package/dist/core/openapi/openapi.js +132 -0
- package/dist/core/openapi/types/openapi.interface.d.ts +7 -1
- package/dist/core/output/access.js +66 -0
- package/dist/core/output/coerce.js +38 -0
- package/dist/core/output/exceptions.js +22 -0
- package/dist/{index-y280ew0k.js → core/output/http.js} +35 -80
- package/dist/core/output/openapi-sink.d.ts +10 -2
- package/dist/{index-hg1z2s2v.js → core/output/openapi-sink.js} +60 -544
- package/dist/core/output/output.js +192 -0
- package/dist/core/output/problem.js +30 -0
- package/dist/core/output/select.js +28 -0
- package/dist/core/output/stage.js +111 -0
- package/dist/core/output/status.js +52 -0
- package/dist/core/output/types/http.interface.d.ts +5 -1
- package/dist/core/output/types/output.interface.d.ts +24 -0
- package/dist/core/parse/issue.js +31 -0
- package/dist/core/parse/parse.d.ts +8 -1
- package/dist/core/parse/parse.js +323 -0
- package/dist/core/parse/strip.js +48 -0
- package/dist/core/property/abstract-property.js +37 -0
- package/dist/core/property/define-property.js +209 -0
- package/dist/core/property/enums/property-states.js +8 -0
- package/dist/core/property/exceptions.js +12 -0
- package/dist/core/property/input.js +10 -0
- package/dist/core/property/is-property.js +3 -0
- package/dist/core/property/lineage.js +8 -0
- package/dist/core/property/property.d.ts +27 -0
- package/dist/core/property/property.js +46 -0
- package/dist/core/property/refine.js +103 -0
- package/dist/core/record/abstract-record.d.ts +2 -1
- package/dist/core/record/abstract-record.js +307 -0
- package/dist/core/record/exceptions.js +49 -0
- package/dist/core/record/marks.js +7 -0
- package/dist/core/record/mask.js +60 -0
- package/dist/core/record/name.js +23 -0
- package/dist/core/record/plugin-catalog.js +115 -0
- package/dist/core/record/plugin.js +198 -0
- package/dist/core/record/record.js +117 -0
- package/dist/core/record/types/record.interface.d.ts +5 -0
- package/dist/core/repository/cursor.js +83 -0
- package/dist/core/repository/exceptions.js +72 -0
- package/dist/core/repository/in-memory.js +196 -0
- package/dist/core/repository/migrate.js +254 -0
- package/dist/core/repository/plan.js +27 -0
- package/dist/core/repository/provider.js +29 -0
- package/dist/core/repository/query.js +153 -0
- package/dist/core/repository/references.js +196 -0
- package/dist/core/repository/repository.d.ts +22 -2
- package/dist/core/repository/repository.js +720 -0
- package/dist/core/repository/state.js +138 -0
- package/dist/core/repository/types/query.interface.d.ts +13 -0
- package/dist/core/repository/types/repository.interface.d.ts +20 -8
- package/dist/core/repository/types/schema.interface.d.ts +8 -2
- package/dist/core/repository/upcast.js +179 -0
- package/dist/core/schema/schema.d.ts +9 -0
- package/dist/core/schema/schema.js +542 -0
- package/dist/core/standard/standard-schema.js +1 -0
- package/dist/core/standard/standard.js +23 -0
- package/dist/core/symbols/demo.d.ts +4 -0
- package/dist/core/symbols/demo.js +1 -0
- package/dist/core/symbols/kind.js +1 -0
- package/dist/core/symbols/meta.js +1 -0
- package/dist/core/symbols/state.d.ts +5 -0
- package/dist/core/symbols/state.js +1 -0
- package/dist/core/tracing/exceptions.d.ts +4 -0
- package/dist/core/tracing/exceptions.js +7 -0
- package/dist/core/tracing/tracing.d.ts +72 -0
- package/dist/core/tracing/tracing.js +101 -0
- package/dist/core/use-case/access.js +56 -0
- package/dist/core/use-case/decorators.js +65 -0
- package/dist/core/use-case/exceptions.d.ts +5 -1
- package/dist/core/use-case/exceptions.js +48 -0
- package/dist/core/use-case/plugin-catalog.js +68 -0
- package/dist/core/use-case/plugin.js +133 -0
- package/dist/core/use-case/route.js +200 -0
- package/dist/core/use-case/scope.d.ts +5 -0
- package/dist/core/use-case/scope.js +4 -0
- package/dist/core/use-case/steps/describe.d.ts +5 -1
- package/dist/core/use-case/steps/describe.js +24 -0
- package/dist/core/use-case/steps/references.js +105 -0
- package/dist/core/use-case/steps/run.js +139 -0
- package/dist/core/use-case/steps/types/check.interface.d.ts +27 -8
- package/dist/core/use-case/steps/types/steps.interface.d.ts +8 -1
- package/dist/core/use-case/steps/validate.js +368 -0
- package/dist/core/use-case/target.js +100 -0
- package/dist/core/use-case/types/route.interface.d.ts +25 -5
- package/dist/core/use-case/use-case.d.ts +77 -2
- package/dist/core/use-case/use-case.js +493 -0
- package/dist/experimental.d.ts +5 -0
- package/dist/experimental.js +18 -161
- package/dist/index.d.ts +7 -5
- package/dist/index.js +213 -367
- package/dist/std/blueprint-plugin.js +42 -0
- package/dist/std/blueprint.d.ts +21 -0
- package/dist/std/blueprint.js +3 -0
- package/dist/std/command.js +3 -0
- package/dist/std/entity.d.ts +20 -1
- package/dist/std/entity.js +30 -0
- package/dist/std/event-policies.js +3 -0
- package/dist/std/properties/contact.d.ts +3 -0
- package/dist/std/properties/contact.js +29 -0
- package/dist/std/properties/enum.js +26 -0
- package/dist/std/properties/exceptions.js +14 -0
- package/dist/std/properties/headless.js +45 -0
- package/dist/std/properties/identity.js +28 -0
- package/dist/std/properties/json.js +161 -0
- package/dist/std/properties/password.js +79 -0
- package/dist/std/properties/primitives.d.ts +4 -0
- package/dist/std/properties/primitives.js +28 -0
- package/dist/std/properties/time.js +55 -0
- package/dist/std/record-plugins.js +73 -0
- package/dist/std/record.d.ts +20 -1
- package/dist/std/record.js +3 -0
- package/dist/std/resource.js +39 -0
- package/dist/std/runtime.js +193 -0
- package/dist/std/stages.js +4 -0
- package/dist/std/use-cases/auth.js +193 -0
- package/dist/std/use-cases/crud.js +261 -0
- package/dist/std/use-cases/index.js +12 -0
- package/dist/std/use-cases/lifecycle.js +97 -0
- package/dist/std/use-cases/shared.js +232 -0
- package/dist/std/use-cases/types.d.ts +5 -1
- package/dist/std/use-cases/types.js +0 -0
- package/dist/testing/http-provider-contract.js +344 -0
- package/dist/testing/index.js +3 -1435
- package/dist/testing/memory-publisher.js +12 -0
- package/dist/testing/provider-contract.d.ts +12 -3
- package/dist/testing/provider-contract.js +1071 -0
- package/package.json +17 -5
- package/dist/index-3aae5jfq.js +0 -2997
- package/dist/index-ny3as79r.js +0 -4567
- package/dist/index-xegv6a1p.js +0 -4291
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { isBlueprint } from "../blueprint/is-blueprint.js";
|
|
2
|
+
import { isBlueprintList } from "../blueprint/list.js";
|
|
3
|
+
import { recordFieldOf } from "../blueprint/record-field.js";
|
|
4
|
+
import {
|
|
5
|
+
blueprintOfVariant,
|
|
6
|
+
isBlueprintVariant,
|
|
7
|
+
namedVariantOf
|
|
8
|
+
} from "../blueprint/variant.js";
|
|
9
|
+
import { isEntityClass } from "../entity/entity.js";
|
|
10
|
+
import { recordNameOf } from "../record/name.js";
|
|
11
|
+
import { scopedProvider } from "../use-case/scope.js";
|
|
12
|
+
import {
|
|
13
|
+
InvalidRepositoryData,
|
|
14
|
+
InvalidRepositoryDeclaration,
|
|
15
|
+
RepositoryEntityNotFound
|
|
16
|
+
} from "./exceptions.js";
|
|
17
|
+
import { contextOf, findState } from "./state.js";
|
|
18
|
+
import { upcastRows } from "./upcast.js";
|
|
19
|
+
function isPlain(value) {
|
|
20
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21
|
+
}
|
|
22
|
+
function recordClassOf(field) {
|
|
23
|
+
return recordFieldOf(field)?.record;
|
|
24
|
+
}
|
|
25
|
+
function entityClassesOf(properties) {
|
|
26
|
+
const found = new Set;
|
|
27
|
+
function visit(field) {
|
|
28
|
+
if (isBlueprintList(field)) {
|
|
29
|
+
visit(field.item);
|
|
30
|
+
} else if (isBlueprint(field)) {
|
|
31
|
+
walk(field.properties);
|
|
32
|
+
} else if (isBlueprintVariant(field)) {
|
|
33
|
+
for (const variant of Object.values(field.variants)) {
|
|
34
|
+
visit(variant);
|
|
35
|
+
}
|
|
36
|
+
} else {
|
|
37
|
+
const record = recordClassOf(field);
|
|
38
|
+
if (!record)
|
|
39
|
+
return;
|
|
40
|
+
if (isEntityClass(record))
|
|
41
|
+
found.add(record);
|
|
42
|
+
else
|
|
43
|
+
walk(record.blueprint.properties);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function walk(properties) {
|
|
47
|
+
for (const field of Object.values(properties))
|
|
48
|
+
visit(field);
|
|
49
|
+
}
|
|
50
|
+
walk(properties);
|
|
51
|
+
return found;
|
|
52
|
+
}
|
|
53
|
+
function mapReferences(properties, data, visit) {
|
|
54
|
+
function mapField(field, value) {
|
|
55
|
+
if (value === undefined || value === null)
|
|
56
|
+
return value;
|
|
57
|
+
if (isBlueprintList(field)) {
|
|
58
|
+
return Array.isArray(value) ? Object.freeze(value.map((item) => mapField(field.item, item))) : value;
|
|
59
|
+
}
|
|
60
|
+
if (isBlueprint(field)) {
|
|
61
|
+
return isPlain(value) ? mapCollection(field.properties, value) : value;
|
|
62
|
+
}
|
|
63
|
+
if (isBlueprintVariant(field)) {
|
|
64
|
+
const variant = namedVariantOf(field, value);
|
|
65
|
+
if (!variant || !isPlain(value))
|
|
66
|
+
return value;
|
|
67
|
+
if (isEntityClass(variant)) {
|
|
68
|
+
const mapped = visit(variant, value.id);
|
|
69
|
+
return mapped === value.id ? value : mapped;
|
|
70
|
+
}
|
|
71
|
+
return mapCollection(blueprintOfVariant(variant).properties, value);
|
|
72
|
+
}
|
|
73
|
+
const record = recordClassOf(field);
|
|
74
|
+
if (!record)
|
|
75
|
+
return value;
|
|
76
|
+
if (isEntityClass(record))
|
|
77
|
+
return visit(record, value);
|
|
78
|
+
return isPlain(value) ? mapCollection(record.blueprint.properties, value) : value;
|
|
79
|
+
}
|
|
80
|
+
function mapCollection(properties, value) {
|
|
81
|
+
return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, item]) => [
|
|
82
|
+
key,
|
|
83
|
+
mapField(properties[key], item)
|
|
84
|
+
])));
|
|
85
|
+
}
|
|
86
|
+
return mapCollection(properties, data);
|
|
87
|
+
}
|
|
88
|
+
export function relationsOf(name, entity, relations) {
|
|
89
|
+
const invalid = (reason) => {
|
|
90
|
+
throw new InvalidRepositoryDeclaration({
|
|
91
|
+
source: name,
|
|
92
|
+
details: { reason }
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
if (relations === undefined)
|
|
96
|
+
relations = [];
|
|
97
|
+
if (!Array.isArray(relations)) {
|
|
98
|
+
return invalid("Expected relations to be a list of repositories.");
|
|
99
|
+
}
|
|
100
|
+
const related = [];
|
|
101
|
+
for (const relation of relations) {
|
|
102
|
+
const state = findState(relation);
|
|
103
|
+
if (!state)
|
|
104
|
+
return invalid("Expected relations made by repositoryOf.");
|
|
105
|
+
if (related.some((other) => other.entity === state.entity)) {
|
|
106
|
+
return invalid(`Relation "${recordNameOf(state.entity)}" is duplicated.`);
|
|
107
|
+
}
|
|
108
|
+
if (typeof state.provider.findMany !== "function") {
|
|
109
|
+
return invalid(`The provider of "${state.name}" has no "findMany" to load it.`);
|
|
110
|
+
}
|
|
111
|
+
related.push(state);
|
|
112
|
+
}
|
|
113
|
+
const used = new Set;
|
|
114
|
+
const map = new Map;
|
|
115
|
+
for (const target of entityClassesOf(entity.blueprint.properties)) {
|
|
116
|
+
const matches = related.filter((state) => state.entity === target || state.entity.prototype instanceof target);
|
|
117
|
+
const exact = matches.find((state) => state.entity === target);
|
|
118
|
+
if (!exact && matches.length === 0) {
|
|
119
|
+
return invalid(`Missing relation: no repository of "${recordNameOf(target)}".`);
|
|
120
|
+
}
|
|
121
|
+
if (!exact && matches.length > 1) {
|
|
122
|
+
return invalid(`Relation "${recordNameOf(target)}" matches more than one repository.`);
|
|
123
|
+
}
|
|
124
|
+
const state = exact ?? matches[0];
|
|
125
|
+
map.set(target, state);
|
|
126
|
+
used.add(state);
|
|
127
|
+
}
|
|
128
|
+
for (const state of related) {
|
|
129
|
+
if (!used.has(state)) {
|
|
130
|
+
return invalid(`Relation "${recordNameOf(state.entity)}" is not held by ${recordNameOf(entity)}.`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return map;
|
|
134
|
+
}
|
|
135
|
+
export function parseEntity(state, data) {
|
|
136
|
+
const parsed = state.entity.safeParse(data);
|
|
137
|
+
if (parsed.issues) {
|
|
138
|
+
throw new InvalidRepositoryData({
|
|
139
|
+
source: state.name,
|
|
140
|
+
cause: parsed.issues.find((issue) => issue.cause)?.cause
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
return parsed.value;
|
|
144
|
+
}
|
|
145
|
+
function loadByIds(state, ids, scope) {
|
|
146
|
+
return scopedProvider(scope, state.provider).findMany(contextOf(state), {
|
|
147
|
+
where: { id: { in: ids } },
|
|
148
|
+
withDeleted: true,
|
|
149
|
+
withExpired: true
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
export async function resolveReferences(state, datas, session, scope) {
|
|
153
|
+
if (state.relations.size === 0)
|
|
154
|
+
return datas;
|
|
155
|
+
const { properties } = state.entity.blueprint;
|
|
156
|
+
const wanted = new Map;
|
|
157
|
+
for (const data of datas) {
|
|
158
|
+
mapReferences(properties, data, (target, value) => {
|
|
159
|
+
const relation = state.relations.get(target);
|
|
160
|
+
if (relation && typeof value === "string") {
|
|
161
|
+
const ids = wanted.get(relation) ?? new Set;
|
|
162
|
+
wanted.set(relation, ids.add(value));
|
|
163
|
+
}
|
|
164
|
+
return value;
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
for (const [relation, ids] of wanted) {
|
|
168
|
+
const loaded = session.get(relation) ?? new Map;
|
|
169
|
+
session.set(relation, loaded);
|
|
170
|
+
const missing = [...ids].filter((id) => !loaded.has(id));
|
|
171
|
+
if (missing.length === 0)
|
|
172
|
+
continue;
|
|
173
|
+
const found = upcastRows(relation, await loadByIds(relation, missing, scope));
|
|
174
|
+
const resolved = await resolveReferences(relation, found, session, scope);
|
|
175
|
+
for (const data of resolved) {
|
|
176
|
+
const id = data.id;
|
|
177
|
+
if (!loaded.has(id))
|
|
178
|
+
loaded.set(id, parseEntity(relation, data));
|
|
179
|
+
}
|
|
180
|
+
for (const id of missing) {
|
|
181
|
+
if (loaded.has(id))
|
|
182
|
+
continue;
|
|
183
|
+
throw new InvalidRepositoryData({
|
|
184
|
+
source: state.name,
|
|
185
|
+
cause: new RepositoryEntityNotFound({
|
|
186
|
+
source: relation.name,
|
|
187
|
+
details: { id }
|
|
188
|
+
})
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return datas.map((data) => mapReferences(properties, data, (target, value) => {
|
|
193
|
+
const relation = state.relations.get(target);
|
|
194
|
+
return relation && typeof value === "string" ? session.get(relation)?.get(value) ?? value : value;
|
|
195
|
+
}));
|
|
196
|
+
}
|
|
@@ -80,9 +80,29 @@ export declare abstract class AbstractRepository<Entity extends AnyRepositoryEnt
|
|
|
80
80
|
* `create`, `update`, `delete`, `restore` (soft deletion only), `increment`, `count`, and the queries `findMany`,
|
|
81
81
|
* `exists`, `findPage` and `findCursor`, and `forEachPage`, which walks the
|
|
82
82
|
* pages of a cursor query, resumably. Every method is async and reads and writes through `provider`
|
|
83
|
-
* (a new
|
|
83
|
+
* (a new {@link inMemoryProvider} by default), which gets the variables of
|
|
84
84
|
* `environmentDependencies` as `context.env`. The repository can be a
|
|
85
|
-
* dependency of a use case, and
|
|
85
|
+
* dependency of a use case, and {@link RepositoryOf} extends it with a class.
|
|
86
|
+
*
|
|
87
|
+
* @throws {@link InvalidRepositoryDeclaration} when `entity` is not an entity class made by `entityOf`, or a setting (`actions`, `deletion`, `scans`, `indexes`...) is not valid.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```ts
|
|
91
|
+
* import { blueprintOf, entityOf, repositoryOf } from "@afora/core";
|
|
92
|
+
*
|
|
93
|
+
* class User extends entityOf(
|
|
94
|
+
* blueprintOf((ctx) => ({ name: ctx.string, email: ctx.email })),
|
|
95
|
+
* { unique: ["email"] },
|
|
96
|
+
* ) {}
|
|
97
|
+
*
|
|
98
|
+
* const Users = repositoryOf({
|
|
99
|
+
* entity: User,
|
|
100
|
+
* actions: ["findByEmail", "create"],
|
|
101
|
+
* });
|
|
102
|
+
*
|
|
103
|
+
* await Users.create({ name: "Ana", email: "ana@example.com" });
|
|
104
|
+
* const ana = await Users.findByEmail("ana@example.com"); // User | undefined
|
|
105
|
+
* ```
|
|
86
106
|
*/
|
|
87
107
|
export declare function repositoryOf<Entity extends AnyRepositoryEntity, const Actions extends readonly RepositoryAction<Entity>[], const Environment extends EnvironmentDependencies | undefined = undefined, const Capabilities extends ProviderCapabilities = {
|
|
88
108
|
readonly keyset: true;
|