@afora/core 0.0.8 → 0.0.11
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 +149 -23
- package/dist/adapters.d.ts +25 -0
- package/dist/adapters.js +32 -0
- package/dist/core/application/application.d.ts +30 -0
- package/dist/core/application/application.js +1291 -0
- package/dist/core/application/exceptions.d.ts +17 -0
- package/dist/core/application/exceptions.js +85 -0
- package/dist/core/application/migrate.d.ts +19 -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.d.ts +30 -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 +60 -3
- package/dist/core/auth/jwt.d.ts +30 -0
- package/dist/core/auth/jwt.js +200 -0
- package/dist/core/auth/types/jwt.interface.d.ts +74 -0
- 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.d.ts +5 -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.d.ts +3 -1
- 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 +95 -5
- package/dist/core/blueprint/variant.d.ts +32 -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 +24 -0
- package/dist/core/entity/abstract-entity.js +181 -0
- package/dist/core/entity/entity.d.ts +1 -1
- 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.d.ts +9 -0
- package/dist/core/entity/exceptions.js +27 -0
- package/dist/core/entity/trust.d.ts +24 -0
- package/dist/core/entity/trust.js +7 -0
- package/dist/core/entity/types/entity.interface.d.ts +30 -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.d.ts +16 -0
- package/dist/core/output/access.js +66 -0
- package/dist/core/output/coerce.js +38 -0
- package/dist/core/output/exceptions.d.ts +16 -0
- package/dist/core/output/exceptions.js +22 -0
- package/dist/core/output/http.d.ts +36 -0
- package/dist/core/output/http.js +351 -0
- package/dist/core/output/openapi-sink.d.ts +33 -4
- package/dist/core/output/openapi-sink.js +571 -0
- 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.d.ts +7 -2
- package/dist/core/output/stage.js +111 -0
- package/dist/core/output/status.d.ts +1 -1
- package/dist/core/output/status.js +52 -0
- package/dist/core/output/types/http.interface.d.ts +134 -0
- package/dist/core/output/types/output.interface.d.ts +49 -5
- package/dist/core/parse/issue.d.ts +28 -3
- package/dist/core/parse/issue.js +31 -0
- package/dist/core/parse/parse.d.ts +16 -0
- package/dist/core/parse/parse.js +323 -0
- package/dist/core/parse/strip.js +48 -0
- package/dist/core/property/abstract-property.d.ts +9 -0
- package/dist/core/property/abstract-property.js +37 -0
- package/dist/core/property/define-property.d.ts +34 -6
- 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/property/types/property-options.interface.d.ts +10 -0
- package/dist/core/property/types/property-type.interface.d.ts +4 -0
- package/dist/core/record/abstract-record.d.ts +8 -0
- 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 +17 -1
- package/dist/core/repository/cursor.js +83 -0
- package/dist/core/repository/exceptions.d.ts +13 -0
- package/dist/core/repository/exceptions.js +72 -0
- package/dist/core/repository/in-memory.d.ts +2 -2
- package/dist/core/repository/in-memory.js +196 -0
- package/dist/core/repository/migrate.d.ts +20 -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.d.ts +2 -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 +50 -5
- package/dist/core/repository/types/schema.interface.d.ts +132 -0
- package/dist/core/repository/upcast.d.ts +64 -0
- package/dist/core/repository/upcast.js +179 -0
- package/dist/core/schema/schema.d.ts +37 -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.d.ts +1 -1
- 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.d.ts +12 -0
- package/dist/core/use-case/access.js +56 -0
- package/dist/core/use-case/decorators.d.ts +40 -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.d.ts +18 -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 +233 -0
- 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/access.interface.d.ts +39 -0
- package/dist/core/use-case/types/route.interface.d.ts +86 -0
- package/dist/core/use-case/use-case.d.ts +127 -2
- package/dist/core/use-case/use-case.js +493 -0
- package/dist/experimental.d.ts +18 -1
- package/dist/experimental.js +18 -123
- package/dist/index.d.ts +49 -51
- package/dist/index.js +213 -443
- 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.d.ts +0 -2
- 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.d.ts +2 -2
- 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.d.ts +31 -0
- package/dist/std/resource.js +39 -0
- package/dist/std/runtime.d.ts +6 -0
- package/dist/std/runtime.js +193 -0
- package/dist/std/stages.js +4 -0
- package/dist/std/use-cases/auth.d.ts +5 -1
- package/dist/std/use-cases/auth.js +193 -0
- package/dist/std/use-cases/crud.js +261 -0
- package/dist/std/use-cases/index.d.ts +1 -1
- package/dist/std/use-cases/index.js +12 -0
- package/dist/std/use-cases/lifecycle.js +97 -0
- package/dist/std/use-cases/shared.d.ts +2 -2
- package/dist/std/use-cases/shared.js +232 -0
- package/dist/std/use-cases/types.d.ts +102 -2
- package/dist/std/use-cases/types.js +0 -0
- package/dist/testing/http-provider-contract.d.ts +32 -0
- package/dist/testing/http-provider-contract.js +344 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +3 -944
- package/dist/testing/memory-publisher.js +12 -0
- package/dist/testing/provider-contract.d.ts +26 -1
- package/dist/testing/provider-contract.js +1071 -0
- package/package.json +23 -3
- package/dist/index-qxfkyd6k.js +0 -1721
- package/dist/index-t5qwtyj5.js +0 -8194
|
@@ -0,0 +1,720 @@
|
|
|
1
|
+
import { fieldOf } from "../blueprint/callable.js";
|
|
2
|
+
import { isBlueprint } from "../blueprint/is-blueprint.js";
|
|
3
|
+
import { isBlueprintList } from "../blueprint/list.js";
|
|
4
|
+
import { recordFieldOf } from "../blueprint/record-field.js";
|
|
5
|
+
import { isBlueprintVariant } from "../blueprint/variant.js";
|
|
6
|
+
import {
|
|
7
|
+
EntityClock,
|
|
8
|
+
EntitySettings,
|
|
9
|
+
EntityUniqueKeys,
|
|
10
|
+
identityKeysOf,
|
|
11
|
+
isVersionedEntityClass,
|
|
12
|
+
storeEntityVersion
|
|
13
|
+
} from "../entity/abstract-entity.js";
|
|
14
|
+
import { isEntity, isEntityClass } from "../entity/entity.js";
|
|
15
|
+
import { enqueueLifecycle } from "../entity/event.js";
|
|
16
|
+
import { InvalidEntitySchemaVersion } from "../entity/exceptions.js";
|
|
17
|
+
import {
|
|
18
|
+
isEnvironment
|
|
19
|
+
} from "../environment/environment.js";
|
|
20
|
+
import { ExceptionLayers } from "../exception/enums/exception-layers.js";
|
|
21
|
+
import { Exception } from "../exception/exception.js";
|
|
22
|
+
import { guard } from "../exception/guard.js";
|
|
23
|
+
import { resolveLogger, writeLog } from "../logger/logger.js";
|
|
24
|
+
import { isProperty } from "../property/is-property.js";
|
|
25
|
+
import {
|
|
26
|
+
recordDataOf,
|
|
27
|
+
recordFieldDataOf
|
|
28
|
+
} from "../record/abstract-record.js";
|
|
29
|
+
import { recordNameOf } from "../record/name.js";
|
|
30
|
+
import { rawTypeOf } from "../schema/schema.js";
|
|
31
|
+
import { Kind } from "../symbols/kind.js";
|
|
32
|
+
import { ScopeKey } from "../use-case/scope.js";
|
|
33
|
+
import { decodeCursor, encodeCursor } from "./cursor.js";
|
|
34
|
+
import {
|
|
35
|
+
InvalidRepositoryDeclaration,
|
|
36
|
+
InvalidRepositoryEntity,
|
|
37
|
+
InvalidRepositoryIncrement,
|
|
38
|
+
InvalidRepositoryQuery,
|
|
39
|
+
RepositoryConcurrencyConflict,
|
|
40
|
+
RepositoryEntityNotFound
|
|
41
|
+
} from "./exceptions.js";
|
|
42
|
+
import { inMemoryProvider } from "./in-memory.js";
|
|
43
|
+
import { planQuery } from "./plan.js";
|
|
44
|
+
import { prepareQuery } from "./query.js";
|
|
45
|
+
import { parseEntity, relationsOf, resolveReferences } from "./references.js";
|
|
46
|
+
import {
|
|
47
|
+
assertUnbound,
|
|
48
|
+
contextOf,
|
|
49
|
+
declareRepository,
|
|
50
|
+
stateOf,
|
|
51
|
+
states
|
|
52
|
+
} from "./state.js";
|
|
53
|
+
import {
|
|
54
|
+
fullUpcastPlanOf,
|
|
55
|
+
upcast,
|
|
56
|
+
upcastRows,
|
|
57
|
+
withSchemaVersion
|
|
58
|
+
} from "./upcast.js";
|
|
59
|
+
const REFERENCES = {
|
|
60
|
+
reference: (record) => isEntity(record) ? record.id : undefined
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export class AbstractRepository {
|
|
64
|
+
get [Kind]() {
|
|
65
|
+
return "repository";
|
|
66
|
+
}
|
|
67
|
+
get entity() {
|
|
68
|
+
return stateOf(this).entity;
|
|
69
|
+
}
|
|
70
|
+
get actions() {
|
|
71
|
+
return stateOf(this).actions;
|
|
72
|
+
}
|
|
73
|
+
get provider() {
|
|
74
|
+
return stateOf(this).provider;
|
|
75
|
+
}
|
|
76
|
+
get context() {
|
|
77
|
+
return contextOf(stateOf(this));
|
|
78
|
+
}
|
|
79
|
+
get env() {
|
|
80
|
+
return this.context.env;
|
|
81
|
+
}
|
|
82
|
+
async hydrate(data) {
|
|
83
|
+
return (await this.hydrateMany([data]))[0];
|
|
84
|
+
}
|
|
85
|
+
async hydrateMany(list) {
|
|
86
|
+
const state = stateOf(this);
|
|
87
|
+
const scope = this[ScopeKey];
|
|
88
|
+
const resolved = await resolveReferences(state, upcastRows(state, list), new Map, scope);
|
|
89
|
+
return resolved.map((data) => parseEntity(state, data));
|
|
90
|
+
}
|
|
91
|
+
dataOf(entity) {
|
|
92
|
+
if (!(entity instanceof stateOf(this).entity)) {
|
|
93
|
+
throw new InvalidRepositoryEntity({ source: stateOf(this).name });
|
|
94
|
+
}
|
|
95
|
+
return withSchemaVersion(stateOf(this).entity, recordDataOf(entity, REFERENCES));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function action(method) {
|
|
99
|
+
return function(...args) {
|
|
100
|
+
const state = stateOf(this);
|
|
101
|
+
return guard({ layer: ExceptionLayers.Repository, source: state.name }, async () => {
|
|
102
|
+
assertUnbound(state);
|
|
103
|
+
return method(this, ...args);
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function queryValueOf(context, key, value) {
|
|
108
|
+
const properties = context.entity.blueprint.properties;
|
|
109
|
+
return recordFieldDataOf(properties[key], value, REFERENCES);
|
|
110
|
+
}
|
|
111
|
+
function expiryOf(self, withExpired) {
|
|
112
|
+
const { context, provider } = self;
|
|
113
|
+
if (context.expiry === undefined)
|
|
114
|
+
return {};
|
|
115
|
+
if (provider.capabilities?.expiry !== true) {
|
|
116
|
+
throw new InvalidRepositoryQuery({
|
|
117
|
+
source: context.name,
|
|
118
|
+
details: {
|
|
119
|
+
reason: "The provider does not leave expired records out (capabilities.expiry), which an entity with expires needs."
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
const clock = context.entity[EntityClock];
|
|
124
|
+
return { withExpired: withExpired ?? false, now: clock.now() };
|
|
125
|
+
}
|
|
126
|
+
function queryOf(self, query) {
|
|
127
|
+
const prepared = prepareQuery(self.context, query, (key, value) => queryValueOf(self.context, key, value));
|
|
128
|
+
return {
|
|
129
|
+
...prepared,
|
|
130
|
+
withDeleted: prepared.withDeleted ?? stateOf(self).deletion === "hard",
|
|
131
|
+
...expiryOf(self, prepared.withExpired)
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
const equals = (self, key, value) => queryOf(self, { where: { [key]: value } });
|
|
135
|
+
function planned(self, method, query, orderBy = query.orderBy) {
|
|
136
|
+
const state = stateOf(self);
|
|
137
|
+
const plan = planQuery(self.context, { where: query.where, orderBy });
|
|
138
|
+
if (plan.access === "scan" && state.scans !== "allow") {
|
|
139
|
+
const fields = {
|
|
140
|
+
repository: state.name,
|
|
141
|
+
method,
|
|
142
|
+
where: Object.keys(query.where ?? {}),
|
|
143
|
+
orderBy: (orderBy ?? []).map(({ key }) => key)
|
|
144
|
+
};
|
|
145
|
+
if (state.scans === "reject") {
|
|
146
|
+
throw new InvalidRepositoryQuery({
|
|
147
|
+
source: state.name,
|
|
148
|
+
details: {
|
|
149
|
+
reason: `"${method}" matches no index of ${state.name} and would read every row (scans: "reject").`,
|
|
150
|
+
scan: true,
|
|
151
|
+
...fields
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
const scope = self[ScopeKey];
|
|
156
|
+
writeLog(resolveLogger(scope?.logger ?? state.logger, undefined), "warn", "repository-scan", fields);
|
|
157
|
+
}
|
|
158
|
+
return plan.index === undefined ? query : { ...query, index: plan.index };
|
|
159
|
+
}
|
|
160
|
+
const findBy = (key) => action(async (self, value) => {
|
|
161
|
+
const data = await self.provider.findOne(self.context, equals(self, key, value));
|
|
162
|
+
return data === undefined ? undefined : await self.hydrate(data);
|
|
163
|
+
});
|
|
164
|
+
const findManyBy = (key) => action(async (self, value) => self.hydrateMany(await self.provider.findMany(self.context, planned(self, `findManyBy${capitalize(key)}`, equals(self, key, value)))));
|
|
165
|
+
const findMany = action(async (self, query) => self.hydrateMany(await self.provider.findMany(self.context, planned(self, "findMany", queryOf(self, query)))));
|
|
166
|
+
const exists = action(async (self, query) => await self.provider.findOne(self.context, planned(self, "exists", queryOf(self, query))) !== undefined);
|
|
167
|
+
const EXPLAIN_OPTIONS = [
|
|
168
|
+
"where",
|
|
169
|
+
"orderBy",
|
|
170
|
+
"limit",
|
|
171
|
+
"offset",
|
|
172
|
+
"withDeleted",
|
|
173
|
+
"after"
|
|
174
|
+
];
|
|
175
|
+
const explain = action(async (self, query = {}) => {
|
|
176
|
+
const prepared = prepareQuery(self.context, query, (key, value) => queryValueOf(self.context, key, value), EXPLAIN_OPTIONS);
|
|
177
|
+
return planQuery(self.context, prepared);
|
|
178
|
+
});
|
|
179
|
+
const findPage = action(async (self, query) => {
|
|
180
|
+
const {
|
|
181
|
+
orderBy,
|
|
182
|
+
where,
|
|
183
|
+
withDeleted,
|
|
184
|
+
withExpired,
|
|
185
|
+
now,
|
|
186
|
+
limit = 20,
|
|
187
|
+
offset = 0,
|
|
188
|
+
index
|
|
189
|
+
} = planned(self, "findPage", queryOf(self, query));
|
|
190
|
+
const expiry = withExpired === undefined ? {} : { withExpired, now };
|
|
191
|
+
const [rows, total] = await Promise.all([
|
|
192
|
+
self.provider.findMany(self.context, {
|
|
193
|
+
where,
|
|
194
|
+
orderBy,
|
|
195
|
+
withDeleted,
|
|
196
|
+
...expiry,
|
|
197
|
+
limit,
|
|
198
|
+
offset,
|
|
199
|
+
...index === undefined ? {} : { index }
|
|
200
|
+
}),
|
|
201
|
+
self.provider.count(self.context, {
|
|
202
|
+
where,
|
|
203
|
+
withDeleted,
|
|
204
|
+
...expiry,
|
|
205
|
+
...index === undefined ? {} : { index }
|
|
206
|
+
})
|
|
207
|
+
]);
|
|
208
|
+
return { items: await self.hydrateMany(rows), total, limit, offset };
|
|
209
|
+
});
|
|
210
|
+
const CURSOR_OPTIONS = [
|
|
211
|
+
"where",
|
|
212
|
+
"orderBy",
|
|
213
|
+
"limit",
|
|
214
|
+
"withDeleted",
|
|
215
|
+
"withExpired",
|
|
216
|
+
"after"
|
|
217
|
+
];
|
|
218
|
+
function cursorReadOf(self, query) {
|
|
219
|
+
const { context } = self;
|
|
220
|
+
const fail = (reason) => {
|
|
221
|
+
throw new InvalidRepositoryQuery({
|
|
222
|
+
source: context.name,
|
|
223
|
+
details: { reason }
|
|
224
|
+
});
|
|
225
|
+
};
|
|
226
|
+
const prepared = prepareQuery(context, query, (key, value) => queryValueOf(context, key, value), CURSOR_OPTIONS);
|
|
227
|
+
const { where, limit = 20 } = prepared;
|
|
228
|
+
if (limit < 1)
|
|
229
|
+
fail("Expected a positive integer for limit.");
|
|
230
|
+
const withDeleted = prepared.withDeleted ?? stateOf(self).deletion === "hard";
|
|
231
|
+
const declared = prepared.orderBy ?? [];
|
|
232
|
+
const orderBy = declared.some(({ key }) => key === "id") ? declared : [...declared, { key: "id", direction: "asc" }];
|
|
233
|
+
for (const { key } of orderBy) {
|
|
234
|
+
if (context.entity.options.sensitive.includes(key))
|
|
235
|
+
fail(`"${key}" is sensitive and cannot order a cursor.`);
|
|
236
|
+
}
|
|
237
|
+
if (self.provider.capabilities?.keyset !== true)
|
|
238
|
+
fail("The provider does not read by cursor (capabilities.keyset).");
|
|
239
|
+
const { index } = planned(self, "findCursor", { where }, declared);
|
|
240
|
+
expiryOf(self, prepared.withExpired);
|
|
241
|
+
return {
|
|
242
|
+
where,
|
|
243
|
+
orderBy,
|
|
244
|
+
withDeleted,
|
|
245
|
+
withExpired: prepared.withExpired,
|
|
246
|
+
limit,
|
|
247
|
+
...index === undefined ? {} : { index }
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
async function readCursor(self, read, after) {
|
|
251
|
+
const { context } = self;
|
|
252
|
+
const { where, orderBy, withDeleted, limit, index } = read;
|
|
253
|
+
const rows = await self.provider.findMany(context, {
|
|
254
|
+
where,
|
|
255
|
+
orderBy,
|
|
256
|
+
withDeleted,
|
|
257
|
+
...expiryOf(self, read.withExpired),
|
|
258
|
+
limit: limit + 1,
|
|
259
|
+
...index === undefined ? {} : { index },
|
|
260
|
+
...after === undefined ? {} : { after: decodeCursor(context.name, orderBy, after) }
|
|
261
|
+
});
|
|
262
|
+
const page = rows.slice(0, limit);
|
|
263
|
+
const last = page.at(-1);
|
|
264
|
+
return {
|
|
265
|
+
items: await self.hydrateMany(page),
|
|
266
|
+
limit,
|
|
267
|
+
next: rows.length > limit && last !== undefined ? encodeCursor(orderBy, last) : undefined
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
const findCursor = action(async (self, query = {}) => readCursor(self, cursorReadOf(self, query), query.after));
|
|
271
|
+
function stopperOf(source, until) {
|
|
272
|
+
if (until === undefined)
|
|
273
|
+
return () => false;
|
|
274
|
+
if (typeof until === "function")
|
|
275
|
+
return () => Boolean(until());
|
|
276
|
+
const deadline = until instanceof Date ? until.getTime() : typeof until === "number" ? until : Number.NaN;
|
|
277
|
+
if (Number.isNaN(deadline)) {
|
|
278
|
+
throw new InvalidRepositoryQuery({
|
|
279
|
+
source,
|
|
280
|
+
details: {
|
|
281
|
+
reason: "Expected until to be a Date, epoch milliseconds or a function."
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
return () => Date.now() >= deadline;
|
|
286
|
+
}
|
|
287
|
+
const forEachPage = action(async (self, query, fn, options) => {
|
|
288
|
+
const { context } = self;
|
|
289
|
+
if (typeof fn !== "function") {
|
|
290
|
+
throw new InvalidRepositoryQuery({
|
|
291
|
+
source: context.name,
|
|
292
|
+
details: { reason: "Expected a function to run on each page." }
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
const read = cursorReadOf(self, query ?? {});
|
|
296
|
+
const stop = stopperOf(context.name, options?.until);
|
|
297
|
+
let after = query?.after;
|
|
298
|
+
let pages = 0;
|
|
299
|
+
let items = 0;
|
|
300
|
+
while (!stop()) {
|
|
301
|
+
const page = await readCursor(self, read, after);
|
|
302
|
+
if (page.items.length > 0) {
|
|
303
|
+
await fn(page.items, { index: pages, next: page.next });
|
|
304
|
+
pages += 1;
|
|
305
|
+
items += page.items.length;
|
|
306
|
+
}
|
|
307
|
+
if (page.next === undefined)
|
|
308
|
+
return { done: true, pages, items };
|
|
309
|
+
after = page.next;
|
|
310
|
+
}
|
|
311
|
+
return { done: false, next: after, pages, items };
|
|
312
|
+
});
|
|
313
|
+
const create = action(async (self, input) => {
|
|
314
|
+
const { entity: Entity } = self.context;
|
|
315
|
+
const entity = input instanceof Entity ? input : new Entity(input);
|
|
316
|
+
await self.provider.insert(self.context, self.dataOf(entity));
|
|
317
|
+
return entity;
|
|
318
|
+
});
|
|
319
|
+
function nowOf(context) {
|
|
320
|
+
return context.entity[EntityClock].now();
|
|
321
|
+
}
|
|
322
|
+
function assertVersioning(self) {
|
|
323
|
+
if (self.provider.capabilities?.versioning === true)
|
|
324
|
+
return;
|
|
325
|
+
invalid(self.context.name, `${recordNameOf(self.context.entity)} is versioned, and the provider does not check versions (capabilities.versioning).`);
|
|
326
|
+
}
|
|
327
|
+
async function write(self, data) {
|
|
328
|
+
if (!isVersionedEntityClass(self.context.entity)) {
|
|
329
|
+
await self.provider.update(self.context, data);
|
|
330
|
+
return data;
|
|
331
|
+
}
|
|
332
|
+
assertVersioning(self);
|
|
333
|
+
const expected = data.version;
|
|
334
|
+
const next = { ...data, version: expected + 1 };
|
|
335
|
+
await self.provider.update(self.context, next, {
|
|
336
|
+
expectedVersion: expected
|
|
337
|
+
});
|
|
338
|
+
return next;
|
|
339
|
+
}
|
|
340
|
+
async function writeEntity(self, entity) {
|
|
341
|
+
const written = await write(self, self.dataOf(entity));
|
|
342
|
+
if (written.version !== undefined && isEntity(entity)) {
|
|
343
|
+
storeEntityVersion(entity, written.version);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
const update = action(async (self, entity) => {
|
|
347
|
+
await writeEntity(self, entity);
|
|
348
|
+
return entity;
|
|
349
|
+
});
|
|
350
|
+
function isPlainObject(value) {
|
|
351
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
352
|
+
}
|
|
353
|
+
function isNumericField(field) {
|
|
354
|
+
if (field === undefined)
|
|
355
|
+
return false;
|
|
356
|
+
const property = fieldOf(field);
|
|
357
|
+
if (!isProperty(property))
|
|
358
|
+
return false;
|
|
359
|
+
const { type } = rawTypeOf(property.type.schema);
|
|
360
|
+
return type === "number" || type === "integer";
|
|
361
|
+
}
|
|
362
|
+
function deltasOf(context, deltas) {
|
|
363
|
+
const { name: source } = context;
|
|
364
|
+
const fail = (reason, issues) => {
|
|
365
|
+
throw new InvalidRepositoryIncrement({
|
|
366
|
+
source,
|
|
367
|
+
details: { reason, issues }
|
|
368
|
+
});
|
|
369
|
+
};
|
|
370
|
+
if (!isPlainObject(deltas) || Object.keys(deltas).length === 0) {
|
|
371
|
+
const message = "Expected an object of the numbers to add, by key.";
|
|
372
|
+
fail(message, [{ code: "invalid_type", path: [], message, source }]);
|
|
373
|
+
}
|
|
374
|
+
const { change } = context.entity.options;
|
|
375
|
+
const properties = context.entity.blueprint.properties;
|
|
376
|
+
const issues = [];
|
|
377
|
+
for (const [key, value] of Object.entries(deltas)) {
|
|
378
|
+
if (!change.includes(key) || !isNumericField(properties[key])) {
|
|
379
|
+
issues.push({
|
|
380
|
+
code: "undeclared_key",
|
|
381
|
+
path: [key],
|
|
382
|
+
message: `"${key}" is not a number in the change list of ${recordNameOf(context.entity)}.`,
|
|
383
|
+
source
|
|
384
|
+
});
|
|
385
|
+
} else if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
386
|
+
issues.push({
|
|
387
|
+
code: "invalid_value",
|
|
388
|
+
path: [key],
|
|
389
|
+
message: "Expected a finite number.",
|
|
390
|
+
source
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
if (issues.length > 0) {
|
|
395
|
+
fail(`"${source}" cannot increment ${issues.map((issue) => `"${issue.path[0]}"`).join(", ")}.`, issues);
|
|
396
|
+
}
|
|
397
|
+
return deltas;
|
|
398
|
+
}
|
|
399
|
+
function createdOf(context, id, input, deltas) {
|
|
400
|
+
const identity = identityKeysOf(context.entity);
|
|
401
|
+
const value = isPlainObject(input) ? Object.fromEntries(Object.entries(input).filter(([key]) => !identity.includes(key))) : {};
|
|
402
|
+
for (const [key, delta] of Object.entries(deltas)) {
|
|
403
|
+
const base = value[key];
|
|
404
|
+
value[key] = (typeof base === "number" ? base : 0) + delta;
|
|
405
|
+
}
|
|
406
|
+
const Entity = context.entity;
|
|
407
|
+
const entity = new Entity({ ...value, id });
|
|
408
|
+
enqueueLifecycle(entity, "onCreate");
|
|
409
|
+
return entity;
|
|
410
|
+
}
|
|
411
|
+
function renamedFromOf(field) {
|
|
412
|
+
if (field === undefined)
|
|
413
|
+
return;
|
|
414
|
+
const property = fieldOf(field);
|
|
415
|
+
if (!isProperty(property))
|
|
416
|
+
return;
|
|
417
|
+
const { renamedFrom } = property.type;
|
|
418
|
+
return typeof renamedFrom === "string" ? renamedFrom : undefined;
|
|
419
|
+
}
|
|
420
|
+
async function bringUpRenamed(self, id, deltas) {
|
|
421
|
+
const { context } = self;
|
|
422
|
+
const properties = context.entity.blueprint.properties;
|
|
423
|
+
const renamed = [];
|
|
424
|
+
for (const key of Object.keys(deltas)) {
|
|
425
|
+
const from = renamedFromOf(properties[key]);
|
|
426
|
+
if (from !== undefined)
|
|
427
|
+
renamed.push([key, from]);
|
|
428
|
+
}
|
|
429
|
+
if (renamed.length === 0)
|
|
430
|
+
return;
|
|
431
|
+
const stored = await self.provider.findOne(context, {
|
|
432
|
+
where: { id },
|
|
433
|
+
withDeleted: true,
|
|
434
|
+
...context.expiry === undefined ? {} : { withExpired: true }
|
|
435
|
+
});
|
|
436
|
+
if (stored === undefined || !renamed.some(([key, from]) => stored[key] === undefined && Object.hasOwn(stored, from))) {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
const data = withSchemaVersion(context.entity, upcast(context.name, fullUpcastPlanOf(context.entity), stored, false));
|
|
440
|
+
const expected = stored.version;
|
|
441
|
+
try {
|
|
442
|
+
await self.provider.update(context, data, isVersionedEntityClass(context.entity) && typeof expected === "number" ? { expectedVersion: expected } : undefined);
|
|
443
|
+
} catch (error) {
|
|
444
|
+
if (!(error instanceof Exception) || !error.is(RepositoryConcurrencyConflict)) {
|
|
445
|
+
throw error;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
const increment = action(async (self, target, deltas, options) => {
|
|
450
|
+
const { context } = self;
|
|
451
|
+
const id = typeof target === "string" ? target : self.dataOf(target).id;
|
|
452
|
+
const checked = deltasOf(context, deltas);
|
|
453
|
+
if (options !== undefined && !isPlainObject(options)) {
|
|
454
|
+
invalid(context.name, "Expected the options of increment as an object.");
|
|
455
|
+
}
|
|
456
|
+
const versioned = isVersionedEntityClass(context.entity);
|
|
457
|
+
if (versioned)
|
|
458
|
+
assertVersioning(self);
|
|
459
|
+
const { create } = options ?? {};
|
|
460
|
+
const created = create === undefined ? undefined : createdOf(context, id, create, checked);
|
|
461
|
+
await bringUpRenamed(self, id, checked);
|
|
462
|
+
const stored = await self.provider.increment(context, id, versioned ? { ...checked, version: 1 } : checked, {
|
|
463
|
+
set: { updatedAt: nowOf(context) },
|
|
464
|
+
...created === undefined ? {} : { create: self.dataOf(created) },
|
|
465
|
+
...context.expiry === undefined ? {} : { now: expiryOf(self, false).now }
|
|
466
|
+
});
|
|
467
|
+
if (created !== undefined && stored.updatedAt === undefined)
|
|
468
|
+
return created;
|
|
469
|
+
const entity = await self.hydrate(stored);
|
|
470
|
+
enqueueLifecycle(entity, "onUpdate");
|
|
471
|
+
return entity;
|
|
472
|
+
});
|
|
473
|
+
const remove = action(async (self, target) => {
|
|
474
|
+
const { context } = self;
|
|
475
|
+
let entity = target;
|
|
476
|
+
if (typeof target === "string") {
|
|
477
|
+
const data = await self.provider.findOne(context, equals(self, "id", target));
|
|
478
|
+
if (data === undefined) {
|
|
479
|
+
throw new RepositoryEntityNotFound({
|
|
480
|
+
source: context.name,
|
|
481
|
+
details: { id: target }
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
entity = await self.hydrate(data);
|
|
485
|
+
}
|
|
486
|
+
const data = self.dataOf(entity);
|
|
487
|
+
const destroyable = entity;
|
|
488
|
+
if (!destroyable.isDeleted)
|
|
489
|
+
destroyable.destroy();
|
|
490
|
+
if (stateOf(self).deletion === "soft") {
|
|
491
|
+
await writeEntity(self, entity);
|
|
492
|
+
} else {
|
|
493
|
+
await self.provider.remove(context, data.id);
|
|
494
|
+
}
|
|
495
|
+
return entity;
|
|
496
|
+
});
|
|
497
|
+
const restore = action(async (self, target) => {
|
|
498
|
+
const { context } = self;
|
|
499
|
+
const id = typeof target === "string" ? target : self.dataOf(target).id;
|
|
500
|
+
const data = await self.provider.findOne(context, {
|
|
501
|
+
...equals(self, "id", id),
|
|
502
|
+
withDeleted: true
|
|
503
|
+
});
|
|
504
|
+
if (data === undefined) {
|
|
505
|
+
throw new RepositoryEntityNotFound({
|
|
506
|
+
source: context.name,
|
|
507
|
+
details: { id }
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
if (data.deletedAt === undefined)
|
|
511
|
+
return self.hydrate(data);
|
|
512
|
+
const [current] = upcastRows(stateOf(self), [data]);
|
|
513
|
+
const { deletedAt: _deletedAt, ...alive } = current;
|
|
514
|
+
const restored = withSchemaVersion(context.entity, {
|
|
515
|
+
...alive,
|
|
516
|
+
updatedAt: nowOf(context)
|
|
517
|
+
});
|
|
518
|
+
return self.hydrate(await write(self, restored));
|
|
519
|
+
});
|
|
520
|
+
const count = action(async (self, query) => {
|
|
521
|
+
const prepared = queryOf(self, query);
|
|
522
|
+
return self.provider.count(self.context, planned(self, "count", prepared, []));
|
|
523
|
+
});
|
|
524
|
+
const NEEDS = {
|
|
525
|
+
findBy: ["findOne"],
|
|
526
|
+
findManyBy: ["findMany"],
|
|
527
|
+
create: ["insert"],
|
|
528
|
+
update: ["update"],
|
|
529
|
+
delete: ["findOne", "remove"],
|
|
530
|
+
restore: ["findOne", "update"],
|
|
531
|
+
count: ["count"],
|
|
532
|
+
findMany: ["findMany"],
|
|
533
|
+
exists: ["findOne"],
|
|
534
|
+
findPage: ["findMany", "count"],
|
|
535
|
+
findCursor: ["findMany"],
|
|
536
|
+
explain: [],
|
|
537
|
+
increment: ["increment"],
|
|
538
|
+
forEachPage: ["findMany"]
|
|
539
|
+
};
|
|
540
|
+
function capitalize(key) {
|
|
541
|
+
return `${key.charAt(0).toUpperCase()}${key.slice(1)}`;
|
|
542
|
+
}
|
|
543
|
+
function unknownAction(entity, action, methods) {
|
|
544
|
+
const many = /^findManyBy(.+)$/.exec(action)?.[1];
|
|
545
|
+
const one = many === undefined ? /^findBy(.+)$/.exec(action)?.[1] : undefined;
|
|
546
|
+
const suffix = many ?? one;
|
|
547
|
+
const key = suffix === undefined ? undefined : `${suffix.charAt(0).toLowerCase()}${suffix.slice(1)}`;
|
|
548
|
+
if (one !== undefined && methods.has(`findManyBy${one}`)) {
|
|
549
|
+
return `"${action}" requires "${key}" to be a unique key of ${entity}.`;
|
|
550
|
+
}
|
|
551
|
+
if (many !== undefined && methods.has(`findBy${many}`)) {
|
|
552
|
+
return `"${action}" is not available for the unique key "${key}" of ${entity}; use "findBy${many}".`;
|
|
553
|
+
}
|
|
554
|
+
return `"${action}" is not an action of ${entity}.`;
|
|
555
|
+
}
|
|
556
|
+
function invalid(source, reason) {
|
|
557
|
+
throw new InvalidRepositoryDeclaration({ source, details: { reason } });
|
|
558
|
+
}
|
|
559
|
+
function assertPreviousNames(entity) {
|
|
560
|
+
const own = recordNameOf(entity);
|
|
561
|
+
const previous = entity[EntitySettings]?.previousNames ?? [];
|
|
562
|
+
if (!previous.includes(own))
|
|
563
|
+
return;
|
|
564
|
+
throw new InvalidEntitySchemaVersion({
|
|
565
|
+
source: own,
|
|
566
|
+
details: {
|
|
567
|
+
reason: `"${own}" cannot be a previous name: expected an identifier other than the name of the entity.`
|
|
568
|
+
}
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
const INDEX_NAME = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
572
|
+
function indexesOf(name, entity, declared) {
|
|
573
|
+
if (declared === undefined)
|
|
574
|
+
return Object.freeze({});
|
|
575
|
+
if (typeof declared !== "object" || declared === null || Array.isArray(declared)) {
|
|
576
|
+
invalid(name, "Expected indexes to be an object of indexes by name.");
|
|
577
|
+
}
|
|
578
|
+
const properties = entity.blueprint.properties;
|
|
579
|
+
const sensitive = entity.options.sensitive;
|
|
580
|
+
const keyOf = (index, key) => {
|
|
581
|
+
if (typeof key !== "string" || !Object.hasOwn(properties, key)) {
|
|
582
|
+
invalid(name, `The index "${index}" names "${String(key)}", which is not a key of ${recordNameOf(entity)}.`);
|
|
583
|
+
}
|
|
584
|
+
if (sensitive.includes(key)) {
|
|
585
|
+
invalid(name, `The index "${index}" names "${key}", which is sensitive and cannot be indexed.`);
|
|
586
|
+
}
|
|
587
|
+
return key;
|
|
588
|
+
};
|
|
589
|
+
const seen = new Map;
|
|
590
|
+
const indexes = {};
|
|
591
|
+
for (const [index, value] of Object.entries(declared)) {
|
|
592
|
+
if (!INDEX_NAME.test(index) || index === "__proto__") {
|
|
593
|
+
invalid(name, `Expected the index "${index}" to be named by an identifier.`);
|
|
594
|
+
}
|
|
595
|
+
if (typeof value !== "object" || value === null || Array.isArray(value) || Object.keys(value).some((key) => key !== "where" && key !== "orderBy") || !Array.isArray(value.where) || value.where.length === 0) {
|
|
596
|
+
invalid(name, `Expected the index "${index}" to be { where: [keys], orderBy?: key }.`);
|
|
597
|
+
}
|
|
598
|
+
const where = value.where.map((key) => keyOf(index, key));
|
|
599
|
+
if (new Set(where).size !== where.length) {
|
|
600
|
+
invalid(name, `The index "${index}" names a key twice in where.`);
|
|
601
|
+
}
|
|
602
|
+
const orderBy = value.orderBy === undefined ? undefined : keyOf(index, value.orderBy);
|
|
603
|
+
if (orderBy !== undefined) {
|
|
604
|
+
const field = properties[orderBy];
|
|
605
|
+
if (isBlueprint(field) || isBlueprintList(field) || isBlueprintVariant(field) || recordFieldOf(field)) {
|
|
606
|
+
invalid(name, `The index "${index}" orders by "${orderBy}", which is not scalar.`);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
const signature = JSON.stringify([[...where].sort(), orderBy ?? null]);
|
|
610
|
+
const other = seen.get(signature);
|
|
611
|
+
if (other !== undefined) {
|
|
612
|
+
invalid(name, `The indexes "${other}" and "${index}" are the same.`);
|
|
613
|
+
}
|
|
614
|
+
seen.set(signature, index);
|
|
615
|
+
indexes[index] = Object.freeze({
|
|
616
|
+
where: Object.freeze(where),
|
|
617
|
+
...orderBy === undefined ? {} : { orderBy }
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
return Object.freeze(indexes);
|
|
621
|
+
}
|
|
622
|
+
export function repositoryOf(settings) {
|
|
623
|
+
const { entity, actions, environmentDependencies } = settings ?? {};
|
|
624
|
+
if (!isEntityClass(entity) || !(EntityUniqueKeys in entity)) {
|
|
625
|
+
invalid("repositoryOf", "Expected an entity class made by entityOf.");
|
|
626
|
+
}
|
|
627
|
+
const name = `${recordNameOf(entity)}Repository`;
|
|
628
|
+
assertPreviousNames(entity);
|
|
629
|
+
const deletion = settings.deletion ?? "hard";
|
|
630
|
+
if (deletion !== "hard" && deletion !== "soft") {
|
|
631
|
+
invalid(name, 'Expected deletion to be "hard" or "soft".');
|
|
632
|
+
}
|
|
633
|
+
const scans = settings.scans ?? "allow";
|
|
634
|
+
if (scans !== "allow" && scans !== "warn" && scans !== "reject") {
|
|
635
|
+
invalid(name, 'Expected scans to be "allow", "warn" or "reject".');
|
|
636
|
+
}
|
|
637
|
+
const provider = settings.provider ?? inMemoryProvider();
|
|
638
|
+
if (!Array.isArray(actions) || actions.length === 0) {
|
|
639
|
+
invalid(name, "Expected a list of actions.");
|
|
640
|
+
}
|
|
641
|
+
if (environmentDependencies !== undefined && !isEnvironment(environmentDependencies) && !(Array.isArray(environmentDependencies) && environmentDependencies.every(isEnvironment))) {
|
|
642
|
+
invalid(name, "Expected environments made by environmentOf.");
|
|
643
|
+
}
|
|
644
|
+
const relations = relationsOf(name, entity, settings.relations);
|
|
645
|
+
const indexes = indexesOf(name, entity, settings.indexes);
|
|
646
|
+
const unique = entity[EntityUniqueKeys];
|
|
647
|
+
const methods = new Map;
|
|
648
|
+
for (const key of Object.keys(entity.blueprint.properties)) {
|
|
649
|
+
const suffix = capitalize(key);
|
|
650
|
+
if (unique.includes(key)) {
|
|
651
|
+
methods.set(`findBy${suffix}`, ["findBy", findBy(key)]);
|
|
652
|
+
} else {
|
|
653
|
+
methods.set(`findManyBy${suffix}`, ["findManyBy", findManyBy(key)]);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
methods.set("create", ["create", create]);
|
|
657
|
+
methods.set("update", ["update", update]);
|
|
658
|
+
methods.set("delete", ["delete", remove]);
|
|
659
|
+
methods.set("restore", ["restore", restore]);
|
|
660
|
+
methods.set("count", ["count", count]);
|
|
661
|
+
methods.set("findMany", ["findMany", findMany]);
|
|
662
|
+
methods.set("exists", ["exists", exists]);
|
|
663
|
+
methods.set("findPage", ["findPage", findPage]);
|
|
664
|
+
methods.set("findCursor", ["findCursor", findCursor]);
|
|
665
|
+
methods.set("explain", ["explain", explain]);
|
|
666
|
+
methods.set("increment", ["increment", increment]);
|
|
667
|
+
methods.set("forEachPage", ["forEachPage", forEachPage]);
|
|
668
|
+
|
|
669
|
+
class DeclaredRepository extends AbstractRepository {
|
|
670
|
+
}
|
|
671
|
+
for (const name_ of new Set(actions)) {
|
|
672
|
+
const method = typeof name_ === "string" ? methods.get(name_) : undefined;
|
|
673
|
+
if (!method)
|
|
674
|
+
invalid(name, unknownAction(recordNameOf(entity), String(name_), methods));
|
|
675
|
+
const [kind, implementation] = method;
|
|
676
|
+
if (kind === "restore" && deletion !== "soft") {
|
|
677
|
+
invalid(name, '"restore" needs deletion to be "soft".');
|
|
678
|
+
}
|
|
679
|
+
if (kind === "findCursor" && settings.provider !== undefined && provider[Kind] !== "provider-slot" && provider.capabilities?.keyset !== true) {
|
|
680
|
+
invalid(name, '"findCursor" needs a provider that reads by cursor (capabilities.keyset).');
|
|
681
|
+
}
|
|
682
|
+
const needs = deletion === "soft" && kind === "delete" ? ["findOne", "update"] : NEEDS[kind] ?? [];
|
|
683
|
+
for (const need of needs) {
|
|
684
|
+
if (typeof provider?.[need] !== "function") {
|
|
685
|
+
invalid(name, `The provider has no "${need}" for "${name_}".`);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
Object.defineProperty(DeclaredRepository.prototype, name_, {
|
|
689
|
+
value: implementation,
|
|
690
|
+
writable: true,
|
|
691
|
+
configurable: true
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
Object.defineProperty(DeclaredRepository, "name", { value: name });
|
|
695
|
+
const state = {
|
|
696
|
+
name,
|
|
697
|
+
entity,
|
|
698
|
+
actions: Object.freeze([...new Set(actions)]),
|
|
699
|
+
deletion,
|
|
700
|
+
provider,
|
|
701
|
+
explicitProvider: settings.provider !== undefined,
|
|
702
|
+
environment: environmentDependencies,
|
|
703
|
+
relations,
|
|
704
|
+
indexes,
|
|
705
|
+
scans
|
|
706
|
+
};
|
|
707
|
+
states.set(DeclaredRepository, state);
|
|
708
|
+
const repository = Object.freeze(new DeclaredRepository);
|
|
709
|
+
declareRepository(state, repository);
|
|
710
|
+
return repository;
|
|
711
|
+
}
|
|
712
|
+
export function RepositoryOf(repository) {
|
|
713
|
+
if (!isRepository(repository)) {
|
|
714
|
+
invalid("RepositoryOf", "Expected a repository made by repositoryOf.");
|
|
715
|
+
}
|
|
716
|
+
return repository.constructor;
|
|
717
|
+
}
|
|
718
|
+
export function isRepository(value) {
|
|
719
|
+
return value instanceof AbstractRepository;
|
|
720
|
+
}
|