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 { PrincipalProvider } from "@valkyr/auth";
|
||||||
|
|
||||||
import { db } from "~stores/read-store/database.ts";
|
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 type { Level } from "../level.ts";
|
||||||
import { getTracedAt } from "../stack.ts";
|
import { getTracedAt } from "../stack.ts";
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
type ServerErrorResponse,
|
type ServerErrorResponse,
|
||||||
UnauthorizedError,
|
UnauthorizedError,
|
||||||
ZodValidationError,
|
ZodValidationError,
|
||||||
} from "@spec/relay";
|
} from "@platform/relay";
|
||||||
import { treeifyError } from "zod";
|
import { treeifyError } from "zod";
|
||||||
|
|
||||||
import { logger } from "~libraries/logger/mod.ts";
|
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";
|
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 { Principal } from "../auth/principal.ts";
|
||||||
import { req } from "./request.ts";
|
import { req } from "./request.ts";
|
||||||
|
|
||||||
declare module "@spec/relay" {
|
declare module "@platform/relay" {
|
||||||
interface ServerContext {
|
interface ServerContext {
|
||||||
/**
|
/**
|
||||||
* Current request instance being handled.
|
* 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'
|
* 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 { Session } from "../auth/auth.ts";
|
||||||
import { storage } from "./storage.ts";
|
import { storage } from "./storage.ts";
|
||||||
|
|||||||
@@ -7,15 +7,15 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cerbos/http": "0.23.1",
|
"@cerbos/http": "0.23.1",
|
||||||
"@felix/bcrypt": "npm:@jsr/felix__bcrypt@1.0.5",
|
"@felix/bcrypt": "npm:@jsr/felix__bcrypt@1.0.5",
|
||||||
"@spec/modules": "workspace:*",
|
"@platform/models": "workspace:*",
|
||||||
"@spec/relay": "workspace:*",
|
"@platform/relay": "workspace:*",
|
||||||
"@spec/shared": "workspace:*",
|
"@platform/spec": "workspace:*",
|
||||||
"@std/cli": "npm:@jsr/std__cli@1.0.22",
|
"@std/cli": "npm:@jsr/std__cli@1.0.22",
|
||||||
"@std/dotenv": "npm:@jsr/std__dotenv@0.225.5",
|
"@std/dotenv": "npm:@jsr/std__dotenv@0.225.5",
|
||||||
"@std/fs": "npm:@jsr/std__fs@1.0.19",
|
"@std/fs": "npm:@jsr/std__fs@1.0.19",
|
||||||
"@std/path": "npm:@jsr/std__path@1.1.2",
|
"@std/path": "npm:@jsr/std__path@1.1.2",
|
||||||
"@valkyr/auth": "npm:@jsr/valkyr__auth@2.1.4",
|
"@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/inverse": "npm:@jsr/valkyr__inverse@1.0.1",
|
||||||
"@valkyr/json-rpc": "npm:@jsr/valkyr__json-rpc@1.1.0",
|
"@valkyr/json-rpc": "npm:@jsr/valkyr__json-rpc@1.1.0",
|
||||||
"cookie": "1.0.2",
|
"cookie": "1.0.2",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { procedure } from "@spec/relay/mod.ts";
|
import { procedure } from "@platform/relay";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
|
|
||||||
const EventSchema = z.object({
|
const EventSchema = z.object({
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { AccountEmailClaimedError } from "@spec/schemas/account/errors.ts";
|
import { AccountEmailClaimedError } from "@platform/spec/account/errors.ts";
|
||||||
import { create } from "@spec/schemas/account/routes.ts";
|
import { create } from "@platform/spec/account/routes.ts";
|
||||||
|
|
||||||
import { Account, isEmailClaimed } from "~stores/event-store/aggregates/account.ts";
|
import { Account, isEmailClaimed } from "~stores/event-store/aggregates/account.ts";
|
||||||
import { eventStore } from "~stores/event-store/event-store.ts";
|
import { eventStore } from "~stores/event-store/event-store.ts";
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { ForbiddenError } from "@spec/relay/mod.ts";
|
import { ForbiddenError, NotFoundError } from "@platform/relay";
|
||||||
import { NotFoundError } from "@spec/relay/mod.ts";
|
import { getById } from "@platform/spec/account/routes.ts";
|
||||||
import { getById } from "@spec/schemas/account/routes.ts";
|
|
||||||
|
|
||||||
import { db } from "~stores/read-store/database.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 cookie from "cookie";
|
||||||
|
|
||||||
import { auth, config } from "~libraries/auth/mod.ts";
|
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 { logger } from "~libraries/logger/mod.ts";
|
||||||
import { Account, getAccountEmailRelation } from "~stores/event-store/aggregates/account.ts";
|
import { Account, getAccountEmailRelation } from "~stores/event-store/aggregates/account.ts";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { BadRequestError } from "@spec/relay";
|
import { BadRequestError } from "@platform/relay";
|
||||||
import { password as route } from "@spec/schemas/auth/routes.ts";
|
import { password as route } from "@platform/spec/auth/routes.ts";
|
||||||
import cookie from "cookie";
|
import cookie from "cookie";
|
||||||
|
|
||||||
import { config } from "~config";
|
import { config } from "~config";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { UnauthorizedError } from "@spec/relay/mod.ts";
|
import { UnauthorizedError } from "@platform/relay";
|
||||||
import { session } from "@spec/schemas/auth/routes.ts";
|
import { session } from "@platform/spec/auth/routes.ts";
|
||||||
|
|
||||||
import { getAccountById } from "~stores/read-store/methods.ts";
|
import { getAccountById } from "~stores/read-store/methods.ts";
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import { toAccountDocument } from "@spec/schemas/account/account.ts";
|
import { toAccountDocument } from "@platform/models/account.ts";
|
||||||
import { Role } from "@spec/schemas/account/role.ts";
|
import { Avatar } from "@platform/models/value-objects/avatar.ts";
|
||||||
import { Strategy } from "@spec/schemas/account/strategies.ts";
|
import { Contact } from "@platform/models/value-objects/contact.ts";
|
||||||
import { Avatar } from "@spec/schemas/avatar.ts";
|
import { Email } from "@platform/models/value-objects/email.ts";
|
||||||
import { Contact } from "@spec/schemas/contact.ts";
|
import { Name } from "@platform/models/value-objects/name.ts";
|
||||||
import { Email } from "@spec/schemas/email.ts";
|
import { Role } from "@platform/spec/account/role.ts";
|
||||||
import { Name } from "@spec/schemas/name.ts";
|
import { Strategy } from "@platform/spec/account/strategies.ts";
|
||||||
import { AggregateRoot, getDate } from "@valkyr/event-store";
|
import { AggregateRoot, getDate } from "@valkyr/event-store";
|
||||||
|
|
||||||
import { db } from "~stores/read-store/database.ts";
|
import { db } from "~stores/read-store/database.ts";
|
||||||
|
|
||||||
import { eventStore } from "../event-store.ts";
|
import { eventStore } from "../event-store.ts";
|
||||||
import { Auditor, systemAuditor } from "../events/auditor.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";
|
import { projector } from "../projector.ts";
|
||||||
|
|
||||||
export class Account extends AggregateRoot<EventStoreFactory> {
|
export class Account extends AggregateRoot<EventStoreFactory> {
|
||||||
@@ -32,11 +32,12 @@ export class Account extends AggregateRoot<EventStoreFactory> {
|
|||||||
// Reducer
|
// Reducer
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
with(event: EventStoreFactory["$events"][number]["$record"]): void {
|
with(event: EventRecord): void {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case "account:created": {
|
case "account:created": {
|
||||||
this.id = event.stream;
|
this.id = event.stream;
|
||||||
this.createdAt = getDate(event.created);
|
this.createdAt = getDate(event.created);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
case "account:avatar:added": {
|
case "account:avatar:added": {
|
||||||
this.avatar = { url: event.data };
|
this.avatar = { url: event.data };
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { AggregateRoot, getDate } from "@valkyr/event-store";
|
import { AggregateRoot, getDate } from "@valkyr/event-store";
|
||||||
|
|
||||||
import { CodeIdentity } from "../events/code.ts";
|
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> {
|
export class Code extends AggregateRoot<EventStoreFactory> {
|
||||||
static override readonly name = "code";
|
static override readonly name = "code";
|
||||||
@@ -24,7 +24,7 @@ export class Code extends AggregateRoot<EventStoreFactory> {
|
|||||||
// Folder
|
// Folder
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
with(event: EventStoreFactory["$events"][number]["$record"]): void {
|
with(event: EventRecord): void {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case "code:created": {
|
case "code:created": {
|
||||||
this.value = event.data.value;
|
this.value = event.data.value;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { RoleSchema } from "@spec/schemas/account/role.ts";
|
import { EmailSchema } from "@platform/models/value-objects/email.ts";
|
||||||
import { EmailSchema } from "@spec/schemas/email.ts";
|
import { NameSchema } from "@platform/models/value-objects/name.ts";
|
||||||
import { NameSchema } from "@spec/schemas/name.ts";
|
import { RoleSchema } from "@platform/spec/account/role.ts";
|
||||||
import { event } from "@valkyr/event-store";
|
import { event } from "@valkyr/event-store";
|
||||||
import z from "zod";
|
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 account from "./account.ts";
|
||||||
import code from "./code.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 const events = new EventFactory([...account, ...code, ...organization, ...strategy]);
|
||||||
|
|
||||||
export type EventStoreFactory = typeof events;
|
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 { config } from "~config";
|
||||||
import { getDatabaseAccessor } from "~libraries/database/accessor.ts";
|
import { getDatabaseAccessor } from "~libraries/database/accessor.ts";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { type Account, fromAccountDocument } from "@spec/schemas/account/account.ts";
|
import { Account, fromAccountDocument } from "@platform/models/account.ts";
|
||||||
import { PasswordStrategy } from "@spec/schemas/auth/strategies.ts";
|
import { PasswordStrategy } from "@platform/spec/auth/strategies.ts";
|
||||||
|
|
||||||
import { db, takeOne } from "./database.ts";
|
import { db, takeOne } from "./database.ts";
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@spec/relay": "workspace:*",
|
"@platform/relay": "workspace:*",
|
||||||
"@spec/schemas": "workspace:*",
|
"@platform/spec": "workspace:*",
|
||||||
"@tanstack/react-query": "5.89.0",
|
"@tanstack/react-query": "5.89.0",
|
||||||
"@tanstack/react-router": "1.131.47",
|
"@tanstack/react-router": "1.131.47",
|
||||||
"@valkyr/db": "npm:@jsr/valkyr__db@2.0.0",
|
"@valkyr/db": "npm:@jsr/valkyr__db@2.0.0",
|
||||||
|
|||||||
@@ -6,8 +6,37 @@ import {
|
|||||||
ServerError,
|
ServerError,
|
||||||
type ServerErrorResponse,
|
type ServerErrorResponse,
|
||||||
type ServerErrorType,
|
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 {
|
export class HttpAdapter implements RelayAdapter {
|
||||||
/**
|
/**
|
||||||
* Instantiate a new HttpAdapter instance.
|
* Instantiate a new HttpAdapter instance.
|
||||||
@@ -39,12 +68,7 @@ export class HttpAdapter implements RelayAdapter {
|
|||||||
return `${this.url}${endpoint}`;
|
return `${this.url}${endpoint}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async send({ method, endpoint, query, body, headers = new Headers() }: RelayInput): Promise<RelayResponse> {
|
||||||
* 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> {
|
|
||||||
const init: RequestInit = { method, headers };
|
const init: RequestInit = { method, headers };
|
||||||
|
|
||||||
// ### Before Request
|
// ### Before Request
|
||||||
@@ -53,66 +77,18 @@ export class HttpAdapter implements RelayAdapter {
|
|||||||
|
|
||||||
await this.#beforeRequest(headers);
|
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
|
// ### Body
|
||||||
|
|
||||||
if (body !== undefined) {
|
if (body !== undefined) {
|
||||||
init.body = JSON.stringify(body);
|
const type = this.#getRequestFormat(body);
|
||||||
}
|
if (type === "form-data") {
|
||||||
|
headers.delete("content-type");
|
||||||
// ### Response
|
init.body = this.#getFormData(body);
|
||||||
|
}
|
||||||
return this.request(`${endpoint}${query}`, init);
|
if (type === "json") {
|
||||||
}
|
headers.set("content-type", "application/json");
|
||||||
|
init.body = JSON.stringify(body);
|
||||||
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
|
// ### 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.
|
* Convert a fetch response to a compliant relay response.
|
||||||
*
|
*
|
||||||
@@ -159,7 +181,6 @@ export class HttpAdapter implements RelayAdapter {
|
|||||||
if (type === null) {
|
if (type === null) {
|
||||||
return {
|
return {
|
||||||
result: "error",
|
result: "error",
|
||||||
headers: response.headers,
|
|
||||||
error: {
|
error: {
|
||||||
status: response.status,
|
status: response.status,
|
||||||
message: "Missing 'content-type' in header returned from server.",
|
message: "Missing 'content-type' in header returned from server.",
|
||||||
@@ -174,34 +195,10 @@ export class HttpAdapter implements RelayAdapter {
|
|||||||
if (response.status === 204) {
|
if (response.status === 204) {
|
||||||
return {
|
return {
|
||||||
result: "success",
|
result: "success",
|
||||||
headers: response.headers,
|
|
||||||
data: null,
|
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
|
// ### JSON
|
||||||
// If the 'content-type' contains 'json' we treat it as a 'json' compliant response
|
// If the 'content-type' contains 'json' we treat it as a 'json' compliant response
|
||||||
// and attempt to resolve it as such.
|
// and attempt to resolve it as such.
|
||||||
@@ -211,20 +208,17 @@ export class HttpAdapter implements RelayAdapter {
|
|||||||
if ("data" in parsed) {
|
if ("data" in parsed) {
|
||||||
return {
|
return {
|
||||||
result: "success",
|
result: "success",
|
||||||
headers: response.headers,
|
|
||||||
data: parsed.data,
|
data: parsed.data,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if ("error" in parsed) {
|
if ("error" in parsed) {
|
||||||
return {
|
return {
|
||||||
result: "error",
|
result: "error",
|
||||||
headers: response.headers,
|
|
||||||
error: this.#toError(parsed),
|
error: this.#toError(parsed),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
result: "error",
|
result: "error",
|
||||||
headers: response.headers,
|
|
||||||
error: {
|
error: {
|
||||||
status: response.status,
|
status: response.status,
|
||||||
message: "Unsupported 'json' body returned from server, missing 'data' or 'error' key.",
|
message: "Unsupported 'json' body returned from server, missing 'data' or 'error' key.",
|
||||||
@@ -234,7 +228,6 @@ export class HttpAdapter implements RelayAdapter {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
result: "error",
|
result: "error",
|
||||||
headers: response.headers,
|
|
||||||
error: {
|
error: {
|
||||||
status: response.status,
|
status: response.status,
|
||||||
message: "Unsupported 'content-type' in header returned from server.",
|
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 = {
|
export type HttpAdapterOptions = {
|
||||||
url: string;
|
url: string;
|
||||||
hooks?: {
|
hooks?: {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { makeClient } from "@spec/relay";
|
import { makeClient } from "@platform/relay";
|
||||||
|
|
||||||
import { HttpAdapter } from "../adapters/http.ts";
|
import { HttpAdapter } from "../adapters/http.ts";
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@ export const api = makeClient(
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
account: (await import("@spec/schemas/account/routes.ts")).routes,
|
account: (await import("@platform/spec/account/routes.ts")).routes,
|
||||||
auth: (await import("@spec/schemas/auth/routes.ts")).routes,
|
auth: (await import("@platform/spec/auth/routes.ts")).routes,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
12
deno.json
12
deno.json
@@ -4,12 +4,14 @@
|
|||||||
"workspace": [
|
"workspace": [
|
||||||
"api",
|
"api",
|
||||||
"apps/react",
|
"apps/react",
|
||||||
"spec/relay",
|
"platform/models",
|
||||||
"spec/schemas"
|
"platform/relay",
|
||||||
|
"platform/spec"
|
||||||
],
|
],
|
||||||
"imports": {
|
"imports": {
|
||||||
"@spec/relay/": "./spec/relay/",
|
"@platform/models/": "./platform/models/",
|
||||||
"@spec/schemas/": "./spec/schemas/"
|
"@platform/relay": "./platform/relay/mod.ts",
|
||||||
|
"@platform/spec/": "./platform/spec/"
|
||||||
},
|
},
|
||||||
"tasks": {
|
"tasks": {
|
||||||
"start:api": {
|
"start:api": {
|
||||||
@@ -21,7 +23,7 @@
|
|||||||
"description": "Start react application instance."
|
"description": "Start react application instance."
|
||||||
},
|
},
|
||||||
"check": {
|
"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."
|
"description": "Runs a check on all the projects main entry files."
|
||||||
},
|
},
|
||||||
"lint": {
|
"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__auth@2.1.4": "2.1.4",
|
||||||
"npm:@jsr/valkyr__db@2.0.0": "2.0.0",
|
"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-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__inverse@1.0.1": "1.0.1",
|
||||||
"npm:@jsr/valkyr__json-rpc@1.1.0": "1.1.0",
|
"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",
|
"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"
|
"tarball": "https://npm.jsr.io/~/11/@jsr/valkyr__event-emitter/1.0.1.tgz"
|
||||||
},
|
},
|
||||||
"@jsr/valkyr__event-store@2.0.0": {
|
"@jsr/valkyr__event-store@2.0.1": {
|
||||||
"integrity": "sha512-izGy/QIGQXoTz0PP1UinSWcSPEEpNuePbmApBbvHq6MFp1p2X/k2eDPKlz2txwXcIn+QKjDhE5F59xPNEKnIng==",
|
"integrity": "sha512-OvSPX0XH5+oS4zQh1O8J7JvsCoH5pBFNuJ1PdNA5B0OascrSWUqpxNEmytOtJhZuhfYzdvyOU1yNEvSI84D5wg==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"@jsr/valkyr__db",
|
"@jsr/valkyr__db",
|
||||||
"@jsr/valkyr__testcontainers",
|
"@jsr/valkyr__testcontainers",
|
||||||
@@ -547,7 +547,7 @@
|
|||||||
"postgres",
|
"postgres",
|
||||||
"zod"
|
"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": {
|
"@jsr/valkyr__inverse@1.0.1": {
|
||||||
"integrity": "sha512-uZpzPct9FGobgl6H+iR3VJlzZbTFVmJSrB4z5In8zHgIJCkmgYj0diU3soU6MuiKR7SFBfD4PGSuUpTTJHNMlg==",
|
"integrity": "sha512-uZpzPct9FGobgl6H+iR3VJlzZbTFVmJSrB4z5In8zHgIJCkmgYj0diU3soU6MuiKR7SFBfD4PGSuUpTTJHNMlg==",
|
||||||
@@ -597,108 +597,108 @@
|
|||||||
"@rolldown/pluginutils@1.0.0-beta.27": {
|
"@rolldown/pluginutils@1.0.0-beta.27": {
|
||||||
"integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="
|
"integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="
|
||||||
},
|
},
|
||||||
"@rollup/rollup-android-arm-eabi@4.50.2": {
|
"@rollup/rollup-android-arm-eabi@4.51.0": {
|
||||||
"integrity": "sha512-uLN8NAiFVIRKX9ZQha8wy6UUs06UNSZ32xj6giK/rmMXAgKahwExvK6SsmgU5/brh4w/nSgj8e0k3c1HBQpa0A==",
|
"integrity": "sha512-VyfldO8T/C5vAXBGIobrAnUE+VJNVLw5z9h4NgSDq/AJZWt/fXqdW+0PJbk+M74xz7yMDRiHtlsuDV7ew6K20w==",
|
||||||
"os": ["android"],
|
"os": ["android"],
|
||||||
"cpu": ["arm"]
|
"cpu": ["arm"]
|
||||||
},
|
},
|
||||||
"@rollup/rollup-android-arm64@4.50.2": {
|
"@rollup/rollup-android-arm64@4.51.0": {
|
||||||
"integrity": "sha512-oEouqQk2/zxxj22PNcGSskya+3kV0ZKH+nQxuCCOGJ4oTXBdNTbv+f/E3c74cNLeMO1S5wVWacSws10TTSB77g==",
|
"integrity": "sha512-Z3ujzDZgsEVSokgIhmOAReh9SGT2qloJJX2Xo1Q3nPU1EhCXrV0PbpR3r7DWRgozqnjrPZQkLe5cgBPIYp70Vg==",
|
||||||
"os": ["android"],
|
"os": ["android"],
|
||||||
"cpu": ["arm64"]
|
"cpu": ["arm64"]
|
||||||
},
|
},
|
||||||
"@rollup/rollup-darwin-arm64@4.50.2": {
|
"@rollup/rollup-darwin-arm64@4.51.0": {
|
||||||
"integrity": "sha512-OZuTVTpj3CDSIxmPgGH8en/XtirV5nfljHZ3wrNwvgkT5DQLhIKAeuFSiwtbMto6oVexV0k1F1zqURPKf5rI1Q==",
|
"integrity": "sha512-T3gskHgArUdR6TCN69li5VELVAZK+iQ4iwMoSMNYixoj+56EC9lTj35rcxhXzIJt40YfBkvDy3GS+t5zh7zM6g==",
|
||||||
"os": ["darwin"],
|
"os": ["darwin"],
|
||||||
"cpu": ["arm64"]
|
"cpu": ["arm64"]
|
||||||
},
|
},
|
||||||
"@rollup/rollup-darwin-x64@4.50.2": {
|
"@rollup/rollup-darwin-x64@4.51.0": {
|
||||||
"integrity": "sha512-Wa/Wn8RFkIkr1vy1k1PB//VYhLnlnn5eaJkfTQKivirOvzu5uVd2It01ukeQstMursuz7S1bU+8WW+1UPXpa8A==",
|
"integrity": "sha512-Hh7n/fh0g5UjH6ATDF56Qdf5bzdLZKIbhp5KftjMYG546Ocjeyg15dxphCpH1FFY2PJ2G6MiOVL4jMq5VLTyrQ==",
|
||||||
"os": ["darwin"],
|
"os": ["darwin"],
|
||||||
"cpu": ["x64"]
|
"cpu": ["x64"]
|
||||||
},
|
},
|
||||||
"@rollup/rollup-freebsd-arm64@4.50.2": {
|
"@rollup/rollup-freebsd-arm64@4.51.0": {
|
||||||
"integrity": "sha512-QkzxvH3kYN9J1w7D1A+yIMdI1pPekD+pWx7G5rXgnIlQ1TVYVC6hLl7SOV9pi5q9uIDF9AuIGkuzcbF7+fAhow==",
|
"integrity": "sha512-0EddADb6FBvfqYoxwVom3hAbAvpSVUbZqmR1wmjk0MSZ06hn/UxxGHKRqEQDMkts7XiZjejVB+TLF28cDTU+gA==",
|
||||||
"os": ["freebsd"],
|
"os": ["freebsd"],
|
||||||
"cpu": ["arm64"]
|
"cpu": ["arm64"]
|
||||||
},
|
},
|
||||||
"@rollup/rollup-freebsd-x64@4.50.2": {
|
"@rollup/rollup-freebsd-x64@4.51.0": {
|
||||||
"integrity": "sha512-dkYXB0c2XAS3a3jmyDkX4Jk0m7gWLFzq1C3qUnJJ38AyxIF5G/dyS4N9B30nvFseCfgtCEdbYFhk0ChoCGxPog==",
|
"integrity": "sha512-MpqaEDLo3JuVPF+wWV4mK7V8akL76WCz8ndfz1aVB7RhvXFO3k7yT7eu8OEuog4VTSyNu5ibvN9n6lgjq/qLEQ==",
|
||||||
"os": ["freebsd"],
|
"os": ["freebsd"],
|
||||||
"cpu": ["x64"]
|
"cpu": ["x64"]
|
||||||
},
|
},
|
||||||
"@rollup/rollup-linux-arm-gnueabihf@4.50.2": {
|
"@rollup/rollup-linux-arm-gnueabihf@4.51.0": {
|
||||||
"integrity": "sha512-9VlPY/BN3AgbukfVHAB8zNFWB/lKEuvzRo1NKev0Po8sYFKx0i+AQlCYftgEjcL43F2h9Ui1ZSdVBc4En/sP2w==",
|
"integrity": "sha512-WEWAGFNFFpvSWAIT3MYvxTkYHv/cJl9yWKpjhheg7ONfB0hetZt/uwBnM3GZqSHrk5bXCDYTFXg3jQyk/j7eXQ==",
|
||||||
"os": ["linux"],
|
"os": ["linux"],
|
||||||
"cpu": ["arm"]
|
"cpu": ["arm"]
|
||||||
},
|
},
|
||||||
"@rollup/rollup-linux-arm-musleabihf@4.50.2": {
|
"@rollup/rollup-linux-arm-musleabihf@4.51.0": {
|
||||||
"integrity": "sha512-+GdKWOvsifaYNlIVf07QYan1J5F141+vGm5/Y8b9uCZnG/nxoGqgCmR24mv0koIWWuqvFYnbURRqw1lv7IBINw==",
|
"integrity": "sha512-9bxtxj8QoAp++LOq5PGDGkEEOpCDk9rOEHUcXadnijedDH8IXrBt6PnBa4Y6NblvGWdoxvXZYghZLaliTCmAng==",
|
||||||
"os": ["linux"],
|
"os": ["linux"],
|
||||||
"cpu": ["arm"]
|
"cpu": ["arm"]
|
||||||
},
|
},
|
||||||
"@rollup/rollup-linux-arm64-gnu@4.50.2": {
|
"@rollup/rollup-linux-arm64-gnu@4.51.0": {
|
||||||
"integrity": "sha512-df0Eou14ojtUdLQdPFnymEQteENwSJAdLf5KCDrmZNsy1c3YaCNaJvYsEUHnrg+/DLBH612/R0xd3dD03uz2dg==",
|
"integrity": "sha512-DdqA+fARqIsfqDYkKo2nrWMp0kvu/wPJ2G8lZ4DjYhn+8QhrjVuzmsh7tTkhULwjvHTN59nWVzAixmOi6rqjNA==",
|
||||||
"os": ["linux"],
|
"os": ["linux"],
|
||||||
"cpu": ["arm64"]
|
"cpu": ["arm64"]
|
||||||
},
|
},
|
||||||
"@rollup/rollup-linux-arm64-musl@4.50.2": {
|
"@rollup/rollup-linux-arm64-musl@4.51.0": {
|
||||||
"integrity": "sha512-iPeouV0UIDtz8j1YFR4OJ/zf7evjauqv7jQ/EFs0ClIyL+by++hiaDAfFipjOgyz6y6xbDvJuiU4HwpVMpRFDQ==",
|
"integrity": "sha512-2XVRNzcUJE1UJua8P4a1GXS5jafFWE+pQ6zhUbZzptOu/70p1F6+0FTi6aGPd6jNtnJqGMjtBCXancC2dhYlWw==",
|
||||||
"os": ["linux"],
|
"os": ["linux"],
|
||||||
"cpu": ["arm64"]
|
"cpu": ["arm64"]
|
||||||
},
|
},
|
||||||
"@rollup/rollup-linux-loong64-gnu@4.50.2": {
|
"@rollup/rollup-linux-loong64-gnu@4.51.0": {
|
||||||
"integrity": "sha512-OL6KaNvBopLlj5fTa5D5bau4W82f+1TyTZRr2BdnfsrnQnmdxh4okMxR2DcDkJuh4KeoQZVuvHvzuD/lyLn2Kw==",
|
"integrity": "sha512-R8QhY0kLIPCAVXWi2yftDSpn7Jtejey/WhMoBESSfwGec5SKdFVupjxFlKoQ7clVRuaDpiQf7wNx3EBZf4Ey6g==",
|
||||||
"os": ["linux"],
|
"os": ["linux"],
|
||||||
"cpu": ["loong64"]
|
"cpu": ["loong64"]
|
||||||
},
|
},
|
||||||
"@rollup/rollup-linux-ppc64-gnu@4.50.2": {
|
"@rollup/rollup-linux-ppc64-gnu@4.51.0": {
|
||||||
"integrity": "sha512-I21VJl1w6z/K5OTRl6aS9DDsqezEZ/yKpbqlvfHbW0CEF5IL8ATBMuUx6/mp683rKTK8thjs/0BaNrZLXetLag==",
|
"integrity": "sha512-I498RPfxx9cMv1KTHQ9tg2Ku1utuQm+T5B+Xro+WNu3FzAFSKp4awKfgMoZwjoPgNbaFGINaOM25cQW6WuBhiQ==",
|
||||||
"os": ["linux"],
|
"os": ["linux"],
|
||||||
"cpu": ["ppc64"]
|
"cpu": ["ppc64"]
|
||||||
},
|
},
|
||||||
"@rollup/rollup-linux-riscv64-gnu@4.50.2": {
|
"@rollup/rollup-linux-riscv64-gnu@4.51.0": {
|
||||||
"integrity": "sha512-Hq6aQJT/qFFHrYMjS20nV+9SKrXL2lvFBENZoKfoTH2kKDOJqff5OSJr4x72ZaG/uUn+XmBnGhfr4lwMRrmqCQ==",
|
"integrity": "sha512-o8COudsb8lvtdm9ixg9aKjfX5aeoc2x9KGE7WjtrmQFquoCRZ9jtzGlonujE4WhvXFepTraWzT4RcwyDDeHXjA==",
|
||||||
"os": ["linux"],
|
"os": ["linux"],
|
||||||
"cpu": ["riscv64"]
|
"cpu": ["riscv64"]
|
||||||
},
|
},
|
||||||
"@rollup/rollup-linux-riscv64-musl@4.50.2": {
|
"@rollup/rollup-linux-riscv64-musl@4.51.0": {
|
||||||
"integrity": "sha512-82rBSEXRv5qtKyr0xZ/YMF531oj2AIpLZkeNYxmKNN6I2sVE9PGegN99tYDLK2fYHJITL1P2Lgb4ZXnv0PjQvw==",
|
"integrity": "sha512-0shJPgSXMdYzOQzpM5BJN2euXY1f8uV8mS6AnrbMcH2KrkNsbpMxWB1wp8UEdiJ1NtyBkCk3U/HfX5mEONBq6w==",
|
||||||
"os": ["linux"],
|
"os": ["linux"],
|
||||||
"cpu": ["riscv64"]
|
"cpu": ["riscv64"]
|
||||||
},
|
},
|
||||||
"@rollup/rollup-linux-s390x-gnu@4.50.2": {
|
"@rollup/rollup-linux-s390x-gnu@4.51.0": {
|
||||||
"integrity": "sha512-4Q3S3Hy7pC6uaRo9gtXUTJ+EKo9AKs3BXKc2jYypEcMQ49gDPFU2P1ariX9SEtBzE5egIX6fSUmbmGazwBVF9w==",
|
"integrity": "sha512-L7pV+ny7865jamSCQwyozBYjFRUKaTsPqDz7ClOtJCDu4paf2uAa0mrcHwSt4XxZP2ogFZS9uuitH3NXdeBEJA==",
|
||||||
"os": ["linux"],
|
"os": ["linux"],
|
||||||
"cpu": ["s390x"]
|
"cpu": ["s390x"]
|
||||||
},
|
},
|
||||||
"@rollup/rollup-linux-x64-gnu@4.50.2": {
|
"@rollup/rollup-linux-x64-gnu@4.51.0": {
|
||||||
"integrity": "sha512-9Jie/At6qk70dNIcopcL4p+1UirusEtznpNtcq/u/C5cC4HBX7qSGsYIcG6bdxj15EYWhHiu02YvmdPzylIZlA==",
|
"integrity": "sha512-4YHhP+Rv3T3+H3TPbUvWOw5tuSwhrVhkHHZhk4hC9VXeAOKR26/IsUAT4FsB4mT+kfIdxxb1BezQDEg/voPO8A==",
|
||||||
"os": ["linux"],
|
"os": ["linux"],
|
||||||
"cpu": ["x64"]
|
"cpu": ["x64"]
|
||||||
},
|
},
|
||||||
"@rollup/rollup-linux-x64-musl@4.50.2": {
|
"@rollup/rollup-linux-x64-musl@4.51.0": {
|
||||||
"integrity": "sha512-HPNJwxPL3EmhzeAnsWQCM3DcoqOz3/IC6de9rWfGR8ZCuEHETi9km66bH/wG3YH0V3nyzyFEGUZeL5PKyy4xvw==",
|
"integrity": "sha512-P7U7U03+E5w7WgJtvSseNLOX1UhknVPmEaqgUENFWfNxNBa1OhExT6qYGmyF8gepcxWSaSfJsAV5UwhWrYefdQ==",
|
||||||
"os": ["linux"],
|
"os": ["linux"],
|
||||||
"cpu": ["x64"]
|
"cpu": ["x64"]
|
||||||
},
|
},
|
||||||
"@rollup/rollup-openharmony-arm64@4.50.2": {
|
"@rollup/rollup-openharmony-arm64@4.51.0": {
|
||||||
"integrity": "sha512-nMKvq6FRHSzYfKLHZ+cChowlEkR2lj/V0jYj9JnGUVPL2/mIeFGmVM2mLaFeNa5Jev7W7TovXqXIG2d39y1KYA==",
|
"integrity": "sha512-FuD8g3u9W6RPwdO1R45hZFORwa1g9YXEMesAKP/sOi7mDqxjbni8S3zAXJiDcRfGfGBqpRYVuH54Gu3FTuSoEw==",
|
||||||
"os": ["openharmony"],
|
"os": ["openharmony"],
|
||||||
"cpu": ["arm64"]
|
"cpu": ["arm64"]
|
||||||
},
|
},
|
||||||
"@rollup/rollup-win32-arm64-msvc@4.50.2": {
|
"@rollup/rollup-win32-arm64-msvc@4.51.0": {
|
||||||
"integrity": "sha512-eFUvvnTYEKeTyHEijQKz81bLrUQOXKZqECeiWH6tb8eXXbZk+CXSG2aFrig2BQ/pjiVRj36zysjgILkqarS2YA==",
|
"integrity": "sha512-zST+FdMCX3QAYfmZX3dp/Fy8qLUetfE17QN5ZmmFGPrhl86qvRr+E9u2bk7fzkIXsfQR30Z7ZRS7WMryPPn4rQ==",
|
||||||
"os": ["win32"],
|
"os": ["win32"],
|
||||||
"cpu": ["arm64"]
|
"cpu": ["arm64"]
|
||||||
},
|
},
|
||||||
"@rollup/rollup-win32-ia32-msvc@4.50.2": {
|
"@rollup/rollup-win32-ia32-msvc@4.51.0": {
|
||||||
"integrity": "sha512-cBaWmXqyfRhH8zmUxK3d3sAhEWLrtMjWBRwdMMHJIXSjvjLKvv49adxiEz+FJ8AP90apSDDBx2Tyd/WylV6ikA==",
|
"integrity": "sha512-U+qhoCVAZmTHCmUKxdQxw1jwAFNFXmOpMME7Npt5GTb1W/7itfgAgNluVOvyeuSeqW+dEQLFuNZF3YZPO8XkMg==",
|
||||||
"os": ["win32"],
|
"os": ["win32"],
|
||||||
"cpu": ["ia32"]
|
"cpu": ["ia32"]
|
||||||
},
|
},
|
||||||
"@rollup/rollup-win32-x64-msvc@4.50.2": {
|
"@rollup/rollup-win32-x64-msvc@4.51.0": {
|
||||||
"integrity": "sha512-APwKy6YUhvZaEoHyM+9xqmTpviEI+9eL7LoCH+aLcvWYHJ663qG5zx7WzWZY+a9qkg5JtzcMyJ9z0WtQBMDmgA==",
|
"integrity": "sha512-z6UpFzMhXSD8NNUfCi2HO+pbpSzSWIIPgb1TZsEZjmZYtk6RUIC63JYjlFBwbBZS3jt3f1q6IGfkj3g+GnBt2Q==",
|
||||||
"os": ["win32"],
|
"os": ["win32"],
|
||||||
"cpu": ["x64"]
|
"cpu": ["x64"]
|
||||||
},
|
},
|
||||||
@@ -1871,8 +1871,8 @@
|
|||||||
"reusify@1.1.0": {
|
"reusify@1.1.0": {
|
||||||
"integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="
|
"integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="
|
||||||
},
|
},
|
||||||
"rollup@4.50.2": {
|
"rollup@4.51.0": {
|
||||||
"integrity": "sha512-BgLRGy7tNS9H66aIMASq1qSYbAAJV6Z6WR4QYTvj5FgF15rZ/ympT1uixHXwzbZUBDbkvqUI1KR0fH1FhMaQ9w==",
|
"integrity": "sha512-7cR0XWrdp/UAj2HMY/Y4QQEUjidn3l2AY1wSeZoFjMbD8aOMPoV9wgTFYbrJpPzzvejDEini1h3CiUP8wLzxQA==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"@types/estree"
|
"@types/estree"
|
||||||
],
|
],
|
||||||
@@ -2194,7 +2194,7 @@
|
|||||||
"npm:@jsr/std__fs@1.0.19",
|
"npm:@jsr/std__fs@1.0.19",
|
||||||
"npm:@jsr/std__path@1.1.2",
|
"npm:@jsr/std__path@1.1.2",
|
||||||
"npm:@jsr/valkyr__auth@2.1.4",
|
"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__inverse@1.0.1",
|
||||||
"npm:@jsr/valkyr__json-rpc@1.1.0",
|
"npm:@jsr/valkyr__json-rpc@1.1.0",
|
||||||
"npm:cookie@1.0.2",
|
"npm:cookie@1.0.2",
|
||||||
@@ -2231,7 +2231,14 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"spec/relay": {
|
"platform/models": {
|
||||||
|
"packageJson": {
|
||||||
|
"dependencies": [
|
||||||
|
"npm:zod@4"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"platform/relay": {
|
||||||
"packageJson": {
|
"packageJson": {
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"npm:path-to-regexp@8",
|
"npm:path-to-regexp@8",
|
||||||
@@ -2239,7 +2246,7 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"spec/schemas": {
|
"platform/spec": {
|
||||||
"packageJson": {
|
"packageJson": {
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"npm:zod@4"
|
"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";
|
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[]) => {
|
return ((value: unknown | unknown[]) => {
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
return value.map((value: unknown) => schema.parse(value));
|
return value.map((value: unknown) => schema.parse(value));
|
||||||
}
|
}
|
||||||
return 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>;
|
||||||
(value: unknown[]): z.infer<TSchema>[];
|
(value: unknown[]): z.infer<TSchema>[];
|
||||||
};
|
};
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "@spec/schemas",
|
"name": "@platform/models",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@spec/relay": "workspace:*",
|
"@platform/spec": "workspace:*",
|
||||||
"zod": "4"
|
"zod": "4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,18 +49,11 @@ export type RelayAdapter = {
|
|||||||
getUrl(endpoint: string): string;
|
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.
|
* @param input - Request input parameters.
|
||||||
*/
|
*/
|
||||||
json(input: RelayInput): Promise<RelayResponse>;
|
send(input: RelayInput): Promise<RelayResponse>;
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a form data request to the configured relay url.
|
|
||||||
*
|
|
||||||
* @param input - Request input parameters.
|
|
||||||
*/
|
|
||||||
data(input: RelayInput): Promise<RelayResponse>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a fetch request using the given options and returns a
|
* Sends a fetch request using the given options and returns a
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-empty-object-type */
|
/* 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 type { RelayAdapter, RelayInput, RelayResponse } from "./adapter.ts";
|
||||||
import { Route, type Routes } from "./route.ts";
|
import { Route, type Routes } from "./route.ts";
|
||||||
@@ -110,10 +110,10 @@ function getRouteFn(route: Route, { adapter }: Config) {
|
|||||||
|
|
||||||
// ### Fetch
|
// ### 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) {
|
if ("data" in response && route.state.response !== undefined) {
|
||||||
response.data = route.state.output.parse(response.data);
|
response.data = route.state.response.parse(response.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
@@ -179,9 +179,7 @@ type RouteResponse<TRoute extends Route> = Promise<RelayResponse<RouteOutput<TRo
|
|||||||
$response: TRoute["$response"];
|
$response: TRoute["$response"];
|
||||||
};
|
};
|
||||||
|
|
||||||
type RouteOutput<TRoute extends Route> = TRoute["state"]["output"] extends ZodType
|
type RouteOutput<TRoute extends Route> = TRoute["state"]["response"] extends ZodType ? TRoute["$response"] : null;
|
||||||
? z.infer<TRoute["state"]["output"]>
|
|
||||||
: null;
|
|
||||||
|
|
||||||
type RouteErrors<TRoute extends Route> = InstanceType<TRoute["state"]["errors"][number]>;
|
type RouteErrors<TRoute extends Route> = InstanceType<TRoute["state"]["errors"][number]>;
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "@spec/relay",
|
"name": "@platform/relay",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ConflictError } from "@spec/relay";
|
import { ConflictError } from "@platform/relay";
|
||||||
|
|
||||||
export class AccountEmailClaimedError extends ConflictError {
|
export class AccountEmailClaimedError extends ConflictError {
|
||||||
constructor(email: string) {
|
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 z from "zod";
|
||||||
|
|
||||||
import { NameSchema } from "../name.ts";
|
|
||||||
import { AccountSchema } from "./account.ts";
|
|
||||||
import { AccountEmailClaimedError } from "./errors.ts";
|
import { AccountEmailClaimedError } from "./errors.ts";
|
||||||
|
|
||||||
export const create = route
|
export const create = route
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { BadRequestError } from "@spec/relay";
|
import { BadRequestError } from "@platform/relay";
|
||||||
|
|
||||||
export class AuthenticationStrategyPayloadError extends BadRequestError {
|
export class AuthenticationStrategyPayloadError extends BadRequestError {
|
||||||
constructor() {
|
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 z from "zod";
|
||||||
|
|
||||||
import { AccountSchema } from "../account/account.ts";
|
|
||||||
|
|
||||||
export * from "./errors.ts";
|
export * from "./errors.ts";
|
||||||
export * from "./strategies.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