Resources
Resource context is the shared model behind route-aware controllers and resource path helpers.
It tells Ginjou what each resource is called, which paths belong to each action, and which defaults should be reused for that resource.
Router context is only needed when Ginjou must read or write the current location, such as resource inference or controller route sync.
| Controller or helper | How it uses resource data |
|---|---|
useCreate | Resolve the resource name and default fetcher for create flows. |
useEdit | Resolve the resource name, default id, and default fetcher for edit flows. |
useShow | Resolve the resource name, default id, and default fetcher for detail pages. |
useList | Resolve the resource name and default fetcher for list pages. |
useInfiniteList | Resolve the resource name and default fetcher for infinite lists. |
useSelect | Resolve the resource name and default fetcher for select data. |
useResource | Resolve the current resource from an explicit name or from the current location. |
useResourcePath | Build action paths from the resource definition. |
Resource Context
Resource context is the registry of resource definitions for your app.
Each definition names one resource, declares its action paths, and can attach resource-level metadata.
Interface
interface Resource {
resources: Array<{
name: string
list?:
| string
| {
pattern: string
parse: (location: any) => any
}
create?:
| string
| {
pattern: string
parse: (location: any) => any
}
show?:
| string
| {
pattern: string
parse: (location: any) => any
}
edit?:
| string
| {
pattern: string
parse: (location: any) => any
}
meta?: {
parent?: string
hide?: boolean
deletable?: boolean
fetcherName?: string
[key: string]: any
}
}>
}
Properties
| Property | Meaning |
|---|---|
resources | The full list of resource definitions. |
Resource Item
| Property | Meaning |
|---|---|
name | The stable identifier for one resource. |
list | The route definition for list pages. |
create | The route definition for create pages. |
show | The route definition for detail pages. |
edit | The route definition for edit pages. |
meta | Extra resource-level settings and metadata. |
Meta
| Property | Meaning |
|---|---|
parent | Optional parent resource name. |
hide | Optional UI hint to hide a resource. |
deletable | Optional flag for delete support. |
fetcherName | The default fetcher used when a composable does not pass its own fetcherName. |
<script setup lang="ts">
import { defineResource } from '@ginjou/core'
import { defineResourceContext } from '@ginjou/vue'
defineResourceContext(defineResource({
resources: [
{
name: 'posts',
list: '/posts',
create: '/posts/create',
show: '/posts/:id',
edit: '/posts/:id/edit',
},
],
}))
</script>
<script lang="ts">
import { defineResource } from '@ginjou/core'
import { defineResourceContext } from '@ginjou/svelte'
defineResourceContext(defineResource({
resources: [
{
name: 'posts',
list: '/posts',
create: '/posts/create',
show: '/posts/:id',
edit: '/posts/:id/edit',
},
],
}))
</script>
Name and Action Paths
name is the stable resource identifier used by higher-level composables. Action paths such as list, create, show, and edit tell Ginjou how to match or build URLs for that resource.
String patterns are enough for standard CRUD pages. When router context exists, route-aware controllers compare the current location against the registered action paths.
With the posts definition above, useShow() can infer the current resource and record id without passing either one explicitly.
<script setup lang="ts">
import { useShow } from '@ginjou/vue'
const post = useShow()
</script>
<script lang="ts">
import { useShow } from '@ginjou/svelte'
const post = useShow()
</script>
If the current route is /posts/42, useShow() resolves the posts resource, the show action, and the current id.
| Current route | Resolved resource | Resolved action | Resolved id |
|---|---|---|---|
/posts | posts | list | - |
/posts/create | posts | create | - |
/posts/42 | posts | show | 42 |
/posts/42/edit | posts | edit | 42 |
Fetcher Selection
Each resource can define meta.fetcherName as its default fetcher.
Controllers such as useList, useShow, useCreate, useEdit, and useSelect read that value automatically.
Fetcher selection follows this order.
| Priority | Source | Used when |
|---|---|---|
| 1 | fetcherName passed directly to a composable | Always wins. |
| 2 | resource.meta.fetcherName | Used when the composable does not pass fetcherName. |
This lets you set one backend per resource without repeating it in every composable call.
Register the resource with a default fetcher.
<script setup lang="ts">
import { defineResource } from '@ginjou/core'
import { defineResourceContext } from '@ginjou/vue'
defineResourceContext(defineResource({
resources: [
{
name: 'posts',
list: '/posts',
show: '/posts/:id',
meta: {
fetcherName: 'cms',
},
},
],
}))
</script>
<script lang="ts">
import { defineResource } from '@ginjou/core'
import { defineResourceContext } from '@ginjou/svelte'
defineResourceContext(defineResource({
resources: [
{
name: 'posts',
list: '/posts',
show: '/posts/:id',
meta: {
fetcherName: 'cms',
},
},
],
}))
</script>
Then consume it from a controller. Passing fetcherName locally still overrides the resource default.
<script setup lang="ts">
import { useList, useShow } from '@ginjou/vue'
const posts = useList({
resource: 'posts',
})
const preview = useShow({
resource: 'posts',
id: 1,
fetcherName: 'preview',
})
</script>
<script lang="ts">
import { useList, useShow } from '@ginjou/svelte'
const posts = useList({
resource: 'posts',
})
const preview = useShow({
resource: 'posts',
id: 1,
fetcherName: 'preview',
})
</script>
In that example, useList() uses cms, while useShow() uses preview because the local prop has higher priority.
Custom Parsing
Each action path can also use an object with pattern and parse.
Use this when plain path matching is not enough and the current route needs custom interpretation.
| Field | Used for |
|---|---|
pattern | Defines the path shape and is still used for path creation. |
parse | Adds custom route inference when pathname matching alone is not enough. |
import { defineResource, ResourceActionType } from '@ginjou/core'
defineResource({
resources: [
{
name: 'posts',
show: {
pattern: '/posts/:id',
parse(location) {
if (location.query?.mode !== 'detail')
return
const matched = /^\/posts\/([^/]+)$/.exec(location.path)
if (!matched)
return
return {
action: ResourceActionType.Show,
id: matched[1],
}
},
},
},
],
})
That lets route inference depend on more than the pathname.
Path creation still uses /posts/:id, because path creation reads pattern, not parse.
Current Resource
useResource() resolves one resource definition and its parsed route state.
When you pass name, Ginjou resolves that resource directly. If router context also exists, it still parses the current location against that resource. When you omit name, Ginjou checks the registered resources in order and returns the first one that matches the current location.
| Field | Meaning |
|---|---|
resource | The matched resource definition. |
action | The matched action such as list, show, or edit. |
id | The parsed record id when the route includes one. |
If resource context is missing, or no definition matches, the result is undefined.
<script setup lang="ts">
import { useResource } from '@ginjou/vue'
const inferred = useResource()
const posts = useResource({
name: 'posts',
})
</script>
<template>
<div>
<p>Current resource: {{ inferred?.resource.name }}</p>
<p>Current action: {{ inferred?.action }}</p>
<p>Current id: {{ inferred?.id }}</p>
<p>Explicit resource: {{ posts?.resource.name }}</p>
</div>
</template>
<script lang="ts">
import { useResource } from '@ginjou/svelte'
const inferred = useResource()
const posts = useResource({
name: 'posts',
})
</script>
<div>
<p>Current resource: {inferred?.resource.name}</p>
<p>Current action: {inferred?.action}</p>
<p>Current id: {inferred?.id}</p>
<p>Explicit resource: {posts?.resource.name}</p>
</div>
This is the lower-level primitive behind route-aware controllers such as useShow() and useEdit().
Build Resource Paths
useResourcePath() builds one action path from the resolved resource definition.
You can pass a resource name explicitly, or let Ginjou start from the current resolved resource.
| Input | Effect |
|---|---|
action | Picks the target action path such as list, create, show, or edit. |
resource | Resolves that resource instead of using the current one. |
params | Fills route params such as :id and overrides inferred values when both exist. |
When the current resource already includes an id, path creation can reuse it.
<script setup lang="ts">
import { ResourceActionType } from '@ginjou/core'
import { useResourcePath } from '@ginjou/vue'
const createPath = useResourcePath({
action: ResourceActionType.Create,
resource: 'posts',
})
const editPath = useResourcePath({
action: ResourceActionType.Edit,
resource: 'posts',
params: {
id: 42,
},
})
</script>
<script lang="ts">
import { ResourceActionType } from '@ginjou/core'
import { useResourcePath } from '@ginjou/svelte'
const createPath = useResourcePath({
action: ResourceActionType.Create,
resource: 'posts',
})
const editPath = useResourcePath({
action: ResourceActionType.Edit,
resource: 'posts',
params: {
id: 42,
},
})
</script>
| Variable | Result |
|---|---|
createPath | /posts/create |
editPath | /posts/42/edit |
If the resource cannot be resolved, or the target action path is missing, the result is undefined.
This keeps links aligned with the same resource definitions that power your controllers.