feat: refactor account
This commit is contained in:
@@ -1,10 +1,10 @@
|
|||||||
import { AggregateRoot, getDate, makeAggregateReducer } from "@valkyr/event-store";
|
import { Strategy } from "@spec/modules/account/strategies.ts";
|
||||||
import { Avatar, Contact, Email, Name, Phone, Strategy } from "relay/schemas";
|
import { Avatar, Contact, Email, Name } from "@spec/shared";
|
||||||
|
import { AggregateRoot, getDate } from "@valkyr/event-store";
|
||||||
|
|
||||||
import { db, toAccountDriver } from "~libraries/read-store/mod.ts";
|
import { db } from "~libraries/read-store/mod.ts";
|
||||||
|
|
||||||
import { eventStore } from "../event-store.ts";
|
import { eventStore } from "../event-store.ts";
|
||||||
import { AccountCreatedData } from "../events/account.ts";
|
|
||||||
import { Auditor } from "../events/auditor.ts";
|
import { Auditor } from "../events/auditor.ts";
|
||||||
import { EventStoreFactory } from "../events/mod.ts";
|
import { EventStoreFactory } from "../events/mod.ts";
|
||||||
import { projector } from "../projector.ts";
|
import { projector } from "../projector.ts";
|
||||||
@@ -12,69 +12,22 @@ import { projector } from "../projector.ts";
|
|||||||
export class Account extends AggregateRoot<EventStoreFactory> {
|
export class Account extends AggregateRoot<EventStoreFactory> {
|
||||||
static override readonly name = "account";
|
static override readonly name = "account";
|
||||||
|
|
||||||
id!: string;
|
|
||||||
organizationId?: string;
|
|
||||||
|
|
||||||
type!: "admin" | "consultant" | "organization";
|
|
||||||
|
|
||||||
avatar?: Avatar;
|
avatar?: Avatar;
|
||||||
name?: Name;
|
name?: Name;
|
||||||
contact: Contact = {
|
contact: Contact = {
|
||||||
emails: [],
|
emails: [],
|
||||||
phones: [],
|
|
||||||
};
|
};
|
||||||
strategies: Strategy[] = [];
|
strategies: Strategy[] = [];
|
||||||
|
|
||||||
createdAt!: Date;
|
createdAt!: Date;
|
||||||
updatedAt!: Date;
|
updatedAt!: Date;
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// Factories
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
static #reducer = makeAggregateReducer(Account);
|
|
||||||
|
|
||||||
static create(data: AccountCreatedData, meta: Auditor): Account {
|
|
||||||
return new Account().push({
|
|
||||||
type: "account:created",
|
|
||||||
data,
|
|
||||||
meta,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static async getById(stream: string): Promise<Account | undefined> {
|
|
||||||
return this.$store.reduce({ name: "account", stream, reducer: this.#reducer });
|
|
||||||
}
|
|
||||||
|
|
||||||
static async getByEmail(email: string): Promise<Account | undefined> {
|
|
||||||
return this.$store.reduce({ name: "account", relation: Account.emailRelation(email), reducer: this.#reducer });
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// Relations
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
static emailRelation(email: string): `account:email:${string}` {
|
|
||||||
return `account:email:${email}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static passwordRelation(alias: string): `account:password:${string}` {
|
|
||||||
return `account:password:${alias}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Reducer
|
// Reducer
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
with(event: EventStoreFactory["$events"][number]["$record"]): void {
|
with(event: EventStoreFactory["$events"][number]["$record"]): void {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case "account:created": {
|
|
||||||
this.id = event.stream;
|
|
||||||
this.organizationId = event.data.type === "organization" ? event.data.organizationId : undefined;
|
|
||||||
this.type = event.data.type;
|
|
||||||
this.createdAt = getDate(event.created);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "account:avatar:added": {
|
case "account:avatar:added": {
|
||||||
this.avatar = { url: event.data };
|
this.avatar = { url: event.data };
|
||||||
this.updatedAt = getDate(event.created);
|
this.updatedAt = getDate(event.created);
|
||||||
@@ -90,11 +43,6 @@ export class Account extends AggregateRoot<EventStoreFactory> {
|
|||||||
this.updatedAt = getDate(event.created);
|
this.updatedAt = getDate(event.created);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "account:phone:added": {
|
|
||||||
this.contact.phones.push(event.data);
|
|
||||||
this.updatedAt = getDate(event.created);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "strategy:email:added": {
|
case "strategy:email:added": {
|
||||||
this.strategies.push({ type: "email", value: event.data });
|
this.strategies.push({ type: "email", value: event.data });
|
||||||
this.updatedAt = getDate(event.created);
|
this.updatedAt = getDate(event.created);
|
||||||
@@ -139,15 +87,6 @@ export class Account extends AggregateRoot<EventStoreFactory> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
addPhone(phone: Phone, meta: Auditor): this {
|
|
||||||
return this.push({
|
|
||||||
stream: this.id,
|
|
||||||
type: "account:phone:added",
|
|
||||||
data: phone,
|
|
||||||
meta,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
addRole(roleId: string, meta: Auditor): this {
|
addRole(roleId: string, meta: Auditor): this {
|
||||||
return this.push({
|
return this.push({
|
||||||
stream: this.id,
|
stream: this.id,
|
||||||
@@ -174,45 +113,7 @@ export class Account extends AggregateRoot<EventStoreFactory> {
|
|||||||
meta,
|
meta,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// Utilities
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
toSession(): Session {
|
|
||||||
if (this.type === "organization") {
|
|
||||||
if (this.organizationId === undefined) {
|
|
||||||
throw new Error("Account .toSession failed, no organization id present");
|
|
||||||
}
|
}
|
||||||
return {
|
|
||||||
type: this.type,
|
|
||||||
accountId: this.id,
|
|
||||||
organizationId: this.organizationId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
type: this.type,
|
|
||||||
accountId: this.id,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------------
|
|
||||||
| Types
|
|
||||||
|--------------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
|
|
||||||
type Session =
|
|
||||||
| {
|
|
||||||
type: "organization";
|
|
||||||
accountId: string;
|
|
||||||
organizationId: string;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "admin" | "consultant";
|
|
||||||
accountId: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------------
|
|--------------------------------------------------------------------------------
|
||||||
@@ -220,49 +121,32 @@ type Session =
|
|||||||
|--------------------------------------------------------------------------------
|
|--------------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
projector.on("account:created", async ({ stream, data }) => {
|
|
||||||
const schema: any = {
|
|
||||||
id: stream,
|
|
||||||
type: data.type,
|
|
||||||
contact: {
|
|
||||||
emails: [],
|
|
||||||
phones: [],
|
|
||||||
},
|
|
||||||
strategies: [],
|
|
||||||
roles: [],
|
|
||||||
};
|
|
||||||
if (data.type === "organization") {
|
|
||||||
schema.organizationId = data.organizationId;
|
|
||||||
}
|
|
||||||
await db.collection("accounts").insertOne(toAccountDriver(schema));
|
|
||||||
});
|
|
||||||
|
|
||||||
projector.on("account:avatar:added", async ({ stream: id, data: url }) => {
|
projector.on("account:avatar:added", async ({ stream: id, data: url }) => {
|
||||||
await db.collection("accounts").updateOne({ id }, { $set: { avatar: { url } } });
|
await db.collection("accounts").updateOne({ id }, { $set: { avatar: { url } } }, { upsert: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
projector.on("account:name:added", async ({ stream: id, data: name }) => {
|
projector.on("account:name:added", async ({ stream: id, data: name }) => {
|
||||||
await db.collection("accounts").updateOne({ id }, { $set: { name } });
|
await db.collection("accounts").updateOne({ id }, { $set: { name } }, { upsert: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
projector.on("account:email:added", async ({ stream: id, data: email }) => {
|
projector.on("account:email:added", async ({ stream: id, data: email }) => {
|
||||||
await db.collection("accounts").updateOne({ id }, { $push: { "contact.emails": email } });
|
await db.collection("accounts").updateOne({ id }, { $push: { "contact.emails": email } }, { upsert: true });
|
||||||
});
|
|
||||||
|
|
||||||
projector.on("account:phone:added", async ({ stream: id, data: phone }) => {
|
|
||||||
await db.collection("accounts").updateOne({ id }, { $push: { "contact.phones": phone } });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
projector.on("account:role:added", async ({ stream: id, data: roleId }) => {
|
projector.on("account:role:added", async ({ stream: id, data: roleId }) => {
|
||||||
await db.collection("accounts").updateOne({ id }, { $push: { roles: roleId } });
|
await db.collection("accounts").updateOne({ id }, { $push: { roles: roleId } }, { upsert: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
projector.on("strategy:email:added", async ({ stream: id, data: email }) => {
|
projector.on("strategy:email:added", async ({ stream: id, data: email }) => {
|
||||||
await eventStore.relations.insert(Account.emailRelation(email), id);
|
await eventStore.relations.insert(`account:email:${email}`, id);
|
||||||
await db.collection("accounts").updateOne({ id }, { $push: { strategies: { type: "email", value: email } } });
|
await db
|
||||||
|
.collection("accounts")
|
||||||
|
.updateOne({ id }, { $push: { strategies: { type: "email", value: email } } }, { upsert: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
projector.on("strategy:password:added", async ({ stream: id, data: strategy }) => {
|
projector.on("strategy:password:added", async ({ stream: id, data: strategy }) => {
|
||||||
await eventStore.relations.insert(Account.passwordRelation(strategy.alias), id);
|
await eventStore.relations.insert(`account:alias:${strategy.alias}`, id);
|
||||||
await db.collection("accounts").updateOne({ id }, { $push: { strategies: { type: "password", ...strategy } } });
|
await db
|
||||||
|
.collection("accounts")
|
||||||
|
.updateOne({ id }, { $push: { strategies: { type: "password", ...strategy } } }, { upsert: true });
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
import { AggregateFactory } from "@valkyr/event-store";
|
|
||||||
|
|
||||||
import { Account } from "./account.ts";
|
|
||||||
import { Code } from "./code.ts";
|
|
||||||
import { Organization } from "./organization.ts";
|
|
||||||
import { Role } from "./role.ts";
|
|
||||||
|
|
||||||
export const aggregates = new AggregateFactory([Account, Code, Organization, Role]);
|
|
||||||
@@ -1,29 +1,12 @@
|
|||||||
|
import { EmailSchema, NameSchema } from "@spec/shared";
|
||||||
import { event } from "@valkyr/event-store";
|
import { event } from "@valkyr/event-store";
|
||||||
import { email, name, phone } from "relay/schemas";
|
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
|
|
||||||
import { auditor } from "./auditor.ts";
|
import { auditor } from "./auditor.ts";
|
||||||
|
|
||||||
const created = z.discriminatedUnion([
|
|
||||||
z.object({
|
|
||||||
type: z.literal("admin"),
|
|
||||||
}),
|
|
||||||
z.object({
|
|
||||||
type: z.literal("consultant"),
|
|
||||||
}),
|
|
||||||
z.object({
|
|
||||||
type: z.literal("organization"),
|
|
||||||
organizationId: z.string(),
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
event.type("account:created").data(created).meta(auditor),
|
|
||||||
event.type("account:avatar:added").data(z.string()).meta(auditor),
|
event.type("account:avatar:added").data(z.string()).meta(auditor),
|
||||||
event.type("account:name:added").data(name).meta(auditor),
|
event.type("account:name:added").data(NameSchema).meta(auditor),
|
||||||
event.type("account:email:added").data(email).meta(auditor),
|
event.type("account:email:added").data(EmailSchema).meta(auditor),
|
||||||
event.type("account:phone:added").data(phone).meta(auditor),
|
|
||||||
event.type("account:role:added").data(z.string()).meta(auditor),
|
event.type("account:role:added").data(z.string()).meta(auditor),
|
||||||
];
|
];
|
||||||
|
|
||||||
export type AccountCreatedData = z.infer<typeof created>;
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import z from "zod";
|
|||||||
|
|
||||||
import { auditor } from "./auditor.ts";
|
import { auditor } from "./auditor.ts";
|
||||||
|
|
||||||
const created = z.object({
|
const CreatedSchema = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
permissions: z.array(
|
permissions: z.array(
|
||||||
z.object({
|
z.object({
|
||||||
@@ -13,7 +13,7 @@ const created = z.object({
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
const operation = z.discriminatedUnion([
|
const OperationSchema = z.discriminatedUnion("type", [
|
||||||
z.object({
|
z.object({
|
||||||
type: z.literal("grant"),
|
type: z.literal("grant"),
|
||||||
resource: z.string(),
|
resource: z.string(),
|
||||||
@@ -27,11 +27,11 @@ const operation = z.discriminatedUnion([
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
event.type("role:created").data(created).meta(auditor),
|
event.type("role:created").data(CreatedSchema).meta(auditor),
|
||||||
event.type("role:name-set").data(z.string()).meta(auditor),
|
event.type("role:name-set").data(z.string()).meta(auditor),
|
||||||
event.type("role:permissions-set").data(z.array(operation)).meta(auditor),
|
event.type("role:permissions-set").data(z.array(OperationSchema)).meta(auditor),
|
||||||
];
|
];
|
||||||
|
|
||||||
export type RoleCreatedData = z.infer<typeof created>;
|
export type RoleCreatedData = z.infer<typeof CreatedSchema>;
|
||||||
|
|
||||||
export type RolePermissionOperation = z.infer<typeof operation>;
|
export type RolePermissionOperation = z.infer<typeof OperationSchema>;
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
import { db, takeOne } from "../database.ts";
|
|
||||||
import { type AccountSchema, fromAccountDriver } from "./schema.ts";
|
|
||||||
|
|
||||||
export async function getAccountById(id: string): Promise<AccountSchema | undefined> {
|
|
||||||
return db.collection("accounts").find({ id }).toArray().then(fromAccountDriver).then(takeOne);
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
const account = z.object({
|
|
||||||
id: z.uuid(),
|
|
||||||
name: z.object({
|
|
||||||
given: z.string(),
|
|
||||||
family: z.string(),
|
|
||||||
}),
|
|
||||||
email: z.email(),
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------------
|
|
||||||
| Parsers
|
|
||||||
|--------------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
|
|
||||||
const select = account;
|
|
||||||
const insert = account;
|
|
||||||
|
|
||||||
export function toAccountDriver(documents: unknown): AccountInsert {
|
|
||||||
return insert.parse(documents);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fromAccountDriver(documents: unknown[]): AccountSchema[] {
|
|
||||||
return documents.map((document) => select.parse(document));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------------
|
|
||||||
| Types
|
|
||||||
|--------------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
|
|
||||||
export type AccountSchema = z.infer<typeof select>;
|
|
||||||
export type AccountInsert = z.infer<typeof insert>;
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
|
import type { AccountDocument } from "@spec/modules/account/account.ts";
|
||||||
|
|
||||||
import { config } from "~config";
|
import { config } from "~config";
|
||||||
import { getDatabaseAccessor } from "~libraries/database/accessor.ts";
|
import { getDatabaseAccessor } from "~libraries/database/accessor.ts";
|
||||||
|
|
||||||
import { AccountInsert } from "./account/schema.ts";
|
|
||||||
|
|
||||||
export const db = getDatabaseAccessor<{
|
export const db = getDatabaseAccessor<{
|
||||||
accounts: AccountInsert;
|
accounts: AccountDocument;
|
||||||
}>(`${config.name}:read-store`);
|
}>(`${config.name}:read-store`);
|
||||||
|
|
||||||
export function takeOne<TDocument>(documents: TDocument[]): TDocument | undefined {
|
export function takeOne<TDocument>(documents: TDocument[]): TDocument | undefined {
|
||||||
|
|||||||
35
api/libraries/read-store/methods.ts
Normal file
35
api/libraries/read-store/methods.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { type Account, parseAccount } from "@spec/modules/account/account.ts";
|
||||||
|
|
||||||
|
import { db, takeOne } from "./database.ts";
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------------
|
||||||
|
| Accounts
|
||||||
|
|--------------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a single account by its primary identifier.
|
||||||
|
*
|
||||||
|
* @param id - Account identifier.
|
||||||
|
*/
|
||||||
|
export async function getAccountById(id: string): Promise<Account | undefined> {
|
||||||
|
return db
|
||||||
|
.collection("accounts")
|
||||||
|
.aggregate([
|
||||||
|
{
|
||||||
|
$match: { id },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$lookup: {
|
||||||
|
from: "roles",
|
||||||
|
localField: "roles",
|
||||||
|
foreignField: "id",
|
||||||
|
as: "roles",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.toArray()
|
||||||
|
.then(parseAccount)
|
||||||
|
.then(takeOne);
|
||||||
|
}
|
||||||
@@ -1,3 +1,2 @@
|
|||||||
export * from "./account/methods.ts";
|
|
||||||
export * from "./account/schema.ts";
|
|
||||||
export * from "./database.ts";
|
export * from "./database.ts";
|
||||||
|
export * from "./methods.ts";
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
"@std/fs": "npm:@jsr/std__fs@1",
|
"@std/fs": "npm:@jsr/std__fs@1",
|
||||||
"@std/path": "npm:@jsr/std__path@1",
|
"@std/path": "npm:@jsr/std__path@1",
|
||||||
"@valkyr/auth": "npm:@jsr/valkyr__auth@2",
|
"@valkyr/auth": "npm:@jsr/valkyr__auth@2",
|
||||||
"@valkyr/event-store": "npm:@jsr/valkyr__event-store@2.0.0-beta.5",
|
"@valkyr/event-store": "npm:@jsr/valkyr__event-store@2.0.0-beta.6",
|
||||||
"@valkyr/inverse": "npm:@jsr/valkyr__inverse@1",
|
"@valkyr/inverse": "npm:@jsr/valkyr__inverse@1",
|
||||||
"cookie": "1",
|
"cookie": "1",
|
||||||
"mongodb": "6",
|
"mongodb": "6",
|
||||||
|
|||||||
@@ -12,10 +12,8 @@ export class SessionController extends Controller<{
|
|||||||
const response = await api.auth.authenticate({
|
const response = await api.auth.authenticate({
|
||||||
body: {
|
body: {
|
||||||
type: "email",
|
type: "email",
|
||||||
payload: {
|
|
||||||
email: "john.doe@fixture.none",
|
email: "john.doe@fixture.none",
|
||||||
},
|
},
|
||||||
},
|
|
||||||
});
|
});
|
||||||
if ("error" in response) {
|
if ("error" in response) {
|
||||||
this.setState("error", undefined);
|
this.setState("error", undefined);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export const api = makeClient(
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
account: (await import("@spec/modules/account/mod.ts")).routes,
|
||||||
auth: (await import("@spec/modules/auth/mod.ts")).routes,
|
auth: (await import("@spec/modules/auth/mod.ts")).routes,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
119
deno.lock
generated
119
deno.lock
generated
@@ -11,15 +11,14 @@
|
|||||||
"npm:@jsr/std__testing@1": "1.0.15",
|
"npm:@jsr/std__testing@1": "1.0.15",
|
||||||
"npm:@jsr/valkyr__auth@2": "2.0.2",
|
"npm:@jsr/valkyr__auth@2": "2.0.2",
|
||||||
"npm:@jsr/valkyr__event-emitter@1": "1.0.1",
|
"npm:@jsr/valkyr__event-emitter@1": "1.0.1",
|
||||||
"npm:@jsr/valkyr__event-store@2.0.0-beta.5": "2.0.0-beta.5",
|
"npm:@jsr/valkyr__event-store@2.0.0-beta.6": "2.0.0-beta.6",
|
||||||
"npm:@jsr/valkyr__inverse@1": "1.0.1",
|
"npm:@jsr/valkyr__inverse@1": "1.0.1",
|
||||||
"npm:@tanstack/react-query@5": "5.84.2_react@19.1.1",
|
"npm:@tanstack/react-query@5": "5.84.2_react@19.1.1",
|
||||||
"npm:@tanstack/react-router@1": "1.131.5_react@19.1.1_react-dom@19.1.1__react@19.1.1",
|
"npm:@tanstack/react-router@1": "1.131.5_react@19.1.1_react-dom@19.1.1__react@19.1.1",
|
||||||
"npm:@types/node@*": "22.15.15",
|
|
||||||
"npm:@types/react-dom@19": "19.1.7_@types+react@19.1.9",
|
"npm:@types/react-dom@19": "19.1.7_@types+react@19.1.9",
|
||||||
"npm:@types/react@19": "19.1.9",
|
"npm:@types/react@19": "19.1.9",
|
||||||
"npm:@valkyr/db@1": "1.0.1",
|
"npm:@valkyr/db@1": "1.0.1",
|
||||||
"npm:@vitejs/plugin-react@4": "4.7.0_vite@7.1.1__picomatch@4.0.3_@babel+core@7.28.0_@types+node@22.15.15",
|
"npm:@vitejs/plugin-react@4": "4.7.0_vite@7.1.2__picomatch@4.0.3_@babel+core@7.28.0",
|
||||||
"npm:cookie@1": "1.0.2",
|
"npm:cookie@1": "1.0.2",
|
||||||
"npm:eslint-plugin-react-hooks@5": "5.2.0_eslint@9.33.0",
|
"npm:eslint-plugin-react-hooks@5": "5.2.0_eslint@9.33.0",
|
||||||
"npm:eslint-plugin-react-refresh@0.4": "0.4.20_eslint@9.33.0",
|
"npm:eslint-plugin-react-refresh@0.4": "0.4.20_eslint@9.33.0",
|
||||||
@@ -32,10 +31,9 @@
|
|||||||
"npm:prettier@3": "3.6.2",
|
"npm:prettier@3": "3.6.2",
|
||||||
"npm:react-dom@19": "19.1.1_react@19.1.1",
|
"npm:react-dom@19": "19.1.1_react@19.1.1",
|
||||||
"npm:react@19": "19.1.1",
|
"npm:react@19": "19.1.1",
|
||||||
"npm:typescript-eslint@8": "8.39.0_eslint@9.33.0_typescript@5.9.2_@typescript-eslint+parser@8.39.0__eslint@9.33.0__typescript@5.9.2",
|
"npm:typescript-eslint@8": "8.39.1_eslint@9.33.0_typescript@5.9.2_@typescript-eslint+parser@8.39.1__eslint@9.33.0__typescript@5.9.2",
|
||||||
"npm:typescript@5": "5.9.2",
|
"npm:typescript@5": "5.9.2",
|
||||||
"npm:vite@7": "7.1.1_picomatch@4.0.3_@types+node@22.15.15",
|
"npm:vite@7": "7.1.2_picomatch@4.0.3",
|
||||||
"npm:vite@7.1.1": "7.1.1_picomatch@4.0.3_@types+node@22.15.15",
|
|
||||||
"npm:zod@4": "4.0.17"
|
"npm:zod@4": "4.0.17"
|
||||||
},
|
},
|
||||||
"npm": {
|
"npm": {
|
||||||
@@ -507,17 +505,16 @@
|
|||||||
],
|
],
|
||||||
"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-beta.5": {
|
"@jsr/valkyr__event-store@2.0.0-beta.6": {
|
||||||
"integrity": "sha512-+xScdSFcIXbQUSofgQJJUdwJWssRzu42oHm8acsmbIStmYa0docCFTPtUQlUrRewND4lmFXvMlidsTb4tS7jww==",
|
"integrity": "sha512-4ybdvjW2SIXPy9WOwG0UyCEu4XYsrorL5ATGgZmKFDLzhlhrLDMlmDSzpMouPEOBlEFohR4080rvWRD0bCe/pA==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"@jsr/valkyr__testcontainers",
|
"@jsr/valkyr__testcontainers",
|
||||||
"@valkyr/db",
|
"@valkyr/db",
|
||||||
"mongodb",
|
"mongodb",
|
||||||
"nanoid@5.1.5",
|
|
||||||
"postgres",
|
"postgres",
|
||||||
"zod@4.0.17"
|
"zod@4.0.17"
|
||||||
],
|
],
|
||||||
"tarball": "https://npm.jsr.io/~/11/@jsr/valkyr__event-store/2.0.0-beta.5.tgz"
|
"tarball": "https://npm.jsr.io/~/11/@jsr/valkyr__event-store/2.0.0-beta.6.tgz"
|
||||||
},
|
},
|
||||||
"@jsr/valkyr__inverse@1.0.1": {
|
"@jsr/valkyr__inverse@1.0.1": {
|
||||||
"integrity": "sha512-uZpzPct9FGobgl6H+iR3VJlzZbTFVmJSrB4z5In8zHgIJCkmgYj0diU3soU6MuiKR7SFBfD4PGSuUpTTJHNMlg==",
|
"integrity": "sha512-uZpzPct9FGobgl6H+iR3VJlzZbTFVmJSrB4z5In8zHgIJCkmgYj0diU3soU6MuiKR7SFBfD4PGSuUpTTJHNMlg==",
|
||||||
@@ -745,12 +742,6 @@
|
|||||||
"@types/json-schema@7.0.15": {
|
"@types/json-schema@7.0.15": {
|
||||||
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="
|
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="
|
||||||
},
|
},
|
||||||
"@types/node@22.15.15": {
|
|
||||||
"integrity": "sha512-R5muMcZob3/Jjchn5LcO8jdKwSCbzqmPB6ruBxMcf9kbxtniZHP327s6C37iOfuw8mbKK3cAQa7sEl7afLrQ8A==",
|
|
||||||
"dependencies": [
|
|
||||||
"undici-types"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"@types/react-dom@19.1.7_@types+react@19.1.9": {
|
"@types/react-dom@19.1.7_@types+react@19.1.9": {
|
||||||
"integrity": "sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==",
|
"integrity": "sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
@@ -772,8 +763,8 @@
|
|||||||
"@types/webidl-conversions"
|
"@types/webidl-conversions"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"@typescript-eslint/eslint-plugin@8.39.0_@typescript-eslint+parser@8.39.0__eslint@9.33.0__typescript@5.9.2_eslint@9.33.0_typescript@5.9.2": {
|
"@typescript-eslint/eslint-plugin@8.39.1_@typescript-eslint+parser@8.39.1__eslint@9.33.0__typescript@5.9.2_eslint@9.33.0_typescript@5.9.2": {
|
||||||
"integrity": "sha512-bhEz6OZeUR+O/6yx9Jk6ohX6H9JSFTaiY0v9/PuKT3oGK0rn0jNplLmyFUGV+a9gfYnVNwGDwS/UkLIuXNb2Rw==",
|
"integrity": "sha512-yYegZ5n3Yr6eOcqgj2nJH8cH/ZZgF+l0YIdKILSDjYFRjgYQMgv/lRjV5Z7Up04b9VYUondt8EPMqg7kTWgJ2g==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"@eslint-community/regexpp",
|
"@eslint-community/regexpp",
|
||||||
"@typescript-eslint/parser",
|
"@typescript-eslint/parser",
|
||||||
@@ -789,8 +780,8 @@
|
|||||||
"typescript"
|
"typescript"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"@typescript-eslint/parser@8.39.0_eslint@9.33.0_typescript@5.9.2": {
|
"@typescript-eslint/parser@8.39.1_eslint@9.33.0_typescript@5.9.2": {
|
||||||
"integrity": "sha512-g3WpVQHngx0aLXn6kfIYCZxM6rRJlWzEkVpqEFLT3SgEDsp9cpCbxxgwnE504q4H+ruSDh/VGS6nqZIDynP+vg==",
|
"integrity": "sha512-pUXGCuHnnKw6PyYq93lLRiZm3vjuslIy7tus1lIQTYVK9bL8XBgJnCWm8a0KcTtHC84Yya1Q6rtll+duSMj0dg==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"@typescript-eslint/scope-manager",
|
"@typescript-eslint/scope-manager",
|
||||||
"@typescript-eslint/types",
|
"@typescript-eslint/types",
|
||||||
@@ -801,8 +792,8 @@
|
|||||||
"typescript"
|
"typescript"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"@typescript-eslint/project-service@8.39.0_typescript@5.9.2": {
|
"@typescript-eslint/project-service@8.39.1_typescript@5.9.2": {
|
||||||
"integrity": "sha512-CTzJqaSq30V/Z2Og9jogzZt8lJRR5TKlAdXmWgdu4hgcC9Kww5flQ+xFvMxIBWVNdxJO7OifgdOK4PokMIWPew==",
|
"integrity": "sha512-8fZxek3ONTwBu9ptw5nCKqZOSkXshZB7uAxuFF0J/wTMkKydjXCzqqga7MlFMpHi9DoG4BadhmTkITBcg8Aybw==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"@typescript-eslint/tsconfig-utils",
|
"@typescript-eslint/tsconfig-utils",
|
||||||
"@typescript-eslint/types",
|
"@typescript-eslint/types",
|
||||||
@@ -810,21 +801,21 @@
|
|||||||
"typescript"
|
"typescript"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"@typescript-eslint/scope-manager@8.39.0": {
|
"@typescript-eslint/scope-manager@8.39.1": {
|
||||||
"integrity": "sha512-8QOzff9UKxOh6npZQ/4FQu4mjdOCGSdO3p44ww0hk8Vu+IGbg0tB/H1LcTARRDzGCC8pDGbh2rissBuuoPgH8A==",
|
"integrity": "sha512-RkBKGBrjgskFGWuyUGz/EtD8AF/GW49S21J8dvMzpJitOF1slLEbbHnNEtAHtnDAnx8qDEdRrULRnWVx27wGBw==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"@typescript-eslint/types",
|
"@typescript-eslint/types",
|
||||||
"@typescript-eslint/visitor-keys"
|
"@typescript-eslint/visitor-keys"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"@typescript-eslint/tsconfig-utils@8.39.0_typescript@5.9.2": {
|
"@typescript-eslint/tsconfig-utils@8.39.1_typescript@5.9.2": {
|
||||||
"integrity": "sha512-Fd3/QjmFV2sKmvv3Mrj8r6N8CryYiCS8Wdb/6/rgOXAWGcFuc+VkQuG28uk/4kVNVZBQuuDHEDUpo/pQ32zsIQ==",
|
"integrity": "sha512-ePUPGVtTMR8XMU2Hee8kD0Pu4NDE1CN9Q1sxGSGd/mbOtGZDM7pnhXNJnzW63zk/q+Z54zVzj44HtwXln5CvHA==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"typescript"
|
"typescript"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"@typescript-eslint/type-utils@8.39.0_eslint@9.33.0_typescript@5.9.2": {
|
"@typescript-eslint/type-utils@8.39.1_eslint@9.33.0_typescript@5.9.2": {
|
||||||
"integrity": "sha512-6B3z0c1DXVT2vYA9+z9axjtc09rqKUPRmijD5m9iv8iQpHBRYRMBcgxSiKTZKm6FwWw1/cI4v6em35OsKCiN5Q==",
|
"integrity": "sha512-gu9/ahyatyAdQbKeHnhT4R+y3YLtqqHyvkfDxaBYk97EcbfChSJXyaJnIL3ygUv7OuZatePHmQvuH5ru0lnVeA==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"@typescript-eslint/types",
|
"@typescript-eslint/types",
|
||||||
"@typescript-eslint/typescript-estree",
|
"@typescript-eslint/typescript-estree",
|
||||||
@@ -835,11 +826,11 @@
|
|||||||
"typescript"
|
"typescript"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"@typescript-eslint/types@8.39.0": {
|
"@typescript-eslint/types@8.39.1": {
|
||||||
"integrity": "sha512-ArDdaOllnCj3yn/lzKn9s0pBQYmmyme/v1HbGIGB0GB/knFI3fWMHloC+oYTJW46tVbYnGKTMDK4ah1sC2v0Kg=="
|
"integrity": "sha512-7sPDKQQp+S11laqTrhHqeAbsCfMkwJMrV7oTDvtDds4mEofJYir414bYKUEb8YPUm9QL3U+8f6L6YExSoAGdQw=="
|
||||||
},
|
},
|
||||||
"@typescript-eslint/typescript-estree@8.39.0_typescript@5.9.2": {
|
"@typescript-eslint/typescript-estree@8.39.1_typescript@5.9.2": {
|
||||||
"integrity": "sha512-ndWdiflRMvfIgQRpckQQLiB5qAKQ7w++V4LlCHwp62eym1HLB/kw7D9f2e8ytONls/jt89TEasgvb+VwnRprsw==",
|
"integrity": "sha512-EKkpcPuIux48dddVDXyQBlKdeTPMmALqBUbEk38McWv0qVEZwOpVJBi7ugK5qVNgeuYjGNQxrrnoM/5+TI/BPw==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"@typescript-eslint/project-service",
|
"@typescript-eslint/project-service",
|
||||||
"@typescript-eslint/tsconfig-utils",
|
"@typescript-eslint/tsconfig-utils",
|
||||||
@@ -854,8 +845,8 @@
|
|||||||
"typescript"
|
"typescript"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"@typescript-eslint/utils@8.39.0_eslint@9.33.0_typescript@5.9.2": {
|
"@typescript-eslint/utils@8.39.1_eslint@9.33.0_typescript@5.9.2": {
|
||||||
"integrity": "sha512-4GVSvNA0Vx1Ktwvf4sFE+exxJ3QGUorQG1/A5mRfRNZtkBT2xrA/BCO2H0eALx/PnvCS6/vmYwRdDA41EoffkQ==",
|
"integrity": "sha512-VF5tZ2XnUSTuiqZFXCZfZs1cgkdd3O/sSYmdo2EpSyDlC86UM/8YytTmKnehOW3TGAlivqTDT6bS87B/GQ/jyg==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"@eslint-community/eslint-utils",
|
"@eslint-community/eslint-utils",
|
||||||
"@typescript-eslint/scope-manager",
|
"@typescript-eslint/scope-manager",
|
||||||
@@ -865,8 +856,8 @@
|
|||||||
"typescript"
|
"typescript"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"@typescript-eslint/visitor-keys@8.39.0": {
|
"@typescript-eslint/visitor-keys@8.39.1": {
|
||||||
"integrity": "sha512-ldgiJ+VAhQCfIjeOgu8Kj5nSxds0ktPOSO9p4+0VDH2R2pLvQraaM5Oen2d7NxzMCm+Sn/vJT+mv2H5u6b/3fA==",
|
"integrity": "sha512-W8FQi6kEh2e8zVhQ0eeRnxdvIoOkAp/CPAahcNio6nO9dsIwb9b34z90KOlheoyuVf6LSOEdjlkxSkapNEc+4A==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"@typescript-eslint/types",
|
"@typescript-eslint/types",
|
||||||
"eslint-visitor-keys@4.2.1"
|
"eslint-visitor-keys@4.2.1"
|
||||||
@@ -884,7 +875,7 @@
|
|||||||
"rxjs"
|
"rxjs"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"@vitejs/plugin-react@4.7.0_vite@7.1.1__picomatch@4.0.3_@babel+core@7.28.0": {
|
"@vitejs/plugin-react@4.7.0_vite@7.1.2__picomatch@4.0.3_@babel+core@7.28.0": {
|
||||||
"integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
|
"integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"@babel/core",
|
"@babel/core",
|
||||||
@@ -893,19 +884,7 @@
|
|||||||
"@rolldown/pluginutils",
|
"@rolldown/pluginutils",
|
||||||
"@types/babel__core",
|
"@types/babel__core",
|
||||||
"react-refresh",
|
"react-refresh",
|
||||||
"vite@7.1.1_picomatch@4.0.3"
|
"vite"
|
||||||
]
|
|
||||||
},
|
|
||||||
"@vitejs/plugin-react@4.7.0_vite@7.1.1__picomatch@4.0.3_@babel+core@7.28.0_@types+node@22.15.15": {
|
|
||||||
"integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
|
|
||||||
"dependencies": [
|
|
||||||
"@babel/core",
|
|
||||||
"@babel/plugin-transform-react-jsx-self",
|
|
||||||
"@babel/plugin-transform-react-jsx-source",
|
|
||||||
"@rolldown/pluginutils",
|
|
||||||
"@types/babel__core",
|
|
||||||
"react-refresh",
|
|
||||||
"vite@7.1.1_picomatch@4.0.3_@types+node@22.15.15"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"acorn-jsx@5.3.2_acorn@8.15.0": {
|
"acorn-jsx@5.3.2_acorn@8.15.0": {
|
||||||
@@ -1031,8 +1010,8 @@
|
|||||||
"type-fest"
|
"type-fest"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"electron-to-chromium@1.5.199": {
|
"electron-to-chromium@1.5.200": {
|
||||||
"integrity": "sha512-3gl0S7zQd88kCAZRO/DnxtBKuhMO4h0EaQIN3YgZfV6+pW+5+bf2AdQeHNESCoaQqo/gjGVYEf2YM4O5HJQqpQ=="
|
"integrity": "sha512-rFCxROw7aOe4uPTfIAx+rXv9cEcGx+buAF4npnhtTqCJk5KDFRnh3+KYj7rdVh6lsFt5/aPs+Irj9rZ33WMA7w=="
|
||||||
},
|
},
|
||||||
"esbuild@0.25.8": {
|
"esbuild@0.25.8": {
|
||||||
"integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==",
|
"integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==",
|
||||||
@@ -1424,10 +1403,6 @@
|
|||||||
"integrity": "sha512-2ustYUX1R2rL/Br5B/FMhi8d5/QzvkJ912rBYxskcpu0myTHzSZfTr1LAS2Sm7jxRUObRrSBFoyzwAhL49aVSg==",
|
"integrity": "sha512-2ustYUX1R2rL/Br5B/FMhi8d5/QzvkJ912rBYxskcpu0myTHzSZfTr1LAS2Sm7jxRUObRrSBFoyzwAhL49aVSg==",
|
||||||
"bin": true
|
"bin": true
|
||||||
},
|
},
|
||||||
"nanoid@5.1.5": {
|
|
||||||
"integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==",
|
|
||||||
"bin": true
|
|
||||||
},
|
|
||||||
"natural-compare@1.4.0": {
|
"natural-compare@1.4.0": {
|
||||||
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="
|
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="
|
||||||
},
|
},
|
||||||
@@ -1659,8 +1634,8 @@
|
|||||||
"type-fest@3.13.1": {
|
"type-fest@3.13.1": {
|
||||||
"integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g=="
|
"integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g=="
|
||||||
},
|
},
|
||||||
"typescript-eslint@8.39.0_eslint@9.33.0_typescript@5.9.2_@typescript-eslint+parser@8.39.0__eslint@9.33.0__typescript@5.9.2": {
|
"typescript-eslint@8.39.1_eslint@9.33.0_typescript@5.9.2_@typescript-eslint+parser@8.39.1__eslint@9.33.0__typescript@5.9.2": {
|
||||||
"integrity": "sha512-lH8FvtdtzcHJCkMOKnN73LIn6SLTpoojgJqDAxPm1jCR14eWSGPX8ul/gggBdPMk/d5+u9V854vTYQ8T5jF/1Q==",
|
"integrity": "sha512-GDUv6/NDYngUlNvwaHM1RamYftxf782IyEDbdj3SeaIHHv8fNQVRC++fITT7kUJV/5rIA/tkoRSSskt6osEfqg==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"@typescript-eslint/eslint-plugin",
|
"@typescript-eslint/eslint-plugin",
|
||||||
"@typescript-eslint/parser",
|
"@typescript-eslint/parser",
|
||||||
@@ -1674,9 +1649,6 @@
|
|||||||
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
|
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
|
||||||
"bin": true
|
"bin": true
|
||||||
},
|
},
|
||||||
"undici-types@6.21.0": {
|
|
||||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="
|
|
||||||
},
|
|
||||||
"update-browserslist-db@1.1.3_browserslist@4.25.2": {
|
"update-browserslist-db@1.1.3_browserslist@4.25.2": {
|
||||||
"integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
|
"integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
@@ -1698,8 +1670,8 @@
|
|||||||
"react"
|
"react"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"vite@7.1.1_picomatch@4.0.3": {
|
"vite@7.1.2_picomatch@4.0.3": {
|
||||||
"integrity": "sha512-yJ+Mp7OyV+4S+afWo+QyoL9jFWD11QFH0i5i7JypnfTcA1rmgxCbiA8WwAICDEtZ1Z1hzrVhN8R8rGTqkTY8ZQ==",
|
"integrity": "sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"esbuild",
|
"esbuild",
|
||||||
"fdir",
|
"fdir",
|
||||||
@@ -1713,25 +1685,6 @@
|
|||||||
],
|
],
|
||||||
"bin": true
|
"bin": true
|
||||||
},
|
},
|
||||||
"vite@7.1.1_picomatch@4.0.3_@types+node@22.15.15": {
|
|
||||||
"integrity": "sha512-yJ+Mp7OyV+4S+afWo+QyoL9jFWD11QFH0i5i7JypnfTcA1rmgxCbiA8WwAICDEtZ1Z1hzrVhN8R8rGTqkTY8ZQ==",
|
|
||||||
"dependencies": [
|
|
||||||
"@types/node",
|
|
||||||
"esbuild",
|
|
||||||
"fdir",
|
|
||||||
"picomatch@4.0.3",
|
|
||||||
"postcss",
|
|
||||||
"rollup",
|
|
||||||
"tinyglobby"
|
|
||||||
],
|
|
||||||
"optionalDependencies": [
|
|
||||||
"fsevents"
|
|
||||||
],
|
|
||||||
"optionalPeers": [
|
|
||||||
"@types/node"
|
|
||||||
],
|
|
||||||
"bin": true
|
|
||||||
},
|
|
||||||
"webidl-conversions@7.0.0": {
|
"webidl-conversions@7.0.0": {
|
||||||
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="
|
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="
|
||||||
},
|
},
|
||||||
@@ -1786,7 +1739,7 @@
|
|||||||
"npm:@jsr/std__fs@1",
|
"npm:@jsr/std__fs@1",
|
||||||
"npm:@jsr/std__path@1",
|
"npm:@jsr/std__path@1",
|
||||||
"npm:@jsr/valkyr__auth@2",
|
"npm:@jsr/valkyr__auth@2",
|
||||||
"npm:@jsr/valkyr__event-store@2.0.0-beta.5",
|
"npm:@jsr/valkyr__event-store@2.0.0-beta.6",
|
||||||
"npm:@jsr/valkyr__inverse@1",
|
"npm:@jsr/valkyr__inverse@1",
|
||||||
"npm:cookie@1",
|
"npm:cookie@1",
|
||||||
"npm:mongodb@6",
|
"npm:mongodb@6",
|
||||||
|
|||||||
12
spec/modules/access/role.ts
Normal file
12
spec/modules/access/role.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { makeSchemaParser } from "@spec/shared";
|
||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
export const RoleSchema = z.object({
|
||||||
|
id: z.uuid(),
|
||||||
|
name: z.string(),
|
||||||
|
permissions: z.record(z.string(), z.array(z.string())),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const parseRole = makeSchemaParser(RoleSchema);
|
||||||
|
|
||||||
|
export type Role = z.infer<typeof RoleSchema>;
|
||||||
23
spec/modules/account/account.ts
Normal file
23
spec/modules/account/account.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { AvatarSchema, ContactSchema, makeSchemaParser, NameSchema } from "@spec/shared";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { RoleSchema } from "../access/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 AccountDocumentSchema = AccountSchema.omit({ roles: true }).extend({ roles: z.string().array() });
|
||||||
|
|
||||||
|
export const parseAccount = makeSchemaParser(AccountSchema);
|
||||||
|
|
||||||
|
export type Account = z.infer<typeof AccountSchema>;
|
||||||
|
export type AccountDocument = z.infer<typeof AccountDocumentSchema>;
|
||||||
5
spec/modules/account/mod.ts
Normal file
5
spec/modules/account/mod.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { create } from "./routes/create.ts";
|
||||||
|
|
||||||
|
export const routes = {
|
||||||
|
create,
|
||||||
|
};
|
||||||
5
spec/modules/account/routes/create.ts
Normal file
5
spec/modules/account/routes/create.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { route } from "@spec/relay";
|
||||||
|
import { NameSchema } from "@spec/shared";
|
||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
export const create = route.post("/api/v1/accounts").body(z.object({ name: NameSchema }));
|
||||||
33
spec/modules/account/strategies.ts
Normal file
33
spec/modules/account/strategies.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
const EmailStrategySchema = z.object({
|
||||||
|
type: z.literal("email"),
|
||||||
|
value: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const PasswordStrategySchema = z.object({
|
||||||
|
type: z.literal("password"),
|
||||||
|
alias: z.string(),
|
||||||
|
password: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const PasskeyStrategySchema = z.object({
|
||||||
|
type: z.literal("passkey"),
|
||||||
|
credId: z.string(),
|
||||||
|
credPublicKey: z.string(),
|
||||||
|
webauthnUserId: z.string(),
|
||||||
|
counter: z.number(),
|
||||||
|
backupEligible: z.boolean(),
|
||||||
|
backupStatus: z.boolean(),
|
||||||
|
transports: z.string(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
lastUsed: z.date(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const StrategySchema = z.discriminatedUnion("type", [
|
||||||
|
EmailStrategySchema,
|
||||||
|
PasswordStrategySchema,
|
||||||
|
PasskeyStrategySchema,
|
||||||
|
]);
|
||||||
|
|
||||||
|
export type Strategy = z.infer<typeof StrategySchema>;
|
||||||
@@ -2,8 +2,6 @@ import { z } from "zod";
|
|||||||
|
|
||||||
export const PasskeyStrategySchema = z.object({
|
export const PasskeyStrategySchema = z.object({
|
||||||
type: z.literal("passkey").describe("Authentication strategy type for WebAuthn/Passkey"),
|
type: z.literal("passkey").describe("Authentication strategy type for WebAuthn/Passkey"),
|
||||||
payload: z
|
|
||||||
.object({
|
|
||||||
id: z.string().describe("Base64URL encoded credential ID"),
|
id: z.string().describe("Base64URL encoded credential ID"),
|
||||||
rawId: z.string().describe("Raw credential ID as base64URL encoded string"),
|
rawId: z.string().describe("Raw credential ID as base64URL encoded string"),
|
||||||
response: z
|
response: z
|
||||||
@@ -23,27 +21,17 @@ export const PasskeyStrategySchema = z.object({
|
|||||||
.enum(["platform", "cross-platform"])
|
.enum(["platform", "cross-platform"])
|
||||||
.optional()
|
.optional()
|
||||||
.describe("Type of authenticator used (platform or cross-platform)"),
|
.describe("Type of authenticator used (platform or cross-platform)"),
|
||||||
})
|
|
||||||
.describe("WebAuthn credential payload"),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const EmailStrategySchema = z.object({
|
export const EmailStrategySchema = z.object({
|
||||||
type: z.literal("email").describe("Authentication strategy type for email"),
|
type: z.literal("email").describe("Authentication strategy type for email"),
|
||||||
payload: z
|
|
||||||
.object({
|
|
||||||
email: z.email().describe("User's email address for authentication"),
|
email: z.email().describe("User's email address for authentication"),
|
||||||
})
|
|
||||||
.describe("Email authentication payload"),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const PasswordStrategySchema = z.object({
|
export const PasswordStrategySchema = z.object({
|
||||||
type: z.literal("password").describe("Authentication strategy type for password"),
|
type: z.literal("password").describe("Authentication strategy type for password"),
|
||||||
payload: z
|
alias: z.string().describe("User alias (username or email)"),
|
||||||
.object({
|
|
||||||
identifier: z.string().describe("User identifier (username or email)"),
|
|
||||||
password: z.string().describe("User's password"),
|
password: z.string().describe("User's password"),
|
||||||
})
|
|
||||||
.describe("Password authentication payload"),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const StrategyPayloadSchema = z
|
export const StrategyPayloadSchema = z
|
||||||
|
|||||||
7
spec/shared/avatar.ts
Normal file
7
spec/shared/avatar.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
export const AvatarSchema = z.object({
|
||||||
|
url: z.string().describe("A valid URL pointing to the user's avatar image."),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Avatar = z.infer<typeof AvatarSchema>;
|
||||||
9
spec/shared/contact.ts
Normal file
9
spec/shared/contact.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
import { EmailSchema } from "./email.ts";
|
||||||
|
|
||||||
|
export const ContactSchema = z.object({
|
||||||
|
emails: z.array(EmailSchema).default([]).describe("A list of email addresses associated with the contact."),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Contact = z.infer<typeof ContactSchema>;
|
||||||
15
spec/shared/database.ts
Normal file
15
spec/shared/database.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import z, { ZodObject } from "zod";
|
||||||
|
|
||||||
|
export function makeSchemaParser<TSchema extends ZodObject>(schema: TSchema): SchemaParserFn<TSchema> {
|
||||||
|
return ((value: unknown | unknown[]) => {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.map((value: unknown) => schema.parse(value));
|
||||||
|
}
|
||||||
|
return schema.parse(value);
|
||||||
|
}) as SchemaParserFn<TSchema>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type SchemaParserFn<TSchema extends ZodObject> = {
|
||||||
|
(value: unknown): z.infer<TSchema>;
|
||||||
|
(value: unknown[]): z.infer<TSchema>[];
|
||||||
|
};
|
||||||
11
spec/shared/email.ts
Normal file
11
spec/shared/email.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
export const EmailSchema = z.object({
|
||||||
|
type: z.enum(["personal", "work"]).describe("The context of the email address, e.g., personal or work."),
|
||||||
|
value: z.email().describe("A valid email address string."),
|
||||||
|
primary: z.boolean().describe("Indicates if this is the primary email."),
|
||||||
|
verified: z.boolean().describe("True if the email address has been verified."),
|
||||||
|
label: z.string().optional().describe("Optional display label for the email address."),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Email = z.infer<typeof EmailSchema>;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export * from "./avatar.ts";
|
||||||
|
export * from "./contact.ts";
|
||||||
|
export * from "./database.ts";
|
||||||
|
export * from "./email.ts";
|
||||||
|
export * from "./name.ts";
|
||||||
|
|||||||
8
spec/shared/name.ts
Normal file
8
spec/shared/name.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const NameSchema = z.object({
|
||||||
|
family: z.string().nullable().describe("Family name, also known as last name or surname."),
|
||||||
|
given: z.string().nullable().describe("Given name, also known as first name."),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Name = z.infer<typeof NameSchema>;
|
||||||
Reference in New Issue
Block a user