@owlmeans/client-flow 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 +877 -0
- package/build/.gitkeep +0 -0
- package/build/client.d.ts +5 -0
- package/build/client.d.ts.map +1 -0
- package/build/client.js +97 -0
- package/build/client.js.map +1 -0
- package/build/consts.d.ts +4 -0
- package/build/consts.d.ts.map +1 -0
- package/build/consts.js +4 -0
- package/build/consts.js.map +1 -0
- package/build/index.d.ts +5 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +4 -0
- package/build/index.js.map +1 -0
- package/build/service.d.ts +3 -0
- package/build/service.d.ts.map +1 -0
- package/build/service.js +86 -0
- package/build/service.js.map +1 -0
- package/build/types.d.ts +34 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/package.json +48 -0
- package/src/client.ts +125 -0
- package/src/consts.ts +6 -0
- package/src/index.ts +4 -0
- package/src/service.ts +114 -0
- package/src/types.ts +45 -0
- package/tsconfig.json +15 -0
- package/tsconfig.tsbuildinfo +1 -0
package/build/.gitkeep
ADDED
|
File without changes
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ClientContext, Navigator } from '@owlmeans/client';
|
|
2
|
+
import type { ClientConfig } from '@owlmeans/client-context';
|
|
3
|
+
import type { FlowClient } from './types.js';
|
|
4
|
+
export declare const createFlowClient: <C extends ClientConfig, T extends ClientContext<C>>(context: T, nav: Navigator) => FlowClient;
|
|
5
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAChE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAQ5D,OAAO,KAAK,EAAE,UAAU,EAA8B,MAAM,YAAY,CAAA;AAIxE,eAAO,MAAM,gBAAgB,GAAI,CAAC,SAAS,YAAY,EAAE,CAAC,SAAS,aAAa,CAAC,CAAC,CAAC,WAAW,CAAC,OAAO,SAAS,KAAG,UA+GjH,CAAA"}
|
package/build/client.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { module, stab } from '@owlmeans/client-module';
|
|
2
|
+
import { route, frontend } from '@owlmeans/route';
|
|
3
|
+
import { FlowStepMissconfigured, FlowTargetError, TARGET_SERVICE } from '@owlmeans/flow';
|
|
4
|
+
import { ResilientError } from '@owlmeans/error';
|
|
5
|
+
import { DEFAULT_ALIAS, FLOW_STATE, REHACK_MOD } from './consts.js';
|
|
6
|
+
import { DISPATCHER_PATH } from '@owlmeans/auth-common';
|
|
7
|
+
export const createFlowClient = (context, nav) => {
|
|
8
|
+
const service = context.service(DEFAULT_ALIAS);
|
|
9
|
+
let model;
|
|
10
|
+
const _client = {
|
|
11
|
+
boot: async (targetAlias, from) => {
|
|
12
|
+
await service.ready();
|
|
13
|
+
let _model = await service.state();
|
|
14
|
+
if (_model == null) {
|
|
15
|
+
if (context.hasResource(FLOW_STATE)) {
|
|
16
|
+
const resource = context.resource(FLOW_STATE);
|
|
17
|
+
const record = await resource.load(FLOW_STATE);
|
|
18
|
+
if (record != null) {
|
|
19
|
+
_model = await service.begin(record.flow);
|
|
20
|
+
_model.setState(record);
|
|
21
|
+
if (targetAlias == null) {
|
|
22
|
+
targetAlias = record.service;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (_model == null) {
|
|
27
|
+
_model = await service.begin(undefined, from);
|
|
28
|
+
}
|
|
29
|
+
if (targetAlias != null) {
|
|
30
|
+
let target;
|
|
31
|
+
try {
|
|
32
|
+
target = context.serviceRoute(targetAlias);
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
const err = ResilientError.ensure(e);
|
|
36
|
+
const error = new FlowTargetError(targetAlias);
|
|
37
|
+
error.oiriginalStack = err.stack;
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
_model.target(target.service);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
model = _model;
|
|
44
|
+
return _client;
|
|
45
|
+
},
|
|
46
|
+
setup: flow => {
|
|
47
|
+
model = flow;
|
|
48
|
+
return _client;
|
|
49
|
+
},
|
|
50
|
+
flow: () => model,
|
|
51
|
+
service: () => context.serviceRoute(model.state().service),
|
|
52
|
+
// @TODO Reuse service's proceed method to avoid code ambiguity and duplication
|
|
53
|
+
proceed: async (transition, req) => {
|
|
54
|
+
const step = model.step(transition.step);
|
|
55
|
+
if (step.module == null) {
|
|
56
|
+
throw new FlowStepMissconfigured(step.step);
|
|
57
|
+
}
|
|
58
|
+
// @TODO I'm not sure it's always ok to pass previous payload.
|
|
59
|
+
// It should be configurable some way.
|
|
60
|
+
const previous = model.payload();
|
|
61
|
+
// @TODO Such payload pass may duplicate some query paramters in some
|
|
62
|
+
// case. But in general payloadMaping should protect from most of issues.
|
|
63
|
+
// In general we may stop to pass any params outside the flow state.
|
|
64
|
+
// !!! Probably we can configure what goes there by the flow config.
|
|
65
|
+
model.transit(transition.transition, true, { ...previous, ...req?.params, ...req?.query });
|
|
66
|
+
let redirectTo;
|
|
67
|
+
// @TODO Properly use target service - as a way to build the redirect URL
|
|
68
|
+
if (step.service === TARGET_SERVICE) {
|
|
69
|
+
context.registerModule(module(route(REHACK_MOD, DISPATCHER_PATH, frontend({ service: model.state().service })), stab));
|
|
70
|
+
redirectTo = context.module(REHACK_MOD);
|
|
71
|
+
await redirectTo.resolve();
|
|
72
|
+
step.module = REHACK_MOD;
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
redirectTo = context.module(step.module);
|
|
76
|
+
}
|
|
77
|
+
const [url] = await redirectTo.call(req);
|
|
78
|
+
if (url.startsWith('http')) {
|
|
79
|
+
await service.proceed(req);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
await nav.navigate(redirectTo, req);
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
persist: async () => {
|
|
86
|
+
if (context.hasResource(FLOW_STATE)) {
|
|
87
|
+
const resource = context.resource(FLOW_STATE);
|
|
88
|
+
const _state = model.state();
|
|
89
|
+
await resource.save({ ..._state, id: FLOW_STATE });
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
return _client;
|
|
96
|
+
};
|
|
97
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,aAAa,SAAS,CAAA;AAEnC,eAAO,MAAM,UAAU,eAAe,CAAA;AAEtC,eAAO,MAAM,UAAU,eAAe,CAAA"}
|
package/build/consts.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,MAAM,CAAC,MAAM,aAAa,GAAG,MAAM,CAAA;AAEnC,MAAM,CAAC,MAAM,UAAU,GAAG,YAAY,CAAA;AAEtC,MAAM,CAAC,MAAM,UAAU,GAAG,YAAY,CAAA"}
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mBAAmB,YAAY,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA"}
|
package/build/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAe,MAAM,YAAY,CAAA;AAI1D,eAAO,MAAM,oBAAoB,WAAW,MAAM,KAAmB,WAwGpE,CAAA"}
|
package/build/service.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { assertContext, createLazyService } from '@owlmeans/context';
|
|
2
|
+
import { DEFAULT_ALIAS } from './consts.js';
|
|
3
|
+
import { DEFAULT_ALIAS as CONFIG_RESOURCE, fromConfigRecord } from '@owlmeans/config';
|
|
4
|
+
import { FLOW_RECORD, FlowUnsupported, makeFlowModel, STD_AUTH_FLOW, UnknownFlow } from '@owlmeans/flow';
|
|
5
|
+
export const makeBasicFlowService = (alias = DEFAULT_ALIAS) => {
|
|
6
|
+
const location = `flow-service:${alias}`;
|
|
7
|
+
const pair = {
|
|
8
|
+
resolve: () => { },
|
|
9
|
+
reject: () => { }
|
|
10
|
+
};
|
|
11
|
+
const flows = {};
|
|
12
|
+
const service = createLazyService(alias, {
|
|
13
|
+
flow: null,
|
|
14
|
+
supplied: new Promise((resolve, reject) => {
|
|
15
|
+
pair.resolve = resolve;
|
|
16
|
+
pair.reject = reject;
|
|
17
|
+
}),
|
|
18
|
+
state: async () => {
|
|
19
|
+
await service.supplied;
|
|
20
|
+
return service.flow;
|
|
21
|
+
},
|
|
22
|
+
resolvePair: () => pair,
|
|
23
|
+
config: () => {
|
|
24
|
+
const ctx = assertContext(service.ctx, location);
|
|
25
|
+
return ctx.cfg.flowConfig ?? {};
|
|
26
|
+
},
|
|
27
|
+
begin: async (slug, from) => {
|
|
28
|
+
slug = slug ?? service.config().defaultFlow ?? STD_AUTH_FLOW;
|
|
29
|
+
service.flow = await makeFlowModel(slug, service.provideFlow);
|
|
30
|
+
service.supplied = Promise.resolve(true);
|
|
31
|
+
return service.flow.enter(from);
|
|
32
|
+
},
|
|
33
|
+
load: async (state) => {
|
|
34
|
+
service.flow = await makeFlowModel(state, service.provideFlow);
|
|
35
|
+
service.supplied = Promise.resolve(true);
|
|
36
|
+
return service.flow;
|
|
37
|
+
},
|
|
38
|
+
provideFlow: async (slug) => {
|
|
39
|
+
if (flows[slug] == null) {
|
|
40
|
+
throw new UnknownFlow(slug);
|
|
41
|
+
}
|
|
42
|
+
return flows[slug];
|
|
43
|
+
},
|
|
44
|
+
proceed: async () => {
|
|
45
|
+
throw new FlowUnsupported('service.proceed');
|
|
46
|
+
}
|
|
47
|
+
}, service => async () => {
|
|
48
|
+
const ctx = assertContext(service.ctx, location);
|
|
49
|
+
const flowCfg = service.config();
|
|
50
|
+
// @TODO Put it into some configuration and make it more flexible
|
|
51
|
+
const configRes = ctx.resource(CONFIG_RESOURCE);
|
|
52
|
+
const flowConfigs = await configRes.list({ recordType: FLOW_RECORD });
|
|
53
|
+
await Promise.all(flowConfigs.items.map(item => fromConfigRecord(item)).map(async (flow) => {
|
|
54
|
+
Object.values(flow.steps).forEach(step => {
|
|
55
|
+
if (step.service.startsWith('$')) {
|
|
56
|
+
const service = flowCfg.services?.[step.service.substring(1)];
|
|
57
|
+
if (service != null) {
|
|
58
|
+
step.service = service;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (step.module != null && step.module.startsWith('$')) {
|
|
62
|
+
const module = flowCfg.modules?.[step.module.substring(1)];
|
|
63
|
+
if (module != null) {
|
|
64
|
+
step.module = module;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (step.path != null && step.path.startsWith('$')) {
|
|
68
|
+
const path = flowCfg.pathes?.[step.path.substring(1)];
|
|
69
|
+
if (path != null) {
|
|
70
|
+
step.path = path;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
flows[flow.flow] = {
|
|
75
|
+
...flow, config: flowCfg, prefabs: Object.fromEntries(await Promise.all(Object.values(flow.steps).filter(step => step.initial === true)
|
|
76
|
+
.map(async (step) => {
|
|
77
|
+
const state = await makeFlowModel(flow);
|
|
78
|
+
return [step.step, state.enter(step.step).serialize()];
|
|
79
|
+
})))
|
|
80
|
+
};
|
|
81
|
+
}));
|
|
82
|
+
service.initialized = true;
|
|
83
|
+
});
|
|
84
|
+
return service;
|
|
85
|
+
};
|
|
86
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
|
package/build/types.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { LazyService } from '@owlmeans/context';
|
|
2
|
+
import type { FlowConfig, FlowModel, FlowProvider, FlowState, FlowTransition } from '@owlmeans/flow';
|
|
3
|
+
import type { AbstractRequest } from '@owlmeans/module';
|
|
4
|
+
import type { ResourceRecord } from '@owlmeans/resource';
|
|
5
|
+
import type { ResolvedServiceRoute } from '@owlmeans/route';
|
|
6
|
+
import type { ClientResource } from '@owlmeans/client-resource';
|
|
7
|
+
export interface FlowService extends LazyService {
|
|
8
|
+
supplied: Promise<boolean>;
|
|
9
|
+
flow: FlowModel | null;
|
|
10
|
+
state: () => Promise<FlowModel | null>;
|
|
11
|
+
resolvePair: () => ResolvePair;
|
|
12
|
+
config: () => FlowConfig;
|
|
13
|
+
begin: (slug?: string, from?: string) => Promise<FlowModel>;
|
|
14
|
+
load: (flow: string) => Promise<FlowModel>;
|
|
15
|
+
provideFlow: FlowProvider;
|
|
16
|
+
proceed: (req?: Partial<AbstractRequest>, dryRun?: boolean) => Promise<string>;
|
|
17
|
+
}
|
|
18
|
+
export interface ResolvePair {
|
|
19
|
+
resolve: (val: boolean) => void;
|
|
20
|
+
reject: (err: Error) => void;
|
|
21
|
+
}
|
|
22
|
+
export interface FlowClient {
|
|
23
|
+
boot: (target: string | null, from?: string) => Promise<FlowClient>;
|
|
24
|
+
setup: (flow: FlowModel) => FlowClient;
|
|
25
|
+
flow: () => FlowModel;
|
|
26
|
+
service: () => ResolvedServiceRoute;
|
|
27
|
+
proceed: (transiation: FlowTransition, req?: Partial<AbstractRequest>) => Promise<void>;
|
|
28
|
+
persist: () => Promise<boolean>;
|
|
29
|
+
}
|
|
30
|
+
export interface StateRecord extends ResourceRecord, FlowState {
|
|
31
|
+
}
|
|
32
|
+
export interface StateResource extends ClientResource<StateRecord> {
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AACpG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAA;AAE/D,MAAM,WAAW,WAAY,SAAQ,WAAW;IAC9C,QAAQ,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;IAE1B,IAAI,EAAE,SAAS,GAAG,IAAI,CAAA;IAEtB,KAAK,EAAE,MAAM,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAAA;IAEtC,WAAW,EAAE,MAAM,WAAW,CAAA;IAE9B,MAAM,EAAE,MAAM,UAAU,CAAA;IAExB,KAAK,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAAC,CAAA;IAE3D,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAAC,CAAA;IAE1C,WAAW,EAAE,YAAY,CAAA;IAEzB,OAAO,EAAE,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;CAC/E;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IAChC,MAAM,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAA;CAC7B;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAA;IACnE,KAAK,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,UAAU,CAAA;IACtC,IAAI,EAAE,MAAM,SAAS,CAAA;IACrB,OAAO,EAAE,MAAM,oBAAoB,CAAA;IACnC,OAAO,EAAE,CAAC,WAAW,EAAE,cAAc,EAAE,GAAG,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACvF,OAAO,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;CAChC;AAED,MAAM,WAAW,WAAY,SAAQ,cAAc,EAAE,SAAS;CAAG;AAEjE,MAAM,WAAW,aAAc,SAAQ,cAAc,CAAC,WAAW,CAAC;CAAG"}
|
package/build/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@owlmeans/client-flow",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"build": "tsc -b",
|
|
7
|
+
"dev": "sleep 90 && nodemon -e ts,tsx,json --watch src --exec \"tsc -p ./tsconfig.json\"",
|
|
8
|
+
"watch": "tsc -b -w --preserveWatchOutput --pretty"
|
|
9
|
+
},
|
|
10
|
+
"main": "build/index.js",
|
|
11
|
+
"module": "build/index.js",
|
|
12
|
+
"types": "build/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"import": "./build/index.js",
|
|
16
|
+
"require": "./build/index.js",
|
|
17
|
+
"default": "./build/index.js",
|
|
18
|
+
"module": "./build/index.js",
|
|
19
|
+
"types": "./build/index.d.ts"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@owlmeans/auth-common": "^0.1.0",
|
|
24
|
+
"@owlmeans/client": "^0.1.0",
|
|
25
|
+
"@owlmeans/client-context": "^0.1.0",
|
|
26
|
+
"@owlmeans/client-module": "^0.1.0",
|
|
27
|
+
"@owlmeans/client-resource": "^0.1.0",
|
|
28
|
+
"@owlmeans/config": "^0.1.0",
|
|
29
|
+
"@owlmeans/context": "^0.1.0",
|
|
30
|
+
"@owlmeans/error": "^0.1.0",
|
|
31
|
+
"@owlmeans/flow": "^0.1.0",
|
|
32
|
+
"@owlmeans/module": "^0.1.0",
|
|
33
|
+
"@owlmeans/resource": "^0.1.0",
|
|
34
|
+
"@owlmeans/route": "^0.1.0"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"react": "*"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"nodemon": "^3.1.7",
|
|
41
|
+
"npm-check": "^6.0.1",
|
|
42
|
+
"typescript": "^5.6.3"
|
|
43
|
+
},
|
|
44
|
+
"private": false,
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
}
|
|
48
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { ClientContext, Navigator } from '@owlmeans/client'
|
|
2
|
+
import type { ClientConfig } from '@owlmeans/client-context'
|
|
3
|
+
import type { ClientModule } from '@owlmeans/client-module'
|
|
4
|
+
import { module, stab } from '@owlmeans/client-module'
|
|
5
|
+
import type { ResolvedServiceRoute } from '@owlmeans/route'
|
|
6
|
+
import { route, frontend } from '@owlmeans/route'
|
|
7
|
+
import { FlowStepMissconfigured, FlowTargetError, TARGET_SERVICE } from '@owlmeans/flow'
|
|
8
|
+
import type { FlowModel } from '@owlmeans/flow'
|
|
9
|
+
import { ResilientError } from '@owlmeans/error'
|
|
10
|
+
import type { FlowClient, FlowService, StateResource } from './types.js'
|
|
11
|
+
import { DEFAULT_ALIAS, FLOW_STATE, REHACK_MOD } from './consts.js'
|
|
12
|
+
import { DISPATCHER_PATH } from '@owlmeans/auth-common'
|
|
13
|
+
|
|
14
|
+
export const createFlowClient = <C extends ClientConfig, T extends ClientContext<C>>(context: T, nav: Navigator): FlowClient => {
|
|
15
|
+
const service = context.service<FlowService>(DEFAULT_ALIAS)
|
|
16
|
+
|
|
17
|
+
let model: FlowModel
|
|
18
|
+
|
|
19
|
+
const _client: FlowClient = {
|
|
20
|
+
boot: async (targetAlias, from) => {
|
|
21
|
+
await service.ready()
|
|
22
|
+
let _model = await service.state()
|
|
23
|
+
if (_model == null) {
|
|
24
|
+
if (context.hasResource(FLOW_STATE)) {
|
|
25
|
+
const resource = context.resource<StateResource>(FLOW_STATE)
|
|
26
|
+
const record = await resource.load(FLOW_STATE)
|
|
27
|
+
if (record != null) {
|
|
28
|
+
_model = await service.begin(record.flow)
|
|
29
|
+
_model.setState(record)
|
|
30
|
+
if (targetAlias == null) {
|
|
31
|
+
targetAlias = record.service
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (_model == null) {
|
|
36
|
+
_model = await service.begin(undefined, from)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (targetAlias != null) {
|
|
40
|
+
let target: ResolvedServiceRoute
|
|
41
|
+
try {
|
|
42
|
+
target = context.serviceRoute(targetAlias) as ResolvedServiceRoute
|
|
43
|
+
} catch (e) {
|
|
44
|
+
const err = ResilientError.ensure(e as Error)
|
|
45
|
+
const error = new FlowTargetError(targetAlias)
|
|
46
|
+
error.oiriginalStack = err.stack
|
|
47
|
+
|
|
48
|
+
throw error
|
|
49
|
+
}
|
|
50
|
+
_model.target(target.service)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
model = _model
|
|
55
|
+
|
|
56
|
+
return _client
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
setup: flow => {
|
|
60
|
+
model = flow
|
|
61
|
+
|
|
62
|
+
return _client
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
flow: () => model,
|
|
66
|
+
|
|
67
|
+
service: () => context.serviceRoute(model.state().service) as ResolvedServiceRoute,
|
|
68
|
+
|
|
69
|
+
// @TODO Reuse service's proceed method to avoid code ambiguity and duplication
|
|
70
|
+
proceed: async (transition, req) => {
|
|
71
|
+
const step = model.step(transition.step)
|
|
72
|
+
if (step.module == null) {
|
|
73
|
+
throw new FlowStepMissconfigured(step.step)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
// @TODO I'm not sure it's always ok to pass previous payload.
|
|
78
|
+
// It should be configurable some way.
|
|
79
|
+
const previous = model.payload()
|
|
80
|
+
// @TODO Such payload pass may duplicate some query paramters in some
|
|
81
|
+
// case. But in general payloadMaping should protect from most of issues.
|
|
82
|
+
// In general we may stop to pass any params outside the flow state.
|
|
83
|
+
// !!! Probably we can configure what goes there by the flow config.
|
|
84
|
+
model.transit(
|
|
85
|
+
transition.transition, true,
|
|
86
|
+
{ ...previous, ...req?.params, ...req?.query } as Record<string, string>
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
let redirectTo: ClientModule<string>
|
|
90
|
+
// @TODO Properly use target service - as a way to build the redirect URL
|
|
91
|
+
if (step.service === TARGET_SERVICE) {
|
|
92
|
+
context.registerModule(module(
|
|
93
|
+
route(REHACK_MOD, DISPATCHER_PATH, frontend({ service: model.state().service })), stab
|
|
94
|
+
))
|
|
95
|
+
redirectTo = context.module<ClientModule<string>>(REHACK_MOD)
|
|
96
|
+
await redirectTo.resolve()
|
|
97
|
+
step.module = REHACK_MOD
|
|
98
|
+
} else {
|
|
99
|
+
redirectTo = context.module<ClientModule>(step.module)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const [url] = await redirectTo.call<string>(req)
|
|
103
|
+
|
|
104
|
+
if (url.startsWith('http')) {
|
|
105
|
+
await service.proceed(req)
|
|
106
|
+
} else {
|
|
107
|
+
await nav.navigate(redirectTo, req)
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
persist: async () => {
|
|
112
|
+
if (context.hasResource(FLOW_STATE)) {
|
|
113
|
+
const resource = context.resource<StateResource>(FLOW_STATE)
|
|
114
|
+
const _state = model.state()
|
|
115
|
+
await resource.save({ ..._state, id: FLOW_STATE })
|
|
116
|
+
|
|
117
|
+
return true
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return false
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return _client
|
|
125
|
+
}
|
package/src/consts.ts
ADDED
package/src/index.ts
ADDED
package/src/service.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { assertContext, createLazyService } from '@owlmeans/context'
|
|
2
|
+
import type { ConfigRecord } from '@owlmeans/context'
|
|
3
|
+
import { DEFAULT_ALIAS } from './consts.js'
|
|
4
|
+
import { DEFAULT_ALIAS as CONFIG_RESOURCE, fromConfigRecord } from '@owlmeans/config'
|
|
5
|
+
import type { ConfigResource } from '@owlmeans/config'
|
|
6
|
+
import type { FlowService, ResolvePair } from './types.js'
|
|
7
|
+
import type { Flow, FlowConfigRecord, WithFlowConfig } from '@owlmeans/flow'
|
|
8
|
+
import { FLOW_RECORD, FlowUnsupported, makeFlowModel, STD_AUTH_FLOW, UnknownFlow } from '@owlmeans/flow'
|
|
9
|
+
|
|
10
|
+
export const makeBasicFlowService = (alias: string = DEFAULT_ALIAS): FlowService => {
|
|
11
|
+
const location = `flow-service:${alias}`
|
|
12
|
+
const pair: ResolvePair = {
|
|
13
|
+
resolve: () => { },
|
|
14
|
+
reject: () => { }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const flows: { [key: string]: Flow } = {}
|
|
18
|
+
|
|
19
|
+
const service: FlowService = createLazyService<FlowService>(alias, {
|
|
20
|
+
flow: null,
|
|
21
|
+
|
|
22
|
+
supplied: new Promise<boolean>((resolve, reject) => {
|
|
23
|
+
pair.resolve = resolve
|
|
24
|
+
pair.reject = reject
|
|
25
|
+
}),
|
|
26
|
+
|
|
27
|
+
state: async () => {
|
|
28
|
+
await service.supplied
|
|
29
|
+
|
|
30
|
+
return service.flow
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
resolvePair: () => pair,
|
|
34
|
+
|
|
35
|
+
config: () => {
|
|
36
|
+
const ctx = assertContext(service.ctx, location)
|
|
37
|
+
return (ctx.cfg as WithFlowConfig).flowConfig ?? {}
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
begin: async (slug, from) => {
|
|
41
|
+
slug = slug ?? service.config().defaultFlow ?? STD_AUTH_FLOW
|
|
42
|
+
service.flow = await makeFlowModel(slug, service.provideFlow)
|
|
43
|
+
|
|
44
|
+
service.supplied = Promise.resolve(true)
|
|
45
|
+
|
|
46
|
+
return service.flow.enter(from)
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
load: async (state) => {
|
|
50
|
+
service.flow = await makeFlowModel(state, service.provideFlow)
|
|
51
|
+
|
|
52
|
+
service.supplied = Promise.resolve(true)
|
|
53
|
+
|
|
54
|
+
return service.flow
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
provideFlow: async slug => {
|
|
58
|
+
if (flows[slug] == null) {
|
|
59
|
+
throw new UnknownFlow(slug)
|
|
60
|
+
}
|
|
61
|
+
return flows[slug]
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
proceed: async () => {
|
|
65
|
+
throw new FlowUnsupported('service.proceed')
|
|
66
|
+
}
|
|
67
|
+
}, service => async () => {
|
|
68
|
+
const ctx = assertContext(service.ctx, location)
|
|
69
|
+
const flowCfg = service.config()
|
|
70
|
+
|
|
71
|
+
// @TODO Put it into some configuration and make it more flexible
|
|
72
|
+
const configRes = ctx.resource<ConfigResource>(CONFIG_RESOURCE)
|
|
73
|
+
const flowConfigs = await configRes.list({ recordType: FLOW_RECORD })
|
|
74
|
+
await Promise.all(flowConfigs.items.map(
|
|
75
|
+
item => fromConfigRecord<ConfigRecord, FlowConfigRecord>(item)
|
|
76
|
+
).map(async flow => {
|
|
77
|
+
Object.values(flow.steps).forEach(step => {
|
|
78
|
+
if (step.service.startsWith('$')) {
|
|
79
|
+
const service = flowCfg.services?.[step.service.substring(1)]
|
|
80
|
+
if (service != null) {
|
|
81
|
+
step.service = service
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (step.module != null && step.module.startsWith('$')) {
|
|
85
|
+
const module = flowCfg.modules?.[step.module.substring(1)]
|
|
86
|
+
if (module != null) {
|
|
87
|
+
step.module = module
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (step.path != null && step.path.startsWith('$')) {
|
|
92
|
+
const path = flowCfg.pathes?.[step.path.substring(1)]
|
|
93
|
+
if (path != null) {
|
|
94
|
+
step.path = path
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
flows[flow.flow] = {
|
|
100
|
+
...flow, config: flowCfg, prefabs: Object.fromEntries(await Promise.all(
|
|
101
|
+
Object.values(flow.steps).filter(step => step.initial === true)
|
|
102
|
+
.map(async step => {
|
|
103
|
+
const state = await makeFlowModel(flow)
|
|
104
|
+
return [step.step, state.enter(step.step).serialize()]
|
|
105
|
+
})
|
|
106
|
+
))
|
|
107
|
+
}
|
|
108
|
+
}))
|
|
109
|
+
|
|
110
|
+
service.initialized = true
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
return service
|
|
114
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
|
|
2
|
+
import type { LazyService } from '@owlmeans/context'
|
|
3
|
+
import type { FlowConfig, FlowModel, FlowProvider, FlowState, FlowTransition } from '@owlmeans/flow'
|
|
4
|
+
import type { AbstractRequest } from '@owlmeans/module'
|
|
5
|
+
import type { ResourceRecord } from '@owlmeans/resource'
|
|
6
|
+
import type { ResolvedServiceRoute } from '@owlmeans/route'
|
|
7
|
+
import type { ClientResource } from '@owlmeans/client-resource'
|
|
8
|
+
|
|
9
|
+
export interface FlowService extends LazyService {
|
|
10
|
+
supplied: Promise<boolean>
|
|
11
|
+
|
|
12
|
+
flow: FlowModel | null
|
|
13
|
+
|
|
14
|
+
state: () => Promise<FlowModel | null>
|
|
15
|
+
|
|
16
|
+
resolvePair: () => ResolvePair
|
|
17
|
+
|
|
18
|
+
config: () => FlowConfig
|
|
19
|
+
|
|
20
|
+
begin: (slug?: string, from?: string) => Promise<FlowModel>
|
|
21
|
+
|
|
22
|
+
load: (flow: string) => Promise<FlowModel>
|
|
23
|
+
|
|
24
|
+
provideFlow: FlowProvider
|
|
25
|
+
|
|
26
|
+
proceed: (req?: Partial<AbstractRequest>, dryRun?: boolean) => Promise<string>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ResolvePair {
|
|
30
|
+
resolve: (val: boolean) => void,
|
|
31
|
+
reject: (err: Error) => void
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface FlowClient {
|
|
35
|
+
boot: (target: string | null, from?: string) => Promise<FlowClient>
|
|
36
|
+
setup: (flow: FlowModel) => FlowClient
|
|
37
|
+
flow: () => FlowModel
|
|
38
|
+
service: () => ResolvedServiceRoute
|
|
39
|
+
proceed: (transiation: FlowTransition, req?: Partial<AbstractRequest>) => Promise<void>
|
|
40
|
+
persist: () => Promise<boolean>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface StateRecord extends ResourceRecord, FlowState {}
|
|
44
|
+
|
|
45
|
+
export interface StateResource extends ClientResource<StateRecord> {}
|