@repo/resource-admin
Resource admin defines shared CRUD resource contracts and generates default admin remotes and Svelte UI.
Docs
Quick Start
Define the shared resource in a file that is safe for server and client imports.
import { defineResource } from '@repo/resource-admin'
import { exampleModelsTable } from './database/dbSchema'
export const exampleModelsResource = defineResource(exampleModelsTable, {
key: 'exampleModels',
basePath: '/example-models',
permissions: { create: false },
listSchema: schema => schema.pick({ name: true }),
insertSchema: schema => schema.pick({ name: true }),
filterSchema: schema => schema.pick({ name: true }),
})
Configure the server helper once in the app.
// src/lib/resourceAdmin.server.ts
import { getTenantDb } from '$lib/db/managedDb.server'
import { createResourceAdminServer } from '@repo/resource-admin/server'
export const { createRemoteFunctions } = createResourceAdminServer({ getDb: getTenantDb })
Generate flat remote exports in .remote.ts.
import { createRemoteFunctions } from '$lib/resourceAdmin.server'
import { exampleModelsResource } from '../resource'
export const {
listQuery,
optionsQuery,
createForm,
getQuery,
updateForm,
deleteForm,
getResourceAccessQuery,
} = createRemoteFunctions(exampleModelsResource)
Generated exports use SvelteKit wrapper names. Override implementations use the action names list, options, create, get, update, and delete.
Backend gates are generated by default from resource.permission. Add base gates or action-specific overrides in .remote.ts.
Set a resource permission to false to disable generated access for that action; disabled create forms reject before validation or DB work.
import { featureFlag } from '$lib/library/authHelpers.server'
import { createRemoteFunctions } from '$lib/resourceAdmin.server'
import { canEditExampleModelGate } from '../gates.server'
import { exampleModelsResource } from '../resource'
export const { listQuery, updateForm, getResourceAccessQuery } = createRemoteFunctions(exampleModelsResource, {
gates: {
domain: 'exampleModels',
base: [featureFlag('example-models')],
update: ({ defaults, id }) => [...defaults, canEditExampleModelGate(id)],
},
})
Render default pages with the MonoStack namespace.
<script lang="ts">
import { MonoStack } from '@repo/resource-admin/components'
import { exampleModelsResource } from '$lib/domains/exampleModels/resource'
import { listQuery, getResourceAccessQuery } from '$lib/domains/exampleModels/database/exampleModels.remote'
</script>
<MonoStack.Overview resource={exampleModelsResource} {listQuery} {getResourceAccessQuery} />
Use MonoStack.Overview's actions snippet for page-specific header actions.
Pass getRowLink={false} for list-only resources without detail pages.
<MonoStack.Overview resource={exampleModelsResource} {listQuery} {getResourceAccessQuery}>
{#snippet actions()}
<ActionButton.Root>
<ActionButton.Item>
<ActionButton.Remote form={runExampleModelActionForm}>
{#snippet label()}Run action{/snippet}
</ActionButton.Remote>
</ActionButton.Item>
</ActionButton.Root>
{/snippet}
</MonoStack.Overview>
Pass the resource's generated audit-log query on update pages to show audit logs. It uses the resource's table and read gates server-side.
<script lang="ts">
import { MonoStack } from '@repo/resource-admin/components'
import { exampleModelsResource } from '$lib/domains/exampleModels/resource'
import { deleteForm, getAuditLogQuery, getQuery, getResourceAccessQuery, updateForm } from '$lib/domains/exampleModels/database/exampleModels.remote'
</script>
<MonoStack.Update
resource={exampleModelsResource}
{getQuery}
{updateForm}
{deleteForm}
{getResourceAccessQuery}
getAuditLogs={getAuditLogQuery}
/>
Users with read access but no update access see the detail form in a disabled fieldset without a save button.
Use MonoStack.Update's actions snippet for page-specific header actions.
The snippet receives the resolved id, loaded data, and component-owned access checks.
<MonoStack.Update
resource={exampleModelsResource}
{getQuery}
{updateForm}
{deleteForm}
{getResourceAccessQuery}
>
{#snippet actions({ id, access })}
{#if access.canUpdate}
<ActionButton.Remote form={startExampleModelJobForm}>
<input type='hidden' name='id' value={id} />
{#snippet label()}Start job{/snippet}
</ActionButton.Remote>
{/if}
{/snippet}
</MonoStack.Update>
Boundaries
resource.ts is shared. Keep it free of DB queries, remote functions, Svelte components, TanStack renderers, and server-only auth logic.
.remote.ts is server-only. Put filters, gates, DB access, custom loaders, and overrides there.
Route pages own UI overrides. Put columns, form layout, field widgets, and copy there.
Exports
| Export | Use |
|---|---|
@repo/resource-admin | defineResource and resource types |
@repo/resource-admin/server | createRemoteFunctions, createResourceAdminServer |
@repo/resource-admin/components | MonoStack.Overview, MonoStack.Create, MonoStack.Update |