@owlmeans/client 0.1.0
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/LICENSE +21 -0
- package/README.md +688 -0
- package/build/app.d.ts +4 -0
- package/build/app.d.ts.map +1 -0
- package/build/app.js +10 -0
- package/build/app.js.map +1 -0
- package/build/components/index.d.ts +4 -0
- package/build/components/index.d.ts.map +1 -0
- package/build/components/index.js +3 -0
- package/build/components/index.js.map +1 -0
- package/build/components/modal.d.ts +7 -0
- package/build/components/modal.d.ts.map +1 -0
- package/build/components/modal.js +66 -0
- package/build/components/modal.js.map +1 -0
- package/build/components/toggle.d.ts +3 -0
- package/build/components/toggle.d.ts.map +1 -0
- package/build/components/toggle.js +20 -0
- package/build/components/toggle.js.map +1 -0
- package/build/components/types.d.ts +50 -0
- package/build/components/types.d.ts.map +1 -0
- package/build/components/types.js +2 -0
- package/build/components/types.js.map +1 -0
- package/build/consts.d.ts +5 -0
- package/build/consts.d.ts.map +1 -0
- package/build/consts.js +5 -0
- package/build/consts.js.map +1 -0
- package/build/context.d.ts +8 -0
- package/build/context.d.ts.map +1 -0
- package/build/context.js +47 -0
- package/build/context.js.map +1 -0
- package/build/debug.d.ts +4 -0
- package/build/debug.d.ts.map +1 -0
- package/build/debug.js +12 -0
- package/build/debug.js.map +1 -0
- package/build/errors.d.ts +18 -0
- package/build/errors.d.ts.map +1 -0
- package/build/errors.js +34 -0
- package/build/errors.js.map +1 -0
- package/build/helper.d.ts +4 -0
- package/build/helper.d.ts.map +1 -0
- package/build/helper.js +30 -0
- package/build/helper.js.map +1 -0
- package/build/index.d.ts +15 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +15 -0
- package/build/index.js.map +1 -0
- package/build/module.d.ts +3 -0
- package/build/module.d.ts.map +1 -0
- package/build/module.js +4 -0
- package/build/module.js.map +1 -0
- package/build/navigate.d.ts +3 -0
- package/build/navigate.d.ts.map +1 -0
- package/build/navigate.js +39 -0
- package/build/navigate.js.map +1 -0
- package/build/router.d.ts +5 -0
- package/build/router.d.ts.map +1 -0
- package/build/router.js +53 -0
- package/build/router.js.map +1 -0
- package/build/services/debug.d.ts +6 -0
- package/build/services/debug.d.ts.map +1 -0
- package/build/services/debug.js +83 -0
- package/build/services/debug.js.map +1 -0
- package/build/services/index.d.ts +2 -0
- package/build/services/index.d.ts.map +1 -0
- package/build/services/index.js +2 -0
- package/build/services/index.js.map +1 -0
- package/build/store.d.ts +4 -0
- package/build/store.d.ts.map +1 -0
- package/build/store.js +49 -0
- package/build/store.js.map +1 -0
- package/build/types.d.ts +57 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/build/utils/index.d.ts +4 -0
- package/build/utils/index.d.ts.map +1 -0
- package/build/utils/index.js +4 -0
- package/build/utils/index.js.map +1 -0
- package/build/utils/module.d.ts +3 -0
- package/build/utils/module.d.ts.map +1 -0
- package/build/utils/module.js +8 -0
- package/build/utils/module.js.map +1 -0
- package/build/utils/route.d.ts +16 -0
- package/build/utils/route.d.ts.map +1 -0
- package/build/utils/route.js +65 -0
- package/build/utils/route.js.map +1 -0
- package/build/utils/router.d.ts +16 -0
- package/build/utils/router.d.ts.map +1 -0
- package/build/utils/router.js +36 -0
- package/build/utils/router.js.map +1 -0
- package/build/value.d.ts +4 -0
- package/build/value.d.ts.map +1 -0
- package/build/value.js +48 -0
- package/build/value.js.map +1 -0
- package/package.json +66 -0
- package/src/app.tsx +17 -0
- package/src/components/index.ts +4 -0
- package/src/components/modal.ts +89 -0
- package/src/components/toggle.ts +22 -0
- package/src/components/types.ts +57 -0
- package/src/consts.ts +7 -0
- package/src/context.ts +61 -0
- package/src/debug.ts +14 -0
- package/src/errors.ts +44 -0
- package/src/helper.tsx +51 -0
- package/src/index.ts +15 -0
- package/src/module.ts +6 -0
- package/src/navigate.ts +51 -0
- package/src/router.ts +78 -0
- package/src/services/debug.ts +102 -0
- package/src/services/index.ts +2 -0
- package/src/store.ts +57 -0
- package/src/types.ts +72 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/module.ts +14 -0
- package/src/utils/route.tsx +82 -0
- package/src/utils/router.ts +60 -0
- package/src/value.ts +60 -0
- package/tsconfig.json +15 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { assertContext, createLazyService } from '@owlmeans/context'
|
|
2
|
+
import { DEBUG_CONFIG_KEY, DEBUGGER_FLAG, DEF_DEBUG_ALIAS } from '../consts.js'
|
|
3
|
+
import type { DebugService, DebugServiceAppend } from '../components/types.js'
|
|
4
|
+
import type { ClientConfig } from '@owlmeans/client-context'
|
|
5
|
+
import type { ClientContext, DebugConfigRecord } from '../types.js'
|
|
6
|
+
import type { ClientDbService, ClientResource } from '@owlmeans/client-resource'
|
|
7
|
+
import { DEFAULT_DB_ALIAS } from '@owlmeans/client-resource'
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
export const createDebugService = (alias: string = DEF_DEBUG_ALIAS): DebugService => {
|
|
11
|
+
const location = `debug-service:${alias}`
|
|
12
|
+
|
|
13
|
+
const service: DebugService = createLazyService<DebugService>(alias, {
|
|
14
|
+
items: [
|
|
15
|
+
{
|
|
16
|
+
alias: 'close',
|
|
17
|
+
title: 'Close',
|
|
18
|
+
action: async (modal) => modal.cancel()
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
alias: 'reset',
|
|
22
|
+
title: 'Reset app',
|
|
23
|
+
action: async (modal, context) => {
|
|
24
|
+
const service = context.service<ClientDbService>(DEFAULT_DB_ALIAS)
|
|
25
|
+
await service.erase()
|
|
26
|
+
context.rerender()
|
|
27
|
+
modal.cancel()
|
|
28
|
+
void modal.navigator?._navigate('/')
|
|
29
|
+
|
|
30
|
+
return 'Client DB cleanedup!'
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
|
|
35
|
+
addItem: item => service.items.push(item),
|
|
36
|
+
|
|
37
|
+
erase: async states => {
|
|
38
|
+
const context = assertContext(service.ctx, location) as ClientContext
|
|
39
|
+
await Promise.all(states.map(async state => {
|
|
40
|
+
await context.resource<ClientResource>(state).erase()
|
|
41
|
+
}))
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
open: () => {
|
|
45
|
+
const context = assertContext(service.ctx, location) as ClientContext
|
|
46
|
+
void context.modal().request(service.Debug)
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
select: alias => {
|
|
50
|
+
const item = service.items.find(item => item.alias === alias)
|
|
51
|
+
if (item != null) {
|
|
52
|
+
const context = assertContext(service.ctx, location) as ClientContext
|
|
53
|
+
if (item.Com != null) {
|
|
54
|
+
void context.modal().request<string>(item.Com)
|
|
55
|
+
.then(result => result != null && alert(result))
|
|
56
|
+
}
|
|
57
|
+
if (item.action != null) {
|
|
58
|
+
void item.action(context.modal(), context).then(result => result != null && alert(result))
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
}, service => async () => {
|
|
63
|
+
service.initialized = true
|
|
64
|
+
const context = assertContext(service.ctx, location) as ClientContext
|
|
65
|
+
const record = await context.getConfigResource().load<DebugConfigRecord>(DEBUG_CONFIG_KEY)
|
|
66
|
+
if (record?.states != null) {
|
|
67
|
+
service.addItem({
|
|
68
|
+
alias: 'reset-states',
|
|
69
|
+
title: `Reset states`,
|
|
70
|
+
action: async (modal, context) => {
|
|
71
|
+
const record = await context.getConfigResource().load<DebugConfigRecord>(DEBUG_CONFIG_KEY)
|
|
72
|
+
if (record?.states != null) {
|
|
73
|
+
await service.erase(record.states)
|
|
74
|
+
context.rerender()
|
|
75
|
+
modal.cancel()
|
|
76
|
+
|
|
77
|
+
return `States erased`
|
|
78
|
+
}
|
|
79
|
+
return 'No states to erase'
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
return service
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export const appendDebugService = <C extends ClientConfig, T extends ClientContext<C>>(
|
|
89
|
+
ctx: T, alias: string = DEF_DEBUG_ALIAS
|
|
90
|
+
): T & DebugServiceAppend => {
|
|
91
|
+
const _ctx = ctx as T & DebugServiceAppend
|
|
92
|
+
|
|
93
|
+
const debuggable = _ctx.cfg.debug.all === true || _ctx.cfg.debug[DEBUGGER_FLAG] === true
|
|
94
|
+
if (debuggable) {
|
|
95
|
+
const service = createDebugService(alias)
|
|
96
|
+
_ctx.registerService(service)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
_ctx.debug = () => debuggable ? _ctx.service<DebugService>(alias) : undefined
|
|
100
|
+
|
|
101
|
+
return _ctx
|
|
102
|
+
}
|
package/src/store.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { useEffect, useId, useMemo, useState } from 'react'
|
|
2
|
+
import type { StateModel, UseStoreHelper, UseStoreHelperOptions, UseStoreListHelper } from '@owlmeans/state'
|
|
3
|
+
import { useContext } from './context.js'
|
|
4
|
+
import type { ResourceRecord } from '@owlmeans/resource'
|
|
5
|
+
|
|
6
|
+
export const useStoreModel: UseStoreHelper = (id, opts) => {
|
|
7
|
+
const list = useStoreList(id, opts)
|
|
8
|
+
|
|
9
|
+
if (list.length < 1) {
|
|
10
|
+
throw new SyntaxError('Store always return model and create it if not exists')
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return list[0]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const useStoreList: UseStoreListHelper = (ids, opts) => {
|
|
17
|
+
if (typeof ids === 'object' && !Array.isArray(ids)) {
|
|
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
|
|
28
|
+
}
|
|
29
|
+
params.listen = params.listen ?? true
|
|
30
|
+
|
|
31
|
+
const context = useContext()
|
|
32
|
+
const resource = useMemo(() => context.getStateResource(params.resource), [params.resource])
|
|
33
|
+
|
|
34
|
+
const deps = [
|
|
35
|
+
Array.isArray(params.id) ? params.id.join(',') : params.id,
|
|
36
|
+
params.query == null,
|
|
37
|
+
JSON.stringify(params.default),
|
|
38
|
+
params.listen
|
|
39
|
+
]
|
|
40
|
+
const id = useId()
|
|
41
|
+
const [unsubscribe, _initialModels] = useMemo(() => {
|
|
42
|
+
return resource.subscribe({
|
|
43
|
+
_systemId: id,
|
|
44
|
+
id: params.id,
|
|
45
|
+
listener: models => {
|
|
46
|
+
params.listen && setModels(models)
|
|
47
|
+
},
|
|
48
|
+
default: params.default,
|
|
49
|
+
query: params.query,
|
|
50
|
+
})
|
|
51
|
+
}, deps)
|
|
52
|
+
const [models, setModels] = useState(_initialModels)
|
|
53
|
+
|
|
54
|
+
useEffect(() => unsubscribe, deps)
|
|
55
|
+
|
|
56
|
+
return models as StateModel<any>[]
|
|
57
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { RouteObject, Location, NavigateFunction } from 'react-router'
|
|
2
|
+
import type { Router as RemixRouter } from '@remix-run/router'
|
|
3
|
+
import type { PropsWithChildren, FC, DependencyList } from 'react'
|
|
4
|
+
import type { AbstractRequest } from '@owlmeans/module'
|
|
5
|
+
import type { ClientConfig, ClientContext as BasicClientContext } from '@owlmeans/client-context'
|
|
6
|
+
import type { StateResourceAppend } from '@owlmeans/state'
|
|
7
|
+
import type { ClientModule } from '@owlmeans/client-module'
|
|
8
|
+
import type { DebugServiceAppend, ModalServiceAppend } from './components/types.js'
|
|
9
|
+
import type { ConfigResourceAppend } from '@owlmeans/config'
|
|
10
|
+
import type { ConfigRecord } from '@owlmeans/context'
|
|
11
|
+
|
|
12
|
+
export interface RouterModel {
|
|
13
|
+
routes: RouteObject[]
|
|
14
|
+
resolve: <C extends ClientConfig, T extends ClientContext<C>>(context: T) => Promise<RouteObject[]>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface RouterProvider {
|
|
18
|
+
(routes: RouteObject[]): RemixRouter | Promise<RemixRouter>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface RouterProps {
|
|
22
|
+
provide: RouterProvider | RemixRouter
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface AppProps extends PropsWithChildren {
|
|
26
|
+
context: ClientContext<any>
|
|
27
|
+
provide?: RouterProvider | RemixRouter
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface RoutedComponent<ExtraProps = {}> extends FC<PropsWithChildren<ModuleContextParams & ExtraProps>> {
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ModuleContextParams<T extends {} = {}> {
|
|
34
|
+
alias: string
|
|
35
|
+
params: AbstractRequest<T>['params']
|
|
36
|
+
path: string
|
|
37
|
+
context: ClientContext
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ClientContext<C extends ClientConfig = ClientConfig> extends BasicClientContext<C>,
|
|
41
|
+
ConfigResourceAppend,
|
|
42
|
+
StateResourceAppend,
|
|
43
|
+
ModalServiceAppend,
|
|
44
|
+
DebugServiceAppend {
|
|
45
|
+
registerRerenderer: (listener: CallableFunction) => () => void
|
|
46
|
+
rerender: () => void
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface NavRequest<T extends Record<string, any> = Record<string, any>>
|
|
50
|
+
extends Partial<AbstractRequest<T>> {
|
|
51
|
+
replace?: boolean
|
|
52
|
+
silent?: boolean
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface Navigator {
|
|
56
|
+
_navigate: NavigateFunction
|
|
57
|
+
navigate: <R extends NavRequest = NavRequest>(module: ClientModule<string, AbstractRequest>, request?: R) => Promise<void>
|
|
58
|
+
go: <R extends NavRequest = NavRequest>(alias: string, request?: R) => Promise<void>
|
|
59
|
+
back: () => Promise<void>
|
|
60
|
+
pressBack: () => () => void
|
|
61
|
+
press: <R extends NavRequest = NavRequest>(alias: string, request?: R) => () => void
|
|
62
|
+
location: <R extends NavRequest = NavRequest>() => Location<R>
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface DebugConfigRecord extends ConfigRecord {
|
|
66
|
+
states?: string[]
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface UseValueParams<T> {
|
|
70
|
+
default?: T
|
|
71
|
+
deps?: DependencyList
|
|
72
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
|
|
2
|
+
import { createContext } from 'react'
|
|
3
|
+
import type { ModuleContextParams, ClientContext } from '../types.js'
|
|
4
|
+
import type { ClientConfig } from '@owlmeans/client-context'
|
|
5
|
+
|
|
6
|
+
type Config = ClientConfig
|
|
7
|
+
interface Context<C extends Config = Config> extends ClientContext<C> { }
|
|
8
|
+
|
|
9
|
+
export const ModuleContext = createContext<ModuleContextParams>({
|
|
10
|
+
alias: '',
|
|
11
|
+
params: {},
|
|
12
|
+
path: '',
|
|
13
|
+
context: {} as Context
|
|
14
|
+
})
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { FC, PropsWithChildren, ReactElement } from 'react'
|
|
2
|
+
import type { ModuleContextParams, RoutedComponent, ClientContext } from '../types.js'
|
|
3
|
+
import { isValidElement, memo, useEffect } from 'react'
|
|
4
|
+
import type { ClientModule } from '@owlmeans/client-module'
|
|
5
|
+
import { provideRequest } from '@owlmeans/client-module'
|
|
6
|
+
import { provideResponse } from '@owlmeans/module'
|
|
7
|
+
import type { GuardService } from '@owlmeans/module'
|
|
8
|
+
import { Outlet, useParams } from 'react-router'
|
|
9
|
+
import { AuthorizationError } from '@owlmeans/auth'
|
|
10
|
+
import type { ClientConfig } from '@owlmeans/client-context'
|
|
11
|
+
|
|
12
|
+
type Config = ClientConfig
|
|
13
|
+
interface Context<C extends Config = Config> extends ClientContext<C> { }
|
|
14
|
+
|
|
15
|
+
export const createRouteRenderer: (params: RendererParams) => FC = ({ context, module, hasChildren }) => () => {
|
|
16
|
+
const params = useParams()
|
|
17
|
+
const reply = provideResponse()
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (module.guards != null) {
|
|
21
|
+
const guards = module.getGuards().map(guard => context.service<GuardService>(guard))
|
|
22
|
+
const request = provideRequest(module.alias, module.getPath())
|
|
23
|
+
const reply = provideResponse()
|
|
24
|
+
let canceled = false
|
|
25
|
+
Promise.all(guards.map(async guard => {
|
|
26
|
+
if (canceled) return
|
|
27
|
+
return await guard.match(request, reply) ? guard : undefined
|
|
28
|
+
})).then(async guards => {
|
|
29
|
+
if (canceled) return
|
|
30
|
+
const guard = guards.find(guard => guard != null)
|
|
31
|
+
if (guard == null) {
|
|
32
|
+
throw new AuthorizationError('frontend-guard')
|
|
33
|
+
}
|
|
34
|
+
await guard.handle(request, reply)
|
|
35
|
+
}).catch(e => {
|
|
36
|
+
if (canceled) return
|
|
37
|
+
// @TODO Process error properly - redirect somewhere
|
|
38
|
+
throw e
|
|
39
|
+
})
|
|
40
|
+
return () => { canceled = true }
|
|
41
|
+
}
|
|
42
|
+
}, module.guards ?? [])
|
|
43
|
+
|
|
44
|
+
let Renderer: HandledRenderer<{}> = module.handle?.({
|
|
45
|
+
alias: module.alias, path: module.getPath(),
|
|
46
|
+
params, body: {}, headers: {}, query: {},
|
|
47
|
+
}, reply) as HandledRenderer<{}>
|
|
48
|
+
|
|
49
|
+
if (reply.error != null) {
|
|
50
|
+
throw reply.error
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (isValidElement(Renderer)) {
|
|
54
|
+
return Renderer
|
|
55
|
+
}
|
|
56
|
+
if (isComponent(Renderer)) {
|
|
57
|
+
const EnsuredRenderer = memo(Renderer) as RoutedComponent
|
|
58
|
+
const props: ModuleContextParams = {
|
|
59
|
+
context, params, alias: module.getAlias(), path: module.getPath()
|
|
60
|
+
}
|
|
61
|
+
if (hasChildren) {
|
|
62
|
+
return <EnsuredRenderer {...props}><Outlet /></EnsuredRenderer>
|
|
63
|
+
} else {
|
|
64
|
+
return <EnsuredRenderer {...props} />
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (hasChildren) {
|
|
68
|
+
return <Outlet />
|
|
69
|
+
}
|
|
70
|
+
return undefined
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export type HandledRenderer<T extends {}> = FC<PropsWithChildren<T> | T> | ReactElement
|
|
74
|
+
|
|
75
|
+
interface RendererParams {
|
|
76
|
+
context: Context,
|
|
77
|
+
module: ClientModule<unknown>
|
|
78
|
+
hasChildren: boolean
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const isComponent = <T extends {}>(element: HandledRenderer<T>): element is FC<PropsWithChildren<T> | T | unknown> =>
|
|
82
|
+
element != null && typeof element === 'function' && !isValidElement(element)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { ClientModule } from '@owlmeans/client-module'
|
|
2
|
+
import { AppType } from '@owlmeans/context'
|
|
3
|
+
import { makeRouterModel } from '../router.js'
|
|
4
|
+
import type { ClientConfig } from '@owlmeans/client-context'
|
|
5
|
+
import type { ClientContext } from '../types.js'
|
|
6
|
+
|
|
7
|
+
type Config = ClientConfig
|
|
8
|
+
interface Context<C extends Config = Config> extends ClientContext<C> { }
|
|
9
|
+
|
|
10
|
+
export const buildModuleTree = <R, C extends Config = Config, T extends Context<C> = Context<C>>(context: T): ModuleTree<R> => {
|
|
11
|
+
const modules = context.modules<ClientModule<R>>().filter(
|
|
12
|
+
module => module.route.route.type === AppType.Frontend
|
|
13
|
+
&& (module.sticky || module.route.route.service == null
|
|
14
|
+
|| module.route.route.service === context.cfg.service)
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
const flatTree = new Map<ClientModule<R>, ClientModule<R>[]>()
|
|
18
|
+
const roots: ClientModule<R>[] = []
|
|
19
|
+
|
|
20
|
+
modules.forEach(module => {
|
|
21
|
+
const parentAlias = module.getParentAlias()
|
|
22
|
+
if (parentAlias == null) {
|
|
23
|
+
roots.push(module)
|
|
24
|
+
} else {
|
|
25
|
+
const parent = context.module<ClientModule<R>>(parentAlias)
|
|
26
|
+
const list = flatTree.get(parent) ?? []
|
|
27
|
+
list.push(module)
|
|
28
|
+
flatTree.set(parent, list)
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const reduceModules = (modules: ClientModule<R>[]): ModuleTree<R> => modules.reduce(
|
|
33
|
+
(tree, module) => tree.set(module, reduceModules(flatTree.get(module) ?? [])), new Map()
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
return reduceModules(roots)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const visitModuleTree = async <T, R>(tree: ModuleTree<T>, visitor: ModuleTreeVisitor<T, R>): Promise<R[]> =>
|
|
40
|
+
Array.from(tree.entries()).reduce<Promise<R[]>>(
|
|
41
|
+
async (result, [module, tree], _, source) => [
|
|
42
|
+
...(await result),
|
|
43
|
+
await visitor(module, await visitModuleTree(tree, visitor), source.length === 1)
|
|
44
|
+
], Promise.resolve([]))
|
|
45
|
+
|
|
46
|
+
export const initializeRouter = async (context: Context) => {
|
|
47
|
+
if (!context.cfg.ready) {
|
|
48
|
+
await context.configure().init()
|
|
49
|
+
await context.waitForInitialized()
|
|
50
|
+
}
|
|
51
|
+
const model = makeRouterModel()
|
|
52
|
+
return await model.resolve(context)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface ModuleTreeVisitor<T, R> {
|
|
56
|
+
(module: ClientModule<T>, children: R[], alone: boolean): Promise<R>
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface ModuleTree<T> extends Map<ClientModule<T>, ModuleTree<T>> {
|
|
60
|
+
}
|
package/src/value.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { useEffect, useState, useRef, useId } from 'react'
|
|
2
|
+
import type { MutableRefObject, DependencyList } from 'react'
|
|
3
|
+
import type { UseValueParams } from './types.js'
|
|
4
|
+
|
|
5
|
+
const _complexValues: { [key: string]: any } = {}
|
|
6
|
+
|
|
7
|
+
export const useValue = <T>(
|
|
8
|
+
loader: (cancel?: MutableRefObject<boolean>) => Promise<T>,
|
|
9
|
+
def?: T | DependencyList | UseValueParams<T> | null,
|
|
10
|
+
forceDefault?: boolean
|
|
11
|
+
): T | null => {
|
|
12
|
+
const deps = Array.isArray(def)
|
|
13
|
+
? def : typeof def === 'object' && def != null && 'deps' in def
|
|
14
|
+
? def.deps ?? [] : []
|
|
15
|
+
def = forceDefault === true
|
|
16
|
+
? def as T | null : Array.isArray(def)
|
|
17
|
+
? null : typeof def === 'object' && def != null && "default" in def
|
|
18
|
+
? def.default : def as T | null
|
|
19
|
+
const id = useId()
|
|
20
|
+
let [value, setValue] = useState<T>()
|
|
21
|
+
const [prevDeps, setPrevDeps] = useState<DependencyList>(deps)
|
|
22
|
+
|
|
23
|
+
const cancel = useRef(false)
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (deps.some((dep, idx) => prevDeps[idx] !== dep)) {
|
|
27
|
+
cancel.current = false
|
|
28
|
+
setPrevDeps(deps)
|
|
29
|
+
}
|
|
30
|
+
void (async () => {
|
|
31
|
+
if (await Promise.resolve(cancel.current)) {
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
const value = await loader(cancel)
|
|
35
|
+
if (typeof value === 'function') {
|
|
36
|
+
_complexValues[id] = value
|
|
37
|
+
setValue(id as any)
|
|
38
|
+
} else {
|
|
39
|
+
if (_complexValues[id] != null) {
|
|
40
|
+
delete _complexValues[id]
|
|
41
|
+
}
|
|
42
|
+
setValue(value)
|
|
43
|
+
}
|
|
44
|
+
})()
|
|
45
|
+
|
|
46
|
+
return () => {
|
|
47
|
+
if (_complexValues[id] != null) {
|
|
48
|
+
delete _complexValues[id]
|
|
49
|
+
value = undefined
|
|
50
|
+
}
|
|
51
|
+
cancel.current = true
|
|
52
|
+
}
|
|
53
|
+
}, deps)
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
typeof value === 'string' && value === id
|
|
57
|
+
? _complexValues[value]
|
|
58
|
+
: value
|
|
59
|
+
) ?? def ?? null
|
|
60
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": [
|
|
3
|
+
"../tsconfig.default.json",
|
|
4
|
+
"../tsconfig.react.json",
|
|
5
|
+
],
|
|
6
|
+
"compilerOptions": {
|
|
7
|
+
"rootDir": "./src/", /* Specify the root folder within your source files. */
|
|
8
|
+
"outDir": "./build/", /* Specify an output folder for all emitted files. */
|
|
9
|
+
},
|
|
10
|
+
"exclude": [
|
|
11
|
+
"./dist/**/*",
|
|
12
|
+
"./build/**/*",
|
|
13
|
+
"./*.ts"
|
|
14
|
+
]
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["./src/app.tsx","./src/consts.ts","./src/context.ts","./src/debug.ts","./src/errors.ts","./src/helper.tsx","./src/index.ts","./src/module.ts","./src/navigate.ts","./src/router.ts","./src/store.ts","./src/types.ts","./src/value.ts","./src/components/index.ts","./src/components/modal.ts","./src/components/toggle.ts","./src/components/types.ts","./src/services/debug.ts","./src/services/index.ts","./src/utils/index.ts","./src/utils/module.ts","./src/utils/route.tsx","./src/utils/router.ts"],"version":"5.6.3"}
|