diff --git a/api/libraries/auth/principal.ts b/api/libraries/auth/principal.ts index 1362395..b3dbd7d 100644 --- a/api/libraries/auth/principal.ts +++ b/api/libraries/auth/principal.ts @@ -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"; diff --git a/api/libraries/logger/format/server.ts b/api/libraries/logger/format/server.ts index ba8e8ea..c8a7584 100644 --- a/api/libraries/logger/format/server.ts +++ b/api/libraries/logger/format/server.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"; diff --git a/api/libraries/server/api.ts b/api/libraries/server/api.ts index 8776b70..44e0f64 100644 --- a/api/libraries/server/api.ts +++ b/api/libraries/server/api.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"; diff --git a/api/libraries/server/context.ts b/api/libraries/server/context.ts index 6972d34..d1e0929 100644 --- a/api/libraries/server/context.ts +++ b/api/libraries/server/context.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. diff --git a/api/libraries/server/modules.ts b/api/libraries/server/modules.ts index c5bbee0..9a4765d 100644 --- a/api/libraries/server/modules.ts +++ b/api/libraries/server/modules.ts @@ -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' diff --git a/api/libraries/server/request.ts b/api/libraries/server/request.ts index a76d5db..d3e9889 100644 --- a/api/libraries/server/request.ts +++ b/api/libraries/server/request.ts @@ -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"; diff --git a/api/package.json b/api/package.json index ac65ba0..adcf4e4 100644 --- a/api/package.json +++ b/api/package.json @@ -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", diff --git a/api/procedures/event.ts b/api/procedures/event.ts index a252e41..3fe67f2 100644 --- a/api/procedures/event.ts +++ b/api/procedures/event.ts @@ -1,4 +1,4 @@ -import { procedure } from "@spec/relay/mod.ts"; +import { procedure } from "@platform/relay"; import z from "zod"; const EventSchema = z.object({ diff --git a/api/routes/account/create.ts b/api/routes/account/create.ts index e496ae5..5cac17d 100644 --- a/api/routes/account/create.ts +++ b/api/routes/account/create.ts @@ -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"; diff --git a/api/routes/account/get-by-id.ts b/api/routes/account/get-by-id.ts index 7675eec..151b9e9 100644 --- a/api/routes/account/get-by-id.ts +++ b/api/routes/account/get-by-id.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"; diff --git a/api/routes/auth/code.ts b/api/routes/auth/code.ts index 82abdf6..d105534 100644 --- a/api/routes/auth/code.ts +++ b/api/routes/auth/code.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"; diff --git a/api/routes/auth/email.ts b/api/routes/auth/email.ts index 29fcc64..56a10d7 100644 --- a/api/routes/auth/email.ts +++ b/api/routes/auth/email.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"; diff --git a/api/routes/auth/password.ts b/api/routes/auth/password.ts index a9249c0..a9ac703 100644 --- a/api/routes/auth/password.ts +++ b/api/routes/auth/password.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"; diff --git a/api/routes/auth/session.ts b/api/routes/auth/session.ts index cd82222..f920cb8 100644 --- a/api/routes/auth/session.ts +++ b/api/routes/auth/session.ts @@ -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"; diff --git a/api/stores/event-store/aggregates/account.ts b/api/stores/event-store/aggregates/account.ts index 5c76481..cdcff45 100644 --- a/api/stores/event-store/aggregates/account.ts +++ b/api/stores/event-store/aggregates/account.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 { @@ -32,11 +32,12 @@ export class Account extends AggregateRoot { // 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 }; diff --git a/api/stores/event-store/aggregates/code.ts b/api/stores/event-store/aggregates/code.ts index 067f5e6..0f01b8f 100644 --- a/api/stores/event-store/aggregates/code.ts +++ b/api/stores/event-store/aggregates/code.ts @@ -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 { static override readonly name = "code"; @@ -24,7 +24,7 @@ export class Code extends AggregateRoot { // Folder // ------------------------------------------------------------------------- - with(event: EventStoreFactory["$events"][number]["$record"]): void { + with(event: EventRecord): void { switch (event.type) { case "code:created": { this.value = event.data.value; diff --git a/api/stores/event-store/events/account.ts b/api/stores/event-store/events/account.ts index 22ca814..0b4e2ce 100644 --- a/api/stores/event-store/events/account.ts +++ b/api/stores/event-store/events/account.ts @@ -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"; diff --git a/api/stores/event-store/events/mod.ts b/api/stores/event-store/events/mod.ts index 5313bae..6aada05 100644 --- a/api/stores/event-store/events/mod.ts +++ b/api/stores/event-store/events/mod.ts @@ -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; diff --git a/api/stores/read-store/database.ts b/api/stores/read-store/database.ts index 6788d41..1ef1e90 100644 --- a/api/stores/read-store/database.ts +++ b/api/stores/read-store/database.ts @@ -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"; diff --git a/api/stores/read-store/methods.ts b/api/stores/read-store/methods.ts index 5e1062e..f9a4602 100644 --- a/api/stores/read-store/methods.ts +++ b/api/stores/read-store/methods.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"; diff --git a/apps/react/package.json b/apps/react/package.json index 34c83c0..5cefc4f 100644 --- a/apps/react/package.json +++ b/apps/react/package.json @@ -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", diff --git a/apps/react/src/adapters/http.ts b/apps/react/src/adapters/http.ts index 1135e02..33fa288 100644 --- a/apps/react/src/adapters/http.ts +++ b/apps/react/src/adapters/http.ts @@ -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 { + async send({ method, endpoint, query, body, headers = new Headers() }: RelayInput): Promise { 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) { - init.body = JSON.stringify(body); - } - - // ### Response - - return this.request(`${endpoint}${query}`, init); - } - - async data({ method, endpoint, query, body, headers = new Headers() }: RelayInput): Promise { - 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)); - } - } + 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); } - 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, 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, formData, `${formKey}[${index}]`); + } else { + formData.append(`${formKey}[${index}]`, String(item)); + } + }); + } else if (typeof value === "object") { + this.#getFormData(value as Record, 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?: { diff --git a/apps/react/src/services/api.ts b/apps/react/src/services/api.ts index 2f23a83..83ff98d 100644 --- a/apps/react/src/services/api.ts +++ b/apps/react/src/services/api.ts @@ -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, }, ); diff --git a/deno.json b/deno.json index 8aab12b..98b4415 100644 --- a/deno.json +++ b/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": { diff --git a/deno.lock b/deno.lock index 40146c0..7ae5159 100644 --- a/deno.lock +++ b/deno.lock @@ -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" diff --git a/platform/models/account.ts b/platform/models/account.ts new file mode 100644 index 0000000..1da1964 --- /dev/null +++ b/platform/models/account.ts @@ -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; +export type AccountDocument = z.infer; diff --git a/spec/schemas/database.ts b/platform/models/helpers/parser.ts similarity index 61% rename from spec/schemas/database.ts rename to platform/models/helpers/parser.ts index a708966..6a76b68 100644 --- a/spec/schemas/database.ts +++ b/platform/models/helpers/parser.ts @@ -1,15 +1,15 @@ import z, { ZodObject } from "zod"; -export function makeSchemaParser(schema: TSchema): SchemaParserFn { +export function makeModelParser(schema: TSchema): ModelParserFn { return ((value: unknown | unknown[]) => { if (Array.isArray(value)) { return value.map((value: unknown) => schema.parse(value)); } return schema.parse(value); - }) as SchemaParserFn; + }) as ModelParserFn; } -type SchemaParserFn = { +type ModelParserFn = { (value: unknown): z.infer; (value: unknown[]): z.infer[]; }; diff --git a/spec/schemas/package.json b/platform/models/package.json similarity index 60% rename from spec/schemas/package.json rename to platform/models/package.json index 2a57cdf..96904ce 100644 --- a/spec/schemas/package.json +++ b/platform/models/package.json @@ -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" } } \ No newline at end of file diff --git a/spec/schemas/avatar.ts b/platform/models/value-objects/avatar.ts similarity index 100% rename from spec/schemas/avatar.ts rename to platform/models/value-objects/avatar.ts diff --git a/spec/schemas/contact.ts b/platform/models/value-objects/contact.ts similarity index 100% rename from spec/schemas/contact.ts rename to platform/models/value-objects/contact.ts diff --git a/spec/schemas/email.ts b/platform/models/value-objects/email.ts similarity index 100% rename from spec/schemas/email.ts rename to platform/models/value-objects/email.ts diff --git a/spec/schemas/name.ts b/platform/models/value-objects/name.ts similarity index 100% rename from spec/schemas/name.ts rename to platform/models/value-objects/name.ts diff --git a/spec/relay/libraries/adapter.ts b/platform/relay/libraries/adapter.ts similarity index 87% rename from spec/relay/libraries/adapter.ts rename to platform/relay/libraries/adapter.ts index 2aec300..f2736ae 100644 --- a/spec/relay/libraries/adapter.ts +++ b/platform/relay/libraries/adapter.ts @@ -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; - - /** - * Send a form data request to the configured relay url. - * - * @param input - Request input parameters. - */ - data(input: RelayInput): Promise; + send(input: RelayInput): Promise; /** * Sends a fetch request using the given options and returns a diff --git a/spec/relay/libraries/client.ts b/platform/relay/libraries/client.ts similarity index 93% rename from spec/relay/libraries/client.ts rename to platform/relay/libraries/client.ts index 075b6c1..f78df05 100644 --- a/spec/relay/libraries/client.ts +++ b/platform/relay/libraries/client.ts @@ -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 = Promise = TRoute["state"]["output"] extends ZodType - ? z.infer - : null; +type RouteOutput = TRoute["state"]["response"] extends ZodType ? TRoute["$response"] : null; type RouteErrors = InstanceType; diff --git a/spec/relay/libraries/errors.ts b/platform/relay/libraries/errors.ts similarity index 100% rename from spec/relay/libraries/errors.ts rename to platform/relay/libraries/errors.ts diff --git a/spec/relay/libraries/hooks.ts b/platform/relay/libraries/hooks.ts similarity index 100% rename from spec/relay/libraries/hooks.ts rename to platform/relay/libraries/hooks.ts diff --git a/spec/relay/libraries/procedure.ts b/platform/relay/libraries/procedure.ts similarity index 100% rename from spec/relay/libraries/procedure.ts rename to platform/relay/libraries/procedure.ts diff --git a/spec/relay/libraries/route.ts b/platform/relay/libraries/route.ts similarity index 100% rename from spec/relay/libraries/route.ts rename to platform/relay/libraries/route.ts diff --git a/spec/relay/mod.ts b/platform/relay/mod.ts similarity index 100% rename from spec/relay/mod.ts rename to platform/relay/mod.ts diff --git a/spec/relay/package.json b/platform/relay/package.json similarity index 86% rename from spec/relay/package.json rename to platform/relay/package.json index beea7f4..fc490eb 100644 --- a/spec/relay/package.json +++ b/platform/relay/package.json @@ -1,5 +1,5 @@ { - "name": "@spec/relay", + "name": "@platform/relay", "version": "0.0.0", "private": true, "type": "module", diff --git a/spec/schemas/account/errors.ts b/platform/spec/account/errors.ts similarity index 77% rename from spec/schemas/account/errors.ts rename to platform/spec/account/errors.ts index 96419fe..50fc6c2 100644 --- a/spec/schemas/account/errors.ts +++ b/platform/spec/account/errors.ts @@ -1,4 +1,4 @@ -import { ConflictError } from "@spec/relay"; +import { ConflictError } from "@platform/relay"; export class AccountEmailClaimedError extends ConflictError { constructor(email: string) { diff --git a/spec/schemas/account/role.ts b/platform/spec/account/role.ts similarity index 100% rename from spec/schemas/account/role.ts rename to platform/spec/account/role.ts diff --git a/spec/schemas/account/routes.ts b/platform/spec/account/routes.ts similarity index 78% rename from spec/schemas/account/routes.ts rename to platform/spec/account/routes.ts index 2a4322c..5443d56 100644 --- a/spec/schemas/account/routes.ts +++ b/platform/spec/account/routes.ts @@ -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 diff --git a/spec/schemas/account/strategies.ts b/platform/spec/account/strategies.ts similarity index 100% rename from spec/schemas/account/strategies.ts rename to platform/spec/account/strategies.ts diff --git a/spec/schemas/auth/errors.ts b/platform/spec/auth/errors.ts similarity index 76% rename from spec/schemas/auth/errors.ts rename to platform/spec/auth/errors.ts index e6d732e..37a807b 100644 --- a/spec/schemas/auth/errors.ts +++ b/platform/spec/auth/errors.ts @@ -1,4 +1,4 @@ -import { BadRequestError } from "@spec/relay"; +import { BadRequestError } from "@platform/relay"; export class AuthenticationStrategyPayloadError extends BadRequestError { constructor() { diff --git a/spec/schemas/auth/routes.ts b/platform/spec/auth/routes.ts similarity index 86% rename from spec/schemas/auth/routes.ts rename to platform/spec/auth/routes.ts index d98ed2f..4943e82 100644 --- a/spec/schemas/auth/routes.ts +++ b/platform/spec/auth/routes.ts @@ -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"; diff --git a/spec/schemas/auth/strategies.ts b/platform/spec/auth/strategies.ts similarity index 100% rename from spec/schemas/auth/strategies.ts rename to platform/spec/auth/strategies.ts diff --git a/platform/spec/package.json b/platform/spec/package.json new file mode 100644 index 0000000..8fc2ac4 --- /dev/null +++ b/platform/spec/package.json @@ -0,0 +1,11 @@ +{ + "name": "@platform/spec", + "version": "0.0.0", + "private": true, + "type": "module", + "dependencies": { + "@platform/models": "workspace:*", + "@platform/relay": "workspace:*", + "zod": "4" + } +} \ No newline at end of file diff --git a/spec/README.md b/spec/README.md deleted file mode 100644 index 4126644..0000000 --- a/spec/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Spec - -Todo ... diff --git a/spec/schemas/README.md b/spec/schemas/README.md deleted file mode 100644 index 016d434..0000000 --- a/spec/schemas/README.md +++ /dev/null @@ -1 +0,0 @@ -# Modules \ No newline at end of file diff --git a/spec/schemas/account/account.ts b/spec/schemas/account/account.ts deleted file mode 100644 index 273f0af..0000000 --- a/spec/schemas/account/account.ts +++ /dev/null @@ -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; -export type AccountDocument = z.infer;