@kubun/graphql 0.12.0 → 0.13.1
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/identifiers.d.ts +1 -0
- package/lib/identifiers.js +23 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/schema.d.ts +1 -0
- package/lib/schema.js +124 -15
- package/package.json +16 -12
package/lib/identifiers.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { GraphQLScalarType } from 'graphql';
|
|
2
2
|
export declare const GraphQLAttachmentID: GraphQLScalarType<string, string>;
|
|
3
|
+
export declare const GraphQLCredentialEntryID: GraphQLScalarType<string, string>;
|
|
3
4
|
export declare const GraphQLDocID: GraphQLScalarType<string, string>;
|
package/lib/identifiers.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AttachmentID, DocumentID } from '@kubun/id';
|
|
1
|
+
import { AttachmentID, CredentialEntryID, DocumentID } from '@kubun/id';
|
|
2
2
|
import { GraphQLError, GraphQLScalarType, Kind } from 'graphql';
|
|
3
3
|
function validateAttachmentID(input) {
|
|
4
4
|
if (typeof input !== 'string') {
|
|
@@ -20,6 +20,28 @@ export const GraphQLAttachmentID = new GraphQLScalarType({
|
|
|
20
20
|
return validateAttachmentID(ast.value);
|
|
21
21
|
}
|
|
22
22
|
});
|
|
23
|
+
function validateCredentialEntryID(input) {
|
|
24
|
+
if (typeof input !== 'string') {
|
|
25
|
+
throw new GraphQLError(`Invalid CredentialEntryID input: ${input}`);
|
|
26
|
+
}
|
|
27
|
+
// The whole check. Every Kubun id matches the scalar's pattern, so a document
|
|
28
|
+
// id or a credential KEY id would otherwise reach a resolver that expects an
|
|
29
|
+
// entry and miss with a message about absence rather than about type.
|
|
30
|
+
CredentialEntryID.fromString(input);
|
|
31
|
+
return input;
|
|
32
|
+
}
|
|
33
|
+
export const GraphQLCredentialEntryID = new GraphQLScalarType({
|
|
34
|
+
name: 'CredentialEntryID',
|
|
35
|
+
description: 'A Kubun Credential Entry identifier',
|
|
36
|
+
serialize: validateCredentialEntryID,
|
|
37
|
+
parseValue: validateCredentialEntryID,
|
|
38
|
+
parseLiteral (ast) {
|
|
39
|
+
if (ast.kind !== Kind.STRING) {
|
|
40
|
+
throw new GraphQLError(`Can only validate strings as CredentialEntryID but got a: ${ast.kind}`);
|
|
41
|
+
}
|
|
42
|
+
return validateCredentialEntryID(ast.value);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
23
45
|
function validateDocID(input) {
|
|
24
46
|
if (typeof input !== 'string') {
|
|
25
47
|
throw new GraphQLError(`Invalid DocumentID input: ${input}`);
|
package/lib/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type { AccessWarning, AggregateScope, ComplexityStats, Context, CreateMutationParams, DocumentEventEmitter, DocumentSavedEvent, MutationContext, PluginContextMap, QueryContext, ReadContextDB, ReadContextParams, ReadOnlyContext, RemoveMutationParams, ScopedConnection, SetDocumentAccessOverrideParams, SetModelAccessDefaultsParams, SetMutationParams, SubscriptionContext, UpdateMutationParams, } from './context.js';
|
|
2
2
|
export { createReadContext } from './context.js';
|
|
3
|
-
export { GraphQLAttachmentID, GraphQLDocID } from './identifiers.js';
|
|
3
|
+
export { GraphQLAttachmentID, GraphQLCredentialEntryID, GraphQLDocID, } from './identifiers.js';
|
|
4
4
|
export type { ExtensionResolvers, SchemaBuilderParams, SharedDefinitions, } from './schema.js';
|
|
5
5
|
export { createSchema, SchemaBuilder } from './schema.js';
|
|
6
6
|
export type * from './types.js';
|
package/lib/index.js
CHANGED
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
// list silently breaks resolver typing (`TS2339: Property '<name>' does not
|
|
5
5
|
// exist on type 'Context'`).
|
|
6
6
|
export { createReadContext } from './context.js';
|
|
7
|
-
export { GraphQLAttachmentID, GraphQLDocID } from './identifiers.js';
|
|
7
|
+
export { GraphQLAttachmentID, GraphQLCredentialEntryID, GraphQLDocID } from './identifiers.js';
|
|
8
8
|
export { createSchema, SchemaBuilder } from './schema.js';
|
package/lib/schema.d.ts
CHANGED
|
@@ -194,6 +194,7 @@ export declare class SchemaBuilder {
|
|
|
194
194
|
_buildArray(schema: ArrayModel, parentName?: string): GraphQLList<GraphQLNonNull<GraphQLOutputType>>;
|
|
195
195
|
_buildObject(id: string, schema: ObjectModel, parentName?: string): GraphQLObjectType;
|
|
196
196
|
_buildObjectFields(id: string, schema: DocumentModelSchema | ObjectModel, parentName?: string): GraphQLFieldConfigMap<Record<string, unknown>, Context>;
|
|
197
|
+
_buildNestedAttachmentField(key: string, fieldRef: string): GraphQLFieldConfig<Record<string, unknown>, Context> | null;
|
|
197
198
|
_buildRelationFields(fields: GraphQLFieldConfigMap<DocumentNode, Context>, fieldName: string, relation: RelationField): void;
|
|
198
199
|
_buildRelationSubscriptions(fields: GraphQLFieldConfigMap<unknown, Context>, modelID: string, fieldName: string, relation: RelationField): void;
|
|
199
200
|
_buildString(schema: StringModel, parentName: string): GraphQLEnumType | GraphQLObjectType | GraphQLScalarType;
|
package/lib/schema.js
CHANGED
|
@@ -4,7 +4,7 @@ import { pascalCase } from 'change-case';
|
|
|
4
4
|
import { extendSchema, GraphQLBoolean, GraphQLEnumType, GraphQLFloat, GraphQLID, GraphQLInputObjectType, GraphQLInt, GraphQLInterfaceType, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLSchema, GraphQLString, isEnumType, isListType, isNonNullType, isObjectType, isScalarType, Kind, parse as parseGraphQL } from 'graphql';
|
|
5
5
|
import { connectionArgs, connectionDefinitions, mutationWithClientMutationId, nodeDefinitions } from 'graphql-relay';
|
|
6
6
|
import { GraphQLDateTimeISO, GraphQLDID, GraphQLDuration, GraphQLEmailAddress, GraphQLJSON, GraphQLJSONObject } from 'graphql-scalars';
|
|
7
|
-
import { GraphQLAttachmentID, GraphQLDocID } from './identifiers.js';
|
|
7
|
+
import { GraphQLAttachmentID, GraphQLCredentialEntryID, GraphQLDocID } from './identifiers.js';
|
|
8
8
|
import { GraphQLURL } from './scalars.js';
|
|
9
9
|
import { getUniqueDocValue, validateAccessIdentifiers } from './utils.js';
|
|
10
10
|
/**
|
|
@@ -44,9 +44,13 @@ const STRING_FORMATS = {
|
|
|
44
44
|
};
|
|
45
45
|
const STRING_TITLES = {
|
|
46
46
|
attachmentid: GraphQLAttachmentID,
|
|
47
|
+
credentialentryid: GraphQLCredentialEntryID,
|
|
47
48
|
did: GraphQLDID,
|
|
48
49
|
docid: GraphQLDocID
|
|
49
50
|
};
|
|
51
|
+
function isAttachmentIDString(schema) {
|
|
52
|
+
return schema.type === 'string' && schema.title?.toLowerCase() === 'attachmentid';
|
|
53
|
+
}
|
|
50
54
|
function getReferenceID(path) {
|
|
51
55
|
return path.slice(REFERENCE_PREFIX.length);
|
|
52
56
|
}
|
|
@@ -105,7 +109,7 @@ function createEnumValueFilterInput(type) {
|
|
|
105
109
|
}
|
|
106
110
|
});
|
|
107
111
|
}
|
|
108
|
-
function createScalarValueFilterInput(type) {
|
|
112
|
+
function createScalarValueFilterInput(type, options = {}) {
|
|
109
113
|
return new GraphQLInputObjectType({
|
|
110
114
|
name: `${type.name}ValueFilter`,
|
|
111
115
|
isOneOf: true,
|
|
@@ -136,6 +140,37 @@ function createScalarValueFilterInput(type) {
|
|
|
136
140
|
},
|
|
137
141
|
greaterThanOrEqualTo: {
|
|
138
142
|
type
|
|
143
|
+
},
|
|
144
|
+
// Substring match only makes sense against string-backed scalars.
|
|
145
|
+
...options.stringBacked ? {
|
|
146
|
+
contains: {
|
|
147
|
+
type: GraphQLString
|
|
148
|
+
}
|
|
149
|
+
} : {}
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
// Opaque identifiers (DID, DocumentID, AttachmentID): equality only, no ordering
|
|
154
|
+
// or substring match — the underlying strings are content-addressed, not text.
|
|
155
|
+
function createExactValueFilterInput(type) {
|
|
156
|
+
return new GraphQLInputObjectType({
|
|
157
|
+
name: `${type.name}ValueFilter`,
|
|
158
|
+
isOneOf: true,
|
|
159
|
+
fields: {
|
|
160
|
+
isNull: {
|
|
161
|
+
type: GraphQLBoolean
|
|
162
|
+
},
|
|
163
|
+
equalTo: {
|
|
164
|
+
type
|
|
165
|
+
},
|
|
166
|
+
notEqualTo: {
|
|
167
|
+
type
|
|
168
|
+
},
|
|
169
|
+
in: {
|
|
170
|
+
type: toList(type)
|
|
171
|
+
},
|
|
172
|
+
notIn: {
|
|
173
|
+
type: toList(type)
|
|
139
174
|
}
|
|
140
175
|
}
|
|
141
176
|
});
|
|
@@ -159,6 +194,7 @@ const VALUE_FILTER_INPUTS = {
|
|
|
159
194
|
}
|
|
160
195
|
}
|
|
161
196
|
}),
|
|
197
|
+
AttachmentIDValueFilter: createExactValueFilterInput(GraphQLAttachmentID),
|
|
162
198
|
BooleanValueFilter: new GraphQLInputObjectType({
|
|
163
199
|
name: 'BooleanValueFilter',
|
|
164
200
|
isOneOf: true,
|
|
@@ -171,16 +207,26 @@ const VALUE_FILTER_INPUTS = {
|
|
|
171
207
|
}
|
|
172
208
|
}
|
|
173
209
|
}),
|
|
210
|
+
DIDValueFilter: createExactValueFilterInput(GraphQLDID),
|
|
211
|
+
DocumentIDValueFilter: createExactValueFilterInput(GraphQLDocID),
|
|
174
212
|
FloatValueFilter: createScalarValueFilterInput(GraphQLFloat),
|
|
175
213
|
IntValueFilter: createScalarValueFilterInput(GraphQLInt),
|
|
176
|
-
StringValueFilter: createScalarValueFilterInput(GraphQLString
|
|
214
|
+
StringValueFilter: createScalarValueFilterInput(GraphQLString, {
|
|
215
|
+
stringBacked: true
|
|
216
|
+
})
|
|
177
217
|
};
|
|
218
|
+
// Keyed by `scalar.name` (the GraphQL type name, e.g. "DID"), not the import
|
|
219
|
+
// binding — keying by binding name silently never matched here before, since
|
|
220
|
+
// scalars are looked up by `scalar.name`. `_owner` bypasses this map entirely
|
|
221
|
+
// (hardcoded to AccountValueFilter), so it is not listed here.
|
|
178
222
|
const VALUE_FILTER_INPUT_TYPES = {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
223
|
+
AttachmentID: 'AttachmentIDValueFilter',
|
|
224
|
+
Boolean: 'BooleanValueFilter',
|
|
225
|
+
DID: 'DIDValueFilter',
|
|
226
|
+
DocumentID: 'DocumentIDValueFilter',
|
|
227
|
+
Float: 'FloatValueFilter',
|
|
228
|
+
Int: 'IntValueFilter',
|
|
229
|
+
String: 'StringValueFilter'
|
|
184
230
|
};
|
|
185
231
|
const PatchSetOperation = new GraphQLInputObjectType({
|
|
186
232
|
name: 'PatchSetOperation',
|
|
@@ -247,6 +293,26 @@ const OrderByDirection = new GraphQLEnumType({
|
|
|
247
293
|
}
|
|
248
294
|
}
|
|
249
295
|
});
|
|
296
|
+
const ArrayPresence = new GraphQLEnumType({
|
|
297
|
+
name: 'ArrayPresence',
|
|
298
|
+
values: {
|
|
299
|
+
NULL: {
|
|
300
|
+
value: 'null'
|
|
301
|
+
},
|
|
302
|
+
NON_NULL: {
|
|
303
|
+
value: 'nonNull'
|
|
304
|
+
},
|
|
305
|
+
EMPTY: {
|
|
306
|
+
value: 'empty'
|
|
307
|
+
},
|
|
308
|
+
NON_EMPTY: {
|
|
309
|
+
value: 'nonEmpty'
|
|
310
|
+
},
|
|
311
|
+
NULL_OR_EMPTY: {
|
|
312
|
+
value: 'nullOrEmpty'
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
});
|
|
250
316
|
const AccessRule = new GraphQLObjectType({
|
|
251
317
|
name: 'AccessRule',
|
|
252
318
|
fields: {
|
|
@@ -2161,16 +2227,34 @@ export class SchemaBuilder {
|
|
|
2161
2227
|
if (schema.type === 'array') {
|
|
2162
2228
|
const name = this._aliases[id] ?? pascalCase(parentName + schema.title);
|
|
2163
2229
|
refParentName = name;
|
|
2164
|
-
|
|
2230
|
+
const itemID = getReferenceID(schema.items.$ref);
|
|
2231
|
+
const itemSchema = this._getReferenceSchema(itemID);
|
|
2232
|
+
// Membership ops need a scalar item type; array-of-object/array-of-array items
|
|
2233
|
+
// fall back to `isNull`-only filtering (no `_buildScalar` support for them).
|
|
2234
|
+
if (itemSchema.type === 'object' || itemSchema.type === 'array') {
|
|
2235
|
+
return new GraphQLInputObjectType({
|
|
2236
|
+
name: `${name}Filter`,
|
|
2237
|
+
isOneOf: true,
|
|
2238
|
+
fields: {
|
|
2239
|
+
isNull: {
|
|
2240
|
+
type: GraphQLBoolean
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
});
|
|
2244
|
+
}
|
|
2245
|
+
const itemScalar = this._buildScalar(itemID, itemSchema, name);
|
|
2165
2246
|
return new GraphQLInputObjectType({
|
|
2166
2247
|
name: `${name}Filter`,
|
|
2167
2248
|
isOneOf: true,
|
|
2168
2249
|
fields: {
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
type:
|
|
2250
|
+
includesAll: {
|
|
2251
|
+
type: toList(itemScalar)
|
|
2252
|
+
},
|
|
2253
|
+
includesAny: {
|
|
2254
|
+
type: toList(itemScalar)
|
|
2255
|
+
},
|
|
2256
|
+
presence: {
|
|
2257
|
+
type: ArrayPresence
|
|
2174
2258
|
}
|
|
2175
2259
|
}
|
|
2176
2260
|
});
|
|
@@ -2180,7 +2264,9 @@ export class SchemaBuilder {
|
|
|
2180
2264
|
if (knownFilter != null) {
|
|
2181
2265
|
return this._inputObjects[knownFilter];
|
|
2182
2266
|
}
|
|
2183
|
-
const filter = isEnumType(scalar) ? createEnumValueFilterInput(scalar) : createScalarValueFilterInput(scalar
|
|
2267
|
+
const filter = isEnumType(scalar) ? createEnumValueFilterInput(scalar) : createScalarValueFilterInput(scalar, {
|
|
2268
|
+
stringBacked: true
|
|
2269
|
+
});
|
|
2184
2270
|
if (this._inputObjects[filter.name] == null) {
|
|
2185
2271
|
this._inputObjects[filter.name] = filter;
|
|
2186
2272
|
}
|
|
@@ -2357,6 +2443,11 @@ export class SchemaBuilder {
|
|
|
2357
2443
|
if (fieldRef == null) {
|
|
2358
2444
|
throw new Error(`Missing ref for field ${key} of object ${id}`);
|
|
2359
2445
|
}
|
|
2446
|
+
const attachmentField = this._buildNestedAttachmentField(key, fieldRef);
|
|
2447
|
+
if (attachmentField != null) {
|
|
2448
|
+
fields[key] = attachmentField;
|
|
2449
|
+
continue;
|
|
2450
|
+
}
|
|
2360
2451
|
const type = this._getReference(fieldRef, parentName);
|
|
2361
2452
|
fields[key] = {
|
|
2362
2453
|
type: required.includes(key) ? new GraphQLNonNull(type) : type
|
|
@@ -2364,6 +2455,24 @@ export class SchemaBuilder {
|
|
|
2364
2455
|
}
|
|
2365
2456
|
return fields;
|
|
2366
2457
|
}
|
|
2458
|
+
// An attachmentid leaf nested inside an object is exposed as the AttachmentNode
|
|
2459
|
+
// object rather than a bare AttachmentID scalar, so state/mimeType/contentLength
|
|
2460
|
+
// resolve — mirroring the top-level attachment convenience fields. Always
|
|
2461
|
+
// nullable (like the top-level fields): the value may be absent, e.g. an
|
|
2462
|
+
// attachment not yet imported. Returns null for non-attachment fields.
|
|
2463
|
+
_buildNestedAttachmentField(key, fieldRef) {
|
|
2464
|
+
const refSchema = this._getReferenceSchema(getReferenceID(fieldRef));
|
|
2465
|
+
if (!isAttachmentIDString(refSchema)) {
|
|
2466
|
+
return null;
|
|
2467
|
+
}
|
|
2468
|
+
return {
|
|
2469
|
+
type: this._getDefinitions().attachmentObject,
|
|
2470
|
+
resolve: (source)=>{
|
|
2471
|
+
const value = source[key];
|
|
2472
|
+
return value ? AttachmentID.fromString(value) : null;
|
|
2473
|
+
}
|
|
2474
|
+
};
|
|
2475
|
+
}
|
|
2367
2476
|
_buildRelationFields(fields, fieldName, relation) {
|
|
2368
2477
|
if (relation.isList) {
|
|
2369
2478
|
// DB does not support matches for array containing values
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubun/graphql",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.1",
|
|
4
4
|
"keywords": [],
|
|
5
5
|
"license": "see LICENSE.md",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -22,20 +22,24 @@
|
|
|
22
22
|
"change-case": "^5.4.4",
|
|
23
23
|
"graphql": "^16.14.2",
|
|
24
24
|
"graphql-relay": "^0.10.2",
|
|
25
|
-
"graphql-scalars": "^
|
|
25
|
+
"graphql-scalars": "^2.0.0",
|
|
26
26
|
"multiformats": "^14.0.5",
|
|
27
|
-
"@kubun/
|
|
28
|
-
"@kubun/logger": "^0.
|
|
29
|
-
"@kubun/
|
|
30
|
-
"@kubun/
|
|
27
|
+
"@kubun/hlc": "^0.13.0",
|
|
28
|
+
"@kubun/logger": "^0.13.0",
|
|
29
|
+
"@kubun/id": "^0.13.0",
|
|
30
|
+
"@kubun/protocol": "^0.13.1"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@kokuin/capability": "^0.
|
|
34
|
-
"@kokuin/token": "^0.
|
|
35
|
-
"@kubun/db": "^0.
|
|
36
|
-
"@kubun/
|
|
37
|
-
"@kubun/
|
|
38
|
-
"@kubun/
|
|
33
|
+
"@kokuin/capability": "^0.3.0",
|
|
34
|
+
"@kokuin/token": "^0.5.0",
|
|
35
|
+
"@kubun/db": "^0.13.0",
|
|
36
|
+
"@kubun/db-better-sqlite": "^0.13.0",
|
|
37
|
+
"@kubun/scalars": "^0.13.0",
|
|
38
|
+
"@kubun/store-graph": "^0.13.1",
|
|
39
|
+
"@kubun/test-utils": "^0.13.0"
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
39
43
|
},
|
|
40
44
|
"scripts": {
|
|
41
45
|
"build": "pnpm run build:clean && pnpm run build:js && pnpm run build:types",
|