@kubun/graphql 0.4.3 → 0.4.5
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/lib/context.d.ts +6 -0
- package/lib/context.d.ts.map +1 -1
- package/lib/context.js +111 -13
- package/lib/schema.d.ts +16 -2
- package/lib/schema.d.ts.map +1 -1
- package/lib/schema.js +239 -34
- package/package.json +6 -3
package/lib/context.d.ts
CHANGED
|
@@ -26,9 +26,11 @@ export type SubscriptionContext = {
|
|
|
26
26
|
* Query and subscription functions requiring server implementation
|
|
27
27
|
*/
|
|
28
28
|
export type ReadOnlyContext = QueryContext & SubscriptionContext;
|
|
29
|
+
export type AccessChecker = (doc: DocumentNode, permissionType: 'read' | 'write') => Promise<boolean>;
|
|
29
30
|
export type ReadContextParams = {
|
|
30
31
|
db: KubunDB;
|
|
31
32
|
viewerDID: string;
|
|
33
|
+
accessChecker?: AccessChecker;
|
|
32
34
|
};
|
|
33
35
|
export declare function createReadContext(params: ReadContextParams): ReadOnlyContext;
|
|
34
36
|
/**
|
|
@@ -39,6 +41,10 @@ export type MutationContext = {
|
|
|
39
41
|
executeSetMutation(modelID: string, unique: Uint8Array, data: DocumentData, info: GraphQLResolveInfo): Promise<DocumentNode>;
|
|
40
42
|
executeUpdateMutation(input: UpdateMutationInput, info: GraphQLResolveInfo): Promise<DocumentNode>;
|
|
41
43
|
executeRemoveMutation(id: string, info: GraphQLResolveInfo): Promise<void>;
|
|
44
|
+
executeSetModelAccessDefaults(modelId: string, permissionType: 'read' | 'write', accessLevel: string, allowedDIDs: Array<string> | null): Promise<void>;
|
|
45
|
+
executeRemoveModelAccessDefaults(modelId: string, permissionTypes: Array<'read' | 'write'>): Promise<void>;
|
|
46
|
+
executeSetDocumentAccessOverride(documentId: string, permissionType: 'read' | 'write', accessLevel: string, allowedDIDs: Array<string> | null): Promise<void>;
|
|
47
|
+
executeRemoveDocumentAccessOverride(documentId: string, permissionTypes: Array<'read' | 'write'>): Promise<void>;
|
|
42
48
|
};
|
|
43
49
|
export type Context = ReadOnlyContext & MutationContext;
|
|
44
50
|
//# sourceMappingURL=context.d.ts.map
|
package/lib/context.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAY,OAAO,EAAE,MAAM,WAAW,CAAA;AAElD,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AACjE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAA;AACjD,OAAO,KAAK,EAAE,UAAU,EAAuB,IAAI,EAAE,MAAM,eAAe,CAAA;AAE1E,OAAO,KAAK,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAE7E;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,SAAS,IAAI,MAAM,GAAG,IAAI,CAAA;IAC1B,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAA;IACtD,iBAAiB,CACf,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,EACvB,IAAI,EAAE,sBAAsB,GAC3B,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC,CAAA;IACpC,WAAW,CAAC,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAA;CACvF,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,0BAA0B,CAAC,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,cAAc,CAAC,YAAY,CAAC,CAAA;IACjF,0BAA0B,CAAC,EAAE,EAAE,MAAM,GAAG,cAAc,CAAC,YAAY,CAAC,CAAA;IACpE,0BAA0B,IAAI,cAAc,CAAC,MAAM,CAAC,CAAA;IACpD,4BAA4B,CAC1B,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,EACvB,WAAW,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,OAAO,GAC3C,cAAc,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAA;IACrC,8BAA8B,CAC5B,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,EACvB,WAAW,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,OAAO,GAC3C,cAAc,CAAC,MAAM,CAAC,CAAA;CAC1B,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,YAAY,GAAG,mBAAmB,CAAA;AAEhE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,EAAE,EAAE,OAAO,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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"}
|
package/lib/context.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defer } from '@enkaku/async';
|
|
2
2
|
import { DocumentID } from '@kubun/id';
|
|
3
3
|
export function createReadContext(params) {
|
|
4
|
-
const { db, viewerDID } = params;
|
|
4
|
+
const { db, viewerDID, accessChecker } = params;
|
|
5
5
|
function createEventGenerator(filter, transform) {
|
|
6
6
|
let isDone = false;
|
|
7
7
|
let pending = null;
|
|
@@ -63,32 +63,130 @@ export function createReadContext(params) {
|
|
|
63
63
|
return viewerDID;
|
|
64
64
|
},
|
|
65
65
|
async loadDocument (id) {
|
|
66
|
-
|
|
66
|
+
const doc = await db.getDocument(DocumentID.fromString(id));
|
|
67
|
+
if (!doc) return null;
|
|
68
|
+
// Check access if access checker is provided
|
|
69
|
+
if (accessChecker) {
|
|
70
|
+
const hasAccess = await accessChecker(doc, 'read');
|
|
71
|
+
if (!hasAccess) {
|
|
72
|
+
// Access denied - return null (error will be added by GraphQL layer)
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return doc;
|
|
67
77
|
},
|
|
68
78
|
async resolveConnection (modelIDs, args) {
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
79
|
+
// If no access checker, return all documents
|
|
80
|
+
if (accessChecker == null) {
|
|
81
|
+
const { entries, hasMore } = await db.queryDocuments({
|
|
82
|
+
...args,
|
|
83
|
+
modelIDs
|
|
84
|
+
});
|
|
85
|
+
return {
|
|
86
|
+
edges: entries.map((e)=>({
|
|
87
|
+
cursor: e.cursor,
|
|
88
|
+
node: e.document
|
|
89
|
+
})),
|
|
90
|
+
pageInfo: {
|
|
91
|
+
hasNextPage: args.first ? hasMore : false,
|
|
92
|
+
hasPreviousPage: args.first ? false : hasMore,
|
|
93
|
+
endCursor: entries.at(-1)?.cursor ?? null,
|
|
94
|
+
startCursor: entries.at(0)?.cursor ?? null
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
// With access control: post-fetch filtering with over-fetching
|
|
99
|
+
const allEntries = [];
|
|
100
|
+
let requestedCount = 0;
|
|
101
|
+
let attempts = 0;
|
|
102
|
+
const maxAttempts = 10 // Prevent infinite loops
|
|
103
|
+
;
|
|
104
|
+
if (args.first != null) {
|
|
105
|
+
requestedCount = args.first;
|
|
106
|
+
const fetchSize = requestedCount * 2 // Over-fetch to reduce round trips
|
|
107
|
+
;
|
|
108
|
+
let cursor = args.after;
|
|
109
|
+
while(allEntries.length < requestedCount && attempts < maxAttempts){
|
|
110
|
+
// Fetch unfiltered batch from DB
|
|
111
|
+
const { entries, hasMore } = await db.queryDocuments({
|
|
112
|
+
...args,
|
|
113
|
+
modelIDs,
|
|
114
|
+
first: fetchSize,
|
|
115
|
+
after: cursor
|
|
116
|
+
});
|
|
117
|
+
if (entries.length === 0 && !hasMore) break;
|
|
118
|
+
// Filter based on access rules
|
|
119
|
+
for (const entry of entries){
|
|
120
|
+
if (await accessChecker(entry.document, 'read')) {
|
|
121
|
+
allEntries.push(entry);
|
|
122
|
+
if (allEntries.length >= requestedCount) break;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (!hasMore) break;
|
|
126
|
+
cursor = entries.at(-1)?.cursor ?? null;
|
|
127
|
+
attempts++;
|
|
128
|
+
}
|
|
129
|
+
} else if (args.last != null) {
|
|
130
|
+
requestedCount = args.last;
|
|
131
|
+
const fetchSize = requestedCount * 2 // Over-fetch to reduce round trips
|
|
132
|
+
;
|
|
133
|
+
let cursor = args.before;
|
|
134
|
+
while(allEntries.length < requestedCount && attempts < maxAttempts){
|
|
135
|
+
// Fetch unfiltered batch from DB
|
|
136
|
+
const { entries, hasMore } = await db.queryDocuments({
|
|
137
|
+
...args,
|
|
138
|
+
modelIDs,
|
|
139
|
+
last: fetchSize,
|
|
140
|
+
before: cursor
|
|
141
|
+
});
|
|
142
|
+
if (entries.length === 0 && !hasMore) break;
|
|
143
|
+
// Filter based on access rules - note we reverse the entries array for backward pagination
|
|
144
|
+
for (const entry of [
|
|
145
|
+
...entries
|
|
146
|
+
].reverse()){
|
|
147
|
+
if (await accessChecker(entry.document, 'read')) {
|
|
148
|
+
allEntries.unshift(entry);
|
|
149
|
+
if (allEntries.length >= requestedCount) break;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (!hasMore) break;
|
|
153
|
+
cursor = entries.at(0)?.cursor ?? null;
|
|
154
|
+
attempts++;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Return only requested count
|
|
158
|
+
const resultEntries = allEntries.slice(0, requestedCount);
|
|
159
|
+
const hasMoreResults = allEntries.length > requestedCount;
|
|
74
160
|
return {
|
|
75
|
-
edges:
|
|
161
|
+
edges: resultEntries.map((e)=>({
|
|
76
162
|
cursor: e.cursor,
|
|
77
163
|
node: e.document
|
|
78
164
|
})),
|
|
79
165
|
pageInfo: {
|
|
80
|
-
hasNextPage: args.first ?
|
|
81
|
-
hasPreviousPage: args.first ? false :
|
|
82
|
-
endCursor:
|
|
83
|
-
startCursor:
|
|
166
|
+
hasNextPage: args.first ? hasMoreResults : false,
|
|
167
|
+
hasPreviousPage: args.first ? false : hasMoreResults,
|
|
168
|
+
endCursor: resultEntries.at(-1)?.cursor ?? null,
|
|
169
|
+
startCursor: resultEntries.at(0)?.cursor ?? null
|
|
84
170
|
}
|
|
85
171
|
};
|
|
86
172
|
},
|
|
87
173
|
async resolveList (modelIDs, docIDs) {
|
|
88
|
-
|
|
174
|
+
const documents = await db.listDocuments({
|
|
89
175
|
modelIDs,
|
|
90
176
|
docIDs
|
|
91
177
|
});
|
|
178
|
+
// If no access checker, return all documents
|
|
179
|
+
if (!accessChecker) {
|
|
180
|
+
return documents;
|
|
181
|
+
}
|
|
182
|
+
// Filter based on access control
|
|
183
|
+
const accessible = [];
|
|
184
|
+
for (const doc of documents){
|
|
185
|
+
if (await accessChecker(doc, 'read')) {
|
|
186
|
+
accessible.push(doc);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return accessible;
|
|
92
190
|
},
|
|
93
191
|
subscribeToDocumentCreated (modelIDs) {
|
|
94
192
|
return createEventGenerator((event)=>event.type === 'create' && modelIDs.includes(event.document.model), (event)=>event.document);
|
package/lib/schema.d.ts
CHANGED
|
@@ -25,22 +25,36 @@ type RelationField = {
|
|
|
25
25
|
};
|
|
26
26
|
type RelationFields = Record<string, Array<RelationField>>;
|
|
27
27
|
export type SchemaBuilderParams = {
|
|
28
|
-
aliases?: Record<string, string>;
|
|
29
28
|
record: DocumentModelsRecord;
|
|
29
|
+
aliases?: Record<string, string>;
|
|
30
|
+
onlyModels?: Array<string> | null;
|
|
31
|
+
includeInterfaceDependencies?: boolean;
|
|
32
|
+
includeRelationDependencies?: boolean;
|
|
33
|
+
includeMutations?: boolean;
|
|
34
|
+
includeSubscriptions?: boolean;
|
|
30
35
|
};
|
|
31
36
|
export declare class SchemaBuilder {
|
|
32
37
|
_aliases: Record<string, string>;
|
|
38
|
+
_aliasToModelID: Record<string, string>;
|
|
33
39
|
_definitions: SharedDefinitions | undefined;
|
|
34
40
|
_implemented: Record<string, Array<string>>;
|
|
41
|
+
_includedModelIDs: Set<string>;
|
|
42
|
+
_includeInterfaceDependencies: boolean;
|
|
43
|
+
_includeMutations: boolean;
|
|
44
|
+
_includeRelationDependencies: boolean;
|
|
45
|
+
_includeSubscriptions: boolean;
|
|
35
46
|
_inputObjects: Record<string, GraphQLInputObjectType>;
|
|
36
47
|
_mutations: GraphQLFieldConfigMap<unknown, Context>;
|
|
37
48
|
_nodeModelIDs: Array<string>;
|
|
49
|
+
_onlyModels: Array<string> | null;
|
|
38
50
|
_record: DocumentModelsRecord;
|
|
39
51
|
_references: ReferencesRecordModel;
|
|
40
52
|
_relationsTo: Record<string, RelationFields>;
|
|
41
53
|
_subscriptions: GraphQLFieldConfigMap<unknown, Context>;
|
|
42
54
|
_types: Record<string, GraphQLOutputType>;
|
|
43
55
|
constructor(params: SchemaBuilderParams);
|
|
56
|
+
_resolveModelIdentifier(identifier: string): string;
|
|
57
|
+
_resolveModelIDsToInclude(): Set<string>;
|
|
44
58
|
getModelIDs(id: string | null): Array<string>;
|
|
45
59
|
build(): GraphQLSchema;
|
|
46
60
|
_getDefinitions(): SharedDefinitions;
|
|
@@ -69,6 +83,6 @@ export declare class SchemaBuilder {
|
|
|
69
83
|
_buildEnumString(schema: StringEnumModel, parentName?: string): GraphQLEnumType;
|
|
70
84
|
_buildFormatString(schema: StringFormatModel): GraphQLScalarType<unknown, unknown>;
|
|
71
85
|
}
|
|
72
|
-
export declare function createSchema(
|
|
86
|
+
export declare function createSchema(params: SchemaBuilderParams): GraphQLSchema;
|
|
73
87
|
export {};
|
|
74
88
|
//# sourceMappingURL=schema.d.ts.map
|
package/lib/schema.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAc,eAAe,EAAY,MAAM,WAAW,CAAA;AAC/E,OAAO,KAAK,EACV,UAAU,EACV,YAAY,EAEZ,aAAa,EACb,mBAAmB,EACnB,oBAAoB,EACpB,YAAY,EACZ,WAAW,EACX,qBAAqB,EACrB,WAAW,EAEX,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,WAAW,EAEX,SAAS,EACV,MAAM,iBAAiB,CAAA;AAGxB,OAAO,EAGL,eAAe,EAEf,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAI1B,sBAAsB,EACtB,KAAK,gBAAgB,EAErB,oBAAoB,EACpB,WAAW,EACX,cAAc,EACd,iBAAiB,EACjB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,aAAa,
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
|
package/lib/schema.js
CHANGED
|
@@ -26,14 +26,17 @@ function isList(type) {
|
|
|
26
26
|
return isListType(type) || isNonNullType(type) && isListType(type.ofType);
|
|
27
27
|
}
|
|
28
28
|
function collectAllModelIDs(collected, record, interfaceID) {
|
|
29
|
-
const
|
|
30
|
-
if (
|
|
29
|
+
const implementedBy = record[interfaceID];
|
|
30
|
+
if (implementedBy == null) {
|
|
31
31
|
throw new Error(`Interface ${interfaceID} not found in record`);
|
|
32
32
|
}
|
|
33
|
-
for (const id of
|
|
34
|
-
if (
|
|
33
|
+
for (const id of implementedBy){
|
|
34
|
+
// Check if this id is itself an interface (exists in record with implementers)
|
|
35
|
+
if (record[id] != null) {
|
|
36
|
+
// The id is an interface that has implementers, resolve recursively
|
|
35
37
|
collectAllModelIDs(collected, record, id);
|
|
36
38
|
} else {
|
|
39
|
+
// The id is a concrete model
|
|
37
40
|
collected.add(id);
|
|
38
41
|
}
|
|
39
42
|
}
|
|
@@ -219,14 +222,21 @@ const OrderByDirection = new GraphQLEnumType({
|
|
|
219
222
|
export class SchemaBuilder {
|
|
220
223
|
// Mapping of model or references IDs to their GraphQL object names
|
|
221
224
|
_aliases;
|
|
225
|
+
_aliasToModelID;
|
|
222
226
|
_definitions;
|
|
223
227
|
_implemented = {};
|
|
228
|
+
_includedModelIDs;
|
|
229
|
+
_includeInterfaceDependencies;
|
|
230
|
+
_includeMutations;
|
|
231
|
+
_includeRelationDependencies;
|
|
232
|
+
_includeSubscriptions;
|
|
224
233
|
_inputObjects = {
|
|
225
234
|
...VALUE_FILTER_INPUTS,
|
|
226
235
|
PatchOperation
|
|
227
236
|
};
|
|
228
237
|
_mutations = {};
|
|
229
238
|
_nodeModelIDs;
|
|
239
|
+
_onlyModels;
|
|
230
240
|
_record;
|
|
231
241
|
_references = {};
|
|
232
242
|
_relationsTo = {};
|
|
@@ -235,24 +245,30 @@ export class SchemaBuilder {
|
|
|
235
245
|
OrderByDirection
|
|
236
246
|
};
|
|
237
247
|
constructor(params){
|
|
238
|
-
this._aliases = params.aliases ?? {};
|
|
239
248
|
this._record = params.record;
|
|
249
|
+
this._onlyModels = params.onlyModels ?? null;
|
|
250
|
+
this._includeInterfaceDependencies = params.includeInterfaceDependencies ?? true;
|
|
251
|
+
this._includeRelationDependencies = params.includeRelationDependencies ?? true;
|
|
252
|
+
this._includeMutations = params.includeMutations ?? true;
|
|
253
|
+
this._includeSubscriptions = params.includeSubscriptions ?? true;
|
|
254
|
+
// params.aliases is { aliasName: modelID }, which is what we need for _aliasToModelID
|
|
255
|
+
this._aliasToModelID = params.aliases ?? {};
|
|
256
|
+
// Build reverse lookup map (modelID -> alias) for internal use
|
|
257
|
+
this._aliases = {};
|
|
258
|
+
for (const [alias, modelID] of Object.entries(this._aliasToModelID)){
|
|
259
|
+
this._aliases[modelID] = alias;
|
|
260
|
+
}
|
|
261
|
+
// Compute which models to include
|
|
262
|
+
this._includedModelIDs = this._resolveModelIDsToInclude();
|
|
240
263
|
const referencedInterfaces = {};
|
|
241
264
|
for (const [modelIDstring, model] of Object.entries(params.record)){
|
|
242
265
|
const modelID = DocumentModelID.fromString(modelIDstring);
|
|
243
266
|
Object.assign(this._references, model.schema.$defs ?? {});
|
|
244
|
-
referencedInterfaces[modelIDstring] ??= {
|
|
245
|
-
isInterface: model.behavior === 'interface',
|
|
246
|
-
implementedBy: new Set()
|
|
247
|
-
};
|
|
248
267
|
// Extract interfaces
|
|
249
268
|
for (const iid of model.interfaces){
|
|
250
269
|
const interfaceID = getGlobalID(iid, modelID).toString();
|
|
251
|
-
referencedInterfaces[interfaceID] ??=
|
|
252
|
-
|
|
253
|
-
implementedBy: new Set()
|
|
254
|
-
};
|
|
255
|
-
referencedInterfaces[interfaceID].implementedBy.add(modelIDstring);
|
|
270
|
+
referencedInterfaces[interfaceID] ??= new Set();
|
|
271
|
+
referencedInterfaces[interfaceID].add(modelIDstring);
|
|
256
272
|
}
|
|
257
273
|
// Extract inverse relations
|
|
258
274
|
for (const [fieldName, meta] of Object.entries(model.fieldsMeta)){
|
|
@@ -274,13 +290,76 @@ export class SchemaBuilder {
|
|
|
274
290
|
for (const id of Object.keys(referencedInterfaces)){
|
|
275
291
|
const implementedBy = new Set();
|
|
276
292
|
collectAllModelIDs(implementedBy, referencedInterfaces, id);
|
|
277
|
-
|
|
293
|
+
if (implementedBy.size !== 0) {
|
|
294
|
+
this._implemented[id] = Array.from(implementedBy);
|
|
295
|
+
}
|
|
278
296
|
for (const modelID of implementedBy){
|
|
279
297
|
nodeModelIDs.add(modelID);
|
|
280
298
|
}
|
|
281
299
|
}
|
|
282
300
|
this._nodeModelIDs = Array.from(nodeModelIDs);
|
|
283
301
|
}
|
|
302
|
+
_resolveModelIdentifier(identifier) {
|
|
303
|
+
// Check if it's a model ID (exists in record)
|
|
304
|
+
if (this._record[identifier] != null) {
|
|
305
|
+
return identifier;
|
|
306
|
+
}
|
|
307
|
+
// Check if it's an alias
|
|
308
|
+
const modelID = this._aliasToModelID[identifier];
|
|
309
|
+
if (modelID != null && this._record[modelID] != null) {
|
|
310
|
+
return modelID;
|
|
311
|
+
}
|
|
312
|
+
throw new Error(`Model identifier "${identifier}" not found. Must be a valid model ID or alias.`);
|
|
313
|
+
}
|
|
314
|
+
_resolveModelIDsToInclude() {
|
|
315
|
+
// If no restriction, include all models
|
|
316
|
+
if (this._onlyModels == null || this._onlyModels.length === 0) {
|
|
317
|
+
return new Set(Object.keys(this._record));
|
|
318
|
+
}
|
|
319
|
+
// Resolve identifiers to model IDs
|
|
320
|
+
const included = new Set();
|
|
321
|
+
for (const identifier of this._onlyModels){
|
|
322
|
+
const modelID = this._resolveModelIdentifier(identifier);
|
|
323
|
+
included.add(modelID);
|
|
324
|
+
}
|
|
325
|
+
// If both dependencies are disabled, return early
|
|
326
|
+
if (!this._includeInterfaceDependencies && !this._includeRelationDependencies) {
|
|
327
|
+
return included;
|
|
328
|
+
}
|
|
329
|
+
// Recursively collect dependencies
|
|
330
|
+
const toProcess = Array.from(included);
|
|
331
|
+
const processed = new Set();
|
|
332
|
+
while(toProcess.length > 0){
|
|
333
|
+
const modelID = toProcess.pop();
|
|
334
|
+
if (processed.has(modelID)) continue;
|
|
335
|
+
processed.add(modelID);
|
|
336
|
+
const model = this._record[modelID];
|
|
337
|
+
if (model == null) continue;
|
|
338
|
+
// Add interface dependencies if enabled
|
|
339
|
+
if (this._includeInterfaceDependencies) {
|
|
340
|
+
for (const iid of model.interfaces){
|
|
341
|
+
const interfaceID = getGlobalID(iid, modelID).toString();
|
|
342
|
+
if (!included.has(interfaceID)) {
|
|
343
|
+
included.add(interfaceID);
|
|
344
|
+
toProcess.push(interfaceID);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
// Add relation dependencies if enabled
|
|
349
|
+
if (this._includeRelationDependencies) {
|
|
350
|
+
for (const meta of Object.values(model.fieldsMeta)){
|
|
351
|
+
if (meta.type === 'document' && meta.model != null && meta.model !== null) {
|
|
352
|
+
const relationID = getGlobalID(meta.model, modelID).toString();
|
|
353
|
+
if (!included.has(relationID)) {
|
|
354
|
+
included.add(relationID);
|
|
355
|
+
toProcess.push(relationID);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return included;
|
|
362
|
+
}
|
|
284
363
|
getModelIDs(id) {
|
|
285
364
|
return id === null ? this._nodeModelIDs : this._implemented[id] ?? [
|
|
286
365
|
id
|
|
@@ -293,7 +372,7 @@ export class SchemaBuilder {
|
|
|
293
372
|
nodes: definitions.nodesField,
|
|
294
373
|
...definitions.queryFields
|
|
295
374
|
};
|
|
296
|
-
for (const id of
|
|
375
|
+
for (const id of this._includedModelIDs){
|
|
297
376
|
this._buildDocument(id);
|
|
298
377
|
const name = this._aliases[id];
|
|
299
378
|
const filterArg = {
|
|
@@ -313,16 +392,95 @@ export class SchemaBuilder {
|
|
|
313
392
|
}
|
|
314
393
|
};
|
|
315
394
|
}
|
|
316
|
-
|
|
395
|
+
// Add access control mutations if mutations are enabled
|
|
396
|
+
if (this._includeMutations) {
|
|
397
|
+
this._mutations.setModelAccessDefaults = {
|
|
398
|
+
type: GraphQLBoolean,
|
|
399
|
+
args: {
|
|
400
|
+
modelId: {
|
|
401
|
+
type: new GraphQLNonNull(GraphQLID)
|
|
402
|
+
},
|
|
403
|
+
permissionType: {
|
|
404
|
+
type: new GraphQLNonNull(GraphQLString)
|
|
405
|
+
},
|
|
406
|
+
accessLevel: {
|
|
407
|
+
type: new GraphQLNonNull(GraphQLString)
|
|
408
|
+
},
|
|
409
|
+
allowedDIDs: {
|
|
410
|
+
type: toList(GraphQLString)
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
resolve: async (_, args, ctx)=>{
|
|
414
|
+
await ctx.executeSetModelAccessDefaults(args.modelId, args.permissionType, args.accessLevel, args.allowedDIDs ?? null);
|
|
415
|
+
return true;
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
this._mutations.removeModelAccessDefaults = {
|
|
419
|
+
type: GraphQLBoolean,
|
|
420
|
+
args: {
|
|
421
|
+
modelId: {
|
|
422
|
+
type: new GraphQLNonNull(GraphQLID)
|
|
423
|
+
},
|
|
424
|
+
permissionTypes: {
|
|
425
|
+
type: new GraphQLNonNull(toList(GraphQLString))
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
resolve: async (_, args, ctx)=>{
|
|
429
|
+
await ctx.executeRemoveModelAccessDefaults(args.modelId, args.permissionTypes);
|
|
430
|
+
return true;
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
this._mutations.setDocumentAccessOverride = {
|
|
434
|
+
type: GraphQLBoolean,
|
|
435
|
+
args: {
|
|
436
|
+
documentId: {
|
|
437
|
+
type: new GraphQLNonNull(GraphQLID)
|
|
438
|
+
},
|
|
439
|
+
permissionType: {
|
|
440
|
+
type: new GraphQLNonNull(GraphQLString)
|
|
441
|
+
},
|
|
442
|
+
accessLevel: {
|
|
443
|
+
type: new GraphQLNonNull(GraphQLString)
|
|
444
|
+
},
|
|
445
|
+
allowedDIDs: {
|
|
446
|
+
type: toList(GraphQLString)
|
|
447
|
+
}
|
|
448
|
+
},
|
|
449
|
+
resolve: async (_, args, ctx)=>{
|
|
450
|
+
await ctx.executeSetDocumentAccessOverride(args.documentId, args.permissionType, args.accessLevel, args.allowedDIDs ?? null);
|
|
451
|
+
return true;
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
this._mutations.removeDocumentAccessOverride = {
|
|
455
|
+
type: GraphQLBoolean,
|
|
456
|
+
args: {
|
|
457
|
+
documentId: {
|
|
458
|
+
type: new GraphQLNonNull(GraphQLID)
|
|
459
|
+
},
|
|
460
|
+
permissionTypes: {
|
|
461
|
+
type: new GraphQLNonNull(toList(GraphQLString))
|
|
462
|
+
}
|
|
463
|
+
},
|
|
464
|
+
resolve: async (_, args, ctx)=>{
|
|
465
|
+
await ctx.executeRemoveDocumentAccessOverride(args.documentId, args.permissionTypes);
|
|
466
|
+
return true;
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
const config = {
|
|
317
471
|
query: new GraphQLObjectType({
|
|
318
472
|
name: 'Query',
|
|
319
473
|
fields: queryFields
|
|
320
|
-
})
|
|
321
|
-
|
|
474
|
+
})
|
|
475
|
+
};
|
|
476
|
+
if (this._includeMutations) {
|
|
477
|
+
config.mutation = new GraphQLObjectType({
|
|
322
478
|
name: 'Mutation',
|
|
323
479
|
fields: this._mutations
|
|
324
|
-
})
|
|
325
|
-
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
if (this._includeSubscriptions) {
|
|
483
|
+
config.subscription = new GraphQLObjectType({
|
|
326
484
|
name: 'Subscription',
|
|
327
485
|
fields: ()=>{
|
|
328
486
|
const fields = {
|
|
@@ -347,6 +505,7 @@ export class SchemaBuilder {
|
|
|
347
505
|
}
|
|
348
506
|
};
|
|
349
507
|
for (const [id, model] of Object.entries(this._record)){
|
|
508
|
+
if (!this._includedModelIDs.has(id)) continue;
|
|
350
509
|
const name = this._aliases[id];
|
|
351
510
|
fields[`new${name}Created`] = {
|
|
352
511
|
type: new GraphQLNonNull(this._types[id]),
|
|
@@ -365,14 +524,16 @@ export class SchemaBuilder {
|
|
|
365
524
|
}
|
|
366
525
|
for (const [fieldName, relations] of Object.entries(this._relationsTo[id] ?? {})){
|
|
367
526
|
for (const relation of relations){
|
|
527
|
+
if (!this._includedModelIDs.has(relation.model)) continue;
|
|
368
528
|
this._buildRelationSubscriptions(fields, id, fieldName, relation);
|
|
369
529
|
}
|
|
370
530
|
}
|
|
371
531
|
}
|
|
372
532
|
return fields;
|
|
373
533
|
}
|
|
374
|
-
})
|
|
375
|
-
}
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
return new GraphQLSchema(config);
|
|
376
537
|
}
|
|
377
538
|
_getDefinitions() {
|
|
378
539
|
if (this._definitions == null) {
|
|
@@ -464,6 +625,7 @@ export class SchemaBuilder {
|
|
|
464
625
|
}
|
|
465
626
|
};
|
|
466
627
|
for (const [id, model] of Object.entries(this._record)){
|
|
628
|
+
if (!this._includedModelIDs.has(id)) continue;
|
|
467
629
|
switch(model.behavior){
|
|
468
630
|
case 'interface':
|
|
469
631
|
continue;
|
|
@@ -530,6 +692,30 @@ export class SchemaBuilder {
|
|
|
530
692
|
}
|
|
531
693
|
}
|
|
532
694
|
});
|
|
695
|
+
// Create AccessRule type for read/write permissions
|
|
696
|
+
const accessRuleObject = new GraphQLObjectType({
|
|
697
|
+
name: 'AccessRule',
|
|
698
|
+
fields: {
|
|
699
|
+
level: {
|
|
700
|
+
type: new GraphQLNonNull(GraphQLString)
|
|
701
|
+
},
|
|
702
|
+
allowedDIDs: {
|
|
703
|
+
type: new GraphQLList(new GraphQLNonNull(GraphQLString))
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
});
|
|
707
|
+
// Create AccessPermissions type
|
|
708
|
+
const accessPermissionsObject = new GraphQLObjectType({
|
|
709
|
+
name: 'AccessPermissions',
|
|
710
|
+
fields: {
|
|
711
|
+
read: {
|
|
712
|
+
type: accessRuleObject
|
|
713
|
+
},
|
|
714
|
+
write: {
|
|
715
|
+
type: accessRuleObject
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
});
|
|
533
719
|
const documentNodeFields = {
|
|
534
720
|
// Node interface
|
|
535
721
|
id: {
|
|
@@ -547,6 +733,11 @@ export class SchemaBuilder {
|
|
|
547
733
|
},
|
|
548
734
|
updatedAt: {
|
|
549
735
|
type: GraphQLDateTimeISO
|
|
736
|
+
},
|
|
737
|
+
// Access control
|
|
738
|
+
accessPermissions: {
|
|
739
|
+
type: accessPermissionsObject,
|
|
740
|
+
resolve: (doc)=>doc.data?.accessPermissions ?? null
|
|
550
741
|
}
|
|
551
742
|
};
|
|
552
743
|
const documentInterface = new GraphQLInterfaceType({
|
|
@@ -592,7 +783,7 @@ export class SchemaBuilder {
|
|
|
592
783
|
definitions.nodeInterface
|
|
593
784
|
].concat(model.interfaces.map((interfaceID)=>{
|
|
594
785
|
return this._types[getGlobalID(interfaceID, id).toString()];
|
|
595
|
-
}));
|
|
786
|
+
}).filter((type)=>type != null));
|
|
596
787
|
},
|
|
597
788
|
fields: ()=>this._buildDocumentFields(id, model, name)
|
|
598
789
|
};
|
|
@@ -612,7 +803,7 @@ export class SchemaBuilder {
|
|
|
612
803
|
this._buildObjectFilterInput(id, model.schema, name, true);
|
|
613
804
|
this._buildObjectOrderByInput(id, model.schema, name, true);
|
|
614
805
|
// TODO: move to dedicated method
|
|
615
|
-
if (!isInterface) {
|
|
806
|
+
if (!isInterface && this._includeMutations) {
|
|
616
807
|
if (model.behavior === 'default') {
|
|
617
808
|
this._mutations[`create${name}`] = mutationWithClientMutationId({
|
|
618
809
|
name: `Create${name}`,
|
|
@@ -690,10 +881,18 @@ export class SchemaBuilder {
|
|
|
690
881
|
type: new GraphQLNonNull(GraphQLID)
|
|
691
882
|
}
|
|
692
883
|
}),
|
|
693
|
-
outputFields: ()=>
|
|
884
|
+
outputFields: ()=>({
|
|
885
|
+
...definitions.queryFields,
|
|
886
|
+
// Return id so it can be used with Relay's @deleteEdge directive
|
|
887
|
+
id: {
|
|
888
|
+
type: new GraphQLNonNull(GraphQLID)
|
|
889
|
+
}
|
|
890
|
+
}),
|
|
694
891
|
mutateAndGetPayload: async (input, ctx, info)=>{
|
|
695
892
|
await ctx.executeRemoveMutation(input.id, info);
|
|
696
|
-
return {
|
|
893
|
+
return {
|
|
894
|
+
id: input.id
|
|
895
|
+
};
|
|
697
896
|
}
|
|
698
897
|
});
|
|
699
898
|
}
|
|
@@ -763,7 +962,14 @@ export class SchemaBuilder {
|
|
|
763
962
|
relationType = definitions.documentInterface;
|
|
764
963
|
} else {
|
|
765
964
|
relationModelID = getGlobalID(relationModel, id).toString();
|
|
766
|
-
|
|
965
|
+
// Check if the related model is included in the schema
|
|
966
|
+
if (this._includedModelIDs.has(relationModelID)) {
|
|
967
|
+
relationType = this._buildDocument(relationModelID);
|
|
968
|
+
} else {
|
|
969
|
+
// Fall back to generic DocumentNode interface
|
|
970
|
+
relationType = definitions.documentInterface;
|
|
971
|
+
relationModelID = null; // Treat as unspecified model
|
|
972
|
+
}
|
|
767
973
|
}
|
|
768
974
|
if (isList(field.type)) {
|
|
769
975
|
// TODO: add suport for filters?
|
|
@@ -791,12 +997,14 @@ export class SchemaBuilder {
|
|
|
791
997
|
const interfaceID = getGlobalID(iid, id).toString();
|
|
792
998
|
for (const [fieldName, relations] of Object.entries(this._relationsTo[interfaceID] ?? {})){
|
|
793
999
|
for (const relation of relations){
|
|
1000
|
+
if (!this._includedModelIDs.has(relation.model)) continue;
|
|
794
1001
|
this._buildRelationFields(fields, fieldName, relation);
|
|
795
1002
|
}
|
|
796
1003
|
}
|
|
797
1004
|
}
|
|
798
1005
|
for (const [fieldName, relations] of Object.entries(this._relationsTo[id] ?? {})){
|
|
799
1006
|
for (const relation of relations){
|
|
1007
|
+
if (!this._includedModelIDs.has(relation.model)) continue;
|
|
800
1008
|
this._buildRelationFields(fields, fieldName, relation);
|
|
801
1009
|
}
|
|
802
1010
|
}
|
|
@@ -825,7 +1033,7 @@ export class SchemaBuilder {
|
|
|
825
1033
|
return model.interfaces.map((interfaceID)=>{
|
|
826
1034
|
const typeID = `${getGlobalID(interfaceID, id).toString()}-data`;
|
|
827
1035
|
return this._types[typeID];
|
|
828
|
-
});
|
|
1036
|
+
}).filter((type)=>type != null);
|
|
829
1037
|
},
|
|
830
1038
|
fields
|
|
831
1039
|
};
|
|
@@ -1196,9 +1404,6 @@ export class SchemaBuilder {
|
|
|
1196
1404
|
return scalar;
|
|
1197
1405
|
}
|
|
1198
1406
|
}
|
|
1199
|
-
export function createSchema(
|
|
1200
|
-
return new SchemaBuilder(
|
|
1201
|
-
record,
|
|
1202
|
-
aliases
|
|
1203
|
-
}).build();
|
|
1407
|
+
export function createSchema(params) {
|
|
1408
|
+
return new SchemaBuilder(params).build();
|
|
1204
1409
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubun/graphql",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5",
|
|
4
4
|
"license": "see LICENSE.md",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"type": "module",
|
|
@@ -23,10 +23,13 @@
|
|
|
23
23
|
"graphql-scalars": "^1.25.0",
|
|
24
24
|
"multiformats": "^13.4.2",
|
|
25
25
|
"@kubun/id": "^0.4.0",
|
|
26
|
-
"@kubun/protocol": "^0.4.
|
|
26
|
+
"@kubun/protocol": "^0.4.1"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"@
|
|
29
|
+
"@enkaku/capability": "^0.12.1",
|
|
30
|
+
"@enkaku/token": "0.12.3",
|
|
31
|
+
"@kubun/db": "^0.4.0",
|
|
32
|
+
"@kubun/test-utils": "^0.4.0"
|
|
30
33
|
},
|
|
31
34
|
"scripts": {
|
|
32
35
|
"build:clean": "del lib",
|