feat: spec to platform
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { RoleSchema } from "@spec/schemas/account/role.ts";
|
||||
import { RoleSchema } from "@platform/spec/account/role.ts";
|
||||
import { PrincipalProvider } from "@valkyr/auth";
|
||||
|
||||
import { db } from "~stores/read-store/database.ts";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ServerError } from "@spec/relay";
|
||||
import { ServerError } from "@platform/relay";
|
||||
|
||||
import type { Level } from "../level.ts";
|
||||
import { getTracedAt } from "../stack.ts";
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
type ServerErrorResponse,
|
||||
UnauthorizedError,
|
||||
ZodValidationError,
|
||||
} from "@spec/relay";
|
||||
} from "@platform/relay";
|
||||
import { treeifyError } from "zod";
|
||||
|
||||
import { logger } from "~libraries/logger/mod.ts";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ServerContext } from "@spec/relay";
|
||||
import { ServerContext } from "@platform/relay";
|
||||
|
||||
import type { Sockets } from "~libraries/socket/sockets.ts";
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Session } from "../auth/auth.ts";
|
||||
import { Principal } from "../auth/principal.ts";
|
||||
import { req } from "./request.ts";
|
||||
|
||||
declare module "@spec/relay" {
|
||||
declare module "@platform/relay" {
|
||||
interface ServerContext {
|
||||
/**
|
||||
* Current request instance being handled.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Route } from "@spec/relay";
|
||||
import { Route } from "@platform/relay";
|
||||
|
||||
/**
|
||||
* Resolve and return all routes that has been created under any 'routes'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { InternalServerError, UnauthorizedError } from "@spec/relay";
|
||||
import { InternalServerError, UnauthorizedError } from "@platform/relay";
|
||||
|
||||
import { Session } from "../auth/auth.ts";
|
||||
import { storage } from "./storage.ts";
|
||||
|
||||
@@ -7,15 +7,15 @@
|
||||
"dependencies": {
|
||||
"@cerbos/http": "0.23.1",
|
||||
"@felix/bcrypt": "npm:@jsr/felix__bcrypt@1.0.5",
|
||||
"@spec/modules": "workspace:*",
|
||||
"@spec/relay": "workspace:*",
|
||||
"@spec/shared": "workspace:*",
|
||||
"@platform/models": "workspace:*",
|
||||
"@platform/relay": "workspace:*",
|
||||
"@platform/spec": "workspace:*",
|
||||
"@std/cli": "npm:@jsr/std__cli@1.0.22",
|
||||
"@std/dotenv": "npm:@jsr/std__dotenv@0.225.5",
|
||||
"@std/fs": "npm:@jsr/std__fs@1.0.19",
|
||||
"@std/path": "npm:@jsr/std__path@1.1.2",
|
||||
"@valkyr/auth": "npm:@jsr/valkyr__auth@2.1.4",
|
||||
"@valkyr/event-store": "npm:@jsr/valkyr__event-store@2",
|
||||
"@valkyr/event-store": "npm:@jsr/valkyr__event-store@2.0.1",
|
||||
"@valkyr/inverse": "npm:@jsr/valkyr__inverse@1.0.1",
|
||||
"@valkyr/json-rpc": "npm:@jsr/valkyr__json-rpc@1.1.0",
|
||||
"cookie": "1.0.2",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { procedure } from "@spec/relay/mod.ts";
|
||||
import { procedure } from "@platform/relay";
|
||||
import z from "zod";
|
||||
|
||||
const EventSchema = z.object({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { AccountEmailClaimedError } from "@spec/schemas/account/errors.ts";
|
||||
import { create } from "@spec/schemas/account/routes.ts";
|
||||
import { AccountEmailClaimedError } from "@platform/spec/account/errors.ts";
|
||||
import { create } from "@platform/spec/account/routes.ts";
|
||||
|
||||
import { Account, isEmailClaimed } from "~stores/event-store/aggregates/account.ts";
|
||||
import { eventStore } from "~stores/event-store/event-store.ts";
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { ForbiddenError } from "@spec/relay/mod.ts";
|
||||
import { NotFoundError } from "@spec/relay/mod.ts";
|
||||
import { getById } from "@spec/schemas/account/routes.ts";
|
||||
import { ForbiddenError, NotFoundError } from "@platform/relay";
|
||||
import { getById } from "@platform/spec/account/routes.ts";
|
||||
|
||||
import { db } from "~stores/read-store/database.ts";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { code } from "@spec/schemas/auth/routes.ts";
|
||||
import { code } from "@platform/spec/auth/routes.ts";
|
||||
import cookie from "cookie";
|
||||
|
||||
import { auth, config } from "~libraries/auth/mod.ts";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { email } from "@spec/schemas/auth/routes.ts";
|
||||
import { email } from "@platform/spec/auth/routes.ts";
|
||||
|
||||
import { logger } from "~libraries/logger/mod.ts";
|
||||
import { Account, getAccountEmailRelation } from "~stores/event-store/aggregates/account.ts";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BadRequestError } from "@spec/relay";
|
||||
import { password as route } from "@spec/schemas/auth/routes.ts";
|
||||
import { BadRequestError } from "@platform/relay";
|
||||
import { password as route } from "@platform/spec/auth/routes.ts";
|
||||
import cookie from "cookie";
|
||||
|
||||
import { config } from "~config";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { UnauthorizedError } from "@spec/relay/mod.ts";
|
||||
import { session } from "@spec/schemas/auth/routes.ts";
|
||||
import { UnauthorizedError } from "@platform/relay";
|
||||
import { session } from "@platform/spec/auth/routes.ts";
|
||||
|
||||
import { getAccountById } from "~stores/read-store/methods.ts";
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { toAccountDocument } from "@spec/schemas/account/account.ts";
|
||||
import { Role } from "@spec/schemas/account/role.ts";
|
||||
import { Strategy } from "@spec/schemas/account/strategies.ts";
|
||||
import { Avatar } from "@spec/schemas/avatar.ts";
|
||||
import { Contact } from "@spec/schemas/contact.ts";
|
||||
import { Email } from "@spec/schemas/email.ts";
|
||||
import { Name } from "@spec/schemas/name.ts";
|
||||
import { toAccountDocument } from "@platform/models/account.ts";
|
||||
import { Avatar } from "@platform/models/value-objects/avatar.ts";
|
||||
import { Contact } from "@platform/models/value-objects/contact.ts";
|
||||
import { Email } from "@platform/models/value-objects/email.ts";
|
||||
import { Name } from "@platform/models/value-objects/name.ts";
|
||||
import { Role } from "@platform/spec/account/role.ts";
|
||||
import { Strategy } from "@platform/spec/account/strategies.ts";
|
||||
import { AggregateRoot, getDate } from "@valkyr/event-store";
|
||||
|
||||
import { db } from "~stores/read-store/database.ts";
|
||||
|
||||
import { eventStore } from "../event-store.ts";
|
||||
import { Auditor, systemAuditor } from "../events/auditor.ts";
|
||||
import { EventStoreFactory } from "../events/mod.ts";
|
||||
import { EventRecord, EventStoreFactory } from "../events/mod.ts";
|
||||
import { projector } from "../projector.ts";
|
||||
|
||||
export class Account extends AggregateRoot<EventStoreFactory> {
|
||||
@@ -32,11 +32,12 @@ export class Account extends AggregateRoot<EventStoreFactory> {
|
||||
// Reducer
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
with(event: EventStoreFactory["$events"][number]["$record"]): void {
|
||||
with(event: EventRecord): void {
|
||||
switch (event.type) {
|
||||
case "account:created": {
|
||||
this.id = event.stream;
|
||||
this.createdAt = getDate(event.created);
|
||||
break;
|
||||
}
|
||||
case "account:avatar:added": {
|
||||
this.avatar = { url: event.data };
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { AggregateRoot, getDate } from "@valkyr/event-store";
|
||||
|
||||
import { CodeIdentity } from "../events/code.ts";
|
||||
import { EventStoreFactory } from "../events/mod.ts";
|
||||
import { EventRecord, EventStoreFactory } from "../events/mod.ts";
|
||||
|
||||
export class Code extends AggregateRoot<EventStoreFactory> {
|
||||
static override readonly name = "code";
|
||||
@@ -24,7 +24,7 @@ export class Code extends AggregateRoot<EventStoreFactory> {
|
||||
// Folder
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
with(event: EventStoreFactory["$events"][number]["$record"]): void {
|
||||
with(event: EventRecord): void {
|
||||
switch (event.type) {
|
||||
case "code:created": {
|
||||
this.value = event.data.value;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { RoleSchema } from "@spec/schemas/account/role.ts";
|
||||
import { EmailSchema } from "@spec/schemas/email.ts";
|
||||
import { NameSchema } from "@spec/schemas/name.ts";
|
||||
import { EmailSchema } from "@platform/models/value-objects/email.ts";
|
||||
import { NameSchema } from "@platform/models/value-objects/name.ts";
|
||||
import { RoleSchema } from "@platform/spec/account/role.ts";
|
||||
import { event } from "@valkyr/event-store";
|
||||
import z from "zod";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { EventFactory } from "@valkyr/event-store";
|
||||
import { EventFactory, Prettify } from "@valkyr/event-store";
|
||||
|
||||
import account from "./account.ts";
|
||||
import code from "./code.ts";
|
||||
@@ -8,3 +8,5 @@ import strategy from "./strategy.ts";
|
||||
export const events = new EventFactory([...account, ...code, ...organization, ...strategy]);
|
||||
|
||||
export type EventStoreFactory = typeof events;
|
||||
|
||||
export type EventRecord = Prettify<EventStoreFactory["$events"][number]["$record"]>;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { AccountDocument } from "@spec/schemas/account/account.ts";
|
||||
import type { AccountDocument } from "@platform/models/account.ts";
|
||||
|
||||
import { config } from "~config";
|
||||
import { getDatabaseAccessor } from "~libraries/database/accessor.ts";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { type Account, fromAccountDocument } from "@spec/schemas/account/account.ts";
|
||||
import { PasswordStrategy } from "@spec/schemas/auth/strategies.ts";
|
||||
import { Account, fromAccountDocument } from "@platform/models/account.ts";
|
||||
import { PasswordStrategy } from "@platform/spec/auth/strategies.ts";
|
||||
|
||||
import { db, takeOne } from "./database.ts";
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@spec/relay": "workspace:*",
|
||||
"@spec/schemas": "workspace:*",
|
||||
"@platform/relay": "workspace:*",
|
||||
"@platform/spec": "workspace:*",
|
||||
"@tanstack/react-query": "5.89.0",
|
||||
"@tanstack/react-router": "1.131.47",
|
||||
"@valkyr/db": "npm:@jsr/valkyr__db@2.0.0",
|
||||
|
||||
@@ -6,8 +6,37 @@ import {
|
||||
ServerError,
|
||||
type ServerErrorResponse,
|
||||
type ServerErrorType,
|
||||
} from "@spec/relay";
|
||||
} from "@platform/relay";
|
||||
|
||||
/**
|
||||
* HttpAdapter provides a unified transport layer for Relay.
|
||||
*
|
||||
* It supports sending JSON objects, nested structures, arrays, and file uploads
|
||||
* via FormData. The adapter automatically detects the payload type and formats
|
||||
* the request accordingly. Responses are normalized into `RelayResponse`.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const adapter = new HttpAdapter({ url: "https://api.example.com" });
|
||||
*
|
||||
* // Sending JSON data
|
||||
* const jsonResponse = await adapter.send({
|
||||
* method: "POST",
|
||||
* endpoint: "/users",
|
||||
* body: { name: "Alice", age: 30 },
|
||||
* });
|
||||
*
|
||||
* // Sending files and nested objects
|
||||
* const formResponse = await adapter.send({
|
||||
* method: "POST",
|
||||
* endpoint: "/upload",
|
||||
* body: {
|
||||
* user: { name: "Bob", avatar: fileInput.files[0] },
|
||||
* documents: [fileInput.files[1], fileInput.files[2]],
|
||||
* },
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export class HttpAdapter implements RelayAdapter {
|
||||
/**
|
||||
* Instantiate a new HttpAdapter instance.
|
||||
@@ -39,12 +68,7 @@ export class HttpAdapter implements RelayAdapter {
|
||||
return `${this.url}${endpoint}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send fetch request to the configured endpoint.
|
||||
*
|
||||
* @param input - Relay input parameters to use for the request.
|
||||
*/
|
||||
async json({ method, endpoint, query, body, headers = new Headers() }: RelayInput): Promise<RelayResponse> {
|
||||
async send({ method, endpoint, query, body, headers = new Headers() }: RelayInput): Promise<RelayResponse> {
|
||||
const init: RequestInit = { method, headers };
|
||||
|
||||
// ### Before Request
|
||||
@@ -53,66 +77,18 @@ export class HttpAdapter implements RelayAdapter {
|
||||
|
||||
await this.#beforeRequest(headers);
|
||||
|
||||
// ### Content Type
|
||||
// JSON requests are always of the type 'application/json' and this ensures that
|
||||
// we override any custom pre-hook values for 'content-type' when executing the
|
||||
// request via the 'json' method.
|
||||
|
||||
headers.set("content-type", "application/json");
|
||||
|
||||
// ### Body
|
||||
|
||||
if (body !== undefined) {
|
||||
const type = this.#getRequestFormat(body);
|
||||
if (type === "form-data") {
|
||||
headers.delete("content-type");
|
||||
init.body = this.#getFormData(body);
|
||||
}
|
||||
if (type === "json") {
|
||||
headers.set("content-type", "application/json");
|
||||
init.body = JSON.stringify(body);
|
||||
}
|
||||
|
||||
// ### Response
|
||||
|
||||
return this.request(`${endpoint}${query}`, init);
|
||||
}
|
||||
|
||||
async data({ method, endpoint, query, body, headers = new Headers() }: RelayInput): Promise<RelayResponse> {
|
||||
const init: RequestInit = { method, headers };
|
||||
|
||||
// ### Before Request
|
||||
// If any before request hooks has been defined, we run them here passing in the
|
||||
// request headers for further modification.
|
||||
|
||||
await this.#beforeRequest(headers);
|
||||
|
||||
// ### Content Type
|
||||
// For multipart uploads we let the browser set the correct boundaries.
|
||||
|
||||
headers.delete("content-type");
|
||||
|
||||
// ### Body
|
||||
|
||||
const formData = new FormData();
|
||||
|
||||
if (body !== undefined) {
|
||||
for (const key in body) {
|
||||
const entity = body[key];
|
||||
if (entity === undefined) {
|
||||
continue;
|
||||
}
|
||||
if (Array.isArray(entity)) {
|
||||
const isFileArray = entity.length > 0 && entity.every((candidate) => candidate instanceof File);
|
||||
if (isFileArray) {
|
||||
for (const file of entity) {
|
||||
formData.append(key, file, file.name);
|
||||
}
|
||||
} else {
|
||||
formData.append(key, JSON.stringify(entity));
|
||||
}
|
||||
} else {
|
||||
if (entity instanceof File) {
|
||||
formData.append(key, entity, entity.name);
|
||||
} else {
|
||||
formData.append(key, typeof entity === "string" ? entity : JSON.stringify(entity));
|
||||
}
|
||||
}
|
||||
}
|
||||
init.body = formData;
|
||||
}
|
||||
|
||||
// ### Response
|
||||
@@ -144,6 +120,52 @@ export class HttpAdapter implements RelayAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the parser method required for the request.
|
||||
*
|
||||
* @param body - Request body.
|
||||
*/
|
||||
#getRequestFormat(body: unknown): "form-data" | "json" {
|
||||
if (containsFile(body) === true) {
|
||||
return "form-data";
|
||||
}
|
||||
return "json";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get FormData instance for the given body.
|
||||
*
|
||||
* @param body - Request body.
|
||||
*/
|
||||
#getFormData(data: Record<string, unknown>, formData = new FormData(), parentKey?: string): FormData {
|
||||
for (const key in data) {
|
||||
const value = data[key];
|
||||
if (value === undefined || value === null) continue;
|
||||
|
||||
const formKey = parentKey ? `${parentKey}[${key}]` : key;
|
||||
|
||||
if (value instanceof File) {
|
||||
formData.append(formKey, value, value.name);
|
||||
} else if (Array.isArray(value)) {
|
||||
value.forEach((item, index) => {
|
||||
if (item instanceof File) {
|
||||
formData.append(`${formKey}[${index}]`, item, item.name);
|
||||
} else if (typeof item === "object") {
|
||||
this.#getFormData(item as Record<string, unknown>, formData, `${formKey}[${index}]`);
|
||||
} else {
|
||||
formData.append(`${formKey}[${index}]`, String(item));
|
||||
}
|
||||
});
|
||||
} else if (typeof value === "object") {
|
||||
this.#getFormData(value as Record<string, unknown>, formData, formKey);
|
||||
} else {
|
||||
formData.append(formKey, String(value));
|
||||
}
|
||||
}
|
||||
|
||||
return formData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a fetch response to a compliant relay response.
|
||||
*
|
||||
@@ -159,7 +181,6 @@ export class HttpAdapter implements RelayAdapter {
|
||||
if (type === null) {
|
||||
return {
|
||||
result: "error",
|
||||
headers: response.headers,
|
||||
error: {
|
||||
status: response.status,
|
||||
message: "Missing 'content-type' in header returned from server.",
|
||||
@@ -174,34 +195,10 @@ export class HttpAdapter implements RelayAdapter {
|
||||
if (response.status === 204) {
|
||||
return {
|
||||
result: "success",
|
||||
headers: response.headers,
|
||||
data: null,
|
||||
};
|
||||
}
|
||||
|
||||
// ### SCIM
|
||||
// If the 'content-type' is of type 'scim' we need to convert the SCIM compliant
|
||||
// response to a valid relay response.
|
||||
|
||||
if (type === "application/scim+json") {
|
||||
const parsed = await response.json();
|
||||
if (response.status >= 400) {
|
||||
return {
|
||||
result: "error",
|
||||
headers: response.headers,
|
||||
error: {
|
||||
status: response.status,
|
||||
message: parsed.detail,
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
result: "success",
|
||||
headers: response.headers,
|
||||
data: parsed,
|
||||
};
|
||||
}
|
||||
|
||||
// ### JSON
|
||||
// If the 'content-type' contains 'json' we treat it as a 'json' compliant response
|
||||
// and attempt to resolve it as such.
|
||||
@@ -211,20 +208,17 @@ export class HttpAdapter implements RelayAdapter {
|
||||
if ("data" in parsed) {
|
||||
return {
|
||||
result: "success",
|
||||
headers: response.headers,
|
||||
data: parsed.data,
|
||||
};
|
||||
}
|
||||
if ("error" in parsed) {
|
||||
return {
|
||||
result: "error",
|
||||
headers: response.headers,
|
||||
error: this.#toError(parsed),
|
||||
};
|
||||
}
|
||||
return {
|
||||
result: "error",
|
||||
headers: response.headers,
|
||||
error: {
|
||||
status: response.status,
|
||||
message: "Unsupported 'json' body returned from server, missing 'data' or 'error' key.",
|
||||
@@ -234,7 +228,6 @@ export class HttpAdapter implements RelayAdapter {
|
||||
|
||||
return {
|
||||
result: "error",
|
||||
headers: response.headers,
|
||||
error: {
|
||||
status: response.status,
|
||||
message: "Unsupported 'content-type' in header returned from server.",
|
||||
@@ -259,6 +252,19 @@ export class HttpAdapter implements RelayAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
function containsFile(value: unknown): boolean {
|
||||
if (value instanceof File) {
|
||||
return true;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
return value.some(containsFile);
|
||||
}
|
||||
if (typeof value === "object" && value !== null) {
|
||||
return Object.values(value).some(containsFile);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export type HttpAdapterOptions = {
|
||||
url: string;
|
||||
hooks?: {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { makeClient } from "@spec/relay";
|
||||
import { makeClient } from "@platform/relay";
|
||||
|
||||
import { HttpAdapter } from "../adapters/http.ts";
|
||||
|
||||
@@ -9,7 +9,7 @@ export const api = makeClient(
|
||||
}),
|
||||
},
|
||||
{
|
||||
account: (await import("@spec/schemas/account/routes.ts")).routes,
|
||||
auth: (await import("@spec/schemas/auth/routes.ts")).routes,
|
||||
account: (await import("@platform/spec/account/routes.ts")).routes,
|
||||
auth: (await import("@platform/spec/auth/routes.ts")).routes,
|
||||
},
|
||||
);
|
||||
|
||||
12
deno.json
12
deno.json
@@ -4,12 +4,14 @@
|
||||
"workspace": [
|
||||
"api",
|
||||
"apps/react",
|
||||
"spec/relay",
|
||||
"spec/schemas"
|
||||
"platform/models",
|
||||
"platform/relay",
|
||||
"platform/spec"
|
||||
],
|
||||
"imports": {
|
||||
"@spec/relay/": "./spec/relay/",
|
||||
"@spec/schemas/": "./spec/schemas/"
|
||||
"@platform/models/": "./platform/models/",
|
||||
"@platform/relay": "./platform/relay/mod.ts",
|
||||
"@platform/spec/": "./platform/spec/"
|
||||
},
|
||||
"tasks": {
|
||||
"start:api": {
|
||||
@@ -21,7 +23,7 @@
|
||||
"description": "Start react application instance."
|
||||
},
|
||||
"check": {
|
||||
"command": "deno check ./api/server.ts",
|
||||
"command": "deno check ./api/server.ts ./platform",
|
||||
"description": "Runs a check on all the projects main entry files."
|
||||
},
|
||||
"lint": {
|
||||
|
||||
109
deno.lock
generated
109
deno.lock
generated
@@ -13,7 +13,7 @@
|
||||
"npm:@jsr/valkyr__auth@2.1.4": "2.1.4",
|
||||
"npm:@jsr/valkyr__db@2.0.0": "2.0.0",
|
||||
"npm:@jsr/valkyr__event-emitter@1.0.1": "1.0.1",
|
||||
"npm:@jsr/valkyr__event-store@2": "2.0.0",
|
||||
"npm:@jsr/valkyr__event-store@2.0.1": "2.0.1",
|
||||
"npm:@jsr/valkyr__inverse@1.0.1": "1.0.1",
|
||||
"npm:@jsr/valkyr__json-rpc@1.1.0": "1.1.0",
|
||||
"npm:@tailwindcss/vite@4.1.13": "4.1.13_vite@7.1.6__picomatch@4.0.3_@types+node@24.2.0",
|
||||
@@ -538,8 +538,8 @@
|
||||
],
|
||||
"tarball": "https://npm.jsr.io/~/11/@jsr/valkyr__event-emitter/1.0.1.tgz"
|
||||
},
|
||||
"@jsr/valkyr__event-store@2.0.0": {
|
||||
"integrity": "sha512-izGy/QIGQXoTz0PP1UinSWcSPEEpNuePbmApBbvHq6MFp1p2X/k2eDPKlz2txwXcIn+QKjDhE5F59xPNEKnIng==",
|
||||
"@jsr/valkyr__event-store@2.0.1": {
|
||||
"integrity": "sha512-OvSPX0XH5+oS4zQh1O8J7JvsCoH5pBFNuJ1PdNA5B0OascrSWUqpxNEmytOtJhZuhfYzdvyOU1yNEvSI84D5wg==",
|
||||
"dependencies": [
|
||||
"@jsr/valkyr__db",
|
||||
"@jsr/valkyr__testcontainers",
|
||||
@@ -547,7 +547,7 @@
|
||||
"postgres",
|
||||
"zod"
|
||||
],
|
||||
"tarball": "https://npm.jsr.io/~/11/@jsr/valkyr__event-store/2.0.0.tgz"
|
||||
"tarball": "https://npm.jsr.io/~/11/@jsr/valkyr__event-store/2.0.1.tgz"
|
||||
},
|
||||
"@jsr/valkyr__inverse@1.0.1": {
|
||||
"integrity": "sha512-uZpzPct9FGobgl6H+iR3VJlzZbTFVmJSrB4z5In8zHgIJCkmgYj0diU3soU6MuiKR7SFBfD4PGSuUpTTJHNMlg==",
|
||||
@@ -597,108 +597,108 @@
|
||||
"@rolldown/pluginutils@1.0.0-beta.27": {
|
||||
"integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="
|
||||
},
|
||||
"@rollup/rollup-android-arm-eabi@4.50.2": {
|
||||
"integrity": "sha512-uLN8NAiFVIRKX9ZQha8wy6UUs06UNSZ32xj6giK/rmMXAgKahwExvK6SsmgU5/brh4w/nSgj8e0k3c1HBQpa0A==",
|
||||
"@rollup/rollup-android-arm-eabi@4.51.0": {
|
||||
"integrity": "sha512-VyfldO8T/C5vAXBGIobrAnUE+VJNVLw5z9h4NgSDq/AJZWt/fXqdW+0PJbk+M74xz7yMDRiHtlsuDV7ew6K20w==",
|
||||
"os": ["android"],
|
||||
"cpu": ["arm"]
|
||||
},
|
||||
"@rollup/rollup-android-arm64@4.50.2": {
|
||||
"integrity": "sha512-oEouqQk2/zxxj22PNcGSskya+3kV0ZKH+nQxuCCOGJ4oTXBdNTbv+f/E3c74cNLeMO1S5wVWacSws10TTSB77g==",
|
||||
"@rollup/rollup-android-arm64@4.51.0": {
|
||||
"integrity": "sha512-Z3ujzDZgsEVSokgIhmOAReh9SGT2qloJJX2Xo1Q3nPU1EhCXrV0PbpR3r7DWRgozqnjrPZQkLe5cgBPIYp70Vg==",
|
||||
"os": ["android"],
|
||||
"cpu": ["arm64"]
|
||||
},
|
||||
"@rollup/rollup-darwin-arm64@4.50.2": {
|
||||
"integrity": "sha512-OZuTVTpj3CDSIxmPgGH8en/XtirV5nfljHZ3wrNwvgkT5DQLhIKAeuFSiwtbMto6oVexV0k1F1zqURPKf5rI1Q==",
|
||||
"@rollup/rollup-darwin-arm64@4.51.0": {
|
||||
"integrity": "sha512-T3gskHgArUdR6TCN69li5VELVAZK+iQ4iwMoSMNYixoj+56EC9lTj35rcxhXzIJt40YfBkvDy3GS+t5zh7zM6g==",
|
||||
"os": ["darwin"],
|
||||
"cpu": ["arm64"]
|
||||
},
|
||||
"@rollup/rollup-darwin-x64@4.50.2": {
|
||||
"integrity": "sha512-Wa/Wn8RFkIkr1vy1k1PB//VYhLnlnn5eaJkfTQKivirOvzu5uVd2It01ukeQstMursuz7S1bU+8WW+1UPXpa8A==",
|
||||
"@rollup/rollup-darwin-x64@4.51.0": {
|
||||
"integrity": "sha512-Hh7n/fh0g5UjH6ATDF56Qdf5bzdLZKIbhp5KftjMYG546Ocjeyg15dxphCpH1FFY2PJ2G6MiOVL4jMq5VLTyrQ==",
|
||||
"os": ["darwin"],
|
||||
"cpu": ["x64"]
|
||||
},
|
||||
"@rollup/rollup-freebsd-arm64@4.50.2": {
|
||||
"integrity": "sha512-QkzxvH3kYN9J1w7D1A+yIMdI1pPekD+pWx7G5rXgnIlQ1TVYVC6hLl7SOV9pi5q9uIDF9AuIGkuzcbF7+fAhow==",
|
||||
"@rollup/rollup-freebsd-arm64@4.51.0": {
|
||||
"integrity": "sha512-0EddADb6FBvfqYoxwVom3hAbAvpSVUbZqmR1wmjk0MSZ06hn/UxxGHKRqEQDMkts7XiZjejVB+TLF28cDTU+gA==",
|
||||
"os": ["freebsd"],
|
||||
"cpu": ["arm64"]
|
||||
},
|
||||
"@rollup/rollup-freebsd-x64@4.50.2": {
|
||||
"integrity": "sha512-dkYXB0c2XAS3a3jmyDkX4Jk0m7gWLFzq1C3qUnJJ38AyxIF5G/dyS4N9B30nvFseCfgtCEdbYFhk0ChoCGxPog==",
|
||||
"@rollup/rollup-freebsd-x64@4.51.0": {
|
||||
"integrity": "sha512-MpqaEDLo3JuVPF+wWV4mK7V8akL76WCz8ndfz1aVB7RhvXFO3k7yT7eu8OEuog4VTSyNu5ibvN9n6lgjq/qLEQ==",
|
||||
"os": ["freebsd"],
|
||||
"cpu": ["x64"]
|
||||
},
|
||||
"@rollup/rollup-linux-arm-gnueabihf@4.50.2": {
|
||||
"integrity": "sha512-9VlPY/BN3AgbukfVHAB8zNFWB/lKEuvzRo1NKev0Po8sYFKx0i+AQlCYftgEjcL43F2h9Ui1ZSdVBc4En/sP2w==",
|
||||
"@rollup/rollup-linux-arm-gnueabihf@4.51.0": {
|
||||
"integrity": "sha512-WEWAGFNFFpvSWAIT3MYvxTkYHv/cJl9yWKpjhheg7ONfB0hetZt/uwBnM3GZqSHrk5bXCDYTFXg3jQyk/j7eXQ==",
|
||||
"os": ["linux"],
|
||||
"cpu": ["arm"]
|
||||
},
|
||||
"@rollup/rollup-linux-arm-musleabihf@4.50.2": {
|
||||
"integrity": "sha512-+GdKWOvsifaYNlIVf07QYan1J5F141+vGm5/Y8b9uCZnG/nxoGqgCmR24mv0koIWWuqvFYnbURRqw1lv7IBINw==",
|
||||
"@rollup/rollup-linux-arm-musleabihf@4.51.0": {
|
||||
"integrity": "sha512-9bxtxj8QoAp++LOq5PGDGkEEOpCDk9rOEHUcXadnijedDH8IXrBt6PnBa4Y6NblvGWdoxvXZYghZLaliTCmAng==",
|
||||
"os": ["linux"],
|
||||
"cpu": ["arm"]
|
||||
},
|
||||
"@rollup/rollup-linux-arm64-gnu@4.50.2": {
|
||||
"integrity": "sha512-df0Eou14ojtUdLQdPFnymEQteENwSJAdLf5KCDrmZNsy1c3YaCNaJvYsEUHnrg+/DLBH612/R0xd3dD03uz2dg==",
|
||||
"@rollup/rollup-linux-arm64-gnu@4.51.0": {
|
||||
"integrity": "sha512-DdqA+fARqIsfqDYkKo2nrWMp0kvu/wPJ2G8lZ4DjYhn+8QhrjVuzmsh7tTkhULwjvHTN59nWVzAixmOi6rqjNA==",
|
||||
"os": ["linux"],
|
||||
"cpu": ["arm64"]
|
||||
},
|
||||
"@rollup/rollup-linux-arm64-musl@4.50.2": {
|
||||
"integrity": "sha512-iPeouV0UIDtz8j1YFR4OJ/zf7evjauqv7jQ/EFs0ClIyL+by++hiaDAfFipjOgyz6y6xbDvJuiU4HwpVMpRFDQ==",
|
||||
"@rollup/rollup-linux-arm64-musl@4.51.0": {
|
||||
"integrity": "sha512-2XVRNzcUJE1UJua8P4a1GXS5jafFWE+pQ6zhUbZzptOu/70p1F6+0FTi6aGPd6jNtnJqGMjtBCXancC2dhYlWw==",
|
||||
"os": ["linux"],
|
||||
"cpu": ["arm64"]
|
||||
},
|
||||
"@rollup/rollup-linux-loong64-gnu@4.50.2": {
|
||||
"integrity": "sha512-OL6KaNvBopLlj5fTa5D5bau4W82f+1TyTZRr2BdnfsrnQnmdxh4okMxR2DcDkJuh4KeoQZVuvHvzuD/lyLn2Kw==",
|
||||
"@rollup/rollup-linux-loong64-gnu@4.51.0": {
|
||||
"integrity": "sha512-R8QhY0kLIPCAVXWi2yftDSpn7Jtejey/WhMoBESSfwGec5SKdFVupjxFlKoQ7clVRuaDpiQf7wNx3EBZf4Ey6g==",
|
||||
"os": ["linux"],
|
||||
"cpu": ["loong64"]
|
||||
},
|
||||
"@rollup/rollup-linux-ppc64-gnu@4.50.2": {
|
||||
"integrity": "sha512-I21VJl1w6z/K5OTRl6aS9DDsqezEZ/yKpbqlvfHbW0CEF5IL8ATBMuUx6/mp683rKTK8thjs/0BaNrZLXetLag==",
|
||||
"@rollup/rollup-linux-ppc64-gnu@4.51.0": {
|
||||
"integrity": "sha512-I498RPfxx9cMv1KTHQ9tg2Ku1utuQm+T5B+Xro+WNu3FzAFSKp4awKfgMoZwjoPgNbaFGINaOM25cQW6WuBhiQ==",
|
||||
"os": ["linux"],
|
||||
"cpu": ["ppc64"]
|
||||
},
|
||||
"@rollup/rollup-linux-riscv64-gnu@4.50.2": {
|
||||
"integrity": "sha512-Hq6aQJT/qFFHrYMjS20nV+9SKrXL2lvFBENZoKfoTH2kKDOJqff5OSJr4x72ZaG/uUn+XmBnGhfr4lwMRrmqCQ==",
|
||||
"@rollup/rollup-linux-riscv64-gnu@4.51.0": {
|
||||
"integrity": "sha512-o8COudsb8lvtdm9ixg9aKjfX5aeoc2x9KGE7WjtrmQFquoCRZ9jtzGlonujE4WhvXFepTraWzT4RcwyDDeHXjA==",
|
||||
"os": ["linux"],
|
||||
"cpu": ["riscv64"]
|
||||
},
|
||||
"@rollup/rollup-linux-riscv64-musl@4.50.2": {
|
||||
"integrity": "sha512-82rBSEXRv5qtKyr0xZ/YMF531oj2AIpLZkeNYxmKNN6I2sVE9PGegN99tYDLK2fYHJITL1P2Lgb4ZXnv0PjQvw==",
|
||||
"@rollup/rollup-linux-riscv64-musl@4.51.0": {
|
||||
"integrity": "sha512-0shJPgSXMdYzOQzpM5BJN2euXY1f8uV8mS6AnrbMcH2KrkNsbpMxWB1wp8UEdiJ1NtyBkCk3U/HfX5mEONBq6w==",
|
||||
"os": ["linux"],
|
||||
"cpu": ["riscv64"]
|
||||
},
|
||||
"@rollup/rollup-linux-s390x-gnu@4.50.2": {
|
||||
"integrity": "sha512-4Q3S3Hy7pC6uaRo9gtXUTJ+EKo9AKs3BXKc2jYypEcMQ49gDPFU2P1ariX9SEtBzE5egIX6fSUmbmGazwBVF9w==",
|
||||
"@rollup/rollup-linux-s390x-gnu@4.51.0": {
|
||||
"integrity": "sha512-L7pV+ny7865jamSCQwyozBYjFRUKaTsPqDz7ClOtJCDu4paf2uAa0mrcHwSt4XxZP2ogFZS9uuitH3NXdeBEJA==",
|
||||
"os": ["linux"],
|
||||
"cpu": ["s390x"]
|
||||
},
|
||||
"@rollup/rollup-linux-x64-gnu@4.50.2": {
|
||||
"integrity": "sha512-9Jie/At6qk70dNIcopcL4p+1UirusEtznpNtcq/u/C5cC4HBX7qSGsYIcG6bdxj15EYWhHiu02YvmdPzylIZlA==",
|
||||
"@rollup/rollup-linux-x64-gnu@4.51.0": {
|
||||
"integrity": "sha512-4YHhP+Rv3T3+H3TPbUvWOw5tuSwhrVhkHHZhk4hC9VXeAOKR26/IsUAT4FsB4mT+kfIdxxb1BezQDEg/voPO8A==",
|
||||
"os": ["linux"],
|
||||
"cpu": ["x64"]
|
||||
},
|
||||
"@rollup/rollup-linux-x64-musl@4.50.2": {
|
||||
"integrity": "sha512-HPNJwxPL3EmhzeAnsWQCM3DcoqOz3/IC6de9rWfGR8ZCuEHETi9km66bH/wG3YH0V3nyzyFEGUZeL5PKyy4xvw==",
|
||||
"@rollup/rollup-linux-x64-musl@4.51.0": {
|
||||
"integrity": "sha512-P7U7U03+E5w7WgJtvSseNLOX1UhknVPmEaqgUENFWfNxNBa1OhExT6qYGmyF8gepcxWSaSfJsAV5UwhWrYefdQ==",
|
||||
"os": ["linux"],
|
||||
"cpu": ["x64"]
|
||||
},
|
||||
"@rollup/rollup-openharmony-arm64@4.50.2": {
|
||||
"integrity": "sha512-nMKvq6FRHSzYfKLHZ+cChowlEkR2lj/V0jYj9JnGUVPL2/mIeFGmVM2mLaFeNa5Jev7W7TovXqXIG2d39y1KYA==",
|
||||
"@rollup/rollup-openharmony-arm64@4.51.0": {
|
||||
"integrity": "sha512-FuD8g3u9W6RPwdO1R45hZFORwa1g9YXEMesAKP/sOi7mDqxjbni8S3zAXJiDcRfGfGBqpRYVuH54Gu3FTuSoEw==",
|
||||
"os": ["openharmony"],
|
||||
"cpu": ["arm64"]
|
||||
},
|
||||
"@rollup/rollup-win32-arm64-msvc@4.50.2": {
|
||||
"integrity": "sha512-eFUvvnTYEKeTyHEijQKz81bLrUQOXKZqECeiWH6tb8eXXbZk+CXSG2aFrig2BQ/pjiVRj36zysjgILkqarS2YA==",
|
||||
"@rollup/rollup-win32-arm64-msvc@4.51.0": {
|
||||
"integrity": "sha512-zST+FdMCX3QAYfmZX3dp/Fy8qLUetfE17QN5ZmmFGPrhl86qvRr+E9u2bk7fzkIXsfQR30Z7ZRS7WMryPPn4rQ==",
|
||||
"os": ["win32"],
|
||||
"cpu": ["arm64"]
|
||||
},
|
||||
"@rollup/rollup-win32-ia32-msvc@4.50.2": {
|
||||
"integrity": "sha512-cBaWmXqyfRhH8zmUxK3d3sAhEWLrtMjWBRwdMMHJIXSjvjLKvv49adxiEz+FJ8AP90apSDDBx2Tyd/WylV6ikA==",
|
||||
"@rollup/rollup-win32-ia32-msvc@4.51.0": {
|
||||
"integrity": "sha512-U+qhoCVAZmTHCmUKxdQxw1jwAFNFXmOpMME7Npt5GTb1W/7itfgAgNluVOvyeuSeqW+dEQLFuNZF3YZPO8XkMg==",
|
||||
"os": ["win32"],
|
||||
"cpu": ["ia32"]
|
||||
},
|
||||
"@rollup/rollup-win32-x64-msvc@4.50.2": {
|
||||
"integrity": "sha512-APwKy6YUhvZaEoHyM+9xqmTpviEI+9eL7LoCH+aLcvWYHJ663qG5zx7WzWZY+a9qkg5JtzcMyJ9z0WtQBMDmgA==",
|
||||
"@rollup/rollup-win32-x64-msvc@4.51.0": {
|
||||
"integrity": "sha512-z6UpFzMhXSD8NNUfCi2HO+pbpSzSWIIPgb1TZsEZjmZYtk6RUIC63JYjlFBwbBZS3jt3f1q6IGfkj3g+GnBt2Q==",
|
||||
"os": ["win32"],
|
||||
"cpu": ["x64"]
|
||||
},
|
||||
@@ -1871,8 +1871,8 @@
|
||||
"reusify@1.1.0": {
|
||||
"integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="
|
||||
},
|
||||
"rollup@4.50.2": {
|
||||
"integrity": "sha512-BgLRGy7tNS9H66aIMASq1qSYbAAJV6Z6WR4QYTvj5FgF15rZ/ympT1uixHXwzbZUBDbkvqUI1KR0fH1FhMaQ9w==",
|
||||
"rollup@4.51.0": {
|
||||
"integrity": "sha512-7cR0XWrdp/UAj2HMY/Y4QQEUjidn3l2AY1wSeZoFjMbD8aOMPoV9wgTFYbrJpPzzvejDEini1h3CiUP8wLzxQA==",
|
||||
"dependencies": [
|
||||
"@types/estree"
|
||||
],
|
||||
@@ -2194,7 +2194,7 @@
|
||||
"npm:@jsr/std__fs@1.0.19",
|
||||
"npm:@jsr/std__path@1.1.2",
|
||||
"npm:@jsr/valkyr__auth@2.1.4",
|
||||
"npm:@jsr/valkyr__event-store@2",
|
||||
"npm:@jsr/valkyr__event-store@2.0.1",
|
||||
"npm:@jsr/valkyr__inverse@1.0.1",
|
||||
"npm:@jsr/valkyr__json-rpc@1.1.0",
|
||||
"npm:cookie@1.0.2",
|
||||
@@ -2231,7 +2231,14 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"spec/relay": {
|
||||
"platform/models": {
|
||||
"packageJson": {
|
||||
"dependencies": [
|
||||
"npm:zod@4"
|
||||
]
|
||||
}
|
||||
},
|
||||
"platform/relay": {
|
||||
"packageJson": {
|
||||
"dependencies": [
|
||||
"npm:path-to-regexp@8",
|
||||
@@ -2239,7 +2246,7 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"spec/schemas": {
|
||||
"platform/spec": {
|
||||
"packageJson": {
|
||||
"dependencies": [
|
||||
"npm:zod@4"
|
||||
|
||||
25
platform/models/account.ts
Normal file
25
platform/models/account.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { RoleSchema } from "@platform/spec/account/role.ts";
|
||||
import { StrategySchema } from "@platform/spec/account/strategies.ts";
|
||||
import { z } from "zod";
|
||||
|
||||
import { makeModelParser } from "./helpers/parser.ts";
|
||||
import { AvatarSchema } from "./value-objects/avatar.ts";
|
||||
import { ContactSchema } from "./value-objects/contact.ts";
|
||||
import { NameSchema } from "./value-objects/name.ts";
|
||||
|
||||
export const AccountSchema = z.object({
|
||||
id: z.uuid(),
|
||||
avatar: AvatarSchema.optional(),
|
||||
name: NameSchema.optional(),
|
||||
contact: ContactSchema.default({
|
||||
emails: [],
|
||||
}),
|
||||
strategies: z.array(StrategySchema).default([]),
|
||||
roles: z.array(RoleSchema).default([]),
|
||||
});
|
||||
|
||||
export const toAccountDocument = makeModelParser(AccountSchema);
|
||||
export const fromAccountDocument = makeModelParser(AccountSchema);
|
||||
|
||||
export type Account = z.infer<typeof AccountSchema>;
|
||||
export type AccountDocument = z.infer<typeof AccountSchema>;
|
||||
@@ -1,15 +1,15 @@
|
||||
import z, { ZodObject } from "zod";
|
||||
|
||||
export function makeSchemaParser<TSchema extends ZodObject>(schema: TSchema): SchemaParserFn<TSchema> {
|
||||
export function makeModelParser<TSchema extends ZodObject>(schema: TSchema): ModelParserFn<TSchema> {
|
||||
return ((value: unknown | unknown[]) => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map((value: unknown) => schema.parse(value));
|
||||
}
|
||||
return schema.parse(value);
|
||||
}) as SchemaParserFn<TSchema>;
|
||||
}) as ModelParserFn<TSchema>;
|
||||
}
|
||||
|
||||
type SchemaParserFn<TSchema extends ZodObject> = {
|
||||
type ModelParserFn<TSchema extends ZodObject> = {
|
||||
(value: unknown): z.infer<TSchema>;
|
||||
(value: unknown[]): z.infer<TSchema>[];
|
||||
};
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "@spec/schemas",
|
||||
"name": "@platform/models",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@spec/relay": "workspace:*",
|
||||
"@platform/spec": "workspace:*",
|
||||
"zod": "4"
|
||||
}
|
||||
}
|
||||
@@ -49,18 +49,11 @@ export type RelayAdapter = {
|
||||
getUrl(endpoint: string): string;
|
||||
|
||||
/**
|
||||
* Send a 'application/json' request to the configured relay url.
|
||||
* Send a request to the configured relay url.
|
||||
*
|
||||
* @param input - Request input parameters.
|
||||
*/
|
||||
json(input: RelayInput): Promise<RelayResponse>;
|
||||
|
||||
/**
|
||||
* Send a form data request to the configured relay url.
|
||||
*
|
||||
* @param input - Request input parameters.
|
||||
*/
|
||||
data(input: RelayInput): Promise<RelayResponse>;
|
||||
send(input: RelayInput): Promise<RelayResponse>;
|
||||
|
||||
/**
|
||||
* Sends a fetch request using the given options and returns a
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-empty-object-type */
|
||||
|
||||
import z, { ZodObject, ZodType } from "zod";
|
||||
import type { ZodObject, ZodType } from "zod";
|
||||
|
||||
import type { RelayAdapter, RelayInput, RelayResponse } from "./adapter.ts";
|
||||
import { Route, type Routes } from "./route.ts";
|
||||
@@ -110,10 +110,10 @@ function getRouteFn(route: Route, { adapter }: Config) {
|
||||
|
||||
// ### Fetch
|
||||
|
||||
const response = route.state.content === "json" ? await adapter.json(input) : await adapter.data(input);
|
||||
const response = await adapter.send(input);
|
||||
|
||||
if ("data" in response && route.state.output !== undefined) {
|
||||
response.data = route.state.output.parse(response.data);
|
||||
if ("data" in response && route.state.response !== undefined) {
|
||||
response.data = route.state.response.parse(response.data);
|
||||
}
|
||||
|
||||
return response;
|
||||
@@ -179,9 +179,7 @@ type RouteResponse<TRoute extends Route> = Promise<RelayResponse<RouteOutput<TRo
|
||||
$response: TRoute["$response"];
|
||||
};
|
||||
|
||||
type RouteOutput<TRoute extends Route> = TRoute["state"]["output"] extends ZodType
|
||||
? z.infer<TRoute["state"]["output"]>
|
||||
: null;
|
||||
type RouteOutput<TRoute extends Route> = TRoute["state"]["response"] extends ZodType ? TRoute["$response"] : null;
|
||||
|
||||
type RouteErrors<TRoute extends Route> = InstanceType<TRoute["state"]["errors"][number]>;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@spec/relay",
|
||||
"name": "@platform/relay",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ConflictError } from "@spec/relay";
|
||||
import { ConflictError } from "@platform/relay";
|
||||
|
||||
export class AccountEmailClaimedError extends ConflictError {
|
||||
constructor(email: string) {
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ForbiddenError, NotFoundError, route, UnauthorizedError } from "@spec/relay";
|
||||
import { AccountSchema } from "@platform/models/account.ts";
|
||||
import { NameSchema } from "@platform/models/value-objects/name.ts";
|
||||
import { ForbiddenError, NotFoundError, route, UnauthorizedError } from "@platform/relay";
|
||||
import z from "zod";
|
||||
|
||||
import { NameSchema } from "../name.ts";
|
||||
import { AccountSchema } from "./account.ts";
|
||||
import { AccountEmailClaimedError } from "./errors.ts";
|
||||
|
||||
export const create = route
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BadRequestError } from "@spec/relay";
|
||||
import { BadRequestError } from "@platform/relay";
|
||||
|
||||
export class AuthenticationStrategyPayloadError extends BadRequestError {
|
||||
constructor() {
|
||||
@@ -1,8 +1,7 @@
|
||||
import { route, UnauthorizedError } from "@spec/relay";
|
||||
import { AccountSchema } from "@platform/models/account.ts";
|
||||
import { route, UnauthorizedError } from "@platform/relay";
|
||||
import z from "zod";
|
||||
|
||||
import { AccountSchema } from "../account/account.ts";
|
||||
|
||||
export * from "./errors.ts";
|
||||
export * from "./strategies.ts";
|
||||
|
||||
11
platform/spec/package.json
Normal file
11
platform/spec/package.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "@platform/spec",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@platform/models": "workspace:*",
|
||||
"@platform/relay": "workspace:*",
|
||||
"zod": "4"
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
# Spec
|
||||
|
||||
Todo ...
|
||||
@@ -1 +0,0 @@
|
||||
# Modules
|
||||
@@ -1,25 +0,0 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { AvatarSchema } from "../avatar.ts";
|
||||
import { ContactSchema } from "../contact.ts";
|
||||
import { makeSchemaParser } from "../database.ts";
|
||||
import { NameSchema } from "../name.ts";
|
||||
import { RoleSchema } from "./role.ts";
|
||||
import { StrategySchema } from "./strategies.ts";
|
||||
|
||||
export const AccountSchema = z.object({
|
||||
id: z.uuid(),
|
||||
avatar: AvatarSchema.optional(),
|
||||
name: NameSchema.optional(),
|
||||
contact: ContactSchema.default({
|
||||
emails: [],
|
||||
}),
|
||||
strategies: z.array(StrategySchema).default([]),
|
||||
roles: z.array(RoleSchema).default([]),
|
||||
});
|
||||
|
||||
export const toAccountDocument = makeSchemaParser(AccountSchema);
|
||||
export const fromAccountDocument = makeSchemaParser(AccountSchema);
|
||||
|
||||
export type Account = z.infer<typeof AccountSchema>;
|
||||
export type AccountDocument = z.infer<typeof AccountSchema>;
|
||||
Reference in New Issue
Block a user