mirror of
https://github.com/versia-pub/api.git
synced 2025-12-06 16:38:20 +01:00
feat(federation): ✨ Improve federation module
This commit is contained in:
parent
e0b6d57470
commit
407e57fe34
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
|
@ -1,3 +1,3 @@
|
||||||
{
|
{
|
||||||
"conventionalCommits.scopes": ["docs", "build"]
|
"conventionalCommits.scopes": ["docs", "build", "federation"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
6
build.ts
6
build.ts
|
|
@ -12,6 +12,12 @@ await Bun.build({
|
||||||
splitting: true,
|
splitting: true,
|
||||||
target: "browser",
|
target: "browser",
|
||||||
plugins: [dts()],
|
plugins: [dts()],
|
||||||
|
}).then((output) => {
|
||||||
|
if (!output.success) {
|
||||||
|
spinner.fail("Failed to build federation module");
|
||||||
|
console.error(output.logs);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
spinner.succeed("Built federation module");
|
spinner.succeed("Built federation module");
|
||||||
|
|
|
||||||
|
|
@ -6,48 +6,301 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { z } from "zod";
|
import type { z } from "zod";
|
||||||
|
import { type ValidationError, fromError } from "zod-validation-error";
|
||||||
import {
|
import {
|
||||||
Action,
|
ActionSchema,
|
||||||
ActorPublicKeyData,
|
ActorPublicKeyDataSchema,
|
||||||
ContentFormat,
|
ContentFormatSchema,
|
||||||
Dislike,
|
CustomEmojiExtension,
|
||||||
Entity,
|
DislikeSchema,
|
||||||
Extension,
|
EntitySchema,
|
||||||
Follow,
|
ExtensionSchema,
|
||||||
FollowAccept,
|
FollowAcceptSchema,
|
||||||
FollowReject,
|
FollowRejectSchema,
|
||||||
Like,
|
FollowSchema,
|
||||||
Note,
|
LikeSchema,
|
||||||
Patch,
|
NoteSchema,
|
||||||
Publication,
|
PatchSchema,
|
||||||
Report,
|
PublicationSchema,
|
||||||
ServerMetadata,
|
ReportSchema,
|
||||||
Undo,
|
ServerMetadataSchema,
|
||||||
User,
|
UndoSchema,
|
||||||
VanityExtension,
|
UserSchema,
|
||||||
Visibility,
|
VanityExtensionSchema,
|
||||||
|
VisibilitySchema,
|
||||||
} from "./schemas/base";
|
} from "./schemas/base";
|
||||||
|
|
||||||
export type InferType<T extends z.AnyZodObject> = z.infer<T>;
|
// biome-ignore lint/suspicious/noExplicitAny: Used only as a base type
|
||||||
|
type AnyZod = z.ZodType<any, any, any>;
|
||||||
|
|
||||||
export {
|
export type InferType<T extends AnyZod> = z.infer<T>;
|
||||||
Entity,
|
|
||||||
ContentFormat,
|
/**
|
||||||
Visibility,
|
* Validates entities against their respective schemas.
|
||||||
Publication,
|
* @module federation/validator
|
||||||
Note,
|
* @see module:federation/schemas/base
|
||||||
Patch,
|
* @example
|
||||||
ActorPublicKeyData,
|
* import { EntityValidator, type ValidationError } from "@lysand-org/federation";
|
||||||
VanityExtension,
|
* const validator = new EntityValidator();
|
||||||
User,
|
*
|
||||||
Action,
|
* // Will throw a special ValidationError with a human-friendly error message
|
||||||
Like,
|
* // and a machine-friendly error object if the data is invalid.
|
||||||
Undo,
|
* const note = await validator.Note({
|
||||||
Dislike,
|
* type: "Note",
|
||||||
Follow,
|
* content: "Hello, world!",
|
||||||
FollowAccept,
|
* });
|
||||||
FollowReject,
|
*
|
||||||
Extension,
|
* try {
|
||||||
Report,
|
* await validator.Note({
|
||||||
ServerMetadata,
|
* type: "Note",
|
||||||
};
|
* content: 123,
|
||||||
|
* });
|
||||||
|
* } catch (error) {
|
||||||
|
* sendUser((error as ValidationError).toString());
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // Types are also included for TypeScript users that don't use the extracted ones
|
||||||
|
* const noteObject: typeof EntityValidator.$Note = {
|
||||||
|
* type: "Note",
|
||||||
|
* // ...
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
class EntityValidator {
|
||||||
|
private async validate<T extends AnyZod>(
|
||||||
|
schema: T,
|
||||||
|
data: unknown,
|
||||||
|
): Promise<InferType<T>> {
|
||||||
|
try {
|
||||||
|
return (await schema.parseAsync(data)) as InferType<T>;
|
||||||
|
} catch (error) {
|
||||||
|
throw fromError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare static $Note: InferType<typeof NoteSchema>;
|
||||||
|
declare static $Patch: InferType<typeof PatchSchema>;
|
||||||
|
declare static $ActorPublicKeyData: InferType<
|
||||||
|
typeof ActorPublicKeyDataSchema
|
||||||
|
>;
|
||||||
|
declare static $VanityExtension: InferType<typeof VanityExtensionSchema>;
|
||||||
|
declare static $User: InferType<typeof UserSchema>;
|
||||||
|
declare static $Action: InferType<typeof ActionSchema>;
|
||||||
|
declare static $Like: InferType<typeof LikeSchema>;
|
||||||
|
declare static $Undo: InferType<typeof UndoSchema>;
|
||||||
|
declare static $Dislike: InferType<typeof DislikeSchema>;
|
||||||
|
declare static $Follow: InferType<typeof FollowSchema>;
|
||||||
|
declare static $FollowAccept: InferType<typeof FollowAcceptSchema>;
|
||||||
|
declare static $FollowReject: InferType<typeof FollowRejectSchema>;
|
||||||
|
declare static $Extension: InferType<typeof ExtensionSchema>;
|
||||||
|
declare static $Report: InferType<typeof ReportSchema>;
|
||||||
|
declare static $ServerMetadata: InferType<typeof ServerMetadataSchema>;
|
||||||
|
declare static $ContentFormat: InferType<typeof ContentFormatSchema>;
|
||||||
|
declare static $CustomEmojiExtension: InferType<
|
||||||
|
typeof CustomEmojiExtension
|
||||||
|
>;
|
||||||
|
declare static $Visibility: InferType<typeof VisibilitySchema>;
|
||||||
|
declare static $Publication: InferType<typeof PublicationSchema>;
|
||||||
|
declare static $Entity: InferType<typeof EntitySchema>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates a Note entity.
|
||||||
|
* @param data - The data to validate
|
||||||
|
* @returns A promise that resolves to the validated data.
|
||||||
|
*/
|
||||||
|
public Note(data: unknown): Promise<InferType<typeof NoteSchema>> {
|
||||||
|
return this.validate(NoteSchema, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates a Patch entity.
|
||||||
|
* @param data - The data to validate
|
||||||
|
* @returns A promise that resolves to the validated data.
|
||||||
|
*/
|
||||||
|
public Patch(data: unknown): Promise<InferType<typeof PatchSchema>> {
|
||||||
|
return this.validate(PatchSchema, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates an ActorPublicKeyData entity.
|
||||||
|
* @param data - The data to validate
|
||||||
|
* @returns A promise that resolves to the validated data.
|
||||||
|
*/
|
||||||
|
public ActorPublicKeyData(
|
||||||
|
data: unknown,
|
||||||
|
): Promise<InferType<typeof ActorPublicKeyDataSchema>> {
|
||||||
|
return this.validate(ActorPublicKeyDataSchema, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates a VanityExtension entity.
|
||||||
|
* @param data - The data to validate
|
||||||
|
* @returns A promise that resolves to the validated data.
|
||||||
|
*/
|
||||||
|
public VanityExtension(
|
||||||
|
data: unknown,
|
||||||
|
): Promise<InferType<typeof VanityExtensionSchema>> {
|
||||||
|
return this.validate(VanityExtensionSchema, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates a User entity.
|
||||||
|
* @param data - The data to validate
|
||||||
|
* @returns A promise that resolves to the validated data.
|
||||||
|
*/
|
||||||
|
public User(data: unknown): Promise<InferType<typeof UserSchema>> {
|
||||||
|
return this.validate(UserSchema, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates an Action entity.
|
||||||
|
* @param data - The data to validate
|
||||||
|
* @returns A promise that resolves to the validated data.
|
||||||
|
*/
|
||||||
|
public Action(data: unknown): Promise<InferType<typeof ActionSchema>> {
|
||||||
|
return this.validate(ActionSchema, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates a Like entity.
|
||||||
|
* @param data - The data to validate
|
||||||
|
* @returns A promise that resolves to the validated data.
|
||||||
|
*/
|
||||||
|
public Like(data: unknown): Promise<InferType<typeof LikeSchema>> {
|
||||||
|
return this.validate(LikeSchema, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates an Undo entity.
|
||||||
|
* @param data - The data to validate
|
||||||
|
* @returns A promise that resolves to the validated data.
|
||||||
|
*/
|
||||||
|
public Undo(data: unknown): Promise<InferType<typeof UndoSchema>> {
|
||||||
|
return this.validate(UndoSchema, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates a Dislike entity.
|
||||||
|
* @param data - The data to validate
|
||||||
|
* @returns A promise that resolves to the validated data.
|
||||||
|
*/
|
||||||
|
public Dislike(data: unknown): Promise<InferType<typeof DislikeSchema>> {
|
||||||
|
return this.validate(DislikeSchema, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates a Follow entity.
|
||||||
|
* @param data - The data to validate
|
||||||
|
* @returns A promise that resolves to the validated data.
|
||||||
|
*/
|
||||||
|
public Follow(data: unknown): Promise<InferType<typeof FollowSchema>> {
|
||||||
|
return this.validate(FollowSchema, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates a FollowAccept entity.
|
||||||
|
* @param data - The data to validate
|
||||||
|
* @returns A promise that resolves to the validated data.
|
||||||
|
*/
|
||||||
|
public FollowAccept(
|
||||||
|
data: unknown,
|
||||||
|
): Promise<InferType<typeof FollowAcceptSchema>> {
|
||||||
|
return this.validate(FollowAcceptSchema, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates a FollowReject entity.
|
||||||
|
* @param data - The data to validate
|
||||||
|
* @returns A promise that resolves to the validated data.
|
||||||
|
*/
|
||||||
|
public FollowReject(
|
||||||
|
data: unknown,
|
||||||
|
): Promise<InferType<typeof FollowRejectSchema>> {
|
||||||
|
return this.validate(FollowRejectSchema, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates an Extension entity.
|
||||||
|
* @param data - The data to validate
|
||||||
|
* @returns A promise that resolves to the validated data.
|
||||||
|
*/
|
||||||
|
public Extension(
|
||||||
|
data: unknown,
|
||||||
|
): Promise<InferType<typeof ExtensionSchema>> {
|
||||||
|
return this.validate(ExtensionSchema, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates a Report entity.
|
||||||
|
* @param data - The data to validate
|
||||||
|
* @returns A promise that resolves to the validated data.
|
||||||
|
*/
|
||||||
|
public Report(data: unknown): Promise<InferType<typeof ReportSchema>> {
|
||||||
|
return this.validate(ReportSchema, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates a ServerMetadata entity.
|
||||||
|
* @param data - The data to validate
|
||||||
|
* @returns A promise that resolves to the validated data.
|
||||||
|
*/
|
||||||
|
public ServerMetadata(
|
||||||
|
data: unknown,
|
||||||
|
): Promise<InferType<typeof ServerMetadataSchema>> {
|
||||||
|
return this.validate(ServerMetadataSchema, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates a ContentFormat entity.
|
||||||
|
* @param data - The data to validate
|
||||||
|
* @returns A promise that resolves to the validated data.
|
||||||
|
*/
|
||||||
|
public ContentFormat(
|
||||||
|
data: unknown,
|
||||||
|
): Promise<InferType<typeof ContentFormatSchema>> {
|
||||||
|
return this.validate(ContentFormatSchema, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates a CustomEmojiExtension entity.
|
||||||
|
* @param data - The data to validate
|
||||||
|
* @returns A promise that resolves to the validated data.
|
||||||
|
*/
|
||||||
|
public CustomEmojiExtension(
|
||||||
|
data: unknown,
|
||||||
|
): Promise<InferType<typeof CustomEmojiExtension>> {
|
||||||
|
return this.validate(CustomEmojiExtension, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates a Visibility entity.
|
||||||
|
* @param data - The data to validate
|
||||||
|
* @returns A promise that resolves to the validated data.
|
||||||
|
*/
|
||||||
|
public Visibility(
|
||||||
|
data: unknown,
|
||||||
|
): Promise<InferType<typeof VisibilitySchema>> {
|
||||||
|
return this.validate(VisibilitySchema, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates a Publication entity.
|
||||||
|
* @param data - The data to validate
|
||||||
|
* @returns A promise that resolves to the validated data.
|
||||||
|
*/
|
||||||
|
public Publication(
|
||||||
|
data: unknown,
|
||||||
|
): Promise<InferType<typeof PublicationSchema>> {
|
||||||
|
return this.validate(PublicationSchema, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates an Entity.
|
||||||
|
* @param data - The data to validate
|
||||||
|
* @returns A promise that resolves to the validated data.
|
||||||
|
*/
|
||||||
|
public Entity(data: unknown): Promise<InferType<typeof EntitySchema>> {
|
||||||
|
return this.validate(EntitySchema, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { EntityValidator, type ValidationError };
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { ContentFormat } from "./content_format";
|
import { ContentFormatSchema } from "./content_format";
|
||||||
import { CustomEmojiExtension } from "./extensions/custom_emojis";
|
import { CustomEmojiExtension } from "./extensions/custom_emojis";
|
||||||
import { VanityExtension } from "./extensions/vanity";
|
import { VanityExtensionSchema } from "./extensions/vanity";
|
||||||
import { extensionTypeRegex } from "./regex";
|
import { extensionTypeRegex } from "./regex";
|
||||||
|
|
||||||
const Entity = z.object({
|
const EntitySchema = z.object({
|
||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
created_at: z.string(),
|
created_at: z.string(),
|
||||||
uri: z.string().url(),
|
uri: z.string().url(),
|
||||||
|
|
@ -14,20 +14,20 @@ const Entity = z.object({
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const Visibility = z.enum(["public", "unlisted", "private", "direct"]);
|
const VisibilitySchema = z.enum(["public", "unlisted", "private", "direct"]);
|
||||||
|
|
||||||
const Publication = Entity.extend({
|
const PublicationSchema = EntitySchema.extend({
|
||||||
type: z.enum(["Note", "Patch"]),
|
type: z.enum(["Note", "Patch"]),
|
||||||
author: z.string().url(),
|
author: z.string().url(),
|
||||||
content: ContentFormat.optional(),
|
content: ContentFormatSchema.optional(),
|
||||||
attachments: z.array(ContentFormat).optional(),
|
attachments: z.array(ContentFormatSchema).optional(),
|
||||||
replies_to: z.string().url().optional(),
|
replies_to: z.string().url().optional(),
|
||||||
quotes: z.string().url().optional(),
|
quotes: z.string().url().optional(),
|
||||||
mentions: z.array(z.string().url()).optional(),
|
mentions: z.array(z.string().url()).optional(),
|
||||||
subject: z.string().optional(),
|
subject: z.string().optional(),
|
||||||
is_sensitive: z.boolean().optional(),
|
is_sensitive: z.boolean().optional(),
|
||||||
visibility: Visibility,
|
visibility: VisibilitySchema,
|
||||||
extensions: Entity.shape.extensions.extend({
|
extensions: EntitySchema.shape.extensions.extend({
|
||||||
"org.lysand:reactions": z
|
"org.lysand:reactions": z
|
||||||
.object({
|
.object({
|
||||||
reactions: z.string(),
|
reactions: z.string(),
|
||||||
|
|
@ -36,7 +36,7 @@ const Publication = Entity.extend({
|
||||||
"org.lysand:polls": z
|
"org.lysand:polls": z
|
||||||
.object({
|
.object({
|
||||||
poll: z.object({
|
poll: z.object({
|
||||||
options: z.array(ContentFormat),
|
options: z.array(ContentFormatSchema),
|
||||||
votes: z.array(z.number().int().nonnegative()),
|
votes: z.array(z.number().int().nonnegative()),
|
||||||
multiple_choice: z.boolean().optional(),
|
multiple_choice: z.boolean().optional(),
|
||||||
expires_at: z.string(),
|
expires_at: z.string(),
|
||||||
|
|
@ -46,35 +46,35 @@ const Publication = Entity.extend({
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const Note = Publication.extend({
|
const NoteSchema = PublicationSchema.extend({
|
||||||
type: z.literal("Note"),
|
type: z.literal("Note"),
|
||||||
});
|
});
|
||||||
|
|
||||||
const Patch = Publication.extend({
|
const PatchSchema = PublicationSchema.extend({
|
||||||
type: z.literal("Patch"),
|
type: z.literal("Patch"),
|
||||||
patched_id: z.string().uuid(),
|
patched_id: z.string().uuid(),
|
||||||
patched_at: z.string(),
|
patched_at: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const ActorPublicKeyData = z.object({
|
const ActorPublicKeyDataSchema = z.object({
|
||||||
public_key: z.string(),
|
public_key: z.string(),
|
||||||
actor: z.string().url(),
|
actor: z.string().url(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const User = Entity.extend({
|
const UserSchema = EntitySchema.extend({
|
||||||
type: z.literal("User"),
|
type: z.literal("User"),
|
||||||
display_name: z.string().optional(),
|
display_name: z.string().optional(),
|
||||||
username: z.string(),
|
username: z.string(),
|
||||||
avatar: ContentFormat.optional(),
|
avatar: ContentFormatSchema.optional(),
|
||||||
header: ContentFormat.optional(),
|
header: ContentFormatSchema.optional(),
|
||||||
indexable: z.boolean(),
|
indexable: z.boolean(),
|
||||||
public_key: ActorPublicKeyData,
|
public_key: ActorPublicKeyDataSchema,
|
||||||
bio: ContentFormat.optional(),
|
bio: ContentFormatSchema.optional(),
|
||||||
fields: z
|
fields: z
|
||||||
.array(
|
.array(
|
||||||
z.object({
|
z.object({
|
||||||
name: ContentFormat,
|
name: ContentFormatSchema,
|
||||||
value: ContentFormat,
|
value: ContentFormatSchema,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.optional(),
|
.optional(),
|
||||||
|
|
@ -85,12 +85,12 @@ const User = Entity.extend({
|
||||||
dislikes: z.string().url(),
|
dislikes: z.string().url(),
|
||||||
inbox: z.string().url(),
|
inbox: z.string().url(),
|
||||||
outbox: z.string().url(),
|
outbox: z.string().url(),
|
||||||
extensions: Entity.shape.extensions.extend({
|
extensions: EntitySchema.shape.extensions.extend({
|
||||||
"org.lysand:vanity": VanityExtension.optional(),
|
"org.lysand:vanity": VanityExtensionSchema.optional(),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const Action = Entity.extend({
|
const ActionSchema = EntitySchema.extend({
|
||||||
type: z.union([
|
type: z.union([
|
||||||
z.literal("Like"),
|
z.literal("Like"),
|
||||||
z.literal("Dislike"),
|
z.literal("Dislike"),
|
||||||
|
|
@ -103,37 +103,37 @@ const Action = Entity.extend({
|
||||||
author: z.string().url(),
|
author: z.string().url(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const Like = Action.extend({
|
const LikeSchema = ActionSchema.extend({
|
||||||
type: z.literal("Like"),
|
type: z.literal("Like"),
|
||||||
object: z.string().url(),
|
object: z.string().url(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const Undo = Action.extend({
|
const UndoSchema = ActionSchema.extend({
|
||||||
type: z.literal("Undo"),
|
type: z.literal("Undo"),
|
||||||
object: z.string().url(),
|
object: z.string().url(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const Dislike = Action.extend({
|
const DislikeSchema = ActionSchema.extend({
|
||||||
type: z.literal("Dislike"),
|
type: z.literal("Dislike"),
|
||||||
object: z.string().url(),
|
object: z.string().url(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const Follow = Action.extend({
|
const FollowSchema = ActionSchema.extend({
|
||||||
type: z.literal("Follow"),
|
type: z.literal("Follow"),
|
||||||
followee: z.string().url(),
|
followee: z.string().url(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const FollowAccept = Action.extend({
|
const FollowAcceptSchema = ActionSchema.extend({
|
||||||
type: z.literal("FollowAccept"),
|
type: z.literal("FollowAccept"),
|
||||||
follower: z.string().url(),
|
follower: z.string().url(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const FollowReject = Action.extend({
|
const FollowRejectSchema = ActionSchema.extend({
|
||||||
type: z.literal("FollowReject"),
|
type: z.literal("FollowReject"),
|
||||||
follower: z.string().url(),
|
follower: z.string().url(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const Extension = Entity.extend({
|
const ExtensionSchema = EntitySchema.extend({
|
||||||
type: z.literal("Extension"),
|
type: z.literal("Extension"),
|
||||||
extension_type: z
|
extension_type: z
|
||||||
.string()
|
.string()
|
||||||
|
|
@ -143,14 +143,14 @@ const Extension = Entity.extend({
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
const Report = Extension.extend({
|
const ReportSchema = ExtensionSchema.extend({
|
||||||
extension_type: z.literal("org.lysand:reports/Report"),
|
extension_type: z.literal("org.lysand:reports/Report"),
|
||||||
objects: z.array(z.string().url()),
|
objects: z.array(z.string().url()),
|
||||||
reason: z.string(),
|
reason: z.string(),
|
||||||
comment: z.string().optional(),
|
comment: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const ServerMetadata = Entity.extend({
|
const ServerMetadataSchema = EntitySchema.extend({
|
||||||
type: z.literal("ServerMetadata"),
|
type: z.literal("ServerMetadata"),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
version: z.string(),
|
version: z.string(),
|
||||||
|
|
@ -158,31 +158,31 @@ const ServerMetadata = Entity.extend({
|
||||||
website: z.string().optional(),
|
website: z.string().optional(),
|
||||||
moderators: z.array(z.string()).optional(),
|
moderators: z.array(z.string()).optional(),
|
||||||
admins: z.array(z.string()).optional(),
|
admins: z.array(z.string()).optional(),
|
||||||
logo: ContentFormat.optional(),
|
logo: ContentFormatSchema.optional(),
|
||||||
banner: ContentFormat.optional(),
|
banner: ContentFormatSchema.optional(),
|
||||||
supported_extensions: z.array(z.string()),
|
supported_extensions: z.array(z.string()),
|
||||||
extensions: z.record(z.string(), z.any()).optional(),
|
extensions: z.record(z.string(), z.any()).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Entity,
|
EntitySchema,
|
||||||
Visibility,
|
VisibilitySchema,
|
||||||
Publication,
|
PublicationSchema,
|
||||||
Note,
|
NoteSchema,
|
||||||
Patch,
|
PatchSchema,
|
||||||
ActorPublicKeyData,
|
ActorPublicKeyDataSchema,
|
||||||
VanityExtension,
|
VanityExtensionSchema,
|
||||||
User,
|
UserSchema,
|
||||||
Action,
|
ActionSchema,
|
||||||
Like,
|
LikeSchema,
|
||||||
Undo,
|
UndoSchema,
|
||||||
Dislike,
|
DislikeSchema,
|
||||||
Follow,
|
FollowSchema,
|
||||||
FollowAccept,
|
FollowAcceptSchema,
|
||||||
FollowReject,
|
FollowRejectSchema,
|
||||||
Extension,
|
ExtensionSchema,
|
||||||
Report,
|
ReportSchema,
|
||||||
ServerMetadata,
|
ServerMetadataSchema,
|
||||||
ContentFormat,
|
ContentFormatSchema,
|
||||||
CustomEmojiExtension,
|
CustomEmojiExtension,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { types } from "mime-types";
|
import { types } from "mime-types";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
export const ContentFormat = z.record(
|
export const ContentFormatSchema = z.record(
|
||||||
z.enum(Object.values(types) as [string, ...string[]]),
|
z.enum(Object.values(types) as [string, ...string[]]),
|
||||||
z.object({
|
z.object({
|
||||||
content: z.string(),
|
content: z.string(),
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
* @see https://lysand.org/extensions/custom-emojis
|
* @see https://lysand.org/extensions/custom-emojis
|
||||||
*/
|
*/
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { ContentFormat } from "../content_format";
|
import { ContentFormatSchema } from "../content_format";
|
||||||
import { emojiRegex } from "../regex";
|
import { emojiRegex } from "../regex";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -44,7 +44,7 @@ export const CustomEmojiExtension = z.object({
|
||||||
emojiRegex,
|
emojiRegex,
|
||||||
"Emoji name must be alphanumeric, underscores, or dashes.",
|
"Emoji name must be alphanumeric, underscores, or dashes.",
|
||||||
),
|
),
|
||||||
url: ContentFormat,
|
url: ContentFormatSchema,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@
|
||||||
* @see https://lysand.org/extensions/polls
|
* @see https://lysand.org/extensions/polls
|
||||||
*/
|
*/
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { Extension } from "../base";
|
import { ExtensionSchema } from "../base";
|
||||||
import { ContentFormat } from "../content_format";
|
import { ContentFormatSchema } from "../content_format";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Poll extension entity
|
* @description Poll extension entity
|
||||||
|
|
@ -43,9 +43,9 @@ import { ContentFormat } from "../content_format";
|
||||||
* "expires_at": "2021-01-04T00:00:00.000Z"
|
* "expires_at": "2021-01-04T00:00:00.000Z"
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
export const Poll = Extension.extend({
|
export const PollSchema = ExtensionSchema.extend({
|
||||||
extension_type: z.literal("org.lysand:polls/Poll"),
|
extension_type: z.literal("org.lysand:polls/Poll"),
|
||||||
options: z.array(ContentFormat),
|
options: z.array(ContentFormatSchema),
|
||||||
votes: z.array(z.number().int().nonnegative()),
|
votes: z.array(z.number().int().nonnegative()),
|
||||||
multiple_choice: z.boolean().optional(),
|
multiple_choice: z.boolean().optional(),
|
||||||
expires_at: z.string(),
|
expires_at: z.string(),
|
||||||
|
|
@ -65,7 +65,7 @@ export const Poll = Extension.extend({
|
||||||
* "option": 1
|
* "option": 1
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
export const Vote = Extension.extend({
|
export const VoteSchema = ExtensionSchema.extend({
|
||||||
extension_type: z.literal("org.lysand:polls/Vote"),
|
extension_type: z.literal("org.lysand:polls/Vote"),
|
||||||
poll: z.string().url(),
|
poll: z.string().url(),
|
||||||
option: z.number(),
|
option: z.number(),
|
||||||
|
|
@ -85,7 +85,7 @@ export const Vote = Extension.extend({
|
||||||
* "votes": [9, 5, 0]
|
* "votes": [9, 5, 0]
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
export const VoteResult = Extension.extend({
|
export const VoteResultSchema = ExtensionSchema.extend({
|
||||||
extension_type: z.literal("org.lysand:polls/VoteResult"),
|
extension_type: z.literal("org.lysand:polls/VoteResult"),
|
||||||
poll: z.string().url(),
|
poll: z.string().url(),
|
||||||
votes: z.array(z.number().int().nonnegative()),
|
votes: z.array(z.number().int().nonnegative()),
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
* @see https://lysand.org/extensions/reactions
|
* @see https://lysand.org/extensions/reactions
|
||||||
*/
|
*/
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { Extension } from "../base";
|
import { ExtensionSchema } from "../base";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Reaction extension entity
|
* @description Reaction extension entity
|
||||||
|
|
@ -21,7 +21,7 @@ import { Extension } from "../base";
|
||||||
* "content": "👍"
|
* "content": "👍"
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
export const Reaction = Extension.extend({
|
export const ReactionSchema = ExtensionSchema.extend({
|
||||||
extension_type: z.literal("org.lysand:reactions/Reaction"),
|
extension_type: z.literal("org.lysand:reactions/Reaction"),
|
||||||
object: z.string().url(),
|
object: z.string().url(),
|
||||||
content: z.string(),
|
content: z.string(),
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@
|
||||||
* @see https://lysand.org/extensions/vanity
|
* @see https://lysand.org/extensions/vanity
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { type AnyZodObject, ZodObject, z } from "zod";
|
import { z } from "zod";
|
||||||
import { ContentFormat } from "../content_format";
|
import { ContentFormatSchema } from "../content_format";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Vanity extension entity
|
* @description Vanity extension entity
|
||||||
|
|
@ -67,11 +67,11 @@ import { ContentFormat } from "../content_format";
|
||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
export const VanityExtension = z.object({
|
export const VanityExtensionSchema = z.object({
|
||||||
avatar_overlay: ContentFormat.optional(),
|
avatar_overlay: ContentFormatSchema.optional(),
|
||||||
avatar_mask: ContentFormat.optional(),
|
avatar_mask: ContentFormatSchema.optional(),
|
||||||
background: ContentFormat.optional(),
|
background: ContentFormatSchema.optional(),
|
||||||
audio: ContentFormat.optional(),
|
audio: ContentFormatSchema.optional(),
|
||||||
pronouns: z.record(
|
pronouns: z.record(
|
||||||
z.string(),
|
z.string(),
|
||||||
z.array(
|
z.array(
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,19 @@
|
||||||
import { describe, expect, it } from "bun:test";
|
import { describe, expect, it } from "bun:test";
|
||||||
import { Note } from "../schemas/base";
|
import type { ValidationError } from "zod-validation-error";
|
||||||
|
import { EntityValidator } from "../index";
|
||||||
|
|
||||||
describe("Package testing", () => {
|
describe("Package testing", () => {
|
||||||
it("should not validate a bad Note", () => {
|
it("should not validate a bad Note", async () => {
|
||||||
const badObject = {
|
const badObject = {
|
||||||
IamBad: "Note",
|
IamBad: "Note",
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(Note.parseAsync(badObject)).rejects.toThrow();
|
const validator = new EntityValidator();
|
||||||
|
|
||||||
|
expect(validator.Note(badObject)).rejects.toThrow();
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
(await validator.Note(badObject).catch((e) => e)).toString(),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue