@webda/content-mapper 0.0.0-stage → 4.0.0-beta.5

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.
Files changed (115) hide show
  1. package/LICENSE +166 -0
  2. package/lib/coercions.d.ts +22 -0
  3. package/lib/coercions.d.ts.map +1 -0
  4. package/lib/coercions.js +10 -0
  5. package/lib/coercions.js.map +1 -0
  6. package/lib/context.d.ts +56 -0
  7. package/lib/context.d.ts.map +1 -0
  8. package/lib/context.js +111 -0
  9. package/lib/context.js.map +1 -0
  10. package/lib/defaults.d.ts +31 -0
  11. package/lib/defaults.d.ts.map +1 -0
  12. package/lib/defaults.js +33 -0
  13. package/lib/defaults.js.map +1 -0
  14. package/lib/generators/accessors.d.ts +15 -0
  15. package/lib/generators/accessors.d.ts.map +1 -0
  16. package/lib/generators/accessors.js +567 -0
  17. package/lib/generators/accessors.js.map +1 -0
  18. package/lib/generators/behaviors.d.ts +21 -0
  19. package/lib/generators/behaviors.d.ts.map +1 -0
  20. package/lib/generators/behaviors.js +292 -0
  21. package/lib/generators/behaviors.js.map +1 -0
  22. package/lib/generators/imports.d.ts +86 -0
  23. package/lib/generators/imports.d.ts.map +1 -0
  24. package/lib/generators/imports.js +205 -0
  25. package/lib/generators/imports.js.map +1 -0
  26. package/lib/generators/loadparameters.d.ts +7 -0
  27. package/lib/generators/loadparameters.d.ts.map +1 -0
  28. package/lib/generators/loadparameters.js +175 -0
  29. package/lib/generators/loadparameters.js.map +1 -0
  30. package/lib/generators/qlvalidator.d.ts +53 -0
  31. package/lib/generators/qlvalidator.d.ts.map +1 -0
  32. package/lib/generators/qlvalidator.js +238 -0
  33. package/lib/generators/qlvalidator.js.map +1 -0
  34. package/lib/index.d.ts +53 -0
  35. package/lib/index.d.ts.map +1 -0
  36. package/lib/index.js +36 -0
  37. package/lib/index.js.map +1 -0
  38. package/lib/module-discovery.d.ts +211 -0
  39. package/lib/module-discovery.d.ts.map +1 -0
  40. package/lib/module-discovery.js +662 -0
  41. package/lib/module-discovery.js.map +1 -0
  42. package/lib/module.d.ts +213 -0
  43. package/lib/module.d.ts.map +1 -0
  44. package/lib/module.js +1243 -0
  45. package/lib/module.js.map +1 -0
  46. package/lib/plan.d.ts +101 -0
  47. package/lib/plan.d.ts.map +1 -0
  48. package/lib/plan.js +61 -0
  49. package/lib/plan.js.map +1 -0
  50. package/lib/property-order.d.ts +37 -0
  51. package/lib/property-order.d.ts.map +1 -0
  52. package/lib/property-order.js +141 -0
  53. package/lib/property-order.js.map +1 -0
  54. package/lib/schema/action.d.ts +46 -0
  55. package/lib/schema/action.d.ts.map +1 -0
  56. package/lib/schema/action.js +190 -0
  57. package/lib/schema/action.js.map +1 -0
  58. package/lib/schema/annotations.d.ts +63 -0
  59. package/lib/schema/annotations.d.ts.map +1 -0
  60. package/lib/schema/annotations.js +116 -0
  61. package/lib/schema/annotations.js.map +1 -0
  62. package/lib/schema/converter.d.ts +363 -0
  63. package/lib/schema/converter.d.ts.map +1 -0
  64. package/lib/schema/converter.js +1377 -0
  65. package/lib/schema/converter.js.map +1 -0
  66. package/lib/schema/jsdoc.d.ts +34 -0
  67. package/lib/schema/jsdoc.d.ts.map +1 -0
  68. package/lib/schema/jsdoc.js +206 -0
  69. package/lib/schema/jsdoc.js.map +1 -0
  70. package/lib/schema/model.d.ts +27 -0
  71. package/lib/schema/model.d.ts.map +1 -0
  72. package/lib/schema/model.js +86 -0
  73. package/lib/schema/model.js.map +1 -0
  74. package/lib/schema/project.d.ts +34 -0
  75. package/lib/schema/project.d.ts.map +1 -0
  76. package/lib/schema/project.js +305 -0
  77. package/lib/schema/project.js.map +1 -0
  78. package/lib/schema/service.d.ts +62 -0
  79. package/lib/schema/service.d.ts.map +1 -0
  80. package/lib/schema/service.js +130 -0
  81. package/lib/schema/service.js.map +1 -0
  82. package/lib/schema/types.d.ts +61 -0
  83. package/lib/schema/types.d.ts.map +1 -0
  84. package/lib/schema/types.js +36 -0
  85. package/lib/schema/types.js.map +1 -0
  86. package/lib/schema/worker-cli.d.ts +3 -0
  87. package/lib/schema/worker-cli.d.ts.map +1 -0
  88. package/lib/schema/worker-cli.js +7 -0
  89. package/lib/schema/worker-cli.js.map +1 -0
  90. package/lib/schema/worker.d.ts +81 -0
  91. package/lib/schema/worker.d.ts.map +1 -0
  92. package/lib/schema/worker.js +186 -0
  93. package/lib/schema/worker.js.map +1 -0
  94. package/lib/server.d.ts +2 -0
  95. package/lib/server.d.ts.map +1 -0
  96. package/lib/server.js +133 -0
  97. package/lib/server.js.map +1 -0
  98. package/lib/session.d.ts +84 -0
  99. package/lib/session.d.ts.map +1 -0
  100. package/lib/session.js +141 -0
  101. package/lib/session.js.map +1 -0
  102. package/lib/spans.d.ts +46 -0
  103. package/lib/spans.d.ts.map +1 -0
  104. package/lib/spans.js +64 -0
  105. package/lib/spans.js.map +1 -0
  106. package/lib/twopass.d.ts +45 -0
  107. package/lib/twopass.d.ts.map +1 -0
  108. package/lib/twopass.js +87 -0
  109. package/lib/twopass.js.map +1 -0
  110. package/lib/vite.d.ts +20 -0
  111. package/lib/vite.d.ts.map +1 -0
  112. package/lib/vite.js +41 -0
  113. package/lib/vite.js.map +1 -0
  114. package/package.json +93 -3
  115. package/README.md +0 -3
@@ -0,0 +1,81 @@
1
+ import type { JSONSchema7 } from "./types.ts";
2
+ /** A single schema request. */
3
+ export interface SchemaRequest {
4
+ /** Caller-chosen key, echoed in the response. */
5
+ id: string;
6
+ /**
7
+ * What to generate. `topLevel` needs no class and answers with a map;
8
+ * `module` needs no class and answers with the whole `webda.module.json`
9
+ * as `{ module, namingViolations, errors }`.
10
+ */
11
+ kind: "service" | "model" | "topLevel" | "module" | "emit";
12
+ /** Absolute path of the file declaring the class. */
13
+ file: string;
14
+ /** Class to generate for. */
15
+ className: string;
16
+ /** Add the free-form `openapi` property; moddas and beans only. */
17
+ addOpenApi?: boolean;
18
+ /** Base type the parameters derive from; `DeployerResources` for deployers. */
19
+ parametersBase?: string;
20
+ /** Source root, for a `topLevel` or `module` request. */
21
+ rootDir?: string;
22
+ /** Output root, for a `topLevel` or `module` request. */
23
+ outDir?: string;
24
+ /** Namespace prefix, for a `topLevel` or `module` request. */
25
+ namespace?: string;
26
+ /** Top-level `capabilities`, for a `module` request; defaults to package.json `webda.capabilities`. */
27
+ capabilities?: Record<string, string>;
28
+ }
29
+ /** A batch of requests against one project. */
30
+ export interface WorkerRequest {
31
+ /** Project root, or an explicit tsconfig path. */
32
+ project: string;
33
+ /** Requests to answer. */
34
+ requests: SchemaRequest[];
35
+ }
36
+ /** The worker's reply. */
37
+ export interface WorkerResponse {
38
+ /** Successful results, keyed by request id. */
39
+ results: Record<string, JSONSchema7>;
40
+ /** Failures, keyed by request id. */
41
+ errors: Record<string, string>;
42
+ }
43
+ /**
44
+ * Answer a batch of requests.
45
+ *
46
+ * One session, and therefore one `Program`, is built per batch — the caller
47
+ * batches precisely so that cost is paid once.
48
+ * @param request - the batch
49
+ * @returns results and per-request errors
50
+ */
51
+ export declare function handle(request: WorkerRequest): WorkerResponse;
52
+ /** What an `emit` request reports. */
53
+ export interface EmitReport {
54
+ /** Pass-2 diagnostics, formatted as `tsc` would print them. */
55
+ diagnostics: string;
56
+ /** Number of diagnostics; nothing is written unless it is zero. */
57
+ diagnosticCount: number;
58
+ /** Files written. */
59
+ written: number;
60
+ /** Edits per generator, for the build log. */
61
+ editCounts: Record<string, number>;
62
+ }
63
+ /**
64
+ * Build a project through the two-pass tsgo pipeline and write its output.
65
+ *
66
+ * Replaces `tsProgram.emit` with the four `@webda/ts-plugin` transformers.
67
+ * Pass 1 plans the generated code over the authored sources; pass 2 type-
68
+ * checks and emits the rewritten text, so what ships is exactly what was
69
+ * checked. If pass 2 reports anything, nothing is written — the old emit
70
+ * wrote files even when `getPreEmitDiagnostics` failed.
71
+ * @param configFile - absolute tsconfig path
72
+ * @param rootDir - source root
73
+ * @returns diagnostics and what was written
74
+ */
75
+ export declare function emitProject(configFile: string, rootDir: string): EmitReport;
76
+ /**
77
+ * Read one request from stdin and write one response to stdout.
78
+ * @returns a promise resolving when the response is written
79
+ */
80
+ export declare function main(): Promise<void>;
81
+ //# sourceMappingURL=worker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/schema/worker.ts"],"names":[],"mappings":"AAuCA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,+BAA+B;AAC/B,MAAM,WAAW,aAAa;IAC5B,iDAAiD;IACjD,EAAE,EAAE,MAAM,CAAC;IACX;;;;OAIG;IACH,IAAI,EAAE,SAAS,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,GAAG,MAAM,CAAC;IAC3D,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,6BAA6B;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,+EAA+E;IAC/E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yDAAyD;IACzD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uGAAuG;IACvG,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACvC;AAED,+CAA+C;AAC/C,MAAM,WAAW,aAAa;IAC5B,kDAAkD;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB,0BAA0B;IAC1B,QAAQ,EAAE,aAAa,EAAE,CAAC;CAC3B;AAED,0BAA0B;AAC1B,MAAM,WAAW,cAAc;IAC7B,+CAA+C;IAC/C,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACrC,qCAAqC;IACrC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED;;;;;;;GAOG;AACH,wBAAgB,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,cAAc,CAuE7D;AAYD,sCAAsC;AACtC,MAAM,WAAW,UAAU;IACzB,+DAA+D;IAC/D,WAAW,EAAE,MAAM,CAAC;IACpB,mEAAmE;IACnE,eAAe,EAAE,MAAM,CAAC;IACxB,qBAAqB;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,8CAA8C;IAC9C,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,CAmB3E;AAuBD;;;GAGG;AACH,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAK1C"}
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Out-of-process schema generation on the TypeScript 7.1 checker.
3
+ *
4
+ * `@webda/compiler` drives this as a subprocess rather than importing it.
5
+ * That is not indirection for its own sake: the compiler still runs on
6
+ * TypeScript 6 and this package needs 7.1, a package can declare only one
7
+ * `typescript`, and a process boundary is the only thing that lets the two
8
+ * coexist until the atomic switch.
9
+ *
10
+ * It began as a way to diff against `@webda/schema`, which generated these
11
+ * schemas before and has since been deleted; the protocol shape is
12
+ * unchanged from that comparison, which is why it names things rather than
13
+ * passing them.
14
+ *
15
+ * Protocol: one JSON request on stdin, one JSON response on stdout.
16
+ *
17
+ * ```
18
+ * { "project": "/abs/path", "requests": [
19
+ * { "id": "Webda/Store", "kind": "service", "file": "/abs/f.ts",
20
+ * "className": "Store", "addOpenApi": true }
21
+ * ]}
22
+ * -> { "results": { "Webda/Store": { } }, "errors": { } }
23
+ * ```
24
+ *
25
+ * `{ "id": "m", "kind": "module" }` answers with
26
+ * `{ module, namingViolations, errors }` — see `../module.ts`.
27
+ */
28
+ import { mkdirSync, writeFileSync } from "node:fs";
29
+ import { dirname, join } from "node:path";
30
+ import { formatDiagnostics } from "typescript/unstable/sync";
31
+ import { runTwoPass } from "../twopass.js";
32
+ import { ModifierFlags } from "typescript/unstable/ast";
33
+ import * as is from "typescript/unstable/ast/is";
34
+ import { openSession } from "../context.js";
35
+ import { generateWebdaModule } from "../module.js";
36
+ import { generateModelSchemas } from "./model.js";
37
+ import { generateTopLevelSchemas, namespaceOf } from "./project.js";
38
+ import { generateServiceSchema } from "./service.js";
39
+ /**
40
+ * Answer a batch of requests.
41
+ *
42
+ * One session, and therefore one `Program`, is built per batch — the caller
43
+ * batches precisely so that cost is paid once.
44
+ * @param request - the batch
45
+ * @returns results and per-request errors
46
+ */
47
+ export function handle(request) {
48
+ const response = { results: {}, errors: {} };
49
+ const configFile = request.project.endsWith(".json") ? request.project : join(request.project, "tsconfig.json");
50
+ const session = openSession(configFile, request.project);
51
+ try {
52
+ for (const item of request.requests) {
53
+ try {
54
+ if (item.kind === "topLevel") {
55
+ // Answers with the whole map: these entries record no provenance,
56
+ // so the caller cannot name them one at a time.
57
+ response.results[item.id] = generateTopLevelSchemas(session.ctx, {
58
+ appPath: request.project,
59
+ rootDir: item.rootDir ?? join(request.project, "src"),
60
+ outDir: item.outDir ?? join(request.project, "lib"),
61
+ namespace: item.namespace ?? namespaceOf(request.project)
62
+ });
63
+ continue;
64
+ }
65
+ if (item.kind === "emit") {
66
+ response.results[item.id] = emitProject(configFile, item.rootDir ?? join(request.project, "src"));
67
+ continue;
68
+ }
69
+ if (item.kind === "module") {
70
+ // The whole module in one answer; naming violations and errors
71
+ // travel with it so the caller decides how to report them.
72
+ response.results[item.id] = generateWebdaModule(session.ctx, {
73
+ appPath: request.project,
74
+ rootDir: item.rootDir ?? join(request.project, "src"),
75
+ outDir: item.outDir ?? join(request.project, "lib"),
76
+ namespace: item.namespace ?? namespaceOf(request.project),
77
+ capabilities: item.capabilities
78
+ });
79
+ continue;
80
+ }
81
+ const declaration = findClass(session.ctx.program, item.file, item.className);
82
+ if (!declaration) {
83
+ response.errors[item.id] = `class ${item.className} not found in ${item.file}`;
84
+ continue;
85
+ }
86
+ if (item.kind === "model") {
87
+ response.results[item.id] = generateModelSchemas(declaration, {
88
+ project: session.ctx.project,
89
+ checker: session.ctx.checker
90
+ });
91
+ continue;
92
+ }
93
+ const schema = generateServiceSchema(declaration, {
94
+ project: session.ctx.project,
95
+ checker: session.ctx.checker,
96
+ addOpenApi: item.addOpenApi,
97
+ parametersBase: item.parametersBase,
98
+ // The declaration's own name, not the requested one: a default
99
+ // export is requested as `"default"` but titled after the class.
100
+ title: declaration.name?.text ?? item.className
101
+ });
102
+ if (!schema) {
103
+ response.errors[item.id] = `no parameters type found on ${item.className}`;
104
+ continue;
105
+ }
106
+ response.results[item.id] = schema;
107
+ }
108
+ catch (error) {
109
+ // Per-request isolation, so one unconvertible type does not hide the
110
+ // rest of the batch. The converter still refuses to emit for it.
111
+ response.errors[item.id] = error instanceof Error ? error.message : String(error);
112
+ }
113
+ }
114
+ }
115
+ finally {
116
+ session.dispose();
117
+ }
118
+ return response;
119
+ }
120
+ /**
121
+ * Build a project through the two-pass tsgo pipeline and write its output.
122
+ *
123
+ * Replaces `tsProgram.emit` with the four `@webda/ts-plugin` transformers.
124
+ * Pass 1 plans the generated code over the authored sources; pass 2 type-
125
+ * checks and emits the rewritten text, so what ships is exactly what was
126
+ * checked. If pass 2 reports anything, nothing is written — the old emit
127
+ * wrote files even when `getPreEmitDiagnostics` failed.
128
+ * @param configFile - absolute tsconfig path
129
+ * @param rootDir - source root
130
+ * @returns diagnostics and what was written
131
+ */
132
+ export function emitProject(configFile, rootDir) {
133
+ const result = runTwoPass({ configFile, rootDir, emit: true });
134
+ const conflicts = result.conflicts.map(c => `${c.fileName}: overlapping edits from ${c.a.source} and ${c.b.source}`);
135
+ const diagnostics = formatDiagnostics(result.diagnostics, {
136
+ getCanonicalFileName: name => name,
137
+ getCurrentDirectory: () => dirname(configFile),
138
+ getNewLine: () => "\n"
139
+ }) + conflicts.join("\n");
140
+ const diagnosticCount = result.diagnostics.length + conflicts.length;
141
+ let written = 0;
142
+ if (diagnosticCount === 0) {
143
+ for (const [path, text] of result.emitted) {
144
+ mkdirSync(dirname(path), { recursive: true });
145
+ writeFileSync(path, text);
146
+ written++;
147
+ }
148
+ }
149
+ return { diagnostics, diagnosticCount, written, editCounts: result.editCounts };
150
+ }
151
+ /**
152
+ * Find a class declaration by file and name.
153
+ * @param program - the program to search
154
+ * @param file - absolute file path
155
+ * @param className - class name
156
+ * @returns the declaration, when found
157
+ */
158
+ function findClass(program, file, className) {
159
+ const sourceFile = program.getSourceFile(file);
160
+ if (!sourceFile)
161
+ return undefined;
162
+ for (const statement of sourceFile.statements) {
163
+ const node = statement;
164
+ if (!is.isClassDeclaration(node))
165
+ continue;
166
+ if (node.name?.text === className)
167
+ return node;
168
+ // `webda.module.json` records a default export as `:default`, so there is
169
+ // no name to match on — the modifier is the only handle.
170
+ if (className === "default" && (node.modifierFlags & ModifierFlags.Default) !== 0)
171
+ return node;
172
+ }
173
+ return undefined;
174
+ }
175
+ /**
176
+ * Read one request from stdin and write one response to stdout.
177
+ * @returns a promise resolving when the response is written
178
+ */
179
+ export async function main() {
180
+ const chunks = [];
181
+ for await (const chunk of process.stdin)
182
+ chunks.push(chunk);
183
+ const request = JSON.parse(Buffer.concat(chunks).toString("utf8"));
184
+ process.stdout.write(JSON.stringify(handle(request)));
185
+ }
186
+ //# sourceMappingURL=worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker.js","sourceRoot":"","sources":["../../src/schema/worker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":""}
package/lib/server.js ADDED
@@ -0,0 +1,133 @@
1
+ /**
2
+ * The content mapper process.
3
+ *
4
+ * Speaks the TypeScript 7.1 content mapper protocol over stdio (JSON-RPC with
5
+ * LSP-style `Content-Length` framing). TypeScript drives every exchange:
6
+ * `initialize` -> `openProject` -> `transform`* -> `closeProject`. Mappers never
7
+ * send requests.
8
+ *
9
+ * All the interesting work is in {@link WarmSession}; this file is transport,
10
+ * project bookkeeping and timing instrumentation.
11
+ *
12
+ * Diagnostics go to a log file rather than stderr, because stderr is consumed by
13
+ * the compiler as mapper log output.
14
+ */
15
+ import { appendFileSync } from "node:fs";
16
+ import { WarmSession } from "./session.js";
17
+ const LOG = process.env.WEBDA_MAPPER_LOG;
18
+ /**
19
+ * Append a line to the debug log, if one is configured.
20
+ * @param parts - message parts
21
+ */
22
+ function log(...parts) {
23
+ if (!LOG)
24
+ return;
25
+ appendFileSync(LOG, parts.map(p => (typeof p === "string" ? p : JSON.stringify(p))).join(" ") + "\n");
26
+ }
27
+ const projects = new Map();
28
+ /**
29
+ * Write a JSON-RPC message with `Content-Length` framing.
30
+ * @param message - the message
31
+ */
32
+ function send(message) {
33
+ const body = JSON.stringify(message);
34
+ process.stdout.write(`Content-Length: ${Buffer.byteLength(body, "utf8")}\r\n\r\n${body}`);
35
+ }
36
+ /**
37
+ * Handle one request and reply.
38
+ * @param message - the decoded request
39
+ */
40
+ function handle(message) {
41
+ const { id, method, params } = message;
42
+ try {
43
+ let result;
44
+ switch (method) {
45
+ case "initialize":
46
+ // UTF-16 matches JavaScript string offsets exactly, so no re-encoding.
47
+ result = {
48
+ positionEncoding: params.positionEncodings.includes("utf-16") ? "utf-16" : params.positionEncodings[0],
49
+ diagnosticSource: "webda"
50
+ };
51
+ break;
52
+ case "openProject": {
53
+ const session = new WarmSession({
54
+ configFile: params.configFileName,
55
+ cwd: params.configFileName.replace(/[/\\][^/\\]+$/, ""),
56
+ storageModule: params.options?.storageModule ?? "@webda/models",
57
+ qlModule: params.options?.qlModule,
58
+ accessorsForAll: !!params.options?.accessorsForAll
59
+ });
60
+ projects.set(params.projectHandle, { session, transforms: 0, timings: [] });
61
+ log("openProject", params.configFileName, `startup=${session.startupMs.toFixed(0)}ms`, `files=${session.fileCount}`);
62
+ result = {};
63
+ break;
64
+ }
65
+ case "transform": {
66
+ const entry = projects.get(params.projectHandle);
67
+ if (!entry)
68
+ throw new Error(`unknown projectHandle ${params.projectHandle}`);
69
+ const outcome = entry.session.transform(params.fileName, params.content);
70
+ entry.transforms++;
71
+ entry.timings.push(outcome.timing);
72
+ const t = outcome.timing;
73
+ log("transform", params.fileName.replace(/^.*\//, ""), `edits=${outcome.editCount}`, `spans=${outcome.mappings.length}`, `total=${t.totalMs.toFixed(1)}ms`, `(snapshot=${t.snapshotMs.toFixed(1)} analyze=${t.analyzeMs.toFixed(1)} splice=${t.spliceMs.toFixed(2)})`, t.changed ? "changed" : "cached");
74
+ // `diagnostics` reports problems against the *authored* file, which is
75
+ // how a generator surfaces something it cannot fix by rewriting.
76
+ result = {
77
+ text: outcome.text,
78
+ extension: ".ts",
79
+ mappings: outcome.mappings,
80
+ ...(outcome.diagnostics.length ? { diagnostics: outcome.diagnostics } : {})
81
+ };
82
+ break;
83
+ }
84
+ case "closeProject": {
85
+ const entry = projects.get(params.projectHandle);
86
+ if (entry) {
87
+ entry.session.dispose();
88
+ projects.delete(params.projectHandle);
89
+ log("closeProject", `transforms=${entry.transforms}`);
90
+ }
91
+ result = {};
92
+ break;
93
+ }
94
+ default:
95
+ send({ jsonrpc: "2.0", id, error: { code: -32601, message: `unknown method ${method}` } });
96
+ return;
97
+ }
98
+ if (id !== undefined)
99
+ send({ jsonrpc: "2.0", id, result });
100
+ }
101
+ catch (error) {
102
+ log("ERROR", method, error?.stack ?? String(error));
103
+ if (id !== undefined)
104
+ send({ jsonrpc: "2.0", id, error: { code: -32603, message: String(error?.message ?? error) } });
105
+ }
106
+ }
107
+ let buffer = Buffer.alloc(0);
108
+ process.stdin.on("data", (chunk) => {
109
+ buffer = Buffer.concat([buffer, chunk]);
110
+ for (;;) {
111
+ const headerEnd = buffer.indexOf("\r\n\r\n");
112
+ if (headerEnd < 0)
113
+ return;
114
+ const header = buffer.subarray(0, headerEnd).toString("ascii");
115
+ const match = /Content-Length:\s*(\d+)/i.exec(header);
116
+ if (!match) {
117
+ log("malformed header", header);
118
+ process.exit(1);
119
+ }
120
+ const length = Number(match[1]);
121
+ if (buffer.length < headerEnd + 4 + length)
122
+ return;
123
+ const body = buffer.subarray(headerEnd + 4, headerEnd + 4 + length).toString("utf8");
124
+ buffer = buffer.subarray(headerEnd + 4 + length);
125
+ handle(JSON.parse(body));
126
+ }
127
+ });
128
+ process.on("exit", () => {
129
+ for (const entry of projects.values())
130
+ entry.session.dispose();
131
+ });
132
+ log("mapper started", process.argv.join(" "));
133
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,84 @@
1
+ import { type GeneratedDiagnostic, type Generator } from "./plan.ts";
2
+ import { type MappedText } from "./spans.ts";
3
+ /** How to open the resident program. */
4
+ export interface WarmSessionOptions {
5
+ /** Absolute tsconfig path, as supplied by `openProject`. */
6
+ configFile: string;
7
+ /** Working directory for the resident API. */
8
+ cwd: string;
9
+ /** Module specifier providing `WEBDA_STORAGE`. */
10
+ storageModule?: string;
11
+ /** Treat every class as eligible, not just models. */
12
+ accessorsForAll?: boolean;
13
+ /** Module specifier providing the WebdaQL `escape` helper. */
14
+ qlModule?: string;
15
+ /** Override the generator set (tests). */
16
+ generators?: Generator[];
17
+ }
18
+ /** Per-request cost breakdown, in milliseconds. */
19
+ export interface TransformTiming {
20
+ /** Refreshing the resident snapshot after a buffer change. */
21
+ snapshotMs: number;
22
+ /** Running the generators, i.e. the checker queries. */
23
+ analyzeMs: number;
24
+ /** Splicing edits and building the span map. */
25
+ spliceMs: number;
26
+ totalMs: number;
27
+ /** False when the buffer was unchanged and the snapshot was reused. */
28
+ changed: boolean;
29
+ }
30
+ /** A transform result plus what it cost. */
31
+ export interface TransformOutcome extends MappedText {
32
+ timing: TransformTiming;
33
+ editCount: number;
34
+ /** Generator diagnostics, for `TransformResult.diagnostics`. */
35
+ diagnostics: GeneratedDiagnostic[];
36
+ }
37
+ /**
38
+ * A resident `Program` + `Checker` that answers `transform` requests.
39
+ */
40
+ export declare class WarmSession {
41
+ private readonly api;
42
+ private readonly options;
43
+ private readonly generators;
44
+ /** Live buffer text per file, served to the resident program. */
45
+ private readonly overlay;
46
+ private snapshot;
47
+ /** Cost of the initial program construction. */
48
+ readonly startupMs: number;
49
+ /**
50
+ * Open the resident program for a project.
51
+ * @param options - how to open it
52
+ */
53
+ constructor(options: WarmSessionOptions);
54
+ /**
55
+ * Files in the resident program, for benchmarking.
56
+ * @returns the source file count
57
+ */
58
+ get fileCount(): number;
59
+ /**
60
+ * The single project held by the current snapshot.
61
+ * @returns the project
62
+ */
63
+ private project;
64
+ /**
65
+ * Build an analysis context restricted to a single file.
66
+ *
67
+ * Restricting `sourceFiles` is what makes re-analysis incremental: the
68
+ * generators only walk the file that changed, while the checker still resolves
69
+ * types across the whole program.
70
+ * @param fileName - the file to analyse
71
+ * @returns a context the generators accept
72
+ */
73
+ private contextFor;
74
+ /**
75
+ * Transform one file, refreshing the resident program only if its text moved.
76
+ * @param fileName - absolute path of the file being transformed
77
+ * @param content - the editor's current buffer text
78
+ * @returns generated text, span map and timings
79
+ */
80
+ transform(fileName: string, content: string): TransformOutcome;
81
+ /** Shut down the resident server process. */
82
+ dispose(): void;
83
+ }
84
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAwBA,OAAO,EAAwB,KAAK,mBAAmB,EAAE,KAAK,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3F,OAAO,EAAmB,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAE9D,wCAAwC;AACxC,MAAM,WAAW,kBAAkB;IACjC,4DAA4D;IAC5D,UAAU,EAAE,MAAM,CAAC;IACnB,8CAA8C;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,kDAAkD;IAClD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,sDAAsD;IACtD,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0CAA0C;IAC1C,UAAU,CAAC,EAAE,SAAS,EAAE,CAAC;CAC1B;AAED,mDAAmD;AACnD,MAAM,WAAW,eAAe;IAC9B,8DAA8D;IAC9D,UAAU,EAAE,MAAM,CAAC;IACnB,wDAAwD;IACxD,SAAS,EAAE,MAAM,CAAC;IAClB,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,4CAA4C;AAC5C,MAAM,WAAW,gBAAiB,SAAQ,UAAU;IAClD,MAAM,EAAE,eAAe,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,WAAW,EAAE,mBAAmB,EAAE,CAAC;CACpC;AAID;;GAEG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAM;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAc;IACzC,iEAAiE;IACjE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6B;IACrD,OAAO,CAAC,QAAQ,CAAM;IACtB,gDAAgD;IAChD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B;;;OAGG;IACH,YAAY,OAAO,EAAE,kBAAkB,EAYtC;IAED;;;OAGG;IACH,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED;;;OAGG;IACH,OAAO,CAAC,OAAO;IAIf;;;;;;;;OAQG;IACH,OAAO,CAAC,UAAU;IASlB;;;;;OAKG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,gBAAgB,CA0C7D;IAED,6CAA6C;IAC7C,OAAO,IAAI,IAAI,CAMd;CACF"}
package/lib/session.js ADDED
@@ -0,0 +1,141 @@
1
+ /**
2
+ * A warm analysis session for the content mapper.
3
+ *
4
+ * This is the part `transform` cannot do on its own. The protocol hands the
5
+ * mapper one file's text and nothing else — no `Program`, no `Checker` — but
6
+ * Webda's dominant coercions (`ManyToOne<T>` -> `ModelLink`, `OneToMany<T>` ->
7
+ * `ModelRelated`, `@WebdaAutoSetter` set-methods) all need type resolution. So
8
+ * the mapper runs *its own* TypeScript program over the original sources and
9
+ * keeps it warm across requests: pass 1 of the two-pass design, resident.
10
+ *
11
+ * Two details make it work:
12
+ *
13
+ * - **The live buffer is served through `fs.readFile`.** The editor's unsaved
14
+ * text is put in an overlay, so the resident program analyses exactly what the
15
+ * user sees rather than what is on disk.
16
+ * - **`.model.ts` degrades to plain TypeScript.** The mapper opens the project's
17
+ * own tsconfig without `runExternalCode`, so content mappers are inert there
18
+ * and no recursion is possible; the files are simply read as authored.
19
+ */
20
+ import { API } from "typescript/unstable/sync";
21
+ import { createFileSystemLayer } from "typescript/unstable/fs";
22
+ import { baseNames, textOf, triviaOf } from "./context.js";
23
+ import { defaultGenerators } from "./defaults.js";
24
+ import { mergePlan } from "./plan.js";
25
+ import { buildMappedText } from "./spans.js";
26
+ const ms = (from) => Number(process.hrtime.bigint() - from) / 1e6;
27
+ /**
28
+ * A resident `Program` + `Checker` that answers `transform` requests.
29
+ */
30
+ export class WarmSession {
31
+ api;
32
+ options;
33
+ generators;
34
+ /** Live buffer text per file, served to the resident program. */
35
+ overlay = new Map();
36
+ snapshot;
37
+ /** Cost of the initial program construction. */
38
+ startupMs;
39
+ /**
40
+ * Open the resident program for a project.
41
+ * @param options - how to open it
42
+ */
43
+ constructor(options) {
44
+ this.options = options;
45
+ this.generators = options.generators ?? defaultGenerators(options);
46
+ const t0 = process.hrtime.bigint();
47
+ this.api = new API({ cwd: options.cwd });
48
+ this.snapshot = this.api.createSnapshot({ openProjects: [options.configFile] });
49
+ if (!this.snapshot.getProjects()[0]) {
50
+ this.api.close();
51
+ throw new Error(`No project loaded for ${options.configFile}`);
52
+ }
53
+ this.startupMs = ms(t0);
54
+ }
55
+ /**
56
+ * Files in the resident program, for benchmarking.
57
+ * @returns the source file count
58
+ */
59
+ get fileCount() {
60
+ return this.project().program.getSourceFileNames().length;
61
+ }
62
+ /**
63
+ * The single project held by the current snapshot.
64
+ * @returns the project
65
+ */
66
+ project() {
67
+ return this.snapshot.getProjects()[0];
68
+ }
69
+ /**
70
+ * Build an analysis context restricted to a single file.
71
+ *
72
+ * Restricting `sourceFiles` is what makes re-analysis incremental: the
73
+ * generators only walk the file that changed, while the checker still resolves
74
+ * types across the whole program.
75
+ * @param fileName - the file to analyse
76
+ * @returns a context the generators accept
77
+ */
78
+ contextFor(fileName) {
79
+ const project = this.project();
80
+ const program = project.program;
81
+ const checker = project.checker;
82
+ const sf = program.getSourceFile(fileName);
83
+ const sourceFiles = sf ? [sf] : [];
84
+ return { project, program, checker, sourceFiles, textOf, triviaOf, baseNames };
85
+ }
86
+ /**
87
+ * Transform one file, refreshing the resident program only if its text moved.
88
+ * @param fileName - absolute path of the file being transformed
89
+ * @param content - the editor's current buffer text
90
+ * @returns generated text, span map and timings
91
+ */
92
+ transform(fileName, content) {
93
+ const t0 = process.hrtime.bigint();
94
+ const changed = this.overlay.get(fileName) !== content;
95
+ if (changed) {
96
+ this.overlay.set(fileName, content);
97
+ const previous = this.snapshot;
98
+ // Two things are load-bearing here, and both were found the hard way:
99
+ //
100
+ // - a filesystem *layer* is consulted ahead of the host filesystem, which
101
+ // is how unsaved buffer text reaches the resident program. The
102
+ // spawn-time `APIOptions.fs` callback is not enough, because source
103
+ // files are cached and a later edit to the same path is never re-read.
104
+ // - `ensurePrograms` is required. Programs update lazily, so without it
105
+ // the new snapshot still hands back the previous program and every
106
+ // transform silently analyses stale text.
107
+ this.snapshot = this.snapshot.update({
108
+ fileSystem: createFileSystemLayer([...this.overlay]),
109
+ ensurePrograms: true
110
+ });
111
+ previous.dispose();
112
+ }
113
+ const snapshotMs = ms(t0);
114
+ const t1 = process.hrtime.bigint();
115
+ const ctx = this.contextFor(fileName);
116
+ const produced = ctx.sourceFiles.length ? this.generators.flatMap(g => g.analyze(ctx)) : [];
117
+ const { plan } = mergePlan(produced);
118
+ const edits = plan.get(fileName) ?? [];
119
+ const diagnostics = produced.filter(f => f.fileName === fileName).flatMap(f => f.diagnostics ?? []);
120
+ const analyzeMs = ms(t1);
121
+ const t2 = process.hrtime.bigint();
122
+ const mapped = buildMappedText(content, edits);
123
+ const spliceMs = ms(t2);
124
+ return {
125
+ ...mapped,
126
+ diagnostics,
127
+ editCount: edits.length,
128
+ timing: { snapshotMs, analyzeMs, spliceMs, totalMs: ms(t0), changed }
129
+ };
130
+ }
131
+ /** Shut down the resident server process. */
132
+ dispose() {
133
+ try {
134
+ this.snapshot?.dispose();
135
+ }
136
+ finally {
137
+ this.api.close();
138
+ }
139
+ }
140
+ }
141
+ //# sourceMappingURL=session.js.map