@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/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"}
@@ -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,4 @@
1
+ export declare const DEFAULT_ALIAS = "flow";
2
+ export declare const FLOW_STATE = "state:flow";
3
+ export declare const REHACK_MOD = "__redirect";
4
+ //# sourceMappingURL=consts.d.ts.map
@@ -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"}
@@ -0,0 +1,4 @@
1
+ export const DEFAULT_ALIAS = 'flow';
2
+ export const FLOW_STATE = 'state:flow';
3
+ export const REHACK_MOD = '__redirect';
4
+ //# sourceMappingURL=consts.js.map
@@ -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"}
@@ -0,0 +1,5 @@
1
+ export type * from './types.js';
2
+ export * from './consts.js';
3
+ export * from './client.js';
4
+ export * from './service.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -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,4 @@
1
+ export * from './consts.js';
2
+ export * from './client.js';
3
+ export * from './service.js';
4
+ //# sourceMappingURL=index.js.map
@@ -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,3 @@
1
+ import type { FlowService } from './types.js';
2
+ export declare const makeBasicFlowService: (alias?: string) => FlowService;
3
+ //# sourceMappingURL=service.d.ts.map
@@ -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"}
@@ -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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"}
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -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
@@ -0,0 +1,6 @@
1
+
2
+ export const DEFAULT_ALIAS = 'flow'
3
+
4
+ export const FLOW_STATE = 'state:flow'
5
+
6
+ export const REHACK_MOD = '__redirect'
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export type * from './types.js'
2
+ export * from './consts.js'
3
+ export * from './client.js'
4
+ export * from './service.js'
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> {}