feat: Improve vocabulary typings

This commit is contained in:
Joachim Van Herwegen 2022-08-10 14:04:51 +02:00
parent 4b39b50b0c
commit 2e1bae90c7
3 changed files with 91 additions and 56 deletions

View File

@ -22,6 +22,7 @@ The following changes are relevant for v5 custom configs that replaced certain f
### Interface changes ### Interface changes
These changes are relevant if you wrote custom modules for the server that depend on existing interfaces. These changes are relevant if you wrote custom modules for the server that depend on existing interfaces.
- `AgentGroupAccessChecker` no longer accepts any input parameters. - `AgentGroupAccessChecker` no longer accepts any input parameters.
- The functions in `Vocabularies.ts` were renamed, the typings have been made more precise and several utility types were added.
## v5.0.0 ## v5.0.0
### New features ### New features

View File

@ -1,11 +1,11 @@
import { createUriAndTermNamespace } from '../../../util/Vocabularies'; import { createVocabulary } from '../../../util/Vocabularies';
export const TEMPLATE = createUriAndTermNamespace('urn:solid-server:template:', export const TEMPLATE = createVocabulary('urn:solid-server:template:',
'ResourceStore'); 'ResourceStore');
// Variables used for configuration templates // Variables used for configuration templates
// This is not an exclusive list // This is not an exclusive list
export const TEMPLATE_VARIABLE = createUriAndTermNamespace(`${TEMPLATE.namespace}variable:`, export const TEMPLATE_VARIABLE = createVocabulary(`${TEMPLATE.namespace}variable:`,
'baseUrl', 'baseUrl',
'rootFilePath', 'rootFilePath',
'sparqlEndpoint', 'sparqlEndpoint',

View File

@ -2,46 +2,77 @@
import { DataFactory } from 'n3'; import { DataFactory } from 'n3';
import type { NamedNode } from 'rdf-js'; import type { NamedNode } from 'rdf-js';
type RecordOf<TKey extends any[], TValue> = Record<TKey[number], TValue>; /**
* A `Record` in which each value is a concatenation of the baseUrl and its key.
*/
type ExpandedRecord<TBase extends string, TLocal extends string> = {[K in TLocal]: `${TBase}${K}` };
export type Namespace<TKey extends any[], TValue> = /**
{ namespace: TValue } & RecordOf<TKey, TValue>; * Has a base URL as `namespace` value and each key has as value the concatenation with that base URL.
*/
type ValueVocabulary<TBase extends string, TLocal extends string> =
{ namespace: TBase } & ExpandedRecord<TBase, TLocal>;
/**
* A {@link ValueVocabulary} where the URI values are {@link NamedNode}s.
*/
type TermVocabulary<T> = T extends ValueVocabulary<any, any> ? {[K in keyof T]: NamedNode<T[K]> } : never;
/**
* Contains a namespace and keys linking to the entries in this namespace.
* The `terms` field contains the same values but as {@link NamedNode} instead of string.
*/
export type Vocabulary<TBase extends string, TKey extends string> =
ValueVocabulary<TBase, TKey> & { terms: TermVocabulary<ValueVocabulary<TBase, TKey>> };
/**
* A {@link Vocabulary} where all the non-namespace fields are of unknown value.
* This is a fallback in case {@link createVocabulary} gets called with a non-strict string array.
*/
export type PartialVocabulary<TBase extends string> =
{ namespace: TBase } &
Partial<Record<string, string>> &
{ terms: { namespace: NamedNode<TBase> } & Partial<Record<string, NamedNode>> };
/**
* A local name of a {@link Vocabulary}.
*/
export type VocabularyLocal<T> = T extends Vocabulary<any, infer TKey> ? TKey : never;
/**
* A URI string entry of a {@link Vocabulary}.
*/
export type VocabularyValue<T> = T extends Vocabulary<any, infer TKey> ? T[TKey] : never;
/**
* A {@link NamedNode} entry of a {@link Vocabulary}.
*/
export type VocabularyTerm<T> = T extends Vocabulary<any, infer TKey> ? T['terms'][TKey] : never;
/** /**
* Creates a function that expands local names from the given base URI, * Creates a function that expands local names from the given base URI,
* and exports the given local names as properties on the returned object. * and exports the given local names as properties on the returned object.
*/ */
export function createNamespace<TKey extends string, TValue>( function createValueVocabulary<TBase extends string, TLocal extends string>(baseUri: TBase, localNames: TLocal[]):
baseUri: string, ValueVocabulary<TBase, TLocal> {
toValue: (expanded: string) => TValue, const expanded: Partial<ExpandedRecord<TBase, TLocal>> = { };
...localNames: TKey[]):
Namespace<typeof localNames, TValue> {
const expanded: Namespace<typeof localNames, TValue> = {} as any;
// Expose the main namespace
expanded.namespace = toValue(baseUri);
// Expose the listed local names as properties // Expose the listed local names as properties
for (const localName of localNames) { for (const localName of localNames) {
(expanded as RecordOf<TKey[], TValue>)[localName] = toValue(`${baseUri}${localName}`); expanded[localName] = `${baseUri}${localName}`;
} }
return expanded; return {
} namespace: baseUri,
...expanded as ExpandedRecord<TBase, TLocal>,
/** };
* Creates a function that expands local names from the given base URI into strings,
* and exports the given local names as properties on the returned object.
*/
export function createUriNamespace<T extends string>(baseUri: string, ...localNames: T[]):
Namespace<typeof localNames, string> {
return createNamespace(baseUri, (expanded): string => expanded, ...localNames);
} }
/** /**
* Creates a function that expands local names from the given base URI into named nodes, * Creates a function that expands local names from the given base URI into named nodes,
* and exports the given local names as properties on the returned object. * and exports the given local names as properties on the returned object.
*/ */
export function createTermNamespace<T extends string>(baseUri: string, ...localNames: T[]): function createTermVocabulary<TBase extends string, TLocal extends string>(namespace: ValueVocabulary<TBase, TLocal>):
Namespace<typeof localNames, NamedNode> { TermVocabulary<ValueVocabulary<TBase, TLocal>> {
return createNamespace(baseUri, DataFactory.namedNode, ...localNames); // Need to cast since `fromEntries` typings aren't strict enough
return Object.fromEntries(
Object.entries(namespace).map(([ key, value ]): [string, NamedNode] => [ key, DataFactory.namedNode(value) ]),
) as TermVocabulary<ValueVocabulary<TBase, TLocal>>;
} }
/** /**
@ -49,13 +80,16 @@ Namespace<typeof localNames, NamedNode> {
* and exports the given local names as properties on the returned object. * and exports the given local names as properties on the returned object.
* Under the `terms` property, it exposes the expanded local names as named nodes. * Under the `terms` property, it exposes the expanded local names as named nodes.
*/ */
export function createUriAndTermNamespace<T extends string>(baseUri: string, ...localNames: T[]): export function createVocabulary<TBase extends string, TLocal extends string>(baseUri: TBase,
Namespace<typeof localNames, string> & { terms: Namespace<typeof localNames, NamedNode> } { ...localNames: TLocal[]): string extends TLocal ? PartialVocabulary<TBase> : Vocabulary<TBase, TLocal> {
return Object.assign(createUriNamespace(baseUri, ...localNames), const namespace = createValueVocabulary(baseUri, localNames);
{ terms: createTermNamespace(baseUri, ...localNames) }); return {
...namespace,
terms: createTermVocabulary(namespace),
};
} }
export const ACL = createUriAndTermNamespace('http://www.w3.org/ns/auth/acl#', export const ACL = createVocabulary('http://www.w3.org/ns/auth/acl#',
'accessTo', 'accessTo',
'agent', 'agent',
'agentClass', 'agentClass',
@ -71,42 +105,42 @@ export const ACL = createUriAndTermNamespace('http://www.w3.org/ns/auth/acl#',
'Control', 'Control',
); );
export const AS = createUriAndTermNamespace('https://www.w3.org/ns/activitystreams#', export const AS = createVocabulary('https://www.w3.org/ns/activitystreams#',
'Create', 'Create',
'Delete', 'Delete',
'Update', 'Update',
); );
export const AUTH = createUriAndTermNamespace('urn:solid:auth:', export const AUTH = createVocabulary('urn:solid:auth:',
'userMode', 'userMode',
'publicMode', 'publicMode',
); );
export const DC = createUriAndTermNamespace('http://purl.org/dc/terms/', export const DC = createVocabulary('http://purl.org/dc/terms/',
'description', 'description',
'modified', 'modified',
'title', 'title',
); );
export const FOAF = createUriAndTermNamespace('http://xmlns.com/foaf/0.1/', export const FOAF = createVocabulary('http://xmlns.com/foaf/0.1/',
'Agent', 'Agent',
); );
export const HH = createUriAndTermNamespace('http://www.w3.org/2011/http-headers#', export const HH = createVocabulary('http://www.w3.org/2011/http-headers#',
'content-length', 'content-length',
); );
export const HTTP = createUriAndTermNamespace('http://www.w3.org/2011/http#', export const HTTP = createVocabulary('http://www.w3.org/2011/http#',
'statusCodeNumber', 'statusCodeNumber',
); );
export const IANA = createUriAndTermNamespace('http://www.w3.org/ns/iana/media-types/'); export const IANA = createVocabulary('http://www.w3.org/ns/iana/media-types/');
export const JSON_LD = createUriAndTermNamespace('http://www.w3.org/ns/json-ld#', export const JSON_LD = createVocabulary('http://www.w3.org/ns/json-ld#',
'context', 'context',
); );
export const LDP = createUriAndTermNamespace('http://www.w3.org/ns/ldp#', export const LDP = createVocabulary('http://www.w3.org/ns/ldp#',
'contains', 'contains',
'BasicContainer', 'BasicContainer',
@ -114,32 +148,32 @@ export const LDP = createUriAndTermNamespace('http://www.w3.org/ns/ldp#',
'Resource', 'Resource',
); );
export const MA = createUriAndTermNamespace('http://www.w3.org/ns/ma-ont#', export const MA = createVocabulary('http://www.w3.org/ns/ma-ont#',
'format', 'format',
); );
export const OIDC = createUriAndTermNamespace('http://www.w3.org/ns/solid/oidc#', export const OIDC = createVocabulary('http://www.w3.org/ns/solid/oidc#',
'redirect_uris', 'redirect_uris',
); );
export const PIM = createUriAndTermNamespace('http://www.w3.org/ns/pim/space#', export const PIM = createVocabulary('http://www.w3.org/ns/pim/space#',
'Storage', 'Storage',
); );
export const POSIX = createUriAndTermNamespace('http://www.w3.org/ns/posix/stat#', export const POSIX = createVocabulary('http://www.w3.org/ns/posix/stat#',
'mtime', 'mtime',
'size', 'size',
); );
export const RDF = createUriAndTermNamespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#', export const RDF = createVocabulary('http://www.w3.org/1999/02/22-rdf-syntax-ns#',
'type', 'type',
); );
export const RDFS = createUriAndTermNamespace('http://www.w3.org/2000/01/rdf-schema#', export const RDFS = createVocabulary('http://www.w3.org/2000/01/rdf-schema#',
'label', 'label',
); );
export const SOLID = createUriAndTermNamespace('http://www.w3.org/ns/solid/terms#', export const SOLID = createVocabulary('http://www.w3.org/ns/solid/terms#',
'deletes', 'deletes',
'inserts', 'inserts',
'oidcIssuer', 'oidcIssuer',
@ -150,22 +184,22 @@ export const SOLID = createUriAndTermNamespace('http://www.w3.org/ns/solid/terms
'InsertDeletePatch', 'InsertDeletePatch',
); );
export const SOLID_AS = createUriAndTermNamespace('http://www.w3.org/ns/solid/activitystreams#', export const SOLID_AS = createVocabulary('http://www.w3.org/ns/solid/activitystreams#',
'Activity', 'Activity',
); );
export const SOLID_ERROR = createUriAndTermNamespace('urn:npm:solid:community-server:error:', export const SOLID_ERROR = createVocabulary('urn:npm:solid:community-server:error:',
'disallowedMethod', 'disallowedMethod',
'errorResponse', 'errorResponse',
'stack', 'stack',
); );
export const SOLID_HTTP = createUriAndTermNamespace('urn:npm:solid:community-server:http:', export const SOLID_HTTP = createVocabulary('urn:npm:solid:community-server:http:',
'location', 'location',
'slug', 'slug',
); );
export const SOLID_META = createUriAndTermNamespace('urn:npm:solid:community-server:meta:', export const SOLID_META = createVocabulary('urn:npm:solid:community-server:meta:',
// This identifier is used as graph for all metadata that is generated on the fly and should not be stored // This identifier is used as graph for all metadata that is generated on the fly and should not be stored
'ResponseMetadata', 'ResponseMetadata',
// This is used to identify templates that can be used for the representation of a resource // This is used to identify templates that can be used for the representation of a resource
@ -177,15 +211,15 @@ export const SOLID_META = createUriAndTermNamespace('urn:npm:solid:community-ser
'preserve', 'preserve',
); );
export const VANN = createUriAndTermNamespace('http://purl.org/vocab/vann/', export const VANN = createVocabulary('http://purl.org/vocab/vann/',
'preferredNamespacePrefix', 'preferredNamespacePrefix',
); );
export const VCARD = createUriAndTermNamespace('http://www.w3.org/2006/vcard/ns#', export const VCARD = createVocabulary('http://www.w3.org/2006/vcard/ns#',
'hasMember', 'hasMember',
); );
export const XSD = createUriAndTermNamespace('http://www.w3.org/2001/XMLSchema#', export const XSD = createVocabulary('http://www.w3.org/2001/XMLSchema#',
'dateTime', 'dateTime',
'integer', 'integer',
); );