mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 18:41:08 +02:00
feat(spaces): validate space metadata on Better Auth organization hooks
Moves the canonical SpaceType + SPACE_MODULE_ALLOWLIST to @mana/shared-types (framework-free) so the Bun services can consume them without pulling in Svelte. shared-branding keeps only the UI-facing labels and descriptions and re-exports the canonical types for frontend convenience. Wires two Better Auth organization hooks in mana-auth: - beforeCreateOrganization asserts metadata.type is a valid SpaceType, rejecting the create with a BAD_REQUEST otherwise. - beforeDeleteOrganization rejects deletion of the personal space. Covered by bun tests (11 assertions) for the helper module. No migration and no schema change — type lives in the existing organization.metadata jsonb column. Plan: docs/plans/spaces-foundation.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9d69e4419d
commit
166d6c6ffb
11 changed files with 683 additions and 331 deletions
|
|
@ -21,6 +21,9 @@
|
|||
"check": "svelte-check --tsconfig ./tsconfig.json",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@mana/shared-types": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"svelte": "^5.0.0",
|
||||
"svelte-check": "^4.0.0",
|
||||
|
|
|
|||
|
|
@ -71,12 +71,16 @@ export {
|
|||
export type { AppId, AppBranding, LogoProps, AppLogoWithNameProps } from './types';
|
||||
|
||||
// Spaces (multi-tenancy primitive — see docs/plans/spaces-foundation.md)
|
||||
// Canonical types live in @mana/shared-types; branding adds only UI strings.
|
||||
export {
|
||||
SPACE_TYPES,
|
||||
SPACE_TYPE_LABELS,
|
||||
SPACE_TYPE_DESCRIPTIONS,
|
||||
SPACE_MODULE_ALLOWLIST,
|
||||
isModuleAllowedInSpace,
|
||||
isSpaceType,
|
||||
parseSpaceMetadata,
|
||||
type SpaceType,
|
||||
type SpaceModuleId,
|
||||
type SpaceMetadata,
|
||||
} from './spaces';
|
||||
|
|
|
|||
|
|
@ -1,40 +1,14 @@
|
|||
/**
|
||||
* Space Types & Module Allowlist
|
||||
* Space — UI-facing labels and descriptions
|
||||
*
|
||||
* A "Space" is the unit of data ownership in Mana. Every record belongs to
|
||||
* exactly one Space. Users join Spaces via Better Auth's `member` relation.
|
||||
*
|
||||
* Space = Better Auth Organization with a typed `metadata.type` field. The
|
||||
* type drives which modules are available inside the space (see
|
||||
* `SPACE_MODULE_ALLOWLIST` below).
|
||||
* The canonical type/allowlist definitions live in `@mana/shared-types`
|
||||
* (framework-free so Bun services can import them). This file adds only
|
||||
* the i18n strings that belong to the UI branding layer.
|
||||
*
|
||||
* See docs/plans/spaces-foundation.md for the full RFC.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The six canonical Space types. Every Better Auth organization must have
|
||||
* exactly one of these as `metadata.type`.
|
||||
*
|
||||
* - `personal` — single-member, auto-created on signup. Holds private data
|
||||
* like mood, sleep, dreams that don't belong in a shared context.
|
||||
* - `brand` — external communication identity (e.g. Edisconet, a creator
|
||||
* persona). Hosts social-relay, mail, landing, public content.
|
||||
* - `club` — association/Verein. Member management, dues, events,
|
||||
* governance. Target for the ClubDesk-replacement roadmap.
|
||||
* - `family` — household/family/WG. Shared calendar, shopping, recipes.
|
||||
* - `team` — work team / project. Tasks, chat, docs.
|
||||
* - `practice` — freelancer/solo-business. Invoicing, clients, time tracking.
|
||||
*/
|
||||
export type SpaceType = 'personal' | 'brand' | 'club' | 'family' | 'team' | 'practice';
|
||||
|
||||
export const SPACE_TYPES: readonly SpaceType[] = [
|
||||
'personal',
|
||||
'brand',
|
||||
'club',
|
||||
'family',
|
||||
'team',
|
||||
'practice',
|
||||
] as const;
|
||||
import type { SpaceType } from '@mana/shared-types';
|
||||
|
||||
export const SPACE_TYPE_LABELS = {
|
||||
de: {
|
||||
|
|
@ -53,7 +27,7 @@ export const SPACE_TYPE_LABELS = {
|
|||
team: 'Team',
|
||||
practice: 'Practice',
|
||||
},
|
||||
} as const;
|
||||
} as const satisfies Record<'de' | 'en', Record<SpaceType, string>>;
|
||||
|
||||
export const SPACE_TYPE_DESCRIPTIONS = {
|
||||
de: {
|
||||
|
|
@ -72,161 +46,18 @@ export const SPACE_TYPE_DESCRIPTIONS = {
|
|||
team: 'Work team or project with multiple collaborators.',
|
||||
practice: 'Freelancer or solo business with clients and invoices.',
|
||||
},
|
||||
} as const;
|
||||
} as const satisfies Record<'de' | 'en', Record<SpaceType, string>>;
|
||||
|
||||
/**
|
||||
* Module IDs referenced by the allowlist. Strings (not a strict enum) because
|
||||
* the allowlist intentionally includes modules that don't exist yet — e.g.
|
||||
* `club-finance`, `social-relay` — so features can be gated before the code
|
||||
* lands.
|
||||
*/
|
||||
export type SpaceModuleId = string;
|
||||
|
||||
/**
|
||||
* Which modules are available inside each Space type.
|
||||
*
|
||||
* The personal space gets everything (sentinel `'*'`). Other types get a
|
||||
* curated subset — modules dealing with intimate personal data (mood,
|
||||
* dreams, period, body measurements, …) are intentionally excluded from
|
||||
* shared spaces.
|
||||
*
|
||||
* Rule of thumb: if a module's data would feel wrong shared with co-workers
|
||||
* or club members, keep it out.
|
||||
*/
|
||||
export const SPACE_MODULE_ALLOWLIST: Record<SpaceType, readonly SpaceModuleId[] | '*'> = {
|
||||
personal: '*',
|
||||
|
||||
brand: [
|
||||
'mana',
|
||||
'social-relay', // future — not yet built
|
||||
'mail',
|
||||
'contacts',
|
||||
'calendar',
|
||||
'storage',
|
||||
'uload',
|
||||
'landing', // future
|
||||
'presi',
|
||||
'cards',
|
||||
'picture',
|
||||
'quotes',
|
||||
'news',
|
||||
'news-research',
|
||||
'research-lab',
|
||||
'ai-agents',
|
||||
'companion',
|
||||
'times',
|
||||
'notes',
|
||||
'photos',
|
||||
'invoices',
|
||||
'activity',
|
||||
'goals',
|
||||
],
|
||||
|
||||
club: [
|
||||
'mana',
|
||||
'contacts',
|
||||
'calendar',
|
||||
'events',
|
||||
'mail',
|
||||
'storage',
|
||||
'uload',
|
||||
'news',
|
||||
'research-lab',
|
||||
'club-members', // future — ClubDesk Paket A
|
||||
'club-finance', // future — ClubDesk Paket B
|
||||
'invoices',
|
||||
'finance',
|
||||
'landing', // future — Paket C (Vereinswebsite)
|
||||
'presi',
|
||||
'cards',
|
||||
'quotes',
|
||||
'companion',
|
||||
'times',
|
||||
'notes',
|
||||
'photos',
|
||||
'activity',
|
||||
'goals',
|
||||
],
|
||||
|
||||
family: [
|
||||
'mana',
|
||||
'contacts',
|
||||
'calendar',
|
||||
'events',
|
||||
'mail',
|
||||
'storage',
|
||||
'uload',
|
||||
'recipes',
|
||||
'food',
|
||||
'places',
|
||||
'presi',
|
||||
'cards',
|
||||
'photos',
|
||||
'notes',
|
||||
'companion',
|
||||
'goals',
|
||||
'activity',
|
||||
'wetter',
|
||||
'wisekeep',
|
||||
'firsts',
|
||||
],
|
||||
|
||||
team: [
|
||||
'mana',
|
||||
'contacts',
|
||||
'calendar',
|
||||
'events',
|
||||
'storage',
|
||||
'mail',
|
||||
'uload',
|
||||
'news',
|
||||
'news-research',
|
||||
'research-lab',
|
||||
'presi',
|
||||
'cards',
|
||||
'picture',
|
||||
'notes',
|
||||
'quotes',
|
||||
'invoices',
|
||||
'companion',
|
||||
'ai-agents',
|
||||
'times',
|
||||
'activity',
|
||||
'goals',
|
||||
],
|
||||
|
||||
practice: [
|
||||
'mana',
|
||||
'contacts',
|
||||
'calendar',
|
||||
'storage',
|
||||
'mail',
|
||||
'uload',
|
||||
'invoices',
|
||||
'finance',
|
||||
'times',
|
||||
'notes',
|
||||
'presi',
|
||||
'cards',
|
||||
'quotes',
|
||||
'companion',
|
||||
'research-lab',
|
||||
'activity',
|
||||
'goals',
|
||||
],
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Check whether a module is available inside a given Space type.
|
||||
*
|
||||
* Used by:
|
||||
* - Scope wrapper (apps/mana/.../data/scope/scoped-db.ts) to block queries
|
||||
* against disallowed modules — structural guard against UI bypass.
|
||||
* - UI module launcher to hide disabled modules in the active space.
|
||||
* - Route guards that check before mounting a module page.
|
||||
*/
|
||||
export function isModuleAllowedInSpace(moduleId: SpaceModuleId, spaceType: SpaceType): boolean {
|
||||
const allow = SPACE_MODULE_ALLOWLIST[spaceType];
|
||||
if (allow === '*') return true;
|
||||
return allow.includes(moduleId);
|
||||
}
|
||||
// Re-export canonical types from shared-types so frontend consumers can
|
||||
// import everything space-related from `@mana/shared-branding` for
|
||||
// convenience.
|
||||
export {
|
||||
SPACE_TYPES,
|
||||
SPACE_MODULE_ALLOWLIST,
|
||||
isModuleAllowedInSpace,
|
||||
isSpaceType,
|
||||
parseSpaceMetadata,
|
||||
type SpaceType,
|
||||
type SpaceModuleId,
|
||||
type SpaceMetadata,
|
||||
} from '@mana/shared-types';
|
||||
|
|
|
|||
|
|
@ -29,6 +29,9 @@ export * from './landing-config';
|
|||
// AI structured-output Zod schemas (shared between mana-api + web frontend)
|
||||
export * from './ai-schemas';
|
||||
|
||||
// Space types (multi-tenancy primitive — see docs/plans/spaces-foundation.md)
|
||||
export * from './spaces';
|
||||
|
||||
// API types
|
||||
export interface User {
|
||||
id: string;
|
||||
|
|
|
|||
225
packages/shared-types/src/spaces.ts
Normal file
225
packages/shared-types/src/spaces.ts
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
/**
|
||||
* Space Types & Module Allowlist
|
||||
*
|
||||
* Framework-free definition of the Space primitive — the unit of data
|
||||
* ownership in Mana. Consumed by both the SvelteKit frontend and the
|
||||
* Bun/Hono services (mana-auth, mana-api, mana-sync), which is why this
|
||||
* lives in shared-types instead of shared-branding (the latter carries
|
||||
* Svelte components and is too heavy for a server).
|
||||
*
|
||||
* UI-facing labels/descriptions for these types live in shared-branding.
|
||||
*
|
||||
* See docs/plans/spaces-foundation.md for the full RFC.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The six canonical Space types. Every Better Auth organization must have
|
||||
* exactly one of these as `metadata.type`.
|
||||
*
|
||||
* - `personal` — single-member, auto-created on signup. Holds private data
|
||||
* like mood, sleep, dreams that don't belong in a shared context.
|
||||
* - `brand` — external communication identity (e.g. Edisconet, a creator
|
||||
* persona). Hosts social-relay, mail, landing, public content.
|
||||
* - `club` — association/Verein. Member management, dues, events,
|
||||
* governance. Target for the ClubDesk-replacement roadmap.
|
||||
* - `family` — household/family/WG. Shared calendar, shopping, recipes.
|
||||
* - `team` — work team / project. Tasks, chat, docs.
|
||||
* - `practice` — freelancer/solo-business. Invoicing, clients, time tracking.
|
||||
*/
|
||||
export type SpaceType = 'personal' | 'brand' | 'club' | 'family' | 'team' | 'practice';
|
||||
|
||||
export const SPACE_TYPES: readonly SpaceType[] = [
|
||||
'personal',
|
||||
'brand',
|
||||
'club',
|
||||
'family',
|
||||
'team',
|
||||
'practice',
|
||||
] as const;
|
||||
|
||||
export function isSpaceType(value: unknown): value is SpaceType {
|
||||
return typeof value === 'string' && (SPACE_TYPES as readonly string[]).includes(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Module IDs referenced by the allowlist. Strings (not a strict enum) because
|
||||
* the allowlist intentionally includes modules that don't exist yet — e.g.
|
||||
* `club-finance`, `social-relay` — so features can be gated before the code
|
||||
* lands.
|
||||
*/
|
||||
export type SpaceModuleId = string;
|
||||
|
||||
/**
|
||||
* Which modules are available inside each Space type.
|
||||
*
|
||||
* The personal space gets everything (sentinel `'*'`). Other types get a
|
||||
* curated subset — modules dealing with intimate personal data (mood,
|
||||
* dreams, period, body measurements, …) are intentionally excluded from
|
||||
* shared spaces.
|
||||
*
|
||||
* Rule of thumb: if a module's data would feel wrong shared with co-workers
|
||||
* or club members, keep it out.
|
||||
*/
|
||||
export const SPACE_MODULE_ALLOWLIST: Record<SpaceType, readonly SpaceModuleId[] | '*'> = {
|
||||
personal: '*',
|
||||
|
||||
brand: [
|
||||
'mana',
|
||||
'social-relay', // future — not yet built
|
||||
'mail',
|
||||
'contacts',
|
||||
'calendar',
|
||||
'storage',
|
||||
'uload',
|
||||
'landing', // future
|
||||
'presi',
|
||||
'cards',
|
||||
'picture',
|
||||
'quotes',
|
||||
'news',
|
||||
'news-research',
|
||||
'research-lab',
|
||||
'ai-agents',
|
||||
'companion',
|
||||
'times',
|
||||
'notes',
|
||||
'photos',
|
||||
'invoices',
|
||||
'activity',
|
||||
'goals',
|
||||
],
|
||||
|
||||
club: [
|
||||
'mana',
|
||||
'contacts',
|
||||
'calendar',
|
||||
'events',
|
||||
'mail',
|
||||
'storage',
|
||||
'uload',
|
||||
'news',
|
||||
'research-lab',
|
||||
'club-members', // future — ClubDesk Paket A
|
||||
'club-finance', // future — ClubDesk Paket B
|
||||
'invoices',
|
||||
'finance',
|
||||
'landing', // future — Paket C (Vereinswebsite)
|
||||
'presi',
|
||||
'cards',
|
||||
'quotes',
|
||||
'companion',
|
||||
'times',
|
||||
'notes',
|
||||
'photos',
|
||||
'activity',
|
||||
'goals',
|
||||
],
|
||||
|
||||
family: [
|
||||
'mana',
|
||||
'contacts',
|
||||
'calendar',
|
||||
'events',
|
||||
'mail',
|
||||
'storage',
|
||||
'uload',
|
||||
'recipes',
|
||||
'food',
|
||||
'places',
|
||||
'presi',
|
||||
'cards',
|
||||
'photos',
|
||||
'notes',
|
||||
'companion',
|
||||
'goals',
|
||||
'activity',
|
||||
'wetter',
|
||||
'wisekeep',
|
||||
'firsts',
|
||||
],
|
||||
|
||||
team: [
|
||||
'mana',
|
||||
'contacts',
|
||||
'calendar',
|
||||
'events',
|
||||
'storage',
|
||||
'mail',
|
||||
'uload',
|
||||
'news',
|
||||
'news-research',
|
||||
'research-lab',
|
||||
'presi',
|
||||
'cards',
|
||||
'picture',
|
||||
'notes',
|
||||
'quotes',
|
||||
'invoices',
|
||||
'companion',
|
||||
'ai-agents',
|
||||
'times',
|
||||
'activity',
|
||||
'goals',
|
||||
],
|
||||
|
||||
practice: [
|
||||
'mana',
|
||||
'contacts',
|
||||
'calendar',
|
||||
'storage',
|
||||
'mail',
|
||||
'uload',
|
||||
'invoices',
|
||||
'finance',
|
||||
'times',
|
||||
'notes',
|
||||
'presi',
|
||||
'cards',
|
||||
'quotes',
|
||||
'companion',
|
||||
'research-lab',
|
||||
'activity',
|
||||
'goals',
|
||||
],
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Check whether a module is available inside a given Space type.
|
||||
*
|
||||
* Used by:
|
||||
* - Scope wrapper (apps/mana/.../data/scope/scoped-db.ts) to block queries
|
||||
* against disallowed modules — structural guard against UI bypass.
|
||||
* - UI module launcher to hide disabled modules in the active space.
|
||||
* - Route guards that check before mounting a module page.
|
||||
*/
|
||||
export function isModuleAllowedInSpace(moduleId: SpaceModuleId, spaceType: SpaceType): boolean {
|
||||
const allow = SPACE_MODULE_ALLOWLIST[spaceType];
|
||||
if (allow === '*') return true;
|
||||
return allow.includes(moduleId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shape of the `metadata` JSONB column on Better Auth's `organization` table
|
||||
* for our Space extension. `type` is required; other fields accumulate as
|
||||
* features land (voiceDoc, legalEntity, uid, aiPersonaId, …).
|
||||
*/
|
||||
export interface SpaceMetadata {
|
||||
type: SpaceType;
|
||||
voiceDoc?: string;
|
||||
legalEntity?: string;
|
||||
uid?: string;
|
||||
aiPersonaId?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Narrow a raw metadata blob (from Better Auth / DB) to a validated
|
||||
* SpaceMetadata. Returns null if no valid type field is present — callers
|
||||
* decide whether to reject or default.
|
||||
*/
|
||||
export function parseSpaceMetadata(raw: unknown): SpaceMetadata | null {
|
||||
if (!raw || typeof raw !== 'object') return null;
|
||||
const obj = raw as Record<string, unknown>;
|
||||
if (!isSpaceType(obj.type)) return null;
|
||||
return obj as SpaceMetadata;
|
||||
}
|
||||
386
pnpm-lock.yaml
generated
386
pnpm-lock.yaml
generated
|
|
@ -138,14 +138,14 @@ importers:
|
|||
version: link:../../../../packages/shared-landing-ui
|
||||
astro:
|
||||
specifier: ^5.16.0
|
||||
version: 5.18.1(@azure/storage-blob@12.31.0)(@types/node@20.19.39)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)
|
||||
version: 5.18.1(@azure/storage-blob@12.31.0)(@types/node@20.19.39)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)
|
||||
typescript:
|
||||
specifier: ^5.9.2
|
||||
version: 5.9.3
|
||||
devDependencies:
|
||||
'@astrojs/tailwind':
|
||||
specifier: ^6.0.2
|
||||
version: 6.0.2(astro@5.18.1(@azure/storage-blob@12.31.0)(@types/node@20.19.39)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3))
|
||||
version: 6.0.2(astro@5.18.1(@azure/storage-blob@12.31.0)(@types/node@20.19.39)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3))
|
||||
'@tailwindcss/typography':
|
||||
specifier: ^0.5.18
|
||||
version: 0.5.19(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3))
|
||||
|
|
@ -154,13 +154,13 @@ importers:
|
|||
version: 20.19.39
|
||||
eslint:
|
||||
specifier: ^9.0.0
|
||||
version: 9.39.4(jiti@2.6.1)
|
||||
version: 9.39.4(jiti@1.21.7)
|
||||
eslint-config-prettier:
|
||||
specifier: ^9.1.0
|
||||
version: 9.1.2(eslint@9.39.4(jiti@2.6.1))
|
||||
version: 9.1.2(eslint@9.39.4(jiti@1.21.7))
|
||||
eslint-plugin-astro:
|
||||
specifier: ^1.0.0
|
||||
version: 1.6.0(eslint@9.39.4(jiti@2.6.1))
|
||||
version: 1.6.0(eslint@9.39.4(jiti@1.21.7))
|
||||
prettier:
|
||||
specifier: ^3.6.2
|
||||
version: 3.8.1
|
||||
|
|
@ -253,10 +253,10 @@ importers:
|
|||
version: 3.7.2
|
||||
'@astrojs/tailwind':
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.2(astro@5.18.1(@azure/storage-blob@12.31.0)(@types/node@24.12.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3))
|
||||
version: 6.0.2(astro@5.18.1(@azure/storage-blob@12.31.0)(@types/node@24.12.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3))
|
||||
astro:
|
||||
specifier: ^5.16.11
|
||||
version: 5.18.1(@azure/storage-blob@12.31.0)(@types/node@24.12.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)
|
||||
version: 5.18.1(@azure/storage-blob@12.31.0)(@types/node@24.12.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)
|
||||
tailwindcss:
|
||||
specifier: ^3.4.17
|
||||
version: 3.4.19(tsx@4.21.0)(yaml@2.8.3)
|
||||
|
|
@ -612,6 +612,9 @@ importers:
|
|||
svelte-sonner:
|
||||
specifier: ^1.0.5
|
||||
version: 1.1.0(svelte@5.55.1)
|
||||
swissqrbill:
|
||||
specifier: ^4.3.0
|
||||
version: 4.3.0(typescript@5.9.3)
|
||||
zod:
|
||||
specifier: ^3.25.76
|
||||
version: 3.25.76
|
||||
|
|
@ -2018,6 +2021,10 @@ importers:
|
|||
version: 5.9.3
|
||||
|
||||
packages/shared-branding:
|
||||
dependencies:
|
||||
'@mana/shared-types':
|
||||
specifier: workspace:*
|
||||
version: link:../shared-types
|
||||
devDependencies:
|
||||
svelte:
|
||||
specifier: ^5.0.0
|
||||
|
|
@ -2586,6 +2593,9 @@ importers:
|
|||
'@mana/shared-hono':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/shared-hono
|
||||
'@mana/shared-types':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/shared-types
|
||||
bcryptjs:
|
||||
specifier: ^3.0.2
|
||||
version: 3.0.3
|
||||
|
|
@ -15061,6 +15071,9 @@ packages:
|
|||
resolution: {integrity: sha512-QjvU7EFemf6mRzdMGlAFttMWtAAVXrax61SZYHdkD6yoVGQ89VeyKfZD4H1JrV1WLmJBxWhFch9H6ig/87VGjw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
svg-engine@0.3.0:
|
||||
resolution: {integrity: sha512-s172jAcwfoCcvM/6DwNBvmWN3brztHGFENCR+RU3CBJKeBxPrRlTltVxX1Je5hst782QgP8PM6U37vUR/RhPng==}
|
||||
|
||||
svg-parser@2.0.4:
|
||||
resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==}
|
||||
|
||||
|
|
@ -15078,6 +15091,18 @@ packages:
|
|||
engines: {node: '>=16'}
|
||||
hasBin: true
|
||||
|
||||
swissqrbill@4.3.0:
|
||||
resolution: {integrity: sha512-FzSPEVWVQ3R6B0vghqi7VJCLp54AMYxCSiJGr2QzXo8OSU8JBv7XJcF67BAVAriGkuaZqVfhxuD6yRFT6WAXEA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
pdfkit: '>=0.13.0'
|
||||
typescript: '>=4.7.0'
|
||||
peerDependenciesMeta:
|
||||
pdfkit:
|
||||
optional: true
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
symbol-observable@4.0.0:
|
||||
resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==}
|
||||
engines: {node: '>=0.10'}
|
||||
|
|
@ -16686,6 +16711,16 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- ts-node
|
||||
|
||||
'@astrojs/tailwind@6.0.2(astro@5.18.1(@azure/storage-blob@12.31.0)(@types/node@20.19.39)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3))':
|
||||
dependencies:
|
||||
astro: 5.18.1(@azure/storage-blob@12.31.0)(@types/node@20.19.39)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)
|
||||
autoprefixer: 10.4.27(postcss@8.5.8)
|
||||
postcss: 8.5.8
|
||||
postcss-load-config: 4.0.2(postcss@8.5.8)
|
||||
tailwindcss: 3.4.19(tsx@4.21.0)(yaml@2.8.3)
|
||||
transitivePeerDependencies:
|
||||
- ts-node
|
||||
|
||||
'@astrojs/tailwind@6.0.2(astro@5.18.1(@azure/storage-blob@12.31.0)(@types/node@20.19.39)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3))':
|
||||
dependencies:
|
||||
astro: 5.18.1(@azure/storage-blob@12.31.0)(@types/node@20.19.39)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)
|
||||
|
|
@ -16706,16 +16741,6 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- ts-node
|
||||
|
||||
'@astrojs/tailwind@6.0.2(astro@5.18.1(@azure/storage-blob@12.31.0)(@types/node@24.12.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3))':
|
||||
dependencies:
|
||||
astro: 5.18.1(@azure/storage-blob@12.31.0)(@types/node@24.12.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)
|
||||
autoprefixer: 10.4.27(postcss@8.5.8)
|
||||
postcss: 8.5.8
|
||||
postcss-load-config: 4.0.2(postcss@8.5.8)
|
||||
tailwindcss: 3.4.19(tsx@4.21.0)(yaml@2.8.3)
|
||||
transitivePeerDependencies:
|
||||
- ts-node
|
||||
|
||||
'@astrojs/tailwind@6.0.2(astro@5.18.1(@azure/storage-blob@12.31.0)(@types/node@24.12.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3))':
|
||||
dependencies:
|
||||
astro: 5.18.1(@azure/storage-blob@12.31.0)(@types/node@24.12.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)
|
||||
|
|
@ -18875,6 +18900,11 @@ snapshots:
|
|||
'@esbuild/win32-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@1.21.7))':
|
||||
dependencies:
|
||||
eslint: 9.39.4(jiti@1.21.7)
|
||||
eslint-visitor-keys: 3.4.3
|
||||
|
||||
'@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@2.6.1))':
|
||||
dependencies:
|
||||
eslint: 9.39.4(jiti@2.6.1)
|
||||
|
|
@ -23156,7 +23186,7 @@ snapshots:
|
|||
obug: 2.1.1
|
||||
std-env: 4.0.0
|
||||
tinyrainbow: 3.1.0
|
||||
vitest: 4.1.3(@opentelemetry/api@1.9.1)(@types/node@22.19.17)(@vitest/coverage-v8@4.1.3)(@vitest/ui@4.1.3)(jsdom@29.0.2(@noble/hashes@2.0.1))(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
vitest: 4.1.3(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.3)(@vitest/ui@4.1.3)(jsdom@29.0.2(@noble/hashes@2.0.1))(vite@6.4.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
|
||||
'@vitest/expect@4.1.3':
|
||||
dependencies:
|
||||
|
|
@ -23218,7 +23248,7 @@ snapshots:
|
|||
sirv: 3.0.2
|
||||
tinyglobby: 0.2.15
|
||||
tinyrainbow: 3.1.0
|
||||
vitest: 4.1.3(@opentelemetry/api@1.9.1)(@types/node@22.19.17)(@vitest/coverage-v8@4.1.3)(@vitest/ui@4.1.3)(jsdom@29.0.2(@noble/hashes@2.0.1))(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
vitest: 4.1.3(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.3)(@vitest/ui@4.1.3)(jsdom@29.0.2(@noble/hashes@2.0.1))(vite@6.4.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
|
||||
'@vitest/utils@4.1.3':
|
||||
dependencies:
|
||||
|
|
@ -23649,6 +23679,108 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
astro@5.18.1(@azure/storage-blob@12.31.0)(@types/node@20.19.39)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3):
|
||||
dependencies:
|
||||
'@astrojs/compiler': 2.13.1
|
||||
'@astrojs/internal-helpers': 0.7.6
|
||||
'@astrojs/markdown-remark': 6.3.11
|
||||
'@astrojs/telemetry': 3.3.0
|
||||
'@capsizecss/unpack': 4.0.0
|
||||
'@oslojs/encoding': 1.1.0
|
||||
'@rollup/pluginutils': 5.3.0(rollup@4.60.1)
|
||||
acorn: 8.16.0
|
||||
aria-query: 5.3.2
|
||||
axobject-query: 4.1.0
|
||||
boxen: 8.0.1
|
||||
ci-info: 4.4.0
|
||||
clsx: 2.1.1
|
||||
common-ancestor-path: 1.0.1
|
||||
cookie: 1.1.1
|
||||
cssesc: 3.0.0
|
||||
debug: 4.4.3
|
||||
deterministic-object-hash: 2.0.2
|
||||
devalue: 5.7.0
|
||||
diff: 8.0.4
|
||||
dlv: 1.1.3
|
||||
dset: 3.1.4
|
||||
es-module-lexer: 1.7.0
|
||||
esbuild: 0.27.7
|
||||
estree-walker: 3.0.3
|
||||
flattie: 1.1.1
|
||||
fontace: 0.4.1
|
||||
github-slugger: 2.0.0
|
||||
html-escaper: 3.0.3
|
||||
http-cache-semantics: 4.2.0
|
||||
import-meta-resolve: 4.2.0
|
||||
js-yaml: 4.1.1
|
||||
magic-string: 0.30.21
|
||||
magicast: 0.5.2
|
||||
mrmime: 2.0.1
|
||||
neotraverse: 0.6.18
|
||||
p-limit: 6.2.0
|
||||
p-queue: 8.1.1
|
||||
package-manager-detector: 1.6.0
|
||||
piccolore: 0.1.3
|
||||
picomatch: 4.0.4
|
||||
prompts: 2.4.2
|
||||
rehype: 13.0.2
|
||||
semver: 7.7.4
|
||||
shiki: 3.23.0
|
||||
smol-toml: 1.6.1
|
||||
svgo: 4.0.1
|
||||
tinyexec: 1.0.4
|
||||
tinyglobby: 0.2.15
|
||||
tsconfck: 3.1.6(typescript@5.9.3)
|
||||
ultrahtml: 1.6.0
|
||||
unifont: 0.7.4
|
||||
unist-util-visit: 5.1.0
|
||||
unstorage: 1.17.5(@azure/storage-blob@12.31.0)(ioredis@5.10.1)
|
||||
vfile: 6.0.3
|
||||
vite: 6.4.2(@types/node@20.19.39)(jiti@1.21.7)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
vitefu: 1.1.3(vite@6.4.2(@types/node@20.19.39)(jiti@1.21.7)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
xxhash-wasm: 1.1.0
|
||||
yargs-parser: 21.1.1
|
||||
yocto-spinner: 0.2.3
|
||||
zod: 3.25.76
|
||||
zod-to-json-schema: 3.25.2(zod@3.25.76)
|
||||
zod-to-ts: 1.2.0(typescript@5.9.3)(zod@3.25.76)
|
||||
optionalDependencies:
|
||||
sharp: 0.34.5
|
||||
transitivePeerDependencies:
|
||||
- '@azure/app-configuration'
|
||||
- '@azure/cosmos'
|
||||
- '@azure/data-tables'
|
||||
- '@azure/identity'
|
||||
- '@azure/keyvault-secrets'
|
||||
- '@azure/storage-blob'
|
||||
- '@capacitor/preferences'
|
||||
- '@deno/kv'
|
||||
- '@netlify/blobs'
|
||||
- '@planetscale/database'
|
||||
- '@types/node'
|
||||
- '@upstash/redis'
|
||||
- '@vercel/blob'
|
||||
- '@vercel/functions'
|
||||
- '@vercel/kv'
|
||||
- aws4fetch
|
||||
- db0
|
||||
- idb-keyval
|
||||
- ioredis
|
||||
- jiti
|
||||
- less
|
||||
- lightningcss
|
||||
- rollup
|
||||
- sass
|
||||
- sass-embedded
|
||||
- stylus
|
||||
- sugarss
|
||||
- supports-color
|
||||
- terser
|
||||
- tsx
|
||||
- typescript
|
||||
- uploadthing
|
||||
- yaml
|
||||
|
||||
astro@5.18.1(@azure/storage-blob@12.31.0)(@types/node@20.19.39)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3):
|
||||
dependencies:
|
||||
'@astrojs/compiler': 2.13.1
|
||||
|
|
@ -23853,108 +23985,6 @@ snapshots:
|
|||
- uploadthing
|
||||
- yaml
|
||||
|
||||
astro@5.18.1(@azure/storage-blob@12.31.0)(@types/node@24.12.2)(ioredis@5.10.1)(jiti@1.21.7)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3):
|
||||
dependencies:
|
||||
'@astrojs/compiler': 2.13.1
|
||||
'@astrojs/internal-helpers': 0.7.6
|
||||
'@astrojs/markdown-remark': 6.3.11
|
||||
'@astrojs/telemetry': 3.3.0
|
||||
'@capsizecss/unpack': 4.0.0
|
||||
'@oslojs/encoding': 1.1.0
|
||||
'@rollup/pluginutils': 5.3.0(rollup@4.60.1)
|
||||
acorn: 8.16.0
|
||||
aria-query: 5.3.2
|
||||
axobject-query: 4.1.0
|
||||
boxen: 8.0.1
|
||||
ci-info: 4.4.0
|
||||
clsx: 2.1.1
|
||||
common-ancestor-path: 1.0.1
|
||||
cookie: 1.1.1
|
||||
cssesc: 3.0.0
|
||||
debug: 4.4.3
|
||||
deterministic-object-hash: 2.0.2
|
||||
devalue: 5.7.0
|
||||
diff: 8.0.4
|
||||
dlv: 1.1.3
|
||||
dset: 3.1.4
|
||||
es-module-lexer: 1.7.0
|
||||
esbuild: 0.27.7
|
||||
estree-walker: 3.0.3
|
||||
flattie: 1.1.1
|
||||
fontace: 0.4.1
|
||||
github-slugger: 2.0.0
|
||||
html-escaper: 3.0.3
|
||||
http-cache-semantics: 4.2.0
|
||||
import-meta-resolve: 4.2.0
|
||||
js-yaml: 4.1.1
|
||||
magic-string: 0.30.21
|
||||
magicast: 0.5.2
|
||||
mrmime: 2.0.1
|
||||
neotraverse: 0.6.18
|
||||
p-limit: 6.2.0
|
||||
p-queue: 8.1.1
|
||||
package-manager-detector: 1.6.0
|
||||
piccolore: 0.1.3
|
||||
picomatch: 4.0.4
|
||||
prompts: 2.4.2
|
||||
rehype: 13.0.2
|
||||
semver: 7.7.4
|
||||
shiki: 3.23.0
|
||||
smol-toml: 1.6.1
|
||||
svgo: 4.0.1
|
||||
tinyexec: 1.0.4
|
||||
tinyglobby: 0.2.15
|
||||
tsconfck: 3.1.6(typescript@5.9.3)
|
||||
ultrahtml: 1.6.0
|
||||
unifont: 0.7.4
|
||||
unist-util-visit: 5.1.0
|
||||
unstorage: 1.17.5(@azure/storage-blob@12.31.0)(ioredis@5.10.1)
|
||||
vfile: 6.0.3
|
||||
vite: 6.4.2(@types/node@24.12.2)(jiti@1.21.7)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
vitefu: 1.1.3(vite@6.4.2(@types/node@24.12.2)(jiti@1.21.7)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
|
||||
xxhash-wasm: 1.1.0
|
||||
yargs-parser: 21.1.1
|
||||
yocto-spinner: 0.2.3
|
||||
zod: 3.25.76
|
||||
zod-to-json-schema: 3.25.2(zod@3.25.76)
|
||||
zod-to-ts: 1.2.0(typescript@5.9.3)(zod@3.25.76)
|
||||
optionalDependencies:
|
||||
sharp: 0.34.5
|
||||
transitivePeerDependencies:
|
||||
- '@azure/app-configuration'
|
||||
- '@azure/cosmos'
|
||||
- '@azure/data-tables'
|
||||
- '@azure/identity'
|
||||
- '@azure/keyvault-secrets'
|
||||
- '@azure/storage-blob'
|
||||
- '@capacitor/preferences'
|
||||
- '@deno/kv'
|
||||
- '@netlify/blobs'
|
||||
- '@planetscale/database'
|
||||
- '@types/node'
|
||||
- '@upstash/redis'
|
||||
- '@vercel/blob'
|
||||
- '@vercel/functions'
|
||||
- '@vercel/kv'
|
||||
- aws4fetch
|
||||
- db0
|
||||
- idb-keyval
|
||||
- ioredis
|
||||
- jiti
|
||||
- less
|
||||
- lightningcss
|
||||
- rollup
|
||||
- sass
|
||||
- sass-embedded
|
||||
- stylus
|
||||
- sugarss
|
||||
- supports-color
|
||||
- terser
|
||||
- tsx
|
||||
- typescript
|
||||
- uploadthing
|
||||
- yaml
|
||||
|
||||
astro@5.18.1(@azure/storage-blob@12.31.0)(@types/node@24.12.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3):
|
||||
dependencies:
|
||||
'@astrojs/compiler': 2.13.1
|
||||
|
|
@ -25758,6 +25788,11 @@ snapshots:
|
|||
eslint: 9.39.4(jiti@2.6.1)
|
||||
semver: 7.7.4
|
||||
|
||||
eslint-compat-utils@0.6.5(eslint@9.39.4(jiti@1.21.7)):
|
||||
dependencies:
|
||||
eslint: 9.39.4(jiti@1.21.7)
|
||||
semver: 7.7.4
|
||||
|
||||
eslint-compat-utils@0.6.5(eslint@9.39.4(jiti@2.6.1)):
|
||||
dependencies:
|
||||
eslint: 9.39.4(jiti@2.6.1)
|
||||
|
|
@ -25767,6 +25802,10 @@ snapshots:
|
|||
dependencies:
|
||||
eslint: 9.39.4(jiti@2.6.1)
|
||||
|
||||
eslint-config-prettier@9.1.2(eslint@9.39.4(jiti@1.21.7)):
|
||||
dependencies:
|
||||
eslint: 9.39.4(jiti@1.21.7)
|
||||
|
||||
eslint-config-prettier@9.1.2(eslint@9.39.4(jiti@2.6.1)):
|
||||
dependencies:
|
||||
eslint: 9.39.4(jiti@2.6.1)
|
||||
|
|
@ -25811,6 +25850,20 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-astro@1.6.0(eslint@9.39.4(jiti@1.21.7)):
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@1.21.7))
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
'@typescript-eslint/types': 8.58.0
|
||||
astro-eslint-parser: 1.4.0
|
||||
eslint: 9.39.4(jiti@1.21.7)
|
||||
eslint-compat-utils: 0.6.5(eslint@9.39.4(jiti@1.21.7))
|
||||
globals: 16.5.0
|
||||
postcss: 8.5.8
|
||||
postcss-selector-parser: 7.1.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-astro@1.6.0(eslint@9.39.4(jiti@2.6.1)):
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1))
|
||||
|
|
@ -25984,6 +26037,47 @@ snapshots:
|
|||
|
||||
eslint-visitor-keys@5.0.1: {}
|
||||
|
||||
eslint@9.39.4(jiti@1.21.7):
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@1.21.7))
|
||||
'@eslint-community/regexpp': 4.12.2
|
||||
'@eslint/config-array': 0.21.2
|
||||
'@eslint/config-helpers': 0.4.2
|
||||
'@eslint/core': 0.17.0
|
||||
'@eslint/eslintrc': 3.3.5
|
||||
'@eslint/js': 9.39.4
|
||||
'@eslint/plugin-kit': 0.4.1
|
||||
'@humanfs/node': 0.16.7
|
||||
'@humanwhocodes/module-importer': 1.0.1
|
||||
'@humanwhocodes/retry': 0.4.3
|
||||
'@types/estree': 1.0.8
|
||||
ajv: 6.14.0
|
||||
chalk: 4.1.2
|
||||
cross-spawn: 7.0.6
|
||||
debug: 4.4.3
|
||||
escape-string-regexp: 4.0.0
|
||||
eslint-scope: 8.4.0
|
||||
eslint-visitor-keys: 4.2.1
|
||||
espree: 10.4.0
|
||||
esquery: 1.7.0
|
||||
esutils: 2.0.3
|
||||
fast-deep-equal: 3.1.3
|
||||
file-entry-cache: 8.0.0
|
||||
find-up: 5.0.0
|
||||
glob-parent: 6.0.2
|
||||
ignore: 5.3.2
|
||||
imurmurhash: 0.1.4
|
||||
is-glob: 4.0.3
|
||||
json-stable-stringify-without-jsonify: 1.0.1
|
||||
lodash.merge: 4.6.2
|
||||
minimatch: 3.1.5
|
||||
natural-compare: 1.4.0
|
||||
optionator: 0.9.4
|
||||
optionalDependencies:
|
||||
jiti: 1.21.7
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint@9.39.4(jiti@2.6.1):
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4(jiti@2.6.1))
|
||||
|
|
@ -32247,6 +32341,8 @@ snapshots:
|
|||
magic-string: 0.30.21
|
||||
zimmerframe: 1.1.4
|
||||
|
||||
svg-engine@0.3.0: {}
|
||||
|
||||
svg-parser@2.0.4: {}
|
||||
|
||||
svg-pathdata@6.0.3:
|
||||
|
|
@ -32272,6 +32368,12 @@ snapshots:
|
|||
picocolors: 1.1.1
|
||||
sax: 1.6.0
|
||||
|
||||
swissqrbill@4.3.0(typescript@5.9.3):
|
||||
dependencies:
|
||||
svg-engine: 0.3.0
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
|
||||
symbol-observable@4.0.0: {}
|
||||
|
||||
symbol-tree@3.2.4: {}
|
||||
|
|
@ -32954,6 +33056,23 @@ snapshots:
|
|||
lightningcss: 1.32.0
|
||||
terser: 5.46.1
|
||||
|
||||
vite@6.4.2(@types/node@20.19.39)(jiti@1.21.7)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3):
|
||||
dependencies:
|
||||
esbuild: 0.25.12
|
||||
fdir: 6.5.0(picomatch@4.0.4)
|
||||
picomatch: 4.0.4
|
||||
postcss: 8.5.8
|
||||
rollup: 4.60.1
|
||||
tinyglobby: 0.2.15
|
||||
optionalDependencies:
|
||||
'@types/node': 20.19.39
|
||||
fsevents: 2.3.3
|
||||
jiti: 1.21.7
|
||||
lightningcss: 1.32.0
|
||||
terser: 5.46.1
|
||||
tsx: 4.21.0
|
||||
yaml: 2.8.3
|
||||
|
||||
vite@6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3):
|
||||
dependencies:
|
||||
esbuild: 0.25.12
|
||||
|
|
@ -32988,23 +33107,6 @@ snapshots:
|
|||
tsx: 4.21.0
|
||||
yaml: 2.8.3
|
||||
|
||||
vite@6.4.2(@types/node@24.12.2)(jiti@1.21.7)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3):
|
||||
dependencies:
|
||||
esbuild: 0.25.12
|
||||
fdir: 6.5.0(picomatch@4.0.4)
|
||||
picomatch: 4.0.4
|
||||
postcss: 8.5.8
|
||||
rollup: 4.60.1
|
||||
tinyglobby: 0.2.15
|
||||
optionalDependencies:
|
||||
'@types/node': 24.12.2
|
||||
fsevents: 2.3.3
|
||||
jiti: 1.21.7
|
||||
lightningcss: 1.32.0
|
||||
terser: 5.46.1
|
||||
tsx: 4.21.0
|
||||
yaml: 2.8.3
|
||||
|
||||
vite@6.4.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3):
|
||||
dependencies:
|
||||
esbuild: 0.25.12
|
||||
|
|
@ -33022,6 +33124,10 @@ snapshots:
|
|||
tsx: 4.21.0
|
||||
yaml: 2.8.3
|
||||
|
||||
vitefu@1.1.3(vite@6.4.2(@types/node@20.19.39)(jiti@1.21.7)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
|
||||
optionalDependencies:
|
||||
vite: 6.4.2(@types/node@20.19.39)(jiti@1.21.7)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
|
||||
vitefu@1.1.3(vite@6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
|
||||
optionalDependencies:
|
||||
vite: 6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
|
|
@ -33030,10 +33136,6 @@ snapshots:
|
|||
optionalDependencies:
|
||||
vite: 6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
|
||||
vitefu@1.1.3(vite@6.4.2(@types/node@24.12.2)(jiti@1.21.7)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
|
||||
optionalDependencies:
|
||||
vite: 6.4.2(@types/node@24.12.2)(jiti@1.21.7)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
|
||||
vitefu@1.1.3(vite@6.4.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)):
|
||||
optionalDependencies:
|
||||
vite: 6.4.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
"dependencies": {
|
||||
"@mana/shared-ai": "workspace:*",
|
||||
"@mana/shared-hono": "workspace:*",
|
||||
"@mana/shared-types": "workspace:*",
|
||||
"hono": "^4.7.0",
|
||||
"better-auth": "^1.4.3",
|
||||
"drizzle-orm": "^0.38.3",
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import {
|
|||
} from '../email/send';
|
||||
import { sourceAppStore, passwordResetRedirectStore } from './stores';
|
||||
import { TRUSTED_ORIGINS } from './sso-origins';
|
||||
import { assertValidSpaceMetadataForCreate, assertSpaceIsDeletable } from '../spaces';
|
||||
|
||||
// Re-export so existing imports (`import { TRUSTED_ORIGINS } from './better-auth.config'`)
|
||||
// keep working. New code should import from './sso-origins' directly.
|
||||
|
|
@ -281,6 +282,21 @@ export function createBetterAuth(databaseUrl: string) {
|
|||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Spaces — enforce that every organization carries a valid
|
||||
* `metadata.type` (the Space type), and block deletion of the
|
||||
* user's personal space. See docs/plans/spaces-foundation.md
|
||||
* and ../spaces/metadata.ts.
|
||||
*/
|
||||
organizationHooks: {
|
||||
beforeCreateOrganization: async ({ organization }) => {
|
||||
assertValidSpaceMetadataForCreate(organization.metadata);
|
||||
},
|
||||
beforeDeleteOrganization: async ({ organization }) => {
|
||||
assertSpaceIsDeletable(organization.metadata);
|
||||
},
|
||||
},
|
||||
|
||||
// Custom roles and permissions
|
||||
organizationRole: {
|
||||
owner: {
|
||||
|
|
|
|||
15
services/mana-auth/src/spaces/index.ts
Normal file
15
services/mana-auth/src/spaces/index.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* Spaces — multi-tenancy helpers for mana-auth.
|
||||
*
|
||||
* The canonical SpaceType + allowlist lives in @mana/shared-types. This
|
||||
* barrel adds auth-side concerns: Better Auth hook helpers for validating
|
||||
* organization metadata, and (future) slug generation for personal spaces.
|
||||
*
|
||||
* See docs/plans/spaces-foundation.md.
|
||||
*/
|
||||
|
||||
export {
|
||||
assertValidSpaceMetadataForCreate,
|
||||
assertSpaceIsDeletable,
|
||||
buildSpaceMetadata,
|
||||
} from './metadata';
|
||||
84
services/mana-auth/src/spaces/metadata.spec.ts
Normal file
84
services/mana-auth/src/spaces/metadata.spec.ts
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
/**
|
||||
* Tests for Space-metadata validation used by Better Auth organization hooks.
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'bun:test';
|
||||
import {
|
||||
assertValidSpaceMetadataForCreate,
|
||||
assertSpaceIsDeletable,
|
||||
buildSpaceMetadata,
|
||||
} from './metadata';
|
||||
|
||||
describe('assertValidSpaceMetadataForCreate', () => {
|
||||
it('accepts metadata with every valid SpaceType', () => {
|
||||
for (const type of ['personal', 'brand', 'club', 'family', 'team', 'practice'] as const) {
|
||||
const parsed = assertValidSpaceMetadataForCreate({ type });
|
||||
expect(parsed.type).toBe(type);
|
||||
}
|
||||
});
|
||||
|
||||
it('preserves extra metadata fields', () => {
|
||||
const parsed = assertValidSpaceMetadataForCreate({
|
||||
type: 'brand',
|
||||
voiceDoc: 'hello',
|
||||
uid: 'CH-123',
|
||||
});
|
||||
expect(parsed.voiceDoc).toBe('hello');
|
||||
expect(parsed.uid).toBe('CH-123');
|
||||
});
|
||||
|
||||
it('rejects missing metadata', () => {
|
||||
expect(() => assertValidSpaceMetadataForCreate(null)).toThrow(/type/i);
|
||||
expect(() => assertValidSpaceMetadataForCreate(undefined)).toThrow(/type/i);
|
||||
});
|
||||
|
||||
it('rejects missing type field', () => {
|
||||
expect(() => assertValidSpaceMetadataForCreate({})).toThrow(/type/i);
|
||||
expect(() => assertValidSpaceMetadataForCreate({ name: 'Edisconet' })).toThrow(/type/i);
|
||||
});
|
||||
|
||||
it('rejects unknown SpaceType values', () => {
|
||||
expect(() => assertValidSpaceMetadataForCreate({ type: 'corporate' })).toThrow(/type/i);
|
||||
expect(() => assertValidSpaceMetadataForCreate({ type: 'PERSONAL' })).toThrow(/type/i);
|
||||
});
|
||||
});
|
||||
|
||||
describe('assertSpaceIsDeletable', () => {
|
||||
it('blocks deletion of personal spaces', () => {
|
||||
expect(() => assertSpaceIsDeletable({ type: 'personal' })).toThrow(
|
||||
/personal space cannot be deleted/i
|
||||
);
|
||||
});
|
||||
|
||||
it('allows deletion of other space types', () => {
|
||||
for (const type of ['brand', 'club', 'family', 'team', 'practice'] as const) {
|
||||
expect(() => assertSpaceIsDeletable({ type })).not.toThrow();
|
||||
}
|
||||
});
|
||||
|
||||
it('allows deletion when metadata is malformed (fail-open by design)', () => {
|
||||
// If metadata is missing or invalid, we don't block — the delete endpoint
|
||||
// enforces other permission checks (owner role, etc.) and we only want to
|
||||
// guard the personal-space special case.
|
||||
expect(() => assertSpaceIsDeletable(null)).not.toThrow();
|
||||
expect(() => assertSpaceIsDeletable({})).not.toThrow();
|
||||
expect(() => assertSpaceIsDeletable({ type: 'unknown' })).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildSpaceMetadata', () => {
|
||||
it('returns a metadata blob with the given type', () => {
|
||||
expect(buildSpaceMetadata('club').type).toBe('club');
|
||||
});
|
||||
|
||||
it('merges extra fields after the type', () => {
|
||||
const meta = buildSpaceMetadata('brand', { voiceDoc: 'X', uid: 'Y' });
|
||||
expect(meta).toEqual({ type: 'brand', voiceDoc: 'X', uid: 'Y' });
|
||||
});
|
||||
|
||||
it('lets explicit type win even if extras try to override', () => {
|
||||
// Extra is typed to exclude `type`, but at runtime someone could try.
|
||||
const meta = buildSpaceMetadata('brand', { voiceDoc: 'X' } as Record<string, unknown>);
|
||||
expect(meta.type).toBe('brand');
|
||||
});
|
||||
});
|
||||
68
services/mana-auth/src/spaces/metadata.ts
Normal file
68
services/mana-auth/src/spaces/metadata.ts
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
/**
|
||||
* Space metadata validation for Better Auth organization hooks.
|
||||
*
|
||||
* Every Better Auth organization in Mana must carry a `metadata.type` field
|
||||
* that identifies the Space type (personal/brand/club/family/team/practice).
|
||||
* This module enforces that contract at the plugin-hook layer.
|
||||
*
|
||||
* See docs/plans/spaces-foundation.md.
|
||||
*/
|
||||
|
||||
import { APIError } from 'better-auth/api';
|
||||
import {
|
||||
SPACE_TYPES,
|
||||
isSpaceType,
|
||||
parseSpaceMetadata,
|
||||
type SpaceMetadata,
|
||||
type SpaceType,
|
||||
} from '@mana/shared-types';
|
||||
|
||||
/**
|
||||
* Validate the metadata blob that will be persisted for a new organization.
|
||||
* Throws a Better Auth `APIError` (BAD_REQUEST) if the shape is invalid.
|
||||
*
|
||||
* Intended for `organizationHooks.beforeCreateOrganization`.
|
||||
*/
|
||||
export function assertValidSpaceMetadataForCreate(raw: unknown): SpaceMetadata {
|
||||
const parsed = parseSpaceMetadata(raw);
|
||||
if (!parsed) {
|
||||
throw new APIError('BAD_REQUEST', {
|
||||
message: `Organization metadata must include a valid "type" field. Expected one of: ${SPACE_TYPES.join(', ')}.`,
|
||||
code: 'SPACE_METADATA_INVALID',
|
||||
});
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guard a delete call against removing the user's personal space.
|
||||
* Better Auth will still allow admins/owners to delete other spaces — we only
|
||||
* protect the auto-created personal one, because losing it would orphan all
|
||||
* the user's private data.
|
||||
*
|
||||
* Intended for `organizationHooks.beforeDeleteOrganization`.
|
||||
*/
|
||||
export function assertSpaceIsDeletable(metadata: unknown): void {
|
||||
const parsed = parseSpaceMetadata(metadata);
|
||||
if (parsed?.type === 'personal') {
|
||||
throw new APIError('FORBIDDEN', {
|
||||
message: 'The personal space cannot be deleted. Delete the user account instead.',
|
||||
code: 'SPACE_PERSONAL_UNDELETABLE',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a metadata blob for a freshly-created space of a given type. Used by
|
||||
* the signup-time personal-space auto-creator and by any future UI that
|
||||
* creates spaces of other types.
|
||||
*/
|
||||
export function buildSpaceMetadata(
|
||||
type: SpaceType,
|
||||
extra: Omit<SpaceMetadata, 'type'> = {}
|
||||
): SpaceMetadata {
|
||||
if (!isSpaceType(type)) {
|
||||
throw new Error(`Invalid SpaceType: ${String(type)}`);
|
||||
}
|
||||
return { ...extra, type };
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue