@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
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { GolemOperation } from './hooks';
|
|
2
|
+
import { BatchResult } from './operations';
|
|
3
|
+
import { PrismaSelect } from './select';
|
|
4
|
+
export interface GolemTypeShape {
|
|
5
|
+
entity: unknown;
|
|
6
|
+
create: unknown;
|
|
7
|
+
update: unknown;
|
|
8
|
+
updateMany: unknown;
|
|
9
|
+
where: unknown;
|
|
10
|
+
whereUnique: unknown;
|
|
11
|
+
orderBy: unknown;
|
|
12
|
+
}
|
|
13
|
+
export type GolemTypesMap = Record<string, GolemTypeShape>;
|
|
14
|
+
export type HookRequestFor<TTypes extends GolemTypesMap, M extends keyof TTypes & string, O extends GolemOperation> = O extends 'create' ? {
|
|
15
|
+
model: M;
|
|
16
|
+
data: TTypes[M]['create'];
|
|
17
|
+
select?: PrismaSelect;
|
|
18
|
+
context?: unknown;
|
|
19
|
+
} : O extends 'update' ? {
|
|
20
|
+
model: M;
|
|
21
|
+
where: TTypes[M]['whereUnique'];
|
|
22
|
+
data: TTypes[M]['update'];
|
|
23
|
+
select?: PrismaSelect;
|
|
24
|
+
context?: unknown;
|
|
25
|
+
} : O extends 'updateMany' ? {
|
|
26
|
+
model: M;
|
|
27
|
+
where?: TTypes[M]['where'];
|
|
28
|
+
data: TTypes[M]['updateMany'];
|
|
29
|
+
context?: unknown;
|
|
30
|
+
} : O extends 'delete' ? {
|
|
31
|
+
model: M;
|
|
32
|
+
where: TTypes[M]['whereUnique'];
|
|
33
|
+
select?: PrismaSelect;
|
|
34
|
+
context?: unknown;
|
|
35
|
+
} : O extends 'deleteMany' ? {
|
|
36
|
+
model: M;
|
|
37
|
+
where?: TTypes[M]['where'];
|
|
38
|
+
context?: unknown;
|
|
39
|
+
} : O extends 'findOne' ? {
|
|
40
|
+
model: M;
|
|
41
|
+
where: TTypes[M]['whereUnique'];
|
|
42
|
+
select?: PrismaSelect;
|
|
43
|
+
context?: unknown;
|
|
44
|
+
} : O extends 'findMany' ? {
|
|
45
|
+
model: M;
|
|
46
|
+
where?: TTypes[M]['where'];
|
|
47
|
+
orderBy?: TTypes[M]['orderBy'] | TTypes[M]['orderBy'][];
|
|
48
|
+
take?: number;
|
|
49
|
+
skip?: number;
|
|
50
|
+
select?: PrismaSelect;
|
|
51
|
+
context?: unknown;
|
|
52
|
+
} : never;
|
|
53
|
+
export type HookResultFor<TTypes extends GolemTypesMap, M extends keyof TTypes & string, O extends GolemOperation> = O extends 'create' | 'update' | 'delete' ? Partial<TTypes[M]['entity']> : O extends 'findOne' ? Partial<TTypes[M]['entity']> | null : O extends 'findMany' ? Partial<TTypes[M]['entity']>[] : BatchResult;
|
package/dist/typemap.js
ADDED
package/dist/verify.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { AuthorizationProvider } from './authorization';
|
|
2
|
+
import { DatamodelModel } from './datamodel';
|
|
3
|
+
import { ModelMetadataIndex } from './model-meta';
|
|
4
|
+
import { NestedRelationWritePlan } from './nested-writes';
|
|
5
|
+
import { PrismaSelect } from './select';
|
|
6
|
+
export interface VerifyContext {
|
|
7
|
+
modelsByName: Map<string, DatamodelModel>;
|
|
8
|
+
metadata?: ModelMetadataIndex;
|
|
9
|
+
provider: AuthorizationProvider;
|
|
10
|
+
context: unknown;
|
|
11
|
+
}
|
|
12
|
+
export declare function modelScalarSelect(model: DatamodelModel): PrismaSelect;
|
|
13
|
+
export declare function touchedRelations(modelsByName: Map<string, DatamodelModel>, model: DatamodelModel, data: Record<string, unknown>): readonly NestedRelationWritePlan[];
|
|
14
|
+
export declare function wideSelect(modelsByName: Map<string, DatamodelModel>, model: DatamodelModel, data: Record<string, unknown>): PrismaSelect;
|
|
15
|
+
export declare function verifyCreatedTree(ctx: VerifyContext, model: DatamodelModel, row: Record<string, unknown>, data: Record<string, unknown>): Promise<void>;
|
|
16
|
+
export declare function verifyUpdatedRow(ctx: VerifyContext, model: DatamodelModel, before: Record<string, unknown>, after: Record<string, unknown>, data: Record<string, unknown>): Promise<void>;
|
|
17
|
+
export interface VerificationPlan {
|
|
18
|
+
select: PrismaSelect;
|
|
19
|
+
fastPath: boolean;
|
|
20
|
+
}
|
|
21
|
+
export declare function planVerification(ctx: VerifyContext, model: DatamodelModel, data: Record<string, unknown>, constraint: unknown, action: 'create' | 'update'): Promise<VerificationPlan>;
|
package/dist/verify.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.modelScalarSelect = modelScalarSelect;
|
|
4
|
+
exports.touchedRelations = touchedRelations;
|
|
5
|
+
exports.wideSelect = wideSelect;
|
|
6
|
+
exports.verifyCreatedTree = verifyCreatedTree;
|
|
7
|
+
exports.verifyUpdatedRow = verifyUpdatedRow;
|
|
8
|
+
exports.planVerification = planVerification;
|
|
9
|
+
const authorization_1 = require("./authorization");
|
|
10
|
+
const concurrency_1 = require("./concurrency");
|
|
11
|
+
const errors_1 = require("./errors");
|
|
12
|
+
const model_meta_1 = require("./model-meta");
|
|
13
|
+
const nested_writes_1 = require("./nested-writes");
|
|
14
|
+
const readtree_1 = require("./readtree");
|
|
15
|
+
function metadataFor(ctx) {
|
|
16
|
+
return ctx.metadata ?? (0, model_meta_1.buildModelMetadata)([...ctx.modelsByName.values()]);
|
|
17
|
+
}
|
|
18
|
+
function modelScalarSelect(model) {
|
|
19
|
+
const select = {};
|
|
20
|
+
for (const field of model.fields) {
|
|
21
|
+
if (field.kind !== 'object') {
|
|
22
|
+
select[field.name] = true;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return select;
|
|
26
|
+
}
|
|
27
|
+
function touchedRelations(modelsByName, model, data) {
|
|
28
|
+
return (0, nested_writes_1.planNestedWrites)((0, model_meta_1.buildModelMetadata)([...modelsByName.values()]), model, data);
|
|
29
|
+
}
|
|
30
|
+
function mergeSelect(into, source) {
|
|
31
|
+
for (const [name, value] of Object.entries(source)) {
|
|
32
|
+
const current = into[name];
|
|
33
|
+
if (current && current !== true && value !== true &&
|
|
34
|
+
typeof current === 'object' && typeof value === 'object' &&
|
|
35
|
+
current.select && value.select) {
|
|
36
|
+
mergeSelect(current.select, value.select);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
into[name] = value;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function wideSelect(modelsByName, model, data) {
|
|
44
|
+
const metadata = (0, model_meta_1.buildModelMetadata)([...modelsByName.values()]);
|
|
45
|
+
return wideSelectWithMetadata(metadata, model, data);
|
|
46
|
+
}
|
|
47
|
+
function wideSelectWithMetadata(metadata, model, data) {
|
|
48
|
+
const select = modelScalarSelect(model);
|
|
49
|
+
for (const relation of (0, nested_writes_1.planNestedWrites)(metadata, model, data)) {
|
|
50
|
+
const nestedSelect = modelScalarSelect(relation.target);
|
|
51
|
+
for (const payload of [...relation.createPayloads, ...relation.updatePayloads]) {
|
|
52
|
+
mergeSelect(nestedSelect, wideSelectWithMetadata(metadata, relation.target, payload));
|
|
53
|
+
}
|
|
54
|
+
select[relation.field.name] = { select: nestedSelect };
|
|
55
|
+
}
|
|
56
|
+
return select;
|
|
57
|
+
}
|
|
58
|
+
function payloadFieldNames(model, data) {
|
|
59
|
+
const names = new Set();
|
|
60
|
+
for (const field of model.fields) {
|
|
61
|
+
if (data[field.name] === undefined) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (field.kind === 'object') {
|
|
65
|
+
if (field.relationFromFields?.length === 1) {
|
|
66
|
+
names.add(field.relationFromFields[0]);
|
|
67
|
+
}
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
names.add(field.name);
|
|
71
|
+
}
|
|
72
|
+
return [...names];
|
|
73
|
+
}
|
|
74
|
+
async function checkRow(ctx, action, model, row) {
|
|
75
|
+
const allowed = await ctx.provider.check(action, model, row, ctx.context);
|
|
76
|
+
if (!allowed) {
|
|
77
|
+
throw new errors_1.GolemForbiddenError(`Cannot ${action} ${model} with the provided values`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async function checkFields(ctx, action, model, entity, fields) {
|
|
81
|
+
await (0, concurrency_1.runPolicyChecks)(fields.map((field) => async () => {
|
|
82
|
+
const allowed = await ctx.provider.checkField(action, model, entity, field, ctx.context);
|
|
83
|
+
if (!allowed)
|
|
84
|
+
throw new errors_1.GolemForbiddenError(`Cannot ${action} field "${field}" on ${model}`);
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
async function verifyAppearedRow(ctx, relation, row) {
|
|
88
|
+
await (0, concurrency_1.runPolicyChecks)([...relation.addedActions].map((action) => () => checkRow(ctx, action, relation.target.name, row)));
|
|
89
|
+
if (relation.addedActions.has('create')) {
|
|
90
|
+
const fieldUnion = new Set();
|
|
91
|
+
for (const payload of relation.createPayloads) {
|
|
92
|
+
for (const name of payloadFieldNames(relation.target, payload)) {
|
|
93
|
+
fieldUnion.add(name);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
await checkFields(ctx, 'create', relation.target.name, row, [...fieldUnion]);
|
|
97
|
+
for (const payload of relation.createPayloads) {
|
|
98
|
+
await verifyCreatedChildren(ctx, relation.target, row, payload);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async function verifyCreatedChildren(ctx, model, row, data) {
|
|
103
|
+
for (const relation of (0, nested_writes_1.planNestedWrites)(metadataFor(ctx), model, data)) {
|
|
104
|
+
const value = row[relation.field.name];
|
|
105
|
+
const children = Array.isArray(value) ? value : value ? [value] : [];
|
|
106
|
+
await (0, concurrency_1.runPolicyChecks)(children.map((child) => () => verifyAppearedRow(ctx, relation, child)));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async function verifyCreatedTree(ctx, model, row, data) {
|
|
110
|
+
await checkRow(ctx, 'create', model.name, row);
|
|
111
|
+
await checkFields(ctx, 'create', model.name, row, payloadFieldNames(model, data));
|
|
112
|
+
await verifyCreatedChildren(ctx, model, row, data);
|
|
113
|
+
}
|
|
114
|
+
function scalarChanged(before, after) {
|
|
115
|
+
if (before instanceof Date || after instanceof Date) {
|
|
116
|
+
const a = before instanceof Date ? before.getTime() : before;
|
|
117
|
+
const b = after instanceof Date ? after.getTime() : after;
|
|
118
|
+
return a !== b;
|
|
119
|
+
}
|
|
120
|
+
return before !== after;
|
|
121
|
+
}
|
|
122
|
+
async function verifyUpdatedRow(ctx, model, before, after, data) {
|
|
123
|
+
const metadata = metadataFor(ctx);
|
|
124
|
+
await checkRow(ctx, 'update', model.name, after);
|
|
125
|
+
const changed = [];
|
|
126
|
+
for (const field of metadata.get(model.name)?.scalarFields ?? []) {
|
|
127
|
+
if (scalarChanged(before[field.name], after[field.name])) {
|
|
128
|
+
changed.push(field.name);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
await checkFields(ctx, 'update', model.name, before, changed);
|
|
132
|
+
for (const relation of (0, nested_writes_1.planNestedWrites)(metadata, model, data)) {
|
|
133
|
+
const pk = metadata.get(relation.target.name)?.primaryKey;
|
|
134
|
+
if (!pk) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const beforeValue = before[relation.field.name];
|
|
138
|
+
const afterValue = after[relation.field.name];
|
|
139
|
+
const beforeRows = Array.isArray(beforeValue) ? beforeValue : beforeValue ? [beforeValue] : [];
|
|
140
|
+
const afterRows = Array.isArray(afterValue) ? afterValue : afterValue ? [afterValue] : [];
|
|
141
|
+
const beforeById = new Map(beforeRows.map((row) => {
|
|
142
|
+
const record = row;
|
|
143
|
+
return [record[pk.name], record];
|
|
144
|
+
}));
|
|
145
|
+
const afterById = new Map(afterRows.map((row) => {
|
|
146
|
+
const record = row;
|
|
147
|
+
return [record[pk.name], record];
|
|
148
|
+
}));
|
|
149
|
+
for (const child of afterRows) {
|
|
150
|
+
const childRow = child;
|
|
151
|
+
const beforeChild = beforeById.get(childRow[pk.name]);
|
|
152
|
+
if (!beforeChild) {
|
|
153
|
+
await verifyAppearedRow(ctx, relation, childRow);
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const childChanged = metadata
|
|
157
|
+
.get(relation.target.name)
|
|
158
|
+
.scalarFields
|
|
159
|
+
.filter((field) => scalarChanged(beforeChild[field.name], childRow[field.name]))
|
|
160
|
+
.map((field) => field.name);
|
|
161
|
+
if (relation.retainedActions.has('update') &&
|
|
162
|
+
(childChanged.length > 0 || (0, nested_writes_1.nestedPlanTargetsRow)(relation, beforeChild))) {
|
|
163
|
+
await checkRow(ctx, 'update', relation.target.name, childRow);
|
|
164
|
+
await checkFields(ctx, 'update', relation.target.name, beforeChild, childChanged);
|
|
165
|
+
for (const payload of relation.updatePayloads) {
|
|
166
|
+
await verifyUpdatedChildren(ctx, relation.target, beforeChild, childRow, payload);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
for (const child of beforeRows) {
|
|
171
|
+
const childRow = child;
|
|
172
|
+
if (afterById.has(childRow[pk.name]))
|
|
173
|
+
continue;
|
|
174
|
+
await (0, concurrency_1.runPolicyChecks)([...relation.removedActions].map((action) => () => checkRow(ctx, action, relation.target.name, childRow)));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async function verifyUpdatedChildren(ctx, model, before, after, data) {
|
|
179
|
+
for (const relation of (0, nested_writes_1.planNestedWrites)(metadataFor(ctx), model, data)) {
|
|
180
|
+
const wrapperBefore = { ...before, [relation.field.name]: before[relation.field.name] };
|
|
181
|
+
const wrapperAfter = { ...after, [relation.field.name]: after[relation.field.name] };
|
|
182
|
+
await verifyRelationOnly(ctx, model, wrapperBefore, wrapperAfter, data, relation.field.name);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
async function verifyRelationOnly(ctx, model, before, after, data, fieldName) {
|
|
186
|
+
const rootBefore = { ...before };
|
|
187
|
+
const rootAfter = { ...after };
|
|
188
|
+
for (const field of metadataFor(ctx).get(model.name)?.scalarFields ?? []) {
|
|
189
|
+
rootAfter[field.name] = rootBefore[field.name];
|
|
190
|
+
}
|
|
191
|
+
const narrowed = { [fieldName]: data[fieldName] };
|
|
192
|
+
// Reuse the same diff verifier while suppressing root scalar differences.
|
|
193
|
+
await verifyUpdatedRow(ctx, model, rootBefore, rootAfter, narrowed);
|
|
194
|
+
}
|
|
195
|
+
async function planVerification(ctx, model, data, constraint, action) {
|
|
196
|
+
const metadata = metadataFor(ctx);
|
|
197
|
+
const relations = (0, nested_writes_1.planNestedWrites)(metadata, model, data);
|
|
198
|
+
if (!ctx.provider.classifyFields) {
|
|
199
|
+
return { select: wideSelectWithMetadata(metadata, model, data), fastPath: false };
|
|
200
|
+
}
|
|
201
|
+
const payload = payloadFieldNames(model, data);
|
|
202
|
+
const classification = payload.length > 0
|
|
203
|
+
? await ctx.provider.classifyFields(action, model.name, payload, ctx.context)
|
|
204
|
+
: {};
|
|
205
|
+
const allAlways = payload.every((f) => classification[f]?.access === 'always');
|
|
206
|
+
if (allAlways && !(0, authorization_1.isConditionalConstraint)(constraint) && relations.length === 0) {
|
|
207
|
+
return { select: {}, fastPath: true };
|
|
208
|
+
}
|
|
209
|
+
const scalarNames = new Set(metadata.get(model.name).scalarFields.map((f) => f.name));
|
|
210
|
+
const needed = new Set();
|
|
211
|
+
for (const field of metadata.get(model.name).scalarFields) {
|
|
212
|
+
if (field.isId || field.isUpdatedAt) {
|
|
213
|
+
needed.add(field.name);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
for (const name of payload) {
|
|
217
|
+
needed.add(name);
|
|
218
|
+
}
|
|
219
|
+
for (const name of (0, readtree_1.constraintFieldNames)(constraint)) {
|
|
220
|
+
needed.add(name);
|
|
221
|
+
}
|
|
222
|
+
for (const name of payload) {
|
|
223
|
+
for (const required of classification[name]?.requires ?? []) {
|
|
224
|
+
needed.add(required);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
const select = {};
|
|
228
|
+
for (const name of needed) {
|
|
229
|
+
if (scalarNames.has(name)) {
|
|
230
|
+
select[name] = true;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
for (const relation of relations) {
|
|
234
|
+
const nestedSelect = modelScalarSelect(relation.target);
|
|
235
|
+
for (const payload of [...relation.createPayloads, ...relation.updatePayloads]) {
|
|
236
|
+
mergeSelect(nestedSelect, wideSelectWithMetadata(metadata, relation.target, payload));
|
|
237
|
+
}
|
|
238
|
+
select[relation.field.name] = { select: nestedSelect };
|
|
239
|
+
}
|
|
240
|
+
return { select, fastPath: false };
|
|
241
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@eleven-am/golem-core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"types": "dist/index.d.ts",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsc -p tsconfig.json",
|
|
8
|
+
"test": "jest"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"graphql-parse-resolve-info": "^4.14.1",
|
|
12
|
+
"pluralize": "^8.0.0"
|
|
13
|
+
},
|
|
14
|
+
"peerDependencies": {
|
|
15
|
+
"graphql": "^16.0.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/jest": "^29.5.14",
|
|
19
|
+
"@types/node": "^22.10.0",
|
|
20
|
+
"@types/pluralize": "^0.0.33",
|
|
21
|
+
"graphql": "^16.11.0",
|
|
22
|
+
"jest": "^29.7.0",
|
|
23
|
+
"ts-jest": "^29.2.5",
|
|
24
|
+
"typescript": "^5.7.0"
|
|
25
|
+
},
|
|
26
|
+
"license": "GPL-3.0",
|
|
27
|
+
"author": "Roy OSSAI",
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=20"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist",
|
|
36
|
+
"README.md"
|
|
37
|
+
],
|
|
38
|
+
"description": "Golem core: datamodel, engine and policy kernel for schema-animated GraphQL backends",
|
|
39
|
+
"keywords": [
|
|
40
|
+
"graphql",
|
|
41
|
+
"prisma",
|
|
42
|
+
"crud",
|
|
43
|
+
"authorization",
|
|
44
|
+
"casl"
|
|
45
|
+
],
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "git+https://github.com/eleven-am/golem.git",
|
|
49
|
+
"directory": "packages/core"
|
|
50
|
+
}
|
|
51
|
+
}
|