Repository navigation
Expand file tree
/
Copy pathmodules.ts
More file actions
117 lines (108 loc) 路 3.43 KB
/
Copy pathmodules.ts
File metadata and controls
117 lines (108 loc) 路 3.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import type { Timings } from '@fuzdev/fuz_util/timings.ts';
import { UnreachableError } from '@fuzdev/fuz_util/error.ts';
import type { Result } from '@fuzdev/fuz_util/result.ts';
import { print_error } from '@fuzdev/fuz_util/print.ts';
import { pathToFileURL } from 'node:url';
import type { PathId } from '@fuzdev/fuz_util/path.ts';
import type { ResolvedInputFile } from './input_path.ts';
import { print_path } from './paths.ts';
export interface ModuleMeta<TModule extends Record<string, any> = Record<string, any>> {
id: PathId;
mod: TModule;
}
export type LoadModuleResult<TModule> = Result<{ id: PathId; mod: TModule }, LoadModuleFailure>;
export type LoadModuleFailure =
| { ok: false; type: 'failed_import'; id: PathId; error: Error }
| {
ok: false;
type: 'failed_validation';
id: PathId;
mod: Record<string, any>;
validation: string;
};
export const load_module = async <TModule extends Record<string, any>>(
id: PathId,
validate?: (mod: Record<string, any>) => mod is TModule,
bust_cache?: boolean
): Promise<LoadModuleResult<TModule>> => {
let mod;
try {
let import_path = id;
if (bust_cache) {
const url = pathToFileurl(/sitelet?url=https%3A%2F%2Fgithub.com%2Ffuzdev%2Fgro%2Fblob%2Fmain%2Fsrc%2Flib%2Fid);
url.searchParams.set('t', Date.now().toString());
import_path = url.href;
}
mod = await import(import_path);
} catch (error) {
return { ok: false, type: 'failed_import', id, error };
}
if (validate && !validate(mod)) {
return { ok: false, type: 'failed_validation', id, mod, validation: validate.name };
}
return { ok: true, id, mod };
};
export interface LoadModulesFailure<TModuleMeta extends ModuleMeta> {
type: 'load_module_failures';
load_module_failures: Array<LoadModuleFailure>;
reasons: Array<string>;
// still return the modules and timings, deferring to the caller
modules: Array<TModuleMeta>;
}
export type LoadModulesResult<TModuleMeta extends ModuleMeta> = Result<
{
modules: Array<TModuleMeta>;
},
LoadModulesFailure<TModuleMeta>
>;
// TODO parallelize and sort afterwards
export const load_modules = async <
TModule extends Record<string, any>,
TModuleMeta extends ModuleMeta<TModule>
>(
resolved_input_files: Array<ResolvedInputFile>,
validate: (mod: any) => mod is TModule,
map_module_meta: (resolved_input_file: ResolvedInputFile, mod: TModule) => TModuleMeta,
timings?: Timings
): Promise<LoadModulesResult<TModuleMeta>> => {
const timing_to_load_modules = timings?.start('load modules');
const modules: Array<TModuleMeta> = [];
const load_module_failures: Array<LoadModuleFailure> = [];
const reasons: Array<string> = [];
for (const resolved_input_file of resolved_input_files.values()) {
const { id, input_path } = resolved_input_file;
const result = await load_module(id, validate);
if (result.ok) {
modules.push(map_module_meta(resolved_input_file, result.mod));
} else {
load_module_failures.push(result);
switch (result.type) {
case 'failed_import': {
reasons.push(
`Module import ${print_path(id)} failed from input ${print_path(
input_path
)}: ${print_error(result.error)}`
);
break;
}
case 'failed_validation': {
reasons.push(`Module ${print_path(id)} failed validation '${result.validation}'.`);
break;
}
default:
throw new UnreachableError(result);
}
}
}
timing_to_load_modules?.();
if (load_module_failures.length) {
return {
ok: false,
type: 'load_module_failures',
load_module_failures,
reasons,
modules
};
}
return { ok: true, modules };
};