@zmdb/compiler 1.0.0-beta.1
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 +674 -0
- package/README.md +40 -0
- package/dist/codegen/index.d.ts +50 -0
- package/dist/codegen/index.d.ts.map +1 -0
- package/dist/codegen/index.js +364 -0
- package/dist/codegen/index.js.map +1 -0
- package/dist/codegen/scan.d.ts +105 -0
- package/dist/codegen/scan.d.ts.map +1 -0
- package/dist/codegen/scan.js +450 -0
- package/dist/codegen/scan.js.map +1 -0
- package/dist/codegen/witness.d.ts +76 -0
- package/dist/codegen/witness.d.ts.map +1 -0
- package/dist/codegen/witness.js +690 -0
- package/dist/codegen/witness.js.map +1 -0
- package/dist/config/contract.d.ts +41 -0
- package/dist/config/contract.d.ts.map +1 -0
- package/dist/config/contract.js +5 -0
- package/dist/config/contract.js.map +1 -0
- package/dist/config/index.d.ts +53 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +277 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/index.zmdb.generated.d.ts +12 -0
- package/dist/config/index.zmdb.generated.js +28 -0
- package/dist/config/index.zmdb.witness.d.ts +3 -0
- package/dist/config/index.zmdb.witness.d.ts.map +1 -0
- package/dist/config/index.zmdb.witness.js +20 -0
- package/dist/config/index.zmdb.witness.js.map +1 -0
- package/dist/configured-plugin.d.ts +11 -0
- package/dist/configured-plugin.d.ts.map +1 -0
- package/dist/configured-plugin.js +27 -0
- package/dist/configured-plugin.js.map +1 -0
- package/dist/emit/index.d.ts +103 -0
- package/dist/emit/index.d.ts.map +1 -0
- package/dist/emit/index.js +1356 -0
- package/dist/emit/index.js.map +1 -0
- package/dist/errors.d.ts +7 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +2 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +254 -0
- package/dist/index.js.map +1 -0
- package/dist/lint/ast.d.ts +17 -0
- package/dist/lint/ast.d.ts.map +1 -0
- package/dist/lint/ast.js +77 -0
- package/dist/lint/ast.js.map +1 -0
- package/dist/lint/host-types.d.ts +9 -0
- package/dist/lint/host-types.d.ts.map +1 -0
- package/dist/lint/host-types.js +2 -0
- package/dist/lint/host-types.js.map +1 -0
- package/dist/lint/index.d.ts +23 -0
- package/dist/lint/index.d.ts.map +1 -0
- package/dist/lint/index.js +53 -0
- package/dist/lint/index.js.map +1 -0
- package/dist/lint/rules/no-distributed-nullable-tags.d.ts +3 -0
- package/dist/lint/rules/no-distributed-nullable-tags.d.ts.map +1 -0
- package/dist/lint/rules/no-distributed-nullable-tags.js +71 -0
- package/dist/lint/rules/no-distributed-nullable-tags.js.map +1 -0
- package/dist/lint/rules/no-empty-patch.d.ts +3 -0
- package/dist/lint/rules/no-empty-patch.d.ts.map +1 -0
- package/dist/lint/rules/no-empty-patch.js +26 -0
- package/dist/lint/rules/no-empty-patch.js.map +1 -0
- package/dist/lint/rules/no-interpolated-sql.d.ts +3 -0
- package/dist/lint/rules/no-interpolated-sql.d.ts.map +1 -0
- package/dist/lint/rules/no-interpolated-sql.js +35 -0
- package/dist/lint/rules/no-interpolated-sql.js.map +1 -0
- package/dist/lint/rules/no-unbounded-find.d.ts +3 -0
- package/dist/lint/rules/no-unbounded-find.d.ts.map +1 -0
- package/dist/lint/rules/no-unbounded-find.js +26 -0
- package/dist/lint/rules/no-unbounded-find.js.map +1 -0
- package/dist/lint/rules/no-unknown-json-column.d.ts +3 -0
- package/dist/lint/rules/no-unknown-json-column.d.ts.map +1 -0
- package/dist/lint/rules/no-unknown-json-column.js +35 -0
- package/dist/lint/rules/no-unknown-json-column.js.map +1 -0
- package/dist/lint/rules/require-sql-on-number.d.ts +3 -0
- package/dist/lint/rules/require-sql-on-number.d.ts.map +1 -0
- package/dist/lint/rules/require-sql-on-number.js +34 -0
- package/dist/lint/rules/require-sql-on-number.js.map +1 -0
- package/dist/lint/types.d.ts +12 -0
- package/dist/lint/types.d.ts.map +1 -0
- package/dist/lint/types.js +2 -0
- package/dist/lint/types.js.map +1 -0
- package/dist/metro/metro.d.ts +22 -0
- package/dist/metro/metro.d.ts.map +1 -0
- package/dist/metro/metro.js +242 -0
- package/dist/metro/metro.js.map +1 -0
- package/dist/protobuf/decode.d.ts +20 -0
- package/dist/protobuf/decode.d.ts.map +1 -0
- package/dist/protobuf/decode.js +289 -0
- package/dist/protobuf/decode.js.map +1 -0
- package/dist/protobuf/descriptor.d.ts +16 -0
- package/dist/protobuf/descriptor.d.ts.map +1 -0
- package/dist/protobuf/descriptor.js +303 -0
- package/dist/protobuf/descriptor.js.map +1 -0
- package/dist/protobuf/encode.d.ts +20 -0
- package/dist/protobuf/encode.d.ts.map +1 -0
- package/dist/protobuf/encode.js +203 -0
- package/dist/protobuf/encode.js.map +1 -0
- package/dist/protobuf/grpc-ir.d.ts +16 -0
- package/dist/protobuf/grpc-ir.d.ts.map +1 -0
- package/dist/protobuf/grpc-ir.js +2 -0
- package/dist/protobuf/grpc-ir.js.map +1 -0
- package/dist/protobuf/plan.d.ts +42 -0
- package/dist/protobuf/plan.d.ts.map +1 -0
- package/dist/protobuf/plan.js +158 -0
- package/dist/protobuf/plan.js.map +1 -0
- package/dist/reflect/callsites.d.ts +40 -0
- package/dist/reflect/callsites.d.ts.map +1 -0
- package/dist/reflect/callsites.js +153 -0
- package/dist/reflect/callsites.js.map +1 -0
- package/dist/reflect/index.d.ts +106 -0
- package/dist/reflect/index.d.ts.map +1 -0
- package/dist/reflect/index.js +1421 -0
- package/dist/reflect/index.js.map +1 -0
- package/dist/reflect/session.d.ts +118 -0
- package/dist/reflect/session.d.ts.map +1 -0
- package/dist/reflect/session.js +228 -0
- package/dist/reflect/session.js.map +1 -0
- package/dist/testing/index.d.ts +69 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +210 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/transform/index.d.ts +74 -0
- package/dist/transform/index.d.ts.map +1 -0
- package/dist/transform/index.js +572 -0
- package/dist/transform/index.js.map +1 -0
- package/dist/unplugin/index.d.ts +53 -0
- package/dist/unplugin/index.d.ts.map +1 -0
- package/dist/unplugin/index.js +130 -0
- package/dist/unplugin/index.js.map +1 -0
- package/dist/unplugin/inline-bench.d.ts +29 -0
- package/dist/unplugin/inline-bench.d.ts.map +1 -0
- package/dist/unplugin/inline-bench.js +82 -0
- package/dist/unplugin/inline-bench.js.map +1 -0
- package/package.json +110 -0
- package/src/codegen/index.ts +451 -0
- package/src/codegen/scan.ts +574 -0
- package/src/codegen/witness.ts +819 -0
- package/src/config/contract.ts +48 -0
- package/src/config/index.ts +384 -0
- package/src/config/index.zmdb.generated.d.ts +12 -0
- package/src/config/index.zmdb.generated.js +28 -0
- package/src/config/index.zmdb.witness.ts +22 -0
- package/src/configured-plugin.ts +35 -0
- package/src/emit/index.ts +1496 -0
- package/src/errors.ts +6 -0
- package/src/index.ts +336 -0
- package/src/lint/ast.ts +96 -0
- package/src/lint/host-types.ts +9 -0
- package/src/lint/index.ts +80 -0
- package/src/lint/rules/no-distributed-nullable-tags.ts +76 -0
- package/src/lint/rules/no-empty-patch.ts +26 -0
- package/src/lint/rules/no-interpolated-sql.ts +45 -0
- package/src/lint/rules/no-unbounded-find.ts +26 -0
- package/src/lint/rules/no-unknown-json-column.ts +36 -0
- package/src/lint/rules/require-sql-on-number.ts +34 -0
- package/src/lint/types.ts +11 -0
- package/src/metro/metro.ts +313 -0
- package/src/protobuf/decode.ts +375 -0
- package/src/protobuf/descriptor.ts +353 -0
- package/src/protobuf/encode.ts +277 -0
- package/src/protobuf/grpc-ir.ts +17 -0
- package/src/protobuf/plan.ts +244 -0
- package/src/reflect/callsites.ts +198 -0
- package/src/reflect/index.ts +1723 -0
- package/src/reflect/session.ts +276 -0
- package/src/testing/index.ts +298 -0
- package/src/transform/index.ts +690 -0
- package/src/unplugin/index.ts +182 -0
- package/src/unplugin/inline-bench.ts +92 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { staticMemberName } from '../ast.js';
|
|
2
|
+
import type { HostLintRule } from '../host-types.js';
|
|
3
|
+
|
|
4
|
+
const message = 'find() and find({}) are unbounded; use list() with a page.';
|
|
5
|
+
|
|
6
|
+
export const noUnboundedFind: HostLintRule = {
|
|
7
|
+
meta: {
|
|
8
|
+
type: 'problem',
|
|
9
|
+
docs: {
|
|
10
|
+
description: 'Report find calls with no syntactic filter.',
|
|
11
|
+
recommended: true,
|
|
12
|
+
},
|
|
13
|
+
messages: { unbounded: message },
|
|
14
|
+
schema: [],
|
|
15
|
+
},
|
|
16
|
+
create(context) {
|
|
17
|
+
return {
|
|
18
|
+
CallExpression(node) {
|
|
19
|
+
if (staticMemberName(node.callee) !== 'find') return;
|
|
20
|
+
const filter = node.arguments[0];
|
|
21
|
+
if (filter !== undefined && (filter.type !== 'ObjectExpression' || filter.properties.length !== 0)) return;
|
|
22
|
+
context.report({ node, messageId: 'unbounded' });
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
},
|
|
26
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { HostLintRule } from '../host-types.js';
|
|
2
|
+
|
|
3
|
+
const message = "unknown & X collapses to X; use object & Sql<'json'> or declare the JSON shape.";
|
|
4
|
+
|
|
5
|
+
export const noUnknownJsonColumn: HostLintRule = {
|
|
6
|
+
meta: {
|
|
7
|
+
type: 'problem',
|
|
8
|
+
docs: {
|
|
9
|
+
description: 'Report unknown where an intersection silently erases its data shape.',
|
|
10
|
+
recommended: true,
|
|
11
|
+
},
|
|
12
|
+
hasSuggestions: true,
|
|
13
|
+
messages: {
|
|
14
|
+
collapsed: message,
|
|
15
|
+
replace: 'Replace unknown with object',
|
|
16
|
+
},
|
|
17
|
+
schema: [],
|
|
18
|
+
},
|
|
19
|
+
create(context) {
|
|
20
|
+
return {
|
|
21
|
+
TSUnknownKeyword(node) {
|
|
22
|
+
if (node.parent.type !== 'TSIntersectionType') return;
|
|
23
|
+
context.report({
|
|
24
|
+
node,
|
|
25
|
+
messageId: 'collapsed',
|
|
26
|
+
suggest: [
|
|
27
|
+
{
|
|
28
|
+
messageId: 'replace',
|
|
29
|
+
fix: fixer => fixer.replaceText(node, 'object'),
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
});
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { importedTagBindings, isTableDeclaration, type ImportedTagBindings } from '../ast.js';
|
|
2
|
+
import type { HostLintRule } from '../host-types.js';
|
|
3
|
+
|
|
4
|
+
const message = "A bare number is ambiguous; add Sql<'integer'> or Sql<'numeric'>.";
|
|
5
|
+
|
|
6
|
+
export const requireSqlOnNumber: HostLintRule = {
|
|
7
|
+
meta: {
|
|
8
|
+
type: 'problem',
|
|
9
|
+
docs: {
|
|
10
|
+
description: 'Report bare number annotations on tagged table properties.',
|
|
11
|
+
recommended: true,
|
|
12
|
+
},
|
|
13
|
+
messages: { ambiguous: message },
|
|
14
|
+
schema: [],
|
|
15
|
+
},
|
|
16
|
+
create(context) {
|
|
17
|
+
let bindings: ImportedTagBindings = { tables: new Set<string>(), tags: new Set<string>() };
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
Program(node) {
|
|
21
|
+
bindings = importedTagBindings(node);
|
|
22
|
+
},
|
|
23
|
+
TSInterfaceDeclaration(node) {
|
|
24
|
+
if (!isTableDeclaration(node, bindings)) return;
|
|
25
|
+
for (const property of node.body.body) {
|
|
26
|
+
if (property.type !== 'TSPropertySignature') continue;
|
|
27
|
+
const annotation = property.typeAnnotation?.typeAnnotation;
|
|
28
|
+
if (annotation?.type !== 'TSNumberKeyword') continue;
|
|
29
|
+
context.report({ node: annotation, messageId: 'ambiguous' });
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The host-neutral rule shape published to consumers.
|
|
3
|
+
*
|
|
4
|
+
* The implementation is checked against oxlint's exact alpha API internally,
|
|
5
|
+
* but the public declaration cannot import one host's types: the same plugin is
|
|
6
|
+
* also loadable by ESLint without oxlint installed.
|
|
7
|
+
*/
|
|
8
|
+
export interface LintRule<Context = never, Visitor extends object = object> {
|
|
9
|
+
readonly meta?: unknown;
|
|
10
|
+
readonly create: (context: Context) => Visitor;
|
|
11
|
+
}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
// Metro's Babel-transformer seam adapted to the same AOT transform used by the
|
|
2
|
+
// bundler plugin and code generator.
|
|
3
|
+
//
|
|
4
|
+
// This entry is loaded through `require()` from CommonJS Metro configuration and
|
|
5
|
+
// worker processes. Keep its import graph synchronous: top-level await would make
|
|
6
|
+
// `require('@zmdb/compiler/metro')` fail before Metro reads the config.
|
|
7
|
+
|
|
8
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
9
|
+
import { createRequire, registerHooks } from 'node:module';
|
|
10
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
|
|
13
|
+
import type { MetroConfig } from 'metro';
|
|
14
|
+
import type { BabelTransformer, BabelTransformerArgs, BabelTransformerCacheKeyOptions } from 'metro-babel-transformer';
|
|
15
|
+
|
|
16
|
+
const require = createRequire(import.meta.url);
|
|
17
|
+
const ENTRY = fileURLToPath(import.meta.url);
|
|
18
|
+
const PACKAGE_ROOT = resolve(dirname(ENTRY), '../..');
|
|
19
|
+
const SOURCE = /\.(?:ts|tsx|mts|cts|js|jsx|mjs)$/;
|
|
20
|
+
const DELEGATE_FIELD = 'unstable_zmdbDelegatePath';
|
|
21
|
+
const PROJECT_FIELD = 'unstable_zmdbProjectPath';
|
|
22
|
+
const OPTIONS_FIELD = 'unstable_zmdbOptions';
|
|
23
|
+
const ENV_DELEGATE = 'ZMDB_METRO_DELEGATE';
|
|
24
|
+
const ENV_MAX_WORKERS = 'ZMDB_METRO_MAX_WORKERS';
|
|
25
|
+
const ENV_PROJECT = 'ZMDB_METRO_PROJECT';
|
|
26
|
+
const ENV_OPTIONS = 'ZMDB_METRO_OPTIONS';
|
|
27
|
+
const VERSION = String(require(join(PACKAGE_ROOT, 'package.json')).version);
|
|
28
|
+
|
|
29
|
+
// Source exports point at `.ts`, while every authored relative specifier correctly
|
|
30
|
+
// names the emitted `.js`. Node's synchronous require of this ESM entry does not
|
|
31
|
+
// apply TypeScript's `.js` -> `.ts` source substitution, so install the same narrow
|
|
32
|
+
// development-time resolver used by the repository scripts. Published `dist` has
|
|
33
|
+
// real `.js` siblings and therefore never takes this branch.
|
|
34
|
+
registerHooks({
|
|
35
|
+
resolve(specifier, context, nextResolve) {
|
|
36
|
+
if (context.parentURL !== undefined && /^\.{1,2}\/.*\.js$/.test(specifier)) {
|
|
37
|
+
const asJavaScript = new URL(specifier, context.parentURL);
|
|
38
|
+
if (!existsSync(fileURLToPath(asJavaScript))) {
|
|
39
|
+
const asTypeScript = new URL(`${specifier.slice(0, -3)}.ts`, context.parentURL);
|
|
40
|
+
if (existsSync(fileURLToPath(asTypeScript))) {
|
|
41
|
+
return { url: asTypeScript.href, shortCircuit: true };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return nextResolve(specifier, context);
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
export interface MetroOptions {
|
|
50
|
+
readonly workerCount?: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface TransformHook {
|
|
54
|
+
transform(code: string, id: string): { readonly code: string } | null;
|
|
55
|
+
buildEnd?(): void;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface SessionHandle {
|
|
59
|
+
sourceFileNames(): readonly string[];
|
|
60
|
+
close(): void;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface PluginModule {
|
|
64
|
+
zmdbAot(options: { readonly project: string; readonly cwd: string; readonly session?: SessionHandle }): TransformHook;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface TransformerModule {
|
|
68
|
+
readonly CALLEES: ReadonlySet<string>;
|
|
69
|
+
transformCode(code: string): string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface SessionModule {
|
|
73
|
+
readonly ReflectSession: {
|
|
74
|
+
open(options: { readonly project: string }): SessionHandle;
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface RuntimeConfig {
|
|
79
|
+
readonly delegate: string;
|
|
80
|
+
readonly maxWorkers: number | undefined;
|
|
81
|
+
readonly project: string;
|
|
82
|
+
readonly options: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
interface ActiveHook {
|
|
86
|
+
readonly key: string;
|
|
87
|
+
readonly hook: TransformHook;
|
|
88
|
+
readonly session?: SessionHandle;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
interface PendingSession {
|
|
92
|
+
readonly key: string;
|
|
93
|
+
readonly projectText: string;
|
|
94
|
+
readonly session: SessionHandle;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
let activeHook: ActiveHook | undefined;
|
|
98
|
+
let pendingSession: PendingSession | undefined;
|
|
99
|
+
let calleePattern: RegExp | undefined;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Wrap Metro's existing Babel transformer without replacing its worker.
|
|
103
|
+
*
|
|
104
|
+
* The same `metro.config.js` form works for bare React Native and Expo because
|
|
105
|
+
* both have already selected their Babel transformer before this wrapper reads it.
|
|
106
|
+
*/
|
|
107
|
+
export function withZmdb<Config extends MetroConfig>(config: Config, options: MetroOptions = {}): Config {
|
|
108
|
+
if (options.workerCount !== undefined && (!Number.isInteger(options.workerCount) || options.workerCount < 1)) {
|
|
109
|
+
throw new RangeError('withZmdb workerCount must be a positive integer');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const projectRoot = resolve(config.projectRoot ?? process.cwd());
|
|
113
|
+
const project = join(projectRoot, 'tsconfig.json');
|
|
114
|
+
if (!existsSync(project)) {
|
|
115
|
+
throw new Error(`withZmdb could not find a TypeScript project at ${project}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const current = config.transformer?.babelTransformerPath ?? 'metro-babel-transformer';
|
|
119
|
+
const previous =
|
|
120
|
+
resolve(current) === resolve(ENTRY)
|
|
121
|
+
? (readString(config.transformer, DELEGATE_FIELD) ?? process.env[ENV_DELEGATE] ?? 'metro-babel-transformer')
|
|
122
|
+
: current;
|
|
123
|
+
const delegate = require.resolve(previous, { paths: [projectRoot] });
|
|
124
|
+
const workerCount = options.workerCount ?? config.maxWorkers;
|
|
125
|
+
const resolvedOptions = JSON.stringify({
|
|
126
|
+
workerCount: options.workerCount ?? null,
|
|
127
|
+
maxWorkers: workerCount ?? null,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// With one worker Metro transforms in the config process itself. Loading a new
|
|
131
|
+
// config is therefore the only observable boundary between two programmatic
|
|
132
|
+
// builds, and the previous build's compiler child must not leak across it.
|
|
133
|
+
disposeActiveHook();
|
|
134
|
+
disposePendingSession();
|
|
135
|
+
|
|
136
|
+
// Metro 0.87 keeps unknown transformer config in the worker config and cache
|
|
137
|
+
// material but does not pass it to the Babel transformer arguments. Environment
|
|
138
|
+
// variables are inherited by the worker process, so they are the fallback channel
|
|
139
|
+
// frozen by the spec; the serialisable fields remain visible to config tooling.
|
|
140
|
+
process.env[ENV_DELEGATE] = delegate;
|
|
141
|
+
if (workerCount === undefined) delete process.env[ENV_MAX_WORKERS];
|
|
142
|
+
else process.env[ENV_MAX_WORKERS] = String(workerCount);
|
|
143
|
+
process.env[ENV_PROJECT] = project;
|
|
144
|
+
process.env[ENV_OPTIONS] = resolvedOptions;
|
|
145
|
+
|
|
146
|
+
const transformer = {
|
|
147
|
+
...config.transformer,
|
|
148
|
+
babelTransformerPath: ENTRY,
|
|
149
|
+
[DELEGATE_FIELD]: delegate,
|
|
150
|
+
[PROJECT_FIELD]: project,
|
|
151
|
+
[OPTIONS_FIELD]: resolvedOptions,
|
|
152
|
+
};
|
|
153
|
+
return Object.assign({}, config, workerCount === undefined ? {} : { maxWorkers: workerCount }, { transformer });
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Run zmdb first, then hand the rewritten source to the transform Metro already used. */
|
|
157
|
+
export function transform(args: BabelTransformerArgs): ReturnType<BabelTransformer['transform']> {
|
|
158
|
+
const config = runtimeConfig();
|
|
159
|
+
const delegate = loadDelegate(config.delegate);
|
|
160
|
+
const fileName = isAbsolute(args.filename) ? args.filename : resolve(args.options.projectRoot, args.filename);
|
|
161
|
+
if (!isProjectSource(fileName, config.project)) return delegate.transform(args);
|
|
162
|
+
|
|
163
|
+
const transformer = loadTransformer();
|
|
164
|
+
if (!pattern(transformer.CALLEES).test(args.src)) {
|
|
165
|
+
const code = transformer.transformCode(args.src);
|
|
166
|
+
return delegate.transform(code === args.src ? args : { ...args, src: code });
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const hook = ensureHook(config);
|
|
170
|
+
const transformed = hook.transform(args.src, fileName)?.code ?? args.src;
|
|
171
|
+
return delegate.transform(transformed === args.src ? args : { ...args, src: transformed });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Return unhashed cache material. Metro folds this into its own digest.
|
|
176
|
+
*
|
|
177
|
+
* A different delegate, zmdb release, wrapper option, tsconfig, or project source
|
|
178
|
+
* stat therefore invalidates every transformed module on the next build.
|
|
179
|
+
*/
|
|
180
|
+
export function getCacheKey(options?: BabelTransformerCacheKeyOptions): string {
|
|
181
|
+
const config = runtimeConfig(options?.projectRoot);
|
|
182
|
+
const delegate = loadDelegate(config.delegate);
|
|
183
|
+
const delegateKey = delegate.getCacheKey?.(options) ?? '';
|
|
184
|
+
const projectText = readFileSync(config.project, 'utf8');
|
|
185
|
+
return [
|
|
186
|
+
'zmdb-metro',
|
|
187
|
+
VERSION,
|
|
188
|
+
config.delegate,
|
|
189
|
+
delegateKey,
|
|
190
|
+
config.options,
|
|
191
|
+
config.project,
|
|
192
|
+
projectText,
|
|
193
|
+
typeFingerprint(config, projectText),
|
|
194
|
+
].join('\0');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function runtimeConfig(projectRoot?: string): RuntimeConfig {
|
|
198
|
+
const delegate = process.env[ENV_DELEGATE];
|
|
199
|
+
const configuredProject = process.env[ENV_PROJECT];
|
|
200
|
+
if (delegate === undefined) {
|
|
201
|
+
throw new Error('withZmdb must wrap the Metro config before the zmdb transformer is loaded');
|
|
202
|
+
}
|
|
203
|
+
const project = configuredProject ?? join(resolve(projectRoot ?? process.cwd()), 'tsconfig.json');
|
|
204
|
+
const maxWorkersText = process.env[ENV_MAX_WORKERS];
|
|
205
|
+
const maxWorkers = maxWorkersText === undefined ? undefined : Number(maxWorkersText);
|
|
206
|
+
return {
|
|
207
|
+
delegate,
|
|
208
|
+
maxWorkers: maxWorkers !== undefined && Number.isInteger(maxWorkers) && maxWorkers > 0 ? maxWorkers : undefined,
|
|
209
|
+
project,
|
|
210
|
+
options: process.env[ENV_OPTIONS] ?? JSON.stringify({ workerCount: null }),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function loadDelegate(path: string): BabelTransformer {
|
|
215
|
+
const loaded = require(path);
|
|
216
|
+
return loaded.__esModule === true && loaded.default !== undefined ? loaded.default : loaded;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function loadPlugin(): PluginModule {
|
|
220
|
+
return require('../unplugin/index.js');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function loadTransformer(): TransformerModule {
|
|
224
|
+
return require('../transform/index.js');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function loadSession(): SessionModule {
|
|
228
|
+
return require('../reflect/session.js');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function ensureHook(config: RuntimeConfig): TransformHook {
|
|
232
|
+
const key = `${config.project}\0${config.options}`;
|
|
233
|
+
if (activeHook?.key === key) return activeHook.hook;
|
|
234
|
+
disposeActiveHook();
|
|
235
|
+
const pending = pendingSession?.key === key ? pendingSession : undefined;
|
|
236
|
+
if (pending !== undefined) pendingSession = undefined;
|
|
237
|
+
else disposePendingSession();
|
|
238
|
+
const hook = loadPlugin().zmdbAot(
|
|
239
|
+
pending === undefined
|
|
240
|
+
? { project: config.project, cwd: dirname(config.project) }
|
|
241
|
+
: { project: config.project, cwd: dirname(config.project), session: pending.session },
|
|
242
|
+
);
|
|
243
|
+
activeHook = pending === undefined ? { key, hook } : { key, hook, session: pending.session };
|
|
244
|
+
return hook;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function pattern(callees: ReadonlySet<string>): RegExp {
|
|
248
|
+
calleePattern ??= new RegExp(`\\b(?:${[...callees].join('|')})\\s*(?:<|\\()`);
|
|
249
|
+
return calleePattern;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function isProjectSource(fileName: string, project: string): boolean {
|
|
253
|
+
if (fileName.includes(`${sep}node_modules${sep}`) || fileName.endsWith('.d.ts') || !SOURCE.test(fileName)) {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
const root = dirname(project);
|
|
257
|
+
const path = relative(root, fileName);
|
|
258
|
+
return path !== '..' && !path.startsWith(`..${sep}`);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function typeFingerprint(config: RuntimeConfig, projectText: string): string {
|
|
262
|
+
const key = `${config.project}\0${config.options}`;
|
|
263
|
+
let session: SessionHandle;
|
|
264
|
+
let retained = false;
|
|
265
|
+
|
|
266
|
+
if (config.maxWorkers === 1) {
|
|
267
|
+
if (pendingSession?.key !== key || pendingSession.projectText !== projectText) {
|
|
268
|
+
disposePendingSession();
|
|
269
|
+
pendingSession = {
|
|
270
|
+
key,
|
|
271
|
+
projectText,
|
|
272
|
+
session: loadSession().ReflectSession.open({ project: config.project }),
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
session = pendingSession.session;
|
|
276
|
+
retained = true;
|
|
277
|
+
} else {
|
|
278
|
+
session = loadSession().ReflectSession.open({ project: config.project });
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const fingerprint = session
|
|
282
|
+
.sourceFileNames()
|
|
283
|
+
.filter(file => !file.includes(`${sep}node_modules${sep}`) && !file.endsWith('.d.ts'))
|
|
284
|
+
.toSorted()
|
|
285
|
+
.map(file => {
|
|
286
|
+
try {
|
|
287
|
+
const stat = statSync(file);
|
|
288
|
+
return `${file}\0${String(stat.size)}\0${String(stat.mtimeMs)}`;
|
|
289
|
+
} catch {
|
|
290
|
+
return `${file}\0missing`;
|
|
291
|
+
}
|
|
292
|
+
})
|
|
293
|
+
.join('\0');
|
|
294
|
+
if (!retained) session.close();
|
|
295
|
+
return fingerprint;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function disposeActiveHook(): void {
|
|
299
|
+
activeHook?.hook.buildEnd?.();
|
|
300
|
+
activeHook?.session?.close();
|
|
301
|
+
activeHook = undefined;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function disposePendingSession(): void {
|
|
305
|
+
pendingSession?.session.close();
|
|
306
|
+
pendingSession = undefined;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function readString(value: object | undefined, key: string): string | undefined {
|
|
310
|
+
if (value === undefined) return undefined;
|
|
311
|
+
const property = Object.getOwnPropertyDescriptor(value, key)?.value;
|
|
312
|
+
return typeof property === 'string' ? property : undefined;
|
|
313
|
+
}
|