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
These changes are relevant if you wrote custom modules for the server that depend on existing interfaces.
- `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
### 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');
// Variables used for configuration templates
// This is not an exclusive list
export const TEMPLATE_VARIABLE = createUriAndTermNamespace(`${TEMPLATE.namespace}variable:`,
export const TEMPLATE_VARIABLE = createVocabulary(`${TEMPLATE.namespace}variable:`,
'baseUrl',
'rootFilePath',
'sparqlEndpoint',

View File

@ -2,46 +2,77 @@
import { DataFactory } from 'n3';
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,
* and exports the given local names as properties on the returned object.
*/
export function createNamespace<TKey extends string, TValue>(
baseUri: string,
toValue: (expanded: string) => TValue,
...localNames: TKey[]):
Namespace<typeof localNames, TValue> {
const expanded: Namespace<typeof localNames, TValue> = {} as any;
// Expose the main namespace
expanded.namespace = toValue(baseUri);
function createValueVocabulary<TBase extends string, TLocal extends string>(baseUri: TBase, localNames: TLocal[]):
ValueVocabulary<TBase, TLocal> {
const expanded: Partial<ExpandedRecord<TBase, TLocal>> = { };
// Expose the listed local names as properties
for (const localName of localNames) {
(expanded as RecordOf<TKey[], TValue>)[localName] = toValue(`${baseUri}${localName}`);
expanded[localName] = `${baseUri}${localName}`;
}
return expanded;
}
/**
* 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);
return {
namespace: baseUri,
...expanded as ExpandedRecord<TBase, TLocal>,
};
}
/**
* 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.
*/
export function createTermNamespace<T extends string>(baseUri: string, ...localNames: T[]):
Namespace<typeof localNames, NamedNode> {
return createNamespace(baseUri, DataFactory.namedNode, ...localNames);
function createTermVocabulary<TBase extends string, TLocal extends string>(namespace: ValueVocabulary<TBase, TLocal>):
TermVocabulary<ValueVocabulary<TBase, TLocal>> {
// 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.
* Under the `terms` property, it exposes the expanded local names as named nodes.
*/
export function createUriAndTermNamespace<T extends string>(baseUri: string, ...localNames: T[]):
Namespace<typeof localNames, string> & { terms: Namespace<typeof localNames, NamedNode> } {
return Object.assign(createUriNamespace(baseUri, ...localNames),
{ terms: createTermNamespace(baseUri, ...localNames) });
export function createVocabulary<TBase extends string, TLocal extends string>(baseUri: TBase,
...localNames: TLocal[]): string extends TLocal ? PartialVocabulary<TBase> : Vocabulary<TBase, TLocal> {
const namespace = createValueVocabulary(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',
'agent',
'agentClass',
@ -71,42 +105,42 @@ export const ACL = createUriAndTermNamespace('http://www.w3.org/ns/auth/acl#',
'Control',
);
export const AS = createUriAndTermNamespace('https://www.w3.org/ns/activitystreams#',
export const AS = createVocabulary('https://www.w3.org/ns/activitystreams#',
'Create',
'Delete',
'Update',
);
export const AUTH = createUriAndTermNamespace('urn:solid:auth:',
export const AUTH = createVocabulary('urn:solid:auth:',
'userMode',
'publicMode',
);
export const DC = createUriAndTermNamespace('http://purl.org/dc/terms/',
export const DC = createVocabulary('http://purl.org/dc/terms/',
'description',
'modified',
'title',
);
export const FOAF = createUriAndTermNamespace('http://xmlns.com/foaf/0.1/',
export const FOAF = createVocabulary('http://xmlns.com/foaf/0.1/',
'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',
);
export const HTTP = createUriAndTermNamespace('http://www.w3.org/2011/http#',
export const HTTP = createVocabulary('http://www.w3.org/2011/http#',
'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',
);
export const LDP = createUriAndTermNamespace('http://www.w3.org/ns/ldp#',
export const LDP = createVocabulary('http://www.w3.org/ns/ldp#',
'contains',
'BasicContainer',
@ -114,32 +148,32 @@ export const LDP = createUriAndTermNamespace('http://www.w3.org/ns/ldp#',
'Resource',
);
export const MA = createUriAndTermNamespace('http://www.w3.org/ns/ma-ont#',
export const MA = createVocabulary('http://www.w3.org/ns/ma-ont#',
'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',
);
export const PIM = createUriAndTermNamespace('http://www.w3.org/ns/pim/space#',
export const PIM = createVocabulary('http://www.w3.org/ns/pim/space#',
'Storage',
);
export const POSIX = createUriAndTermNamespace('http://www.w3.org/ns/posix/stat#',
export const POSIX = createVocabulary('http://www.w3.org/ns/posix/stat#',
'mtime',
'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',
);
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',
);
export const SOLID = createUriAndTermNamespace('http://www.w3.org/ns/solid/terms#',
export const SOLID = createVocabulary('http://www.w3.org/ns/solid/terms#',
'deletes',
'inserts',
'oidcIssuer',
@ -150,22 +184,22 @@ export const SOLID = createUriAndTermNamespace('http://www.w3.org/ns/solid/terms
'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',
);
export const SOLID_ERROR = createUriAndTermNamespace('urn:npm:solid:community-server:error:',
export const SOLID_ERROR = createVocabulary('urn:npm:solid:community-server:error:',
'disallowedMethod',
'errorResponse',
'stack',
);
export const SOLID_HTTP = createUriAndTermNamespace('urn:npm:solid:community-server:http:',
export const SOLID_HTTP = createVocabulary('urn:npm:solid:community-server:http:',
'location',
'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
'ResponseMetadata',
// 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',
);
export const VANN = createUriAndTermNamespace('http://purl.org/vocab/vann/',
export const VANN = createVocabulary('http://purl.org/vocab/vann/',
'preferredNamespacePrefix',
);
export const VCARD = createUriAndTermNamespace('http://www.w3.org/2006/vcard/ns#',
export const VCARD = createVocabulary('http://www.w3.org/2006/vcard/ns#',
'hasMember',
);
export const XSD = createUriAndTermNamespace('http://www.w3.org/2001/XMLSchema#',
export const XSD = createVocabulary('http://www.w3.org/2001/XMLSchema#',
'dateTime',
'integer',
);