This release is 23 versions behind 1.4.9 — the latest version of @fedify/fedify. Jump to latest
Built and signed on GitHub ActionsBuilt and signed on GitHub Actions
Built and signed on GitHub Actions
An ActivityPub/fediverse server framework
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295import { type Schema as JsonSchema, Validator } from "npm:@cfworker/json-schema@^2.0.1"; import { join } from "jsr:@std/path@^1.0.6"; import * as url from "jsr:@std/url@^0.225.1"; import { parse } from "jsr:@std/yaml@^0.224.3"; import { readDirRecursive } from "./fs.ts"; /** * The qualified URI of a type. It is used as the range of a property. */ export type TypeUri = | `https://${string}` | `http://${string}` | `fedify:${string}`; /** * The schema of a type. It is used to generate a class. */ export interface TypeSchema { /** * The type name. It is used as the name of the generated class. */ name: string; /** * The qualified URI of the type. */ uri: TypeUri; /** * The qualified URIs of the base type of the type (if any). */ extends?: TypeUri; /** * The type name used in the compacted JSON-LD document. It is used as the * value of the `type` field. */ compactName?: string; /** * Marks the type an entity type rather than a value type. Turning on this * flag will make property accessors for the type asynchronous, so that they * can load the values of the properties from the remote server. * * The extended subtypes must have the consistent value of this flag. */ entity: boolean; /** * The description of the type. It is used as the doc comment of * the generated class. */ description: string; /** * The possible properties of the type. */ properties: PropertySchema[]; /** * The default JSON-LD context of the type. It is used as the default * context of the generated `toJsonLd()` method. */ defaultContext: Context; } export interface PropertySchemaBase { /** * The singular form of the property name. It is used as the name of the * generated property accessors. */ singularName: string; /** * The qualified URI of the property. */ uri: string; /** * The property name used in the compacted JSON-LD document. It is used as * the key of the property. */ compactName?: string; /** * The qualified URI of the superproperty of the property (if any). * It means that the property is a specialization of the referenced property. */ subpropertyOf?: string; /** * The description of the property. It is used as the doc comment of * the generated property accessors. */ description: string; /** * Whether the enclosed object should have its own context when the document * is compacted. */ embedContext?: { /** * The compact name of the property that contains the context. */ compactName: string; /** * Whether the embedded context should be the same as the context of * the enclosing document. */ inherit: true; }; } export type PropertySchemaTyping = { /** * Whether the property value has `@type` field. If `true`, the `range` must * have only one element. */ untyped?: false; /** * The qualified URIs of all possible types of the property values. */ range: [TypeUri] | [TypeUri, ...TypeUri[]]; } | { /** * Whether the property value has `@type` field. If `true`, the `range` must * have only one element. */ untyped: true; /** * The qualified URIs of all possible types of the property values. */ range: [TypeUri]; }; /** * The schema of a property. It is used to generate property accessors of * a class. */ export type PropertySchema = | PropertySchemaBase & PropertySchemaTyping & { /** * Marks the property that it can have only one value. Turning on this * flag will generate only singular property accessors, so `pluralName` * and `singularAccessor` should not be specified. */ functional?: false; /** * The plural form of the property name. It is used as the name of the * generated property accessors. */ pluralName: string; /** * Whether to generate singular property accessors. Regardless of this * flag, plural property accessors are generated (unless `functional` is * turned on). */ singularAccessor?: boolean; /** * The container type of the property values. It can be unspecified. */ container?: "graph" | "list"; } | PropertySchemaBase & PropertySchemaTyping & { /** * Marks the property that it can have only one value. Turning on this * flag will generate only singular property accessors, so `pluralName` * and `singularAccessor` should not be specified. */ functional: true; /** * If it's present, those redundant properties are also filled with * the same value altogether when the object is serialized into * JSON-LD. When it's deserialized from JSON-LD, it tries to * parse the values of the specified properties in order. */ redundantProperties?: { /** * The qualified URI of the property. */ uri: string; /** * The property name used in the compacted JSON-LD document. It is used * as the key of the property. */ compactName?: string; }[]; }; /** * A JSON-LD context, which is placed in the `@context` property of a JSON-LD * document. */ export type Context = Uri | EmbeddedContext | (Uri | EmbeddedContext)[]; type Uri = "http://{string}" | "https://{string}"; type EmbeddedContext = Record<string, TermDefinition>; type TermDefinition = Uri | Record<string, Uri | "@id">; /** * An error that occurred while loading a schema file. */ export class SchemaError extends Error { /** * The path of the schema file. */ readonly path: string; /** * Constructs a new `SchemaError`. * @param path The path of the schema file. * @param message The error message. */ constructor(path: string, message?: string) { super(message); this.path = path; } } async function loadSchemaValidator(): Promise<Validator> { const thisFile = new URL(import.meta.url); const schemaFile = url.join(url.dirname(thisFile), "schema.yaml"); let content: string; if (schemaFile.protocol !== "file:") { const response = await fetch(schemaFile); content = await response.text(); } else { content = await Deno.readTextFile(schemaFile); } const schemaObject = parse(content); return new Validator(schemaObject as JsonSchema); } const schemaValidator: Validator = await loadSchemaValidator(); async function loadSchema(path: string): Promise<TypeSchema> { const content = await Deno.readTextFile(path); const schema = parse(content); const result = schemaValidator.validate(schema); const errors: SchemaError[] = []; if (result.valid) return schema as TypeSchema; for (const e of result.errors) { errors.push( new SchemaError(path, `${path}:${e.instanceLocation}: ${e.error}`), ); } throw new AggregateError(errors); } /** * Loads all schema files in the directory. * @param dir The path of the directory to load schema files from. * @returns A map from the qualified URI of a type to its {@link SchemaFile}. * @throws {@link AggregateError} if any schema file is invalid. It contains * all {@link SchemaError}s of the invalid schema files. */ export async function loadSchemaFiles( dir: string, ): Promise<Record<string, TypeSchema>> { if (typeof dir !== "string") { throw new TypeError("Expected a directory path in string"); } const result: Record<string, TypeSchema> = {}; const errors: SchemaError[] = []; for await (const relPath of readDirRecursive(dir)) { if (!relPath.match(/\.ya?ml$/i)) continue; const path = join(dir, relPath); let schema: TypeSchema; try { schema = await loadSchema(path); } catch (e) { if ( e instanceof AggregateError && e.errors.length > 0 && e.errors[0] instanceof SchemaError ) { errors.push(...e.errors); continue; } throw e; } result[schema.uri] = schema; } if (errors.length > 0) throw new AggregateError(errors); const entries = Object.entries(result); entries.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0); return Object.fromEntries(entries); }