@owlmeans/client 0.1.18-rc.2 → 0.1.18-rc.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -12
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/client/SKILL.md +194 -19
- package/build/context.d.ts.map +1 -1
- package/build/context.js +1 -3
- package/build/context.js.map +1 -1
- package/build/entrypoint.d.ts +3 -0
- package/build/entrypoint.d.ts.map +1 -0
- package/build/entrypoint.js +4 -0
- package/build/entrypoint.js.map +1 -0
- package/build/helper.js +3 -3
- package/build/helper.js.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -1
- package/build/index.js.map +1 -1
- package/build/navigate.d.ts.map +1 -1
- package/build/navigate.js +15 -18
- package/build/navigate.js.map +1 -1
- package/build/router.d.ts.map +1 -1
- package/build/router.js +12 -6
- package/build/router.js.map +1 -1
- package/build/services/debug.js.map +1 -1
- package/build/store.d.ts +19 -3
- package/build/store.d.ts.map +1 -1
- package/build/store.js +57 -50
- package/build/store.js.map +1 -1
- package/build/types.d.ts +16 -5
- package/build/types.d.ts.map +1 -1
- package/build/utils/entrypoint.d.ts +3 -0
- package/build/utils/entrypoint.d.ts.map +1 -0
- package/build/utils/{module.js → entrypoint.js} +2 -2
- package/build/utils/entrypoint.js.map +1 -0
- package/build/utils/index.d.ts +1 -1
- package/build/utils/index.d.ts.map +1 -1
- package/build/utils/index.js +1 -1
- package/build/utils/index.js.map +1 -1
- package/build/utils/route.d.ts.map +1 -1
- package/build/utils/route.js +12 -6
- package/build/utils/route.js.map +1 -1
- package/build/utils/router.d.ts +4 -4
- package/build/utils/router.d.ts.map +1 -1
- package/build/utils/router.js +8 -8
- package/build/utils/router.js.map +1 -1
- package/package.json +12 -12
- package/src/context.ts +1 -4
- package/src/entrypoint.ts +6 -0
- package/src/helper.tsx +6 -6
- package/src/index.ts +1 -1
- package/src/navigate.ts +16 -19
- package/src/router.ts +14 -10
- package/src/services/debug.ts +2 -2
- package/src/store.ts +71 -54
- package/src/types.ts +18 -5
- package/src/utils/{module.ts → entrypoint.ts} +2 -2
- package/src/utils/index.ts +1 -1
- package/src/utils/route.tsx +15 -8
- package/src/utils/router.ts +11 -11
- package/build/module.d.ts +0 -3
- package/build/module.d.ts.map +0 -1
- package/build/module.js +0 -4
- package/build/module.js.map +0 -1
- package/build/utils/module.d.ts +0 -3
- package/build/utils/module.d.ts.map +0 -1
- package/build/utils/module.js.map +0 -1
- package/src/module.ts +0 -6
package/src/navigate.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { useMemo } from 'react'
|
|
2
2
|
import type { Navigator } from './types.js'
|
|
3
|
-
import { EntrypointOutcome } from '@owlmeans/entrypoint'
|
|
4
3
|
import { useContext } from './context.js'
|
|
5
4
|
import type { ClientEntrypoint } from '@owlmeans/client-entrypoint'
|
|
6
5
|
|
|
@@ -12,28 +11,26 @@ export const useNavigate = (): Navigator => {
|
|
|
12
11
|
const navigator: Navigator = {
|
|
13
12
|
_navigate: navigate,
|
|
14
13
|
|
|
15
|
-
navigate: async (
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
if (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
})
|
|
28
|
-
}
|
|
14
|
+
navigate: async (entrypoint, request) => {
|
|
15
|
+
const url = await entrypoint.url(request)
|
|
16
|
+
|
|
17
|
+
if (url.startsWith('http')) {
|
|
18
|
+
globalThis.location.href = url
|
|
19
|
+
} else {
|
|
20
|
+
navigate(url, {
|
|
21
|
+
state: {
|
|
22
|
+
...entrypoint.route.route, silent: request?.silent
|
|
23
|
+
},
|
|
24
|
+
replace: request?.replace ?? false
|
|
25
|
+
})
|
|
29
26
|
}
|
|
30
27
|
},
|
|
31
28
|
|
|
32
|
-
go: async (
|
|
33
|
-
navigator.navigate(context.entrypoint<ClientEntrypoint<string>>(
|
|
29
|
+
go: async (target, request) =>
|
|
30
|
+
navigator.navigate(context.entrypoint<ClientEntrypoint<string>>(target), request),
|
|
34
31
|
|
|
35
|
-
press: (
|
|
36
|
-
void navigator.go(
|
|
32
|
+
press: (target, request) => () => {
|
|
33
|
+
void navigator.go(target, request)
|
|
37
34
|
},
|
|
38
35
|
|
|
39
36
|
back: async () => {
|
package/src/router.ts
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
import type { FC } from 'react'
|
|
2
2
|
import type { RouterModel, RouterProps, RouterProvider } from './types.js'
|
|
3
3
|
import { createElement, useEffect, useRef, useState } from 'react'
|
|
4
|
-
import {
|
|
4
|
+
import { buildEntrypointTree, initializeRouter, visitEntrypointTree } from './utils/router.js'
|
|
5
5
|
import { createRouteRenderer } from './utils/route.js'
|
|
6
6
|
import { useContext } from './context.js'
|
|
7
7
|
import type { ClientConfig } from '@owlmeans/client-context'
|
|
8
8
|
import type { ClientContext } from './types.js'
|
|
9
9
|
import { assertContext } from '@owlmeans/context'
|
|
10
|
-
import type { BasicConfig, BasicContext } from '@owlmeans/context'
|
|
11
10
|
import type { LibraryRouter, RouteObject } from '@owlmeans/router'
|
|
12
11
|
|
|
13
12
|
type Config = ClientConfig
|
|
14
13
|
interface Context<C extends Config = Config> extends ClientContext<C> { }
|
|
15
14
|
|
|
15
|
+
/** A child path that keeps its leading separator reads as absolute, and the router rejects it. */
|
|
16
|
+
const relative = (segment: string): string => {
|
|
17
|
+
segment = segment.trim()
|
|
18
|
+
segment = segment.endsWith('/') ? segment.slice(0, -1) : segment
|
|
19
|
+
|
|
20
|
+
return segment.startsWith('/') ? segment.substring(1) : segment
|
|
21
|
+
}
|
|
22
|
+
|
|
16
23
|
export const Router: FC<RouterProps> = ({ provide }) => {
|
|
17
24
|
const progress = useRef(false)
|
|
18
25
|
const context = useContext()
|
|
@@ -55,21 +62,18 @@ export const makeRouterModel = (): RouterModel => {
|
|
|
55
62
|
routes: [],
|
|
56
63
|
resolve: async (ctx) => {
|
|
57
64
|
const context = assertContext<Config, Context>(ctx as Context, location)
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
const reactRoutes: RouteObject[] = await visitModuleTree(moduleTree, async (module, children) => {
|
|
61
|
-
const ctx = assertContext<BasicConfig, BasicContext<BasicConfig>>(
|
|
62
|
-
(module.ctx ?? context) as BasicContext<BasicConfig>, location
|
|
63
|
-
)
|
|
64
|
-
await module.route.resolve(ctx)
|
|
65
|
+
const entrypointTree = buildEntrypointTree(context as Context)
|
|
65
66
|
|
|
67
|
+
const reactRoutes: RouteObject[] = await visitEntrypointTree(entrypointTree, async (module, children) => {
|
|
66
68
|
const renderer = module.handle != null
|
|
67
69
|
? { Component: createRouteRenderer({ context, module, hasChildren: children.length > 0 }) }
|
|
68
70
|
: undefined
|
|
69
71
|
|
|
72
|
+
// The React tree nests, so each node contributes only its OWN segment — its ancestors are
|
|
73
|
+
// the branches it hangs under and already carry theirs.
|
|
70
74
|
const route: RouteObject = {
|
|
71
75
|
...(module.route.route.default ? { index: true } as any : undefined),
|
|
72
|
-
...(!module.route.route.default ? { path: module.
|
|
76
|
+
...(!module.route.route.default ? { path: relative(module.segment()), children } : undefined),
|
|
73
77
|
...renderer
|
|
74
78
|
}
|
|
75
79
|
|
package/src/services/debug.ts
CHANGED
|
@@ -62,13 +62,13 @@ export const createDebugService = (alias: string = DEF_DEBUG_ALIAS): DebugServic
|
|
|
62
62
|
}, service => async () => {
|
|
63
63
|
service.initialized = true
|
|
64
64
|
const context = assertContext(service.ctx, location) as ClientContext
|
|
65
|
-
const record = await context.getConfigResource().load
|
|
65
|
+
const record = await context.getConfigResource<DebugConfigRecord>().load(DEBUG_CONFIG_KEY)
|
|
66
66
|
if (record?.states != null) {
|
|
67
67
|
service.addItem({
|
|
68
68
|
alias: 'reset-states',
|
|
69
69
|
title: `Reset states`,
|
|
70
70
|
action: async (modal, context) => {
|
|
71
|
-
const record = await context.getConfigResource().load
|
|
71
|
+
const record = await context.getConfigResource<DebugConfigRecord>().load(DEBUG_CONFIG_KEY)
|
|
72
72
|
if (record?.states != null) {
|
|
73
73
|
await service.erase(record.states)
|
|
74
74
|
context.rerender()
|
package/src/store.ts
CHANGED
|
@@ -1,68 +1,85 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { StateModel, UseStoreHelper, UseStoreHelperOptions, UseStoreListHelper } from '@owlmeans/state'
|
|
3
|
-
import { useContext } from './context.js'
|
|
1
|
+
import { useMemo, useSyncExternalStore } from 'react'
|
|
4
2
|
import type { ResourceRecord } from '@owlmeans/resource'
|
|
3
|
+
import type { StateModel } from '@owlmeans/state'
|
|
4
|
+
import { useContext } from './context.js'
|
|
5
|
+
import type { UseStoreListOptions } from './types.js'
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Turn one state subscription into the pair `useSyncExternalStore` wants.
|
|
9
|
+
*
|
|
10
|
+
* The value is cached rather than rebuilt per render: the hook is asked for a snapshot on every
|
|
11
|
+
* render and must answer with the same reference until something actually changed, or React
|
|
12
|
+
* re-renders forever. A state subscription hands its value over synchronously, so the first
|
|
13
|
+
* snapshot is taken by subscribing and unsubscribing again — no render ever runs without one,
|
|
14
|
+
* and no subscription outlives the call.
|
|
15
|
+
*/
|
|
16
|
+
const bind = <V>(subscribe: (listener: (value: V) => void) => () => void) => {
|
|
17
|
+
let current: V | undefined
|
|
18
|
+
let seeded = false
|
|
8
19
|
|
|
9
|
-
|
|
10
|
-
|
|
20
|
+
const capture = (notify?: () => void) => (value: V) => {
|
|
21
|
+
current = value
|
|
22
|
+
seeded = true
|
|
23
|
+
notify?.()
|
|
11
24
|
}
|
|
12
25
|
|
|
13
|
-
return
|
|
14
|
-
|
|
26
|
+
return {
|
|
27
|
+
subscribe: (notify: () => void) => subscribe(capture(notify)),
|
|
28
|
+
snapshot: (): V => {
|
|
29
|
+
if (!seeded) {
|
|
30
|
+
subscribe(capture())()
|
|
31
|
+
}
|
|
15
32
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
opts = ids
|
|
19
|
-
}
|
|
20
|
-
const params: UseStoreHelperOptions<ResourceRecord> = typeof opts === 'object' ? opts : {}
|
|
21
|
-
if (typeof ids === 'string' || Array.isArray(ids)) {
|
|
22
|
-
params.id = ids
|
|
23
|
-
}
|
|
24
|
-
if (typeof opts === 'string') {
|
|
25
|
-
params.resource = opts
|
|
26
|
-
} else if (typeof opts === 'boolean') {
|
|
27
|
-
params.listen = opts
|
|
33
|
+
return current as V
|
|
34
|
+
}
|
|
28
35
|
}
|
|
29
|
-
|
|
36
|
+
}
|
|
30
37
|
|
|
38
|
+
/**
|
|
39
|
+
* One record from a state resource, live.
|
|
40
|
+
*
|
|
41
|
+
* It never throws for missing data: an id the store knows nothing about yields a model whose
|
|
42
|
+
* `empty` is true — reading `record` gives the resource's configured default, and nothing is
|
|
43
|
+
* written into the store on the way. An ABSENT id is the same answer rather than an error, since
|
|
44
|
+
* a screen renders before the record that supplies the id has arrived; on a `single` resource an
|
|
45
|
+
* absent id addresses its sole record. Writing through an empty model with no id is refused.
|
|
46
|
+
*/
|
|
47
|
+
export const useStoreModel = <T extends ResourceRecord = ResourceRecord>(
|
|
48
|
+
id?: string, alias?: string
|
|
49
|
+
): StateModel<T> => {
|
|
31
50
|
const context = useContext()
|
|
32
|
-
const resource = useMemo(() => context.getStateResource(
|
|
51
|
+
const resource = useMemo(() => context.getStateResource<T>(alias), [context, alias])
|
|
52
|
+
const store = useMemo(
|
|
53
|
+
() => bind<StateModel<T>>(listener => resource.watch(id, listener)),
|
|
54
|
+
[resource, id]
|
|
55
|
+
)
|
|
33
56
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
params.query == null,
|
|
37
|
-
JSON.stringify(params.default),
|
|
38
|
-
params.listen
|
|
39
|
-
]
|
|
40
|
-
|
|
41
|
-
const id = useId()
|
|
42
|
-
const [models, setModels] = useState<StateModel<ResourceRecord>[]>([])
|
|
43
|
-
|
|
44
|
-
let _return = [...models]
|
|
45
|
-
|
|
46
|
-
const unsubscribe = useMemo(() => {
|
|
47
|
-
const [unsubscribe, _initialModels] = resource.subscribe({
|
|
48
|
-
_systemId: id,
|
|
49
|
-
id: params.id,
|
|
50
|
-
listener: models => {
|
|
51
|
-
params.listen && setModels(models)
|
|
52
|
-
},
|
|
53
|
-
default: params.default,
|
|
54
|
-
query: params.query,
|
|
55
|
-
})
|
|
56
|
-
setModels(_initialModels)
|
|
57
|
-
|
|
58
|
-
if (_return.length < 1) {
|
|
59
|
-
_return.push(..._initialModels)
|
|
60
|
-
}
|
|
57
|
+
return useSyncExternalStore(store.subscribe, store.snapshot, store.snapshot)
|
|
58
|
+
}
|
|
61
59
|
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
/**
|
|
61
|
+
* A live LIST from a state resource: every record the query accepts, re-evaluated on every write
|
|
62
|
+
* that changes the answer, so a screen never recomputes ids and never re-subscribes to keep up.
|
|
63
|
+
* Omitting `query` matches everything.
|
|
64
|
+
*/
|
|
65
|
+
export const useStoreList = <T extends ResourceRecord = ResourceRecord>(
|
|
66
|
+
opts?: UseStoreListOptions<T>
|
|
67
|
+
): StateModel<T>[] => {
|
|
68
|
+
const context = useContext()
|
|
69
|
+
const resource = useMemo(() => context.getStateResource<T>(opts?.resource), [context, opts?.resource])
|
|
64
70
|
|
|
65
|
-
|
|
71
|
+
/**
|
|
72
|
+
* The subscription is keyed on the query's CONTENT, not on whether one was given: a screen
|
|
73
|
+
* that narrows its filter has to re-subscribe, and keyed on identity it kept answering the
|
|
74
|
+
* first filter it ever saw with nothing to say the list had gone stale.
|
|
75
|
+
*/
|
|
76
|
+
const key = JSON.stringify([opts?.query ?? null, opts?.sort ?? null])
|
|
77
|
+
const store = useMemo(
|
|
78
|
+
() => bind<StateModel<T>[]>(
|
|
79
|
+
listener => resource.query(opts?.query, listener, { sort: opts?.sort })
|
|
80
|
+
),
|
|
81
|
+
[resource, key]
|
|
82
|
+
)
|
|
66
83
|
|
|
67
|
-
return
|
|
84
|
+
return useSyncExternalStore(store.subscribe, store.snapshot, store.snapshot)
|
|
68
85
|
}
|
package/src/types.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { PropsWithChildren, FC, DependencyList } from 'react'
|
|
2
2
|
import type { AbstractRequest } from '@owlmeans/entrypoint'
|
|
3
3
|
import type { ClientConfig, ClientContext as BasicClientContext } from '@owlmeans/client-context'
|
|
4
|
+
import type { Criteria, ResourceRecord, Sort } from '@owlmeans/resource'
|
|
4
5
|
import type { StateResourceAppend } from '@owlmeans/state'
|
|
5
6
|
import type { ClientEntrypoint } from '@owlmeans/client-entrypoint'
|
|
6
7
|
import type { DebugServiceAppend, ModalServiceAppend } from './components/types.js'
|
|
7
8
|
import type { ConfigResourceAppend } from '@owlmeans/config'
|
|
8
|
-
import type { ConfigRecord } from '@owlmeans/context'
|
|
9
|
+
import type { ConfigRecord, EntrypointReference } from '@owlmeans/context'
|
|
9
10
|
import type { NavigateFunction, RouterService, Location, RouteObject, LibraryRouter } from '@owlmeans/router'
|
|
10
11
|
|
|
11
12
|
|
|
@@ -35,10 +36,10 @@ export interface AppProps extends PropsWithChildren {
|
|
|
35
36
|
noRouter?: boolean
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
export interface RoutedComponent<ExtraProps = {}> extends FC<PropsWithChildren<
|
|
39
|
+
export interface RoutedComponent<ExtraProps = {}> extends FC<PropsWithChildren<EntrypointContextParams & ExtraProps>> {
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
export interface
|
|
42
|
+
export interface EntrypointContextParams<T extends {} = {}> {
|
|
42
43
|
alias: string
|
|
43
44
|
params: AbstractRequest<T>['params']
|
|
44
45
|
path: string
|
|
@@ -61,13 +62,16 @@ export interface NavRequest<T extends Record<string, any> = Record<string, any>>
|
|
|
61
62
|
silent?: boolean
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
/** A navigation target is a protocol reference in application code, with strings retained for adapters. */
|
|
66
|
+
export type EntrypointTarget = EntrypointReference | string
|
|
67
|
+
|
|
64
68
|
export interface Navigator {
|
|
65
69
|
_navigate: NavigateFunction
|
|
66
70
|
navigate: <R extends NavRequest = NavRequest>(module: ClientEntrypoint<string, AbstractRequest>, request?: R) => Promise<void>
|
|
67
|
-
go: <R extends NavRequest = NavRequest>(
|
|
71
|
+
go: <R extends NavRequest = NavRequest>(target: EntrypointTarget, request?: R) => Promise<void>
|
|
68
72
|
back: () => Promise<void>
|
|
69
73
|
pressBack: () => () => void
|
|
70
|
-
press: <R extends NavRequest = NavRequest>(
|
|
74
|
+
press: <R extends NavRequest = NavRequest>(target: EntrypointTarget, request?: R) => () => void
|
|
71
75
|
location: <R extends NavRequest = NavRequest>() => Location<R>
|
|
72
76
|
}
|
|
73
77
|
|
|
@@ -79,3 +83,12 @@ export interface UseValueParams<T> {
|
|
|
79
83
|
default?: T
|
|
80
84
|
deps?: DependencyList
|
|
81
85
|
}
|
|
86
|
+
|
|
87
|
+
/** What `useStoreList` subscribes to. Every field is optional; nothing at all means everything. */
|
|
88
|
+
export interface UseStoreListOptions<T extends ResourceRecord = ResourceRecord> {
|
|
89
|
+
/** The live query. Omitted or `{}` matches every record the resource holds. */
|
|
90
|
+
query?: Criteria<T>
|
|
91
|
+
sort?: Sort<T>[]
|
|
92
|
+
/** Which state resource to read; the context's default one when omitted. */
|
|
93
|
+
resource?: string
|
|
94
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
|
|
2
2
|
import { createContext } from 'react'
|
|
3
|
-
import type {
|
|
3
|
+
import type { EntrypointContextParams, ClientContext } from '../types.js'
|
|
4
4
|
import type { ClientConfig } from '@owlmeans/client-context'
|
|
5
5
|
|
|
6
6
|
type Config = ClientConfig
|
|
7
7
|
interface Context<C extends Config = Config> extends ClientContext<C> { }
|
|
8
8
|
|
|
9
|
-
export const
|
|
9
|
+
export const EntrypointContext = createContext<EntrypointContextParams>({
|
|
10
10
|
alias: '',
|
|
11
11
|
params: {},
|
|
12
12
|
path: '',
|
package/src/utils/index.ts
CHANGED
package/src/utils/route.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FC, PropsWithChildren, ReactElement } from 'react'
|
|
2
|
-
import type {
|
|
2
|
+
import type { EntrypointContextParams, RoutedComponent, ClientContext } from '../types.js'
|
|
3
3
|
import { isValidElement, memo, useEffect } from 'react'
|
|
4
4
|
import type { ClientEntrypoint } from '@owlmeans/client-entrypoint'
|
|
5
5
|
import { provideRequest } from '@owlmeans/client-entrypoint'
|
|
@@ -15,10 +15,17 @@ export const createRouteRenderer: (params: RendererParams) => FC = ({ context, m
|
|
|
15
15
|
const params = context.router().useParams()
|
|
16
16
|
const reply = provideResponse()
|
|
17
17
|
|
|
18
|
+
// Own guards plus every ancestor's — the same set the request would be judged by. Whether a
|
|
19
|
+
// screen is guarded is a question about THIS list being non-empty, never about the declaration
|
|
20
|
+
// carrying a `guards` property: binding always assigns the array, empty when nothing was
|
|
21
|
+
// declared, and an empty list means an open screen. Asking the wrong question refuses every
|
|
22
|
+
// guest screen with `frontend-guard`, since no guard can match when there are none.
|
|
23
|
+
const aliases = module.getGuards()
|
|
24
|
+
|
|
18
25
|
useEffect(() => {
|
|
19
|
-
if (
|
|
20
|
-
const guards =
|
|
21
|
-
const request = provideRequest(module.alias, module.
|
|
26
|
+
if (aliases.length > 0) {
|
|
27
|
+
const guards = aliases.map(guard => context.service<GuardService>(guard))
|
|
28
|
+
const request = provideRequest(module.alias, module.path())
|
|
22
29
|
const reply = provideResponse()
|
|
23
30
|
let canceled = false
|
|
24
31
|
Promise.all(guards.map(async guard => {
|
|
@@ -38,10 +45,10 @@ export const createRouteRenderer: (params: RendererParams) => FC = ({ context, m
|
|
|
38
45
|
})
|
|
39
46
|
return () => { canceled = true }
|
|
40
47
|
}
|
|
41
|
-
},
|
|
48
|
+
}, aliases)
|
|
42
49
|
|
|
43
50
|
let Renderer: HandledRenderer<{}> = module.handle?.({
|
|
44
|
-
alias: module.alias, path: module.
|
|
51
|
+
alias: module.alias, path: module.path(),
|
|
45
52
|
params, body: {}, headers: {}, query: {},
|
|
46
53
|
}, reply) as HandledRenderer<{}>
|
|
47
54
|
|
|
@@ -55,8 +62,8 @@ export const createRouteRenderer: (params: RendererParams) => FC = ({ context, m
|
|
|
55
62
|
const Outlet = context.router().outlet()
|
|
56
63
|
if (isComponent(Renderer)) {
|
|
57
64
|
const EnsuredRenderer = memo(Renderer) as RoutedComponent
|
|
58
|
-
const props:
|
|
59
|
-
context, params, alias: module.
|
|
65
|
+
const props: EntrypointContextParams = {
|
|
66
|
+
context, params, alias: module.alias, path: module.path()
|
|
60
67
|
}
|
|
61
68
|
if (hasChildren) {
|
|
62
69
|
return <EnsuredRenderer {...props}><Outlet /></EnsuredRenderer>
|
package/src/utils/router.ts
CHANGED
|
@@ -7,8 +7,8 @@ import type { ClientContext } from '../types.js'
|
|
|
7
7
|
type Config = ClientConfig
|
|
8
8
|
interface Context<C extends Config = Config> extends ClientContext<C> { }
|
|
9
9
|
|
|
10
|
-
export const
|
|
11
|
-
const
|
|
10
|
+
export const buildEntrypointTree = <R, C extends Config = Config, T extends Context<C> = Context<C>>(context: T): EntrypointTree<R> => {
|
|
11
|
+
const entrypoints = context.entrypoints<ClientEntrypoint<R>>().filter(
|
|
12
12
|
module => module.route.route.type === AppType.Frontend
|
|
13
13
|
&& (module.sticky || module.route.route.service == null
|
|
14
14
|
|| module.route.route.service === context.cfg.service)
|
|
@@ -17,8 +17,8 @@ export const buildModuleTree = <R, C extends Config = Config, T extends Context<
|
|
|
17
17
|
const flatTree = new Map<ClientEntrypoint<R>, ClientEntrypoint<R>[]>()
|
|
18
18
|
const roots: ClientEntrypoint<R>[] = []
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
const parentAlias = module.
|
|
20
|
+
entrypoints.forEach(module => {
|
|
21
|
+
const parentAlias = module.route.route.parent
|
|
22
22
|
if (parentAlias == null) {
|
|
23
23
|
roots.push(module)
|
|
24
24
|
} else {
|
|
@@ -29,18 +29,18 @@ export const buildModuleTree = <R, C extends Config = Config, T extends Context<
|
|
|
29
29
|
}
|
|
30
30
|
})
|
|
31
31
|
|
|
32
|
-
const
|
|
33
|
-
(tree, module) => tree.set(module,
|
|
32
|
+
const reduceEntrypoints = (entrypoints: ClientEntrypoint<R>[]): EntrypointTree<R> => entrypoints.reduce(
|
|
33
|
+
(tree, module) => tree.set(module, reduceEntrypoints(flatTree.get(module) ?? [])), new Map()
|
|
34
34
|
)
|
|
35
35
|
|
|
36
|
-
return
|
|
36
|
+
return reduceEntrypoints(roots)
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
export const
|
|
39
|
+
export const visitEntrypointTree = async <T, R>(tree: EntrypointTree<T>, visitor: EntrypointTreeVisitor<T, R>): Promise<R[]> =>
|
|
40
40
|
Array.from(tree.entries()).reduce<Promise<R[]>>(
|
|
41
41
|
async (result, [module, tree], _, source) => [
|
|
42
42
|
...(await result),
|
|
43
|
-
await visitor(module, await
|
|
43
|
+
await visitor(module, await visitEntrypointTree(tree, visitor), source.length === 1)
|
|
44
44
|
], Promise.resolve([]))
|
|
45
45
|
|
|
46
46
|
export const initializeRouter = async (context: Context) => {
|
|
@@ -52,9 +52,9 @@ export const initializeRouter = async (context: Context) => {
|
|
|
52
52
|
return await model.resolve(context)
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
export interface
|
|
55
|
+
export interface EntrypointTreeVisitor<T, R> {
|
|
56
56
|
(module: ClientEntrypoint<T>, children: R[], alone: boolean): Promise<R>
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
interface
|
|
59
|
+
interface EntrypointTree<T> extends Map<ClientEntrypoint<T>, EntrypointTree<T>> {
|
|
60
60
|
}
|
package/build/module.d.ts
DELETED
package/build/module.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAErD,eAAO,MAAM,SAAS,GAAI,CAAC,SAAS,EAAE,GAAG,EAAE,OAAoC,mBAAmB,CAAC,CAAC,CAAC,CAAA"}
|
package/build/module.js
DELETED
package/build/module.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"module.js","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAA;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAGhD,MAAM,CAAC,MAAM,SAAS,GAAG,GAAsB,EAAE,CAAC,UAAU,CAAC,aAAa,CAA2B,CAAA"}
|
package/build/utils/module.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../src/utils/module.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAiB,MAAM,aAAa,CAAA;AAMrE,eAAO,MAAM,aAAa,kDAKxB,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"module.js","sourceRoot":"","sources":["../../src/utils/module.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAOrC,MAAM,CAAC,MAAM,aAAa,GAAG,aAAa,CAAsB;IAC9D,KAAK,EAAE,EAAE;IACT,MAAM,EAAE,EAAE;IACV,IAAI,EAAE,EAAE;IACR,OAAO,EAAE,EAAa;CACvB,CAAC,CAAA"}
|
package/src/module.ts
DELETED