diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index b0825556b..9befba75c 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -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 diff --git a/src/pods/generate/variables/Variables.ts b/src/pods/generate/variables/Variables.ts index 1440134bb..b55460e61 100644 --- a/src/pods/generate/variables/Variables.ts +++ b/src/pods/generate/variables/Variables.ts @@ -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', diff --git a/src/util/Vocabularies.ts b/src/util/Vocabularies.ts index 45314e23a..efc6a26ec 100644 --- a/src/util/Vocabularies.ts +++ b/src/util/Vocabularies.ts @@ -2,46 +2,77 @@ import { DataFactory } from 'n3'; import type { NamedNode } from 'rdf-js'; -type RecordOf = Record; +/** + * A `Record` in which each value is a concatenation of the baseUrl and its key. + */ +type ExpandedRecord = {[K in TLocal]: `${TBase}${K}` }; -export type Namespace = - { namespace: TValue } & RecordOf; +/** + * Has a base URL as `namespace` value and each key has as value the concatenation with that base URL. + */ +type ValueVocabulary = + { namespace: TBase } & ExpandedRecord; +/** + * A {@link ValueVocabulary} where the URI values are {@link NamedNode}s. + */ +type TermVocabulary = T extends ValueVocabulary ? {[K in keyof T]: NamedNode } : 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 = + ValueVocabulary & { terms: TermVocabulary> }; + +/** + * 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 = + { namespace: TBase } & + Partial> & + { terms: { namespace: NamedNode } & Partial> }; + +/** + * A local name of a {@link Vocabulary}. + */ +export type VocabularyLocal = T extends Vocabulary ? TKey : never; +/** + * A URI string entry of a {@link Vocabulary}. + */ +export type VocabularyValue = T extends Vocabulary ? T[TKey] : never; +/** + * A {@link NamedNode} entry of a {@link Vocabulary}. + */ +export type VocabularyTerm = T extends Vocabulary ? 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( - baseUri: string, - toValue: (expanded: string) => TValue, - ...localNames: TKey[]): - Namespace { - const expanded: Namespace = {} as any; - // Expose the main namespace - expanded.namespace = toValue(baseUri); +function createValueVocabulary(baseUri: TBase, localNames: TLocal[]): +ValueVocabulary { + const expanded: Partial> = { }; // Expose the listed local names as properties for (const localName of localNames) { - (expanded as RecordOf)[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(baseUri: string, ...localNames: T[]): -Namespace { - return createNamespace(baseUri, (expanded): string => expanded, ...localNames); + return { + namespace: baseUri, + ...expanded as ExpandedRecord, + }; } /** * 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(baseUri: string, ...localNames: T[]): -Namespace { - return createNamespace(baseUri, DataFactory.namedNode, ...localNames); +function createTermVocabulary(namespace: ValueVocabulary): +TermVocabulary> { + // 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>; } /** @@ -49,13 +80,16 @@ Namespace { * 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(baseUri: string, ...localNames: T[]): -Namespace & { terms: Namespace } { - return Object.assign(createUriNamespace(baseUri, ...localNames), - { terms: createTermNamespace(baseUri, ...localNames) }); +export function createVocabulary(baseUri: TBase, + ...localNames: TLocal[]): string extends TLocal ? PartialVocabulary : Vocabulary { + 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', );