@wexample/js-app 0.0.3 → 0.0.12
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/.wex/config.yml +1 -1
- package/.wex/python/app_manager/app_workdir.py +3 -2
- package/.wex/python/app_manager/pyproject.toml +0 -2
- package/README.md +3 -3
- package/package.json +4 -1
- package/src/Common/App.ts +115 -0
- package/src/Common/AppChild.ts +10 -0
- package/src/Common/AppService.ts +24 -0
- package/src/Interfaces/ServicesRegistryInterface.ts +5 -0
- package/src/Services/MixinsService.ts +87 -0
- package/src/Types/AppServiceTypes.ts +12 -0
- package/tsconfig.json +26 -0
- package/version.txt +1 -1
package/.wex/config.yml
CHANGED
|
@@ -9,12 +9,13 @@ class AppWorkdir(JavascriptPackageWorkdir):
|
|
|
9
9
|
from wexample_helpers.helpers.string import string_to_kebab_case
|
|
10
10
|
|
|
11
11
|
raw_value = super().prepare_value(raw_value=raw_value)
|
|
12
|
+
name_config = self.get_runtime_config().search("global.name")
|
|
12
13
|
|
|
13
14
|
def _build_remote_github(target: AppWorkdir) -> str:
|
|
14
|
-
return f"git@github.com:wexample/{string_to_kebab_case(
|
|
15
|
+
return f"git@github.com:wexample/{string_to_kebab_case(name_config.get_str())}.git"
|
|
15
16
|
|
|
16
17
|
def _build_remote_gitlab(target: AppWorkdir) -> str:
|
|
17
|
-
return f"ssh://git@gitlab.wexample.com:4567/wexample-javascript/{string_to_kebab_case(
|
|
18
|
+
return f"ssh://git@gitlab.wexample.com:4567/wexample-javascript/{string_to_kebab_case(name_config.get_str())}.git"
|
|
18
19
|
|
|
19
20
|
raw_value["git"] = {
|
|
20
21
|
"main_branch": "main",
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @wexample/js-app
|
|
2
2
|
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.12
|
|
4
4
|
|
|
5
5
|
## Table of Contents
|
|
6
6
|
|
|
@@ -111,7 +111,7 @@ Free to use in both personal and commercial projects.
|
|
|
111
111
|
|
|
112
112
|
## Integration in the Suite
|
|
113
113
|
|
|
114
|
-
This package is part of the
|
|
114
|
+
This package is part of the Wexample Suite — a collection of high-quality, modular tools designed to work seamlessly together across multiple languages and environments.
|
|
115
115
|
|
|
116
116
|
### Related Packages
|
|
117
117
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import MixinsService from '@wexample/js-app/Services/MixinsService';
|
|
2
|
+
import AsyncConstructor from '@wexample/js-helpers/Common/AsyncConstructor';
|
|
3
|
+
import { arrayUnique } from '@wexample/js-helpers/Helper/Array';
|
|
4
|
+
import type ServicesRegistryInterface from '../Interfaces/ServicesRegistryInterface';
|
|
5
|
+
import type { AppServiceConstructor, ServiceDefinition } from '../Types/AppServiceTypes';
|
|
6
|
+
import type AppService from './AppService';
|
|
7
|
+
|
|
8
|
+
type ReadyCallback = (() => void) | (() => Promise<void>);
|
|
9
|
+
|
|
10
|
+
export default class App extends AsyncConstructor {
|
|
11
|
+
public services: ServicesRegistryInterface = {};
|
|
12
|
+
|
|
13
|
+
constructor(readyCallback?: ReadyCallback, globalName: string = 'app') {
|
|
14
|
+
super();
|
|
15
|
+
|
|
16
|
+
window[globalName] = this;
|
|
17
|
+
|
|
18
|
+
const doc = window.document;
|
|
19
|
+
|
|
20
|
+
const run = async () => {
|
|
21
|
+
// Allow children to perform setup before sealing.
|
|
22
|
+
await this.beforeReady();
|
|
23
|
+
|
|
24
|
+
// Every core properties has been set,
|
|
25
|
+
// block any try to add extra property.
|
|
26
|
+
this.seal();
|
|
27
|
+
|
|
28
|
+
// Execute ready callbacks.
|
|
29
|
+
await this.readyComplete();
|
|
30
|
+
|
|
31
|
+
readyCallback && (await readyCallback());
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const readyState = doc.readyState;
|
|
35
|
+
|
|
36
|
+
// Document has been parsed.
|
|
37
|
+
// Allows running after loaded event.
|
|
38
|
+
if (['complete', 'loaded', 'interactive'].indexOf(readyState) !== -1) {
|
|
39
|
+
this.defer(run);
|
|
40
|
+
} else {
|
|
41
|
+
doc.addEventListener('DOMContentLoaded', run);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Hook for children: executed after DOM is ready but before seal().
|
|
46
|
+
protected async beforeReady(): Promise<void> {
|
|
47
|
+
await this.loadAndInitServices(this.getServices());
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
getServices(): ServiceDefinition[] {
|
|
51
|
+
return [MixinsService];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
loadServices(services: ServiceDefinition[]): AppService[] {
|
|
55
|
+
services = this.getServicesAndDependencies(services);
|
|
56
|
+
const instances = [];
|
|
57
|
+
|
|
58
|
+
services.forEach((service: ServiceDefinition) => {
|
|
59
|
+
let serviceClass: AppServiceConstructor;
|
|
60
|
+
let serviceArgs: unknown[] = [];
|
|
61
|
+
|
|
62
|
+
if (Array.isArray(service)) {
|
|
63
|
+
serviceClass = service[0];
|
|
64
|
+
serviceArgs = service[1];
|
|
65
|
+
} else {
|
|
66
|
+
serviceClass = service;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const name = serviceClass.serviceName;
|
|
70
|
+
|
|
71
|
+
if (!this.services[name]) {
|
|
72
|
+
this.services[name] = new serviceClass(this, ...serviceArgs);
|
|
73
|
+
instances.push(this.services[name]);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return instances;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async loadAndInitServices(services: ServiceDefinition[]): Promise<unknown> {
|
|
81
|
+
const loadedServices = this.loadServices(services);
|
|
82
|
+
|
|
83
|
+
return this.services.mixins.invokeUntilComplete(
|
|
84
|
+
'hookInit',
|
|
85
|
+
'app',
|
|
86
|
+
[],
|
|
87
|
+
undefined,
|
|
88
|
+
loadedServices
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
getServicesAndDependencies(services: ServiceDefinition[]): ServiceDefinition[] {
|
|
93
|
+
services.forEach((serviceDef: ServiceDefinition) => {
|
|
94
|
+
let serviceClass: AppServiceConstructor;
|
|
95
|
+
|
|
96
|
+
if (Array.isArray(serviceDef)) {
|
|
97
|
+
serviceClass = serviceDef[0];
|
|
98
|
+
} else {
|
|
99
|
+
serviceClass = serviceDef;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (serviceClass.dependencies) {
|
|
103
|
+
services = [...services, ...this.getServicesAndDependencies(serviceClass.dependencies)];
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return arrayUnique(services) as ServiceDefinition[];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
getService(name: string | { serviceName: string }): AppService {
|
|
111
|
+
name = typeof name === 'string' ? name : name.serviceName;
|
|
112
|
+
|
|
113
|
+
return this.services[name];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ServiceDefinition } from '../Types/AppServiceTypes';
|
|
2
|
+
import type App from './App';
|
|
3
|
+
import AppChild from './AppChild';
|
|
4
|
+
|
|
5
|
+
export default abstract class AppService extends AppChild {
|
|
6
|
+
public static LOAD_STATUS_COMPLETE = 'complete';
|
|
7
|
+
public static LOAD_STATUS_WAIT = 'wait';
|
|
8
|
+
|
|
9
|
+
public app: App;
|
|
10
|
+
public static dependencies: ServiceDefinition[] = [];
|
|
11
|
+
public static serviceName: string;
|
|
12
|
+
|
|
13
|
+
registerHooks(): {
|
|
14
|
+
app?: Record<string, unknown>;
|
|
15
|
+
page?: Record<string, unknown>;
|
|
16
|
+
renderNode?: Record<string, unknown>;
|
|
17
|
+
} {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
registerMethods(_object: unknown, _group: string) {
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import AppService from '@wexample/js-app/Common/AppService';
|
|
2
|
+
import type { AppServiceConstructor } from '../Types/AppServiceTypes';
|
|
3
|
+
|
|
4
|
+
export default class MixinsService extends AppService {
|
|
5
|
+
public static serviceName = 'mixins';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Execute a hook until all ext do not return false.
|
|
9
|
+
* Useful to manage order when processing : an ext can wait for
|
|
10
|
+
* another one to be executed.
|
|
11
|
+
*
|
|
12
|
+
* The pre-last arg of callback will be a registry of ext statuses.
|
|
13
|
+
* The last arg of callback well be a next() method in case of async operation.
|
|
14
|
+
*
|
|
15
|
+
* @param method
|
|
16
|
+
* @param args
|
|
17
|
+
* @param group
|
|
18
|
+
* @param timeoutLimit
|
|
19
|
+
* @param services
|
|
20
|
+
*/
|
|
21
|
+
async invokeUntilComplete(
|
|
22
|
+
method: string,
|
|
23
|
+
group: string = 'app',
|
|
24
|
+
args: unknown[] = [],
|
|
25
|
+
timeoutLimit: number = 2000,
|
|
26
|
+
services: AppService[] = Object.values(this.app.services) as AppService[]
|
|
27
|
+
): Promise<boolean> {
|
|
28
|
+
const errorTrace: AppService[] = [];
|
|
29
|
+
let loops: number = 0;
|
|
30
|
+
const loopsLimit: number = 100;
|
|
31
|
+
const registry: Record<string, unknown> = {};
|
|
32
|
+
|
|
33
|
+
while (true) {
|
|
34
|
+
const service = services.shift();
|
|
35
|
+
if (!service) {
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const currentName = (service.constructor as AppServiceConstructor).serviceName;
|
|
40
|
+
const timeout = setTimeout(() => {
|
|
41
|
+
const message = [
|
|
42
|
+
`Mixins invocation timeout on method "${method}", stopping at "${currentName}".`,
|
|
43
|
+
`Registry: ${JSON.stringify(registry)}.`,
|
|
44
|
+
].join(' ');
|
|
45
|
+
|
|
46
|
+
throw new Error(message);
|
|
47
|
+
}, timeoutLimit);
|
|
48
|
+
|
|
49
|
+
const hooks = service.registerHooks() as Record<string, Record<string, unknown>> | undefined;
|
|
50
|
+
const hook = hooks?.[group]?.[method];
|
|
51
|
+
|
|
52
|
+
if (loops++ > loopsLimit) {
|
|
53
|
+
const message = [
|
|
54
|
+
`Stopping more than ${loops} recursions during services invocation on method "${method}", stopping at ${currentName}.`,
|
|
55
|
+
`Trace: ${errorTrace.join(' -> ') || 'none'}.`,
|
|
56
|
+
`Registry: ${JSON.stringify(registry)}.`,
|
|
57
|
+
].join(' ');
|
|
58
|
+
|
|
59
|
+
throw new Error(message);
|
|
60
|
+
} else if (loops > loopsLimit - 10) {
|
|
61
|
+
errorTrace.push(service);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (typeof hook === 'function') {
|
|
65
|
+
const argsLocal = args.concat([registry]);
|
|
66
|
+
registry[currentName] = await (hook as (...hookArgs: unknown[]) => unknown).apply(
|
|
67
|
+
service,
|
|
68
|
+
argsLocal
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (registry[currentName] === undefined) {
|
|
73
|
+
registry[currentName] = AppService.LOAD_STATUS_COMPLETE;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// "wait" says to retry after processing other services.
|
|
77
|
+
if (registry[currentName] === AppService.LOAD_STATUS_WAIT) {
|
|
78
|
+
// Enqueue again.
|
|
79
|
+
services.push(service);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
clearTimeout(timeout);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type App from '../Common/App';
|
|
2
|
+
import type AppService from '../Common/AppService';
|
|
3
|
+
|
|
4
|
+
export type AppServiceConstructor = (new (
|
|
5
|
+
app: App,
|
|
6
|
+
...args: unknown[]
|
|
7
|
+
) => AppService) & {
|
|
8
|
+
serviceName: string;
|
|
9
|
+
dependencies?: ServiceDefinition[];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type ServiceDefinition = AppServiceConstructor | [AppServiceConstructor, unknown[]];
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "node",
|
|
6
|
+
"strict": false,
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"forceConsistentCasingInFileNames": true,
|
|
10
|
+
"declaration": false,
|
|
11
|
+
"declarationMap": false,
|
|
12
|
+
"isolatedModules": true,
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"baseUrl": ".",
|
|
15
|
+
"paths": {
|
|
16
|
+
"@wexample/js-app": ["./src"],
|
|
17
|
+
"@wexample/js-app/*": ["./src/*"],
|
|
18
|
+
"@wexample/js-helpers": ["../js-helpers/src"],
|
|
19
|
+
"@wexample/js-helpers/*": ["../js-helpers/src/*"],
|
|
20
|
+
"@wexample/js-pseudocode": ["../js-pseudocode/src"],
|
|
21
|
+
"@wexample/js-pseudocode/*": ["../js-pseudocode/src/*"]
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"include": ["src"],
|
|
25
|
+
"exclude": ["dist", "node_modules"]
|
|
26
|
+
}
|
package/version.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.0.
|
|
1
|
+
0.0.12
|