@eleven-am/golem-core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +674 -0
- package/README.md +5 -0
- package/dist/authorization.d.ts +16 -0
- package/dist/authorization.js +33 -0
- package/dist/concurrency.d.ts +2 -0
- package/dist/concurrency.js +16 -0
- package/dist/datamodel.d.ts +48 -0
- package/dist/datamodel.js +2 -0
- package/dist/errors.d.ts +20 -0
- package/dist/errors.js +42 -0
- package/dist/event-buffer.d.ts +5 -0
- package/dist/event-buffer.js +22 -0
- package/dist/events.d.ts +13 -0
- package/dist/events.js +6 -0
- package/dist/extensions.d.ts +17 -0
- package/dist/extensions.js +12 -0
- package/dist/hooks.d.ts +18 -0
- package/dist/hooks.js +38 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +32 -0
- package/dist/inputs.d.ts +29 -0
- package/dist/inputs.js +172 -0
- package/dist/model-meta.d.ts +10 -0
- package/dist/model-meta.js +37 -0
- package/dist/naming.d.ts +10 -0
- package/dist/naming.js +50 -0
- package/dist/nested-writes.d.ts +24 -0
- package/dist/nested-writes.js +193 -0
- package/dist/operations.d.ts +122 -0
- package/dist/operations.js +484 -0
- package/dist/publisher.d.ts +16 -0
- package/dist/publisher.js +55 -0
- package/dist/readtree.d.ts +40 -0
- package/dist/readtree.js +206 -0
- package/dist/schema.d.ts +23 -0
- package/dist/schema.js +580 -0
- package/dist/select.d.ts +15 -0
- package/dist/select.js +67 -0
- package/dist/testing.d.ts +2 -0
- package/dist/testing.js +16 -0
- package/dist/typemap.d.ts +53 -0
- package/dist/typemap.js +2 -0
- package/dist/verify.d.ts +21 -0
- package/dist/verify.js +241 -0
- package/package.json +51 -0
package/dist/schema.js
ADDED
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DateTimeScalar = void 0;
|
|
4
|
+
exports.createGolemEngine = createGolemEngine;
|
|
5
|
+
exports.subscribableModels = subscribableModels;
|
|
6
|
+
exports.buildGolemSchema = buildGolemSchema;
|
|
7
|
+
const graphql_1 = require("graphql");
|
|
8
|
+
const errors_1 = require("./errors");
|
|
9
|
+
const events_1 = require("./events");
|
|
10
|
+
const extensions_1 = require("./extensions");
|
|
11
|
+
const hooks_1 = require("./hooks");
|
|
12
|
+
const inputs_1 = require("./inputs");
|
|
13
|
+
const naming_1 = require("./naming");
|
|
14
|
+
const operations_1 = require("./operations");
|
|
15
|
+
const select_1 = require("./select");
|
|
16
|
+
exports.DateTimeScalar = new graphql_1.GraphQLScalarType({
|
|
17
|
+
name: 'DateTime',
|
|
18
|
+
serialize: (value) => (value instanceof Date ? value.toISOString() : value),
|
|
19
|
+
parseValue: (value) => new Date(value),
|
|
20
|
+
parseLiteral: (ast) => (ast.kind === graphql_1.Kind.STRING ? new Date(ast.value) : null),
|
|
21
|
+
});
|
|
22
|
+
const SCALAR_MAP = {
|
|
23
|
+
String: graphql_1.GraphQLString,
|
|
24
|
+
Int: graphql_1.GraphQLInt,
|
|
25
|
+
Float: graphql_1.GraphQLFloat,
|
|
26
|
+
Boolean: graphql_1.GraphQLBoolean,
|
|
27
|
+
DateTime: exports.DateTimeScalar,
|
|
28
|
+
};
|
|
29
|
+
const ORDERED_OPERATORS = ['lt', 'lte', 'gt', 'gte'];
|
|
30
|
+
const STRING_OPERATORS = ['contains', 'startsWith', 'endsWith'];
|
|
31
|
+
function resolveModelSettings(model, config, defaults) {
|
|
32
|
+
const fieldNames = new Set(model.fields.map((f) => f.name));
|
|
33
|
+
const hidden = new Set(config?.hidden ?? []);
|
|
34
|
+
const immutable = new Set(config?.immutable ?? []);
|
|
35
|
+
for (const name of [...hidden, ...immutable]) {
|
|
36
|
+
if (!fieldNames.has(name)) {
|
|
37
|
+
throw new Error(`Unknown field ${name} in configuration for model ${model.name}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
for (const field of model.fields) {
|
|
41
|
+
if (field.isId && hidden.has(field.name)) {
|
|
42
|
+
throw new Error(`Cannot hide primary key ${model.name}.${field.name}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const operations = config?.operations ?? defaults.operations ?? hooks_1.ALL_OPERATIONS;
|
|
46
|
+
for (const operation of operations) {
|
|
47
|
+
if (!hooks_1.ALL_OPERATIONS.includes(operation)) {
|
|
48
|
+
throw new Error(`Unknown operation ${operation} in configuration for model ${model.name}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
operations: new Set(operations),
|
|
53
|
+
hidden,
|
|
54
|
+
immutable,
|
|
55
|
+
maxTake: config?.maxTake ?? defaults.maxTake,
|
|
56
|
+
subscriptions: config?.subscriptions ?? defaults.subscriptions ?? false,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function wrapResolve(resolve) {
|
|
60
|
+
return async (root, args, ctx, info) => {
|
|
61
|
+
try {
|
|
62
|
+
return await resolve(root, args, ctx, info);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
if (error instanceof errors_1.GolemError) {
|
|
66
|
+
throw new graphql_1.GraphQLError(error.message, { extensions: { code: error.code } });
|
|
67
|
+
}
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function resolveGolem(options) {
|
|
73
|
+
const excluded = new Set(Object.entries(options.models ?? {})
|
|
74
|
+
.filter(([, value]) => value === false)
|
|
75
|
+
.map(([key]) => key));
|
|
76
|
+
const models = options.datamodel.models.filter((m) => !excluded.has(m.name));
|
|
77
|
+
const modelsByName = new Map(models.map((m) => [m.name, m]));
|
|
78
|
+
const settings = new Map(models.map((m) => [
|
|
79
|
+
m.name,
|
|
80
|
+
resolveModelSettings(m, (options.models?.[m.name] || undefined), options.defaults ?? {}),
|
|
81
|
+
]));
|
|
82
|
+
const subscribable = new Set(models.filter((m) => settings.get(m.name).subscriptions).map((m) => m.name));
|
|
83
|
+
const takeLimits = new Map();
|
|
84
|
+
for (const [name, resolved] of settings) {
|
|
85
|
+
if (resolved.maxTake !== undefined) {
|
|
86
|
+
takeLimits.set(name, resolved.maxTake);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return { models, modelsByName, settings, subscribable, takeLimits };
|
|
90
|
+
}
|
|
91
|
+
function createGolemEngine(options) {
|
|
92
|
+
const { models, takeLimits } = resolveGolem(options);
|
|
93
|
+
return new operations_1.GolemEngine(options.client, models, {
|
|
94
|
+
hooks: options.hooks,
|
|
95
|
+
takeLimits,
|
|
96
|
+
authorization: options.authorization,
|
|
97
|
+
maxDepth: options.defaults?.maxDepth,
|
|
98
|
+
checkWriteResults: options.defaults?.checkWriteResults,
|
|
99
|
+
checkReadFields: options.defaults?.checkReadFields,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
function subscribableModels(options) {
|
|
103
|
+
return resolveGolem({ ...options, client: {} }).subscribable;
|
|
104
|
+
}
|
|
105
|
+
function buildGolemSchema(options) {
|
|
106
|
+
const { models, modelsByName, settings, subscribable, takeLimits } = resolveGolem(options);
|
|
107
|
+
if (subscribable.size > 0 && !options.eventBus) {
|
|
108
|
+
throw new Error(`Subscriptions are enabled for ${[...subscribable].join(', ')} but no event bus was provided`);
|
|
109
|
+
}
|
|
110
|
+
const engine = options.engine ??
|
|
111
|
+
new operations_1.GolemEngine(options.client, models, {
|
|
112
|
+
hooks: options.hooks,
|
|
113
|
+
takeLimits,
|
|
114
|
+
authorization: options.authorization,
|
|
115
|
+
maxDepth: options.defaults?.maxDepth,
|
|
116
|
+
checkWriteResults: options.defaults?.checkWriteResults,
|
|
117
|
+
checkReadFields: options.defaults?.checkReadFields,
|
|
118
|
+
});
|
|
119
|
+
const hiddenFor = (name) => settings.get(name)?.hidden ?? new Set();
|
|
120
|
+
const immutableFor = (name) => settings.get(name)?.immutable ?? new Set();
|
|
121
|
+
const visibleFields = (model) => model.fields.filter((f) => !hiddenFor(model.name).has(f.name));
|
|
122
|
+
const computedSpecs = options.computedFields ?? [];
|
|
123
|
+
for (const spec of computedSpecs) {
|
|
124
|
+
const model = modelsByName.get(spec.model);
|
|
125
|
+
if (!model) {
|
|
126
|
+
throw new Error(`Computed field ${spec.name} targets unknown model ${spec.model}`);
|
|
127
|
+
}
|
|
128
|
+
if (model.fields.some((f) => f.name === spec.name)) {
|
|
129
|
+
throw new Error(`Computed field ${spec.model}.${spec.name} collides with an existing field`);
|
|
130
|
+
}
|
|
131
|
+
const fieldNames = new Set(model.fields.map((f) => f.name));
|
|
132
|
+
for (const required of spec.requires) {
|
|
133
|
+
if (!fieldNames.has(required)) {
|
|
134
|
+
throw new Error(`Computed field ${spec.model}.${spec.name} requires unknown field ${required}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const computedByModel = new Map();
|
|
139
|
+
for (const spec of computedSpecs) {
|
|
140
|
+
const list = computedByModel.get(spec.model) ?? [];
|
|
141
|
+
list.push(spec);
|
|
142
|
+
computedByModel.set(spec.model, list);
|
|
143
|
+
}
|
|
144
|
+
const computedRequires = (0, extensions_1.buildComputedRequiresMap)(computedSpecs);
|
|
145
|
+
const enumTypes = new Map(options.datamodel.enums.map((e) => [
|
|
146
|
+
e.name,
|
|
147
|
+
new graphql_1.GraphQLEnumType({
|
|
148
|
+
name: e.name,
|
|
149
|
+
values: Object.fromEntries(e.values.map((v) => [v, {}])),
|
|
150
|
+
}),
|
|
151
|
+
]));
|
|
152
|
+
const sortOrder = new graphql_1.GraphQLEnumType({
|
|
153
|
+
name: 'SortOrder',
|
|
154
|
+
values: { asc: {}, desc: {} },
|
|
155
|
+
});
|
|
156
|
+
const batchPayload = new graphql_1.GraphQLObjectType({
|
|
157
|
+
name: 'BatchPayload',
|
|
158
|
+
fields: { count: { type: new graphql_1.GraphQLNonNull(graphql_1.GraphQLInt) } },
|
|
159
|
+
});
|
|
160
|
+
const filterTypes = new Map();
|
|
161
|
+
function scalarType(model, field) {
|
|
162
|
+
const mapped = SCALAR_MAP[field.type];
|
|
163
|
+
if (!mapped) {
|
|
164
|
+
throw new Error(`Unsupported scalar type ${field.type} on ${model.name}.${field.name}`);
|
|
165
|
+
}
|
|
166
|
+
return mapped;
|
|
167
|
+
}
|
|
168
|
+
function filterTypeFor(name, type, operators) {
|
|
169
|
+
const existing = filterTypes.get(name);
|
|
170
|
+
if (existing) {
|
|
171
|
+
return existing;
|
|
172
|
+
}
|
|
173
|
+
const filter = new graphql_1.GraphQLInputObjectType({
|
|
174
|
+
name,
|
|
175
|
+
fields: () => {
|
|
176
|
+
const fields = {
|
|
177
|
+
equals: { type },
|
|
178
|
+
in: { type: new graphql_1.GraphQLList(new graphql_1.GraphQLNonNull(type)) },
|
|
179
|
+
notIn: { type: new graphql_1.GraphQLList(new graphql_1.GraphQLNonNull(type)) },
|
|
180
|
+
not: { type },
|
|
181
|
+
};
|
|
182
|
+
for (const op of operators) {
|
|
183
|
+
fields[op] = { type };
|
|
184
|
+
}
|
|
185
|
+
return fields;
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
filterTypes.set(name, filter);
|
|
189
|
+
return filter;
|
|
190
|
+
}
|
|
191
|
+
function filterFor(model, field) {
|
|
192
|
+
if (field.kind === 'enum') {
|
|
193
|
+
const enumType = enumTypes.get(field.type);
|
|
194
|
+
if (!enumType) {
|
|
195
|
+
throw new Error(`Unknown enum ${field.type} on ${model.name}.${field.name}`);
|
|
196
|
+
}
|
|
197
|
+
return filterTypeFor(`${field.type}EnumFilter`, enumType, []);
|
|
198
|
+
}
|
|
199
|
+
const type = scalarType(model, field);
|
|
200
|
+
if (field.type === 'String') {
|
|
201
|
+
return filterTypeFor('StringFilter', type, [...ORDERED_OPERATORS, ...STRING_OPERATORS]);
|
|
202
|
+
}
|
|
203
|
+
if (field.type === 'Boolean') {
|
|
204
|
+
return filterTypeFor('BoolFilter', type, []);
|
|
205
|
+
}
|
|
206
|
+
return filterTypeFor(`${field.type}Filter`, type, ORDERED_OPERATORS);
|
|
207
|
+
}
|
|
208
|
+
const objectTypes = new Map();
|
|
209
|
+
const whereInputs = new Map();
|
|
210
|
+
const whereUniqueInputs = new Map();
|
|
211
|
+
const orderByInputs = new Map();
|
|
212
|
+
for (const model of models) {
|
|
213
|
+
objectTypes.set(model.name, new graphql_1.GraphQLObjectType({
|
|
214
|
+
name: model.name,
|
|
215
|
+
fields: () => {
|
|
216
|
+
const fields = {};
|
|
217
|
+
for (const field of visibleFields(model)) {
|
|
218
|
+
if (field.kind === 'object') {
|
|
219
|
+
const target = objectTypes.get(field.type);
|
|
220
|
+
if (!target) {
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
const type = field.isList
|
|
224
|
+
? new graphql_1.GraphQLNonNull(new graphql_1.GraphQLList(new graphql_1.GraphQLNonNull(target)))
|
|
225
|
+
: target;
|
|
226
|
+
fields[field.name] = { type };
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
const base = field.kind === 'enum' ? enumTypes.get(field.type) : scalarType(model, field);
|
|
230
|
+
fields[field.name] = {
|
|
231
|
+
type: field.isRequired ? new graphql_1.GraphQLNonNull(base) : base,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
for (const spec of computedByModel.get(model.name) ?? []) {
|
|
236
|
+
fields[spec.name] = {
|
|
237
|
+
type: resolveTypeRef(spec.type, outputTypeByName),
|
|
238
|
+
resolve: wrapResolve((parent, _args, ctx, info) => spec.resolve(parent, ctx, info)),
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
return fields;
|
|
242
|
+
},
|
|
243
|
+
}));
|
|
244
|
+
whereInputs.set(model.name, new graphql_1.GraphQLInputObjectType({
|
|
245
|
+
name: `${model.name}WhereInput`,
|
|
246
|
+
fields: () => {
|
|
247
|
+
const self = whereInputs.get(model.name);
|
|
248
|
+
const fields = {
|
|
249
|
+
AND: { type: new graphql_1.GraphQLList(new graphql_1.GraphQLNonNull(self)) },
|
|
250
|
+
OR: { type: new graphql_1.GraphQLList(new graphql_1.GraphQLNonNull(self)) },
|
|
251
|
+
NOT: { type: new graphql_1.GraphQLList(new graphql_1.GraphQLNonNull(self)) },
|
|
252
|
+
};
|
|
253
|
+
for (const field of visibleFields(model)) {
|
|
254
|
+
if (field.kind === 'object' || field.isList) {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
fields[field.name] = { type: filterFor(model, field) };
|
|
258
|
+
}
|
|
259
|
+
return fields;
|
|
260
|
+
},
|
|
261
|
+
}));
|
|
262
|
+
whereUniqueInputs.set(model.name, new graphql_1.GraphQLInputObjectType({
|
|
263
|
+
name: `${model.name}WhereUniqueInput`,
|
|
264
|
+
fields: () => {
|
|
265
|
+
const fields = {};
|
|
266
|
+
for (const field of visibleFields(model)) {
|
|
267
|
+
if (field.kind === 'scalar' && (field.isId || field.isUnique)) {
|
|
268
|
+
fields[field.name] = { type: scalarType(model, field) };
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return fields;
|
|
272
|
+
},
|
|
273
|
+
}));
|
|
274
|
+
orderByInputs.set(model.name, new graphql_1.GraphQLInputObjectType({
|
|
275
|
+
name: `${model.name}OrderByInput`,
|
|
276
|
+
fields: () => {
|
|
277
|
+
const fields = {};
|
|
278
|
+
for (const field of visibleFields(model)) {
|
|
279
|
+
if (field.kind === 'object' || field.isList) {
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
fields[field.name] = { type: sortOrder };
|
|
283
|
+
}
|
|
284
|
+
return fields;
|
|
285
|
+
},
|
|
286
|
+
}));
|
|
287
|
+
}
|
|
288
|
+
const inputs = new inputs_1.InputTypeRegistry({
|
|
289
|
+
modelsByName,
|
|
290
|
+
enumTypes,
|
|
291
|
+
whereUniqueInputs,
|
|
292
|
+
scalarType,
|
|
293
|
+
hiddenFor,
|
|
294
|
+
immutableFor,
|
|
295
|
+
});
|
|
296
|
+
const namedScalars = {
|
|
297
|
+
...SCALAR_MAP,
|
|
298
|
+
ID: graphql_1.GraphQLID,
|
|
299
|
+
};
|
|
300
|
+
function resolveTypeRef(ref, lookup) {
|
|
301
|
+
const trimmed = ref.trim();
|
|
302
|
+
if (trimmed.endsWith('!')) {
|
|
303
|
+
return new graphql_1.GraphQLNonNull(resolveTypeRef(trimmed.slice(0, -1), lookup));
|
|
304
|
+
}
|
|
305
|
+
if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
|
|
306
|
+
return new graphql_1.GraphQLList(resolveTypeRef(trimmed.slice(1, -1), lookup));
|
|
307
|
+
}
|
|
308
|
+
const named = lookup(trimmed);
|
|
309
|
+
if (!named) {
|
|
310
|
+
throw new Error(`Unknown type ${trimmed} in extension type reference`);
|
|
311
|
+
}
|
|
312
|
+
return named;
|
|
313
|
+
}
|
|
314
|
+
function outputTypeByName(name) {
|
|
315
|
+
if (name === 'BatchPayload') {
|
|
316
|
+
return batchPayload;
|
|
317
|
+
}
|
|
318
|
+
return namedScalars[name] ?? enumTypes.get(name) ?? objectTypes.get(name);
|
|
319
|
+
}
|
|
320
|
+
function inputTypeByName(name) {
|
|
321
|
+
const direct = namedScalars[name] ?? enumTypes.get(name) ?? filterTypes.get(name) ?? inputs.find(name);
|
|
322
|
+
if (direct) {
|
|
323
|
+
return direct;
|
|
324
|
+
}
|
|
325
|
+
const suffixes = [
|
|
326
|
+
['WhereUniqueInput', (m) => whereUniqueInputs.get(m.name)],
|
|
327
|
+
['WhereInput', (m) => whereInputs.get(m.name)],
|
|
328
|
+
['OrderByInput', (m) => orderByInputs.get(m.name)],
|
|
329
|
+
['CreateInput', (m) => inputs.createInput(m)],
|
|
330
|
+
['UpdateManyInput', (m) => inputs.updateManyInput(m)],
|
|
331
|
+
['UpdateInput', (m) => inputs.updateInput(m)],
|
|
332
|
+
];
|
|
333
|
+
for (const [suffix, get] of suffixes) {
|
|
334
|
+
if (name.endsWith(suffix)) {
|
|
335
|
+
const model = modelsByName.get(name.slice(0, -suffix.length));
|
|
336
|
+
if (model) {
|
|
337
|
+
return get(model);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return undefined;
|
|
342
|
+
}
|
|
343
|
+
const queryFields = {};
|
|
344
|
+
const mutationFields = {};
|
|
345
|
+
const subscriptionFields = {};
|
|
346
|
+
const golemEventType = new graphql_1.GraphQLEnumType({
|
|
347
|
+
name: 'GolemEventType',
|
|
348
|
+
values: { CREATED: {}, UPDATED: {}, DELETED: {} },
|
|
349
|
+
});
|
|
350
|
+
for (const model of models) {
|
|
351
|
+
const objectType = objectTypes.get(model.name);
|
|
352
|
+
const whereInput = whereInputs.get(model.name);
|
|
353
|
+
const whereUniqueInput = whereUniqueInputs.get(model.name);
|
|
354
|
+
const orderByInput = orderByInputs.get(model.name);
|
|
355
|
+
const operations = settings.get(model.name).operations;
|
|
356
|
+
if (operations.has('findOne')) {
|
|
357
|
+
queryFields[(0, naming_1.findOneFieldName)(model.name)] = {
|
|
358
|
+
type: objectType,
|
|
359
|
+
args: {
|
|
360
|
+
where: { type: new graphql_1.GraphQLNonNull(whereUniqueInput) },
|
|
361
|
+
},
|
|
362
|
+
resolve: wrapResolve((_root, args, ctx, info) => engine.findOne({
|
|
363
|
+
model: model.name,
|
|
364
|
+
where: args.where,
|
|
365
|
+
select: (0, select_1.buildSelect)(info, model, modelsByName, computedRequires),
|
|
366
|
+
context: ctx,
|
|
367
|
+
})),
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
if (operations.has('findMany')) {
|
|
371
|
+
queryFields[(0, naming_1.findManyFieldName)(model.name)] = {
|
|
372
|
+
type: new graphql_1.GraphQLNonNull(new graphql_1.GraphQLList(new graphql_1.GraphQLNonNull(objectType))),
|
|
373
|
+
args: {
|
|
374
|
+
where: { type: whereInput },
|
|
375
|
+
orderBy: { type: new graphql_1.GraphQLList(new graphql_1.GraphQLNonNull(orderByInput)) },
|
|
376
|
+
take: { type: graphql_1.GraphQLInt },
|
|
377
|
+
skip: { type: graphql_1.GraphQLInt },
|
|
378
|
+
},
|
|
379
|
+
resolve: wrapResolve((_root, args, ctx, info) => engine.findMany({
|
|
380
|
+
model: model.name,
|
|
381
|
+
where: args.where ?? undefined,
|
|
382
|
+
orderBy: args.orderBy ?? undefined,
|
|
383
|
+
take: args.take ?? undefined,
|
|
384
|
+
skip: args.skip ?? undefined,
|
|
385
|
+
select: (0, select_1.buildSelect)(info, model, modelsByName, computedRequires),
|
|
386
|
+
context: ctx,
|
|
387
|
+
})),
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
if (operations.has('create')) {
|
|
391
|
+
mutationFields[(0, naming_1.createFieldName)(model.name)] = {
|
|
392
|
+
type: new graphql_1.GraphQLNonNull(objectType),
|
|
393
|
+
args: {
|
|
394
|
+
data: { type: new graphql_1.GraphQLNonNull(inputs.createInput(model)) },
|
|
395
|
+
},
|
|
396
|
+
resolve: wrapResolve((_root, args, ctx, info) => engine.create({
|
|
397
|
+
model: model.name,
|
|
398
|
+
data: args.data,
|
|
399
|
+
select: (0, select_1.buildSelect)(info, model, modelsByName, computedRequires),
|
|
400
|
+
context: ctx,
|
|
401
|
+
})),
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
if (operations.has('update')) {
|
|
405
|
+
mutationFields[(0, naming_1.updateFieldName)(model.name)] = {
|
|
406
|
+
type: new graphql_1.GraphQLNonNull(objectType),
|
|
407
|
+
args: {
|
|
408
|
+
where: { type: new graphql_1.GraphQLNonNull(whereUniqueInput) },
|
|
409
|
+
data: { type: new graphql_1.GraphQLNonNull(inputs.updateInput(model)) },
|
|
410
|
+
},
|
|
411
|
+
resolve: wrapResolve((_root, args, ctx, info) => engine.update({
|
|
412
|
+
model: model.name,
|
|
413
|
+
where: args.where,
|
|
414
|
+
data: args.data,
|
|
415
|
+
select: (0, select_1.buildSelect)(info, model, modelsByName, computedRequires),
|
|
416
|
+
context: ctx,
|
|
417
|
+
})),
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
if (operations.has('delete')) {
|
|
421
|
+
mutationFields[(0, naming_1.deleteFieldName)(model.name)] = {
|
|
422
|
+
type: new graphql_1.GraphQLNonNull(objectType),
|
|
423
|
+
args: {
|
|
424
|
+
where: { type: new graphql_1.GraphQLNonNull(whereUniqueInput) },
|
|
425
|
+
},
|
|
426
|
+
resolve: wrapResolve((_root, args, ctx, info) => engine.delete({
|
|
427
|
+
model: model.name,
|
|
428
|
+
where: args.where,
|
|
429
|
+
select: (0, select_1.buildSelect)(info, model, modelsByName, computedRequires),
|
|
430
|
+
context: ctx,
|
|
431
|
+
})),
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
if (operations.has('updateMany')) {
|
|
435
|
+
mutationFields[(0, naming_1.updateManyFieldName)(model.name)] = {
|
|
436
|
+
type: new graphql_1.GraphQLNonNull(batchPayload),
|
|
437
|
+
args: {
|
|
438
|
+
where: { type: whereInput },
|
|
439
|
+
data: { type: new graphql_1.GraphQLNonNull(inputs.updateManyInput(model)) },
|
|
440
|
+
},
|
|
441
|
+
resolve: wrapResolve((_root, args, ctx) => engine.updateMany({
|
|
442
|
+
model: model.name,
|
|
443
|
+
where: args.where ?? undefined,
|
|
444
|
+
data: args.data,
|
|
445
|
+
context: ctx,
|
|
446
|
+
})),
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
if (operations.has('deleteMany')) {
|
|
450
|
+
mutationFields[(0, naming_1.deleteManyFieldName)(model.name)] = {
|
|
451
|
+
type: new graphql_1.GraphQLNonNull(batchPayload),
|
|
452
|
+
args: {
|
|
453
|
+
where: { type: whereInput },
|
|
454
|
+
},
|
|
455
|
+
resolve: wrapResolve((_root, args, ctx) => engine.deleteMany({
|
|
456
|
+
model: model.name,
|
|
457
|
+
where: args.where ?? undefined,
|
|
458
|
+
context: ctx,
|
|
459
|
+
})),
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
if (subscribable.has(model.name)) {
|
|
463
|
+
const eventBus = options.eventBus;
|
|
464
|
+
const pkField = model.fields.find((f) => f.isId);
|
|
465
|
+
if (!pkField) {
|
|
466
|
+
throw new Error(`Model ${model.name} has no primary key field and cannot be subscribable`);
|
|
467
|
+
}
|
|
468
|
+
const eventTypeName = `${model.name}Event`;
|
|
469
|
+
const eventType = new graphql_1.GraphQLObjectType({
|
|
470
|
+
name: eventTypeName,
|
|
471
|
+
fields: {
|
|
472
|
+
type: { type: new graphql_1.GraphQLNonNull(golemEventType) },
|
|
473
|
+
id: { type: new graphql_1.GraphQLNonNull(scalarType(model, pkField)) },
|
|
474
|
+
entity: { type: objectType },
|
|
475
|
+
},
|
|
476
|
+
});
|
|
477
|
+
subscriptionFields[(0, naming_1.eventsFieldName)(model.name)] = {
|
|
478
|
+
type: new graphql_1.GraphQLNonNull(eventType),
|
|
479
|
+
args: {
|
|
480
|
+
where: { type: whereInput },
|
|
481
|
+
},
|
|
482
|
+
subscribe: async function* (_root, args, ctx, info) {
|
|
483
|
+
const authz = options.authorization;
|
|
484
|
+
const eventContext = () => (authz ? authz.freshContext?.(ctx) ?? ctx : undefined);
|
|
485
|
+
if (authz) {
|
|
486
|
+
await authz.authorize('read', model.name, eventContext());
|
|
487
|
+
}
|
|
488
|
+
const entitySelect = (0, select_1.buildEventEntitySelect)(info, eventTypeName, model, modelsByName, computedRequires);
|
|
489
|
+
for await (const payload of eventBus.iterate((0, events_1.eventTopic)(model.name))) {
|
|
490
|
+
if (payload.type === 'DELETED') {
|
|
491
|
+
// A deleted row can no longer be queried safely. Filtered deletion events are
|
|
492
|
+
// therefore suppressed, and authorized subscriptions require a pre-delete
|
|
493
|
+
// snapshot that passes a fresh instance check.
|
|
494
|
+
if (args.where)
|
|
495
|
+
continue;
|
|
496
|
+
if (authz) {
|
|
497
|
+
if (!payload.entity || !authz.check)
|
|
498
|
+
continue;
|
|
499
|
+
try {
|
|
500
|
+
const allowed = await authz.check('read', model.name, payload.entity, eventContext());
|
|
501
|
+
if (!allowed)
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
catch (error) {
|
|
505
|
+
if (error instanceof errors_1.GolemError &&
|
|
506
|
+
(error.code === 'FORBIDDEN' || error.code === 'UNAUTHENTICATED'))
|
|
507
|
+
continue;
|
|
508
|
+
throw error;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
yield { type: payload.type, id: payload.id, entity: null };
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
if (!args.where && !entitySelect && !authz) {
|
|
515
|
+
yield { type: payload.type, id: payload.id, entity: null };
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
const where = args.where
|
|
519
|
+
? { AND: [{ [pkField.name]: payload.id }, args.where] }
|
|
520
|
+
: { [pkField.name]: payload.id };
|
|
521
|
+
let entity;
|
|
522
|
+
try {
|
|
523
|
+
entity = await engine.findFirst({
|
|
524
|
+
model: model.name,
|
|
525
|
+
where,
|
|
526
|
+
select: entitySelect ?? (0, select_1.primaryKeySelect)(model),
|
|
527
|
+
context: eventContext(),
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
catch (error) {
|
|
531
|
+
if (error instanceof errors_1.GolemError &&
|
|
532
|
+
(error.code === 'FORBIDDEN' || error.code === 'UNAUTHENTICATED')) {
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
535
|
+
throw error;
|
|
536
|
+
}
|
|
537
|
+
if (!entity && (args.where || authz)) {
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
yield { type: payload.type, id: payload.id, entity: entitySelect ? entity : null };
|
|
541
|
+
}
|
|
542
|
+
},
|
|
543
|
+
resolve: (payload) => payload,
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
for (const spec of options.customOperations ?? []) {
|
|
548
|
+
const target = spec.kind === 'query' ? queryFields : mutationFields;
|
|
549
|
+
if (target[spec.name]) {
|
|
550
|
+
throw new Error(`Custom ${spec.kind} ${spec.name} collides with an existing field`);
|
|
551
|
+
}
|
|
552
|
+
const args = {};
|
|
553
|
+
for (const [argName, argRef] of Object.entries(spec.args ?? {})) {
|
|
554
|
+
args[argName] = { type: resolveTypeRef(argRef, inputTypeByName) };
|
|
555
|
+
}
|
|
556
|
+
target[spec.name] = {
|
|
557
|
+
type: resolveTypeRef(spec.type, outputTypeByName),
|
|
558
|
+
args,
|
|
559
|
+
resolve: wrapResolve((_root, resolvedArgs, ctx, info) => spec.resolve(resolvedArgs, ctx, info)),
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
return new graphql_1.GraphQLSchema({
|
|
563
|
+
query: new graphql_1.GraphQLObjectType({
|
|
564
|
+
name: 'Query',
|
|
565
|
+
fields: queryFields,
|
|
566
|
+
}),
|
|
567
|
+
mutation: Object.keys(mutationFields).length > 0
|
|
568
|
+
? new graphql_1.GraphQLObjectType({
|
|
569
|
+
name: 'Mutation',
|
|
570
|
+
fields: mutationFields,
|
|
571
|
+
})
|
|
572
|
+
: undefined,
|
|
573
|
+
subscription: Object.keys(subscriptionFields).length > 0
|
|
574
|
+
? new graphql_1.GraphQLObjectType({
|
|
575
|
+
name: 'Subscription',
|
|
576
|
+
fields: subscriptionFields,
|
|
577
|
+
})
|
|
578
|
+
: undefined,
|
|
579
|
+
});
|
|
580
|
+
}
|
package/dist/select.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { GraphQLResolveInfo } from 'graphql';
|
|
2
|
+
import { DatamodelModel } from './datamodel';
|
|
3
|
+
import { ComputedRequiresMap } from './extensions';
|
|
4
|
+
export interface PrismaSelectRelation {
|
|
5
|
+
select?: PrismaSelect;
|
|
6
|
+
include?: PrismaSelect;
|
|
7
|
+
where?: unknown;
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
}
|
|
10
|
+
export type PrismaSelect = {
|
|
11
|
+
[field: string]: boolean | PrismaSelectRelation;
|
|
12
|
+
};
|
|
13
|
+
export declare function primaryKeySelect(model: DatamodelModel): PrismaSelect;
|
|
14
|
+
export declare function buildSelect(info: GraphQLResolveInfo, model: DatamodelModel, modelsByName: Map<string, DatamodelModel>, computed?: ComputedRequiresMap): PrismaSelect;
|
|
15
|
+
export declare function buildEventEntitySelect(info: GraphQLResolveInfo, eventTypeName: string, model: DatamodelModel, modelsByName: Map<string, DatamodelModel>, computed?: ComputedRequiresMap): PrismaSelect | null;
|
package/dist/select.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.primaryKeySelect = primaryKeySelect;
|
|
4
|
+
exports.buildSelect = buildSelect;
|
|
5
|
+
exports.buildEventEntitySelect = buildEventEntitySelect;
|
|
6
|
+
const graphql_parse_resolve_info_1 = require("graphql-parse-resolve-info");
|
|
7
|
+
function primaryKeySelect(model) {
|
|
8
|
+
const select = {};
|
|
9
|
+
for (const field of model.fields) {
|
|
10
|
+
if (field.isId) {
|
|
11
|
+
select[field.name] = true;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
if (Object.keys(select).length === 0) {
|
|
15
|
+
throw new Error(`Model ${model.name} has no primary key field to select`);
|
|
16
|
+
}
|
|
17
|
+
return select;
|
|
18
|
+
}
|
|
19
|
+
function selectFromFields(fields, model, modelsByName, computed) {
|
|
20
|
+
const select = {};
|
|
21
|
+
const modelComputed = computed?.get(model.name);
|
|
22
|
+
for (const key of Object.keys(fields)) {
|
|
23
|
+
const tree = fields[key];
|
|
24
|
+
const field = model.fields.find((f) => f.name === tree.name);
|
|
25
|
+
if (!field) {
|
|
26
|
+
const requires = modelComputed?.get(tree.name);
|
|
27
|
+
if (requires) {
|
|
28
|
+
for (const column of requires) {
|
|
29
|
+
select[column] = true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (field.kind === 'object') {
|
|
35
|
+
const target = modelsByName.get(field.type);
|
|
36
|
+
if (!target) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
select[field.name] = {
|
|
40
|
+
select: selectFromFields(tree.fieldsByTypeName[target.name] ?? {}, target, modelsByName, computed),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
select[field.name] = true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (Object.keys(select).length === 0) {
|
|
48
|
+
return primaryKeySelect(model);
|
|
49
|
+
}
|
|
50
|
+
return select;
|
|
51
|
+
}
|
|
52
|
+
function buildSelect(info, model, modelsByName, computed) {
|
|
53
|
+
const tree = (0, graphql_parse_resolve_info_1.parseResolveInfo)(info);
|
|
54
|
+
if (!tree) {
|
|
55
|
+
return primaryKeySelect(model);
|
|
56
|
+
}
|
|
57
|
+
return selectFromFields(tree.fieldsByTypeName[model.name] ?? {}, model, modelsByName, computed);
|
|
58
|
+
}
|
|
59
|
+
function buildEventEntitySelect(info, eventTypeName, model, modelsByName, computed) {
|
|
60
|
+
const tree = (0, graphql_parse_resolve_info_1.parseResolveInfo)(info);
|
|
61
|
+
const eventFields = tree?.fieldsByTypeName[eventTypeName] ?? {};
|
|
62
|
+
const entityTree = Object.values(eventFields).find((f) => f.name === 'entity');
|
|
63
|
+
if (!entityTree) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
return selectFromFields(entityTree.fieldsByTypeName[model.name] ?? {}, model, modelsByName, computed);
|
|
67
|
+
}
|
package/dist/testing.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.field = field;
|
|
4
|
+
function field(overrides) {
|
|
5
|
+
return {
|
|
6
|
+
kind: 'scalar',
|
|
7
|
+
isList: false,
|
|
8
|
+
isRequired: true,
|
|
9
|
+
isUnique: false,
|
|
10
|
+
isId: false,
|
|
11
|
+
hasDefaultValue: false,
|
|
12
|
+
isReadOnly: false,
|
|
13
|
+
isUpdatedAt: false,
|
|
14
|
+
...overrides,
|
|
15
|
+
};
|
|
16
|
+
}
|