@luckystack/devkit 0.6.0 → 0.6.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/CHANGELOG.md +26 -0
- package/dist/index.js +81 -6
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
Codegen fixes surfaced by a MikroORM/MongoDB consumer (verified against a real
|
|
13
|
+
MikroORM project + `tsc`); consumers can drop the corresponding
|
|
14
|
+
`node_modules/@luckystack/devkit/dist` patches once on this version.
|
|
15
|
+
|
|
16
|
+
- **DEVKIT-1** — a route returning a MikroORM entity no longer corrupts
|
|
17
|
+
`apiTypes.generated.ts`. Symbol-keyed entity members (`[OptionalProps]`,
|
|
18
|
+
`[loadedType]`, `[selectedType]`) serialize as the invalid identifier
|
|
19
|
+
`__@<name>@<id>`; they are now dropped from the inlined type (skipped in
|
|
20
|
+
`expandTypeDetailed`, plus a brace-aware content-level safety net for the
|
|
21
|
+
cycle/depth `typeToString` fallback paths).
|
|
22
|
+
- **DEVKIT-2** — `SessionLayout` used to sit on an internal whitelist that kept
|
|
23
|
+
the identifier without ever emitting its import, tripping the unresolved-type
|
|
24
|
+
validator. It now runs through normal import collection, like `AuthProps`.
|
|
25
|
+
- **DEVKIT-3** — the per-route error arm lists its real fields explicitly
|
|
26
|
+
(`message?`, `errorParams?`, `httpStatus?`) alongside the `[key: string]:
|
|
27
|
+
unknown` index signature, so `res.errorParams` narrows to its type after a
|
|
28
|
+
status check instead of collapsing to `unknown`.
|
|
29
|
+
- **DEVKIT-4** — a route that hands its stream emitter to a helper (no literal
|
|
30
|
+
`stream(...)` call in `main`) now infers the payload type from the declared
|
|
31
|
+
`stream: ApiStreamEmitter<T>` on `ApiParams` instead of degrading to `never`.
|
|
32
|
+
- **DEVKIT-5** — private helper subtrees under a marker (`_api/_lib/*`,
|
|
33
|
+
`_sync/_lib/__tests__/*`) are skipped by route-naming validation + discovery
|
|
34
|
+
instead of being flagged as invalid route files.
|
|
35
|
+
|
|
10
36
|
## [0.1.0]
|
|
11
37
|
|
|
12
38
|
### Added
|
package/dist/index.js
CHANGED
|
@@ -552,6 +552,38 @@ var writeFileIfChanged = (filePath, content) => {
|
|
|
552
552
|
var indentStr = (str, indentText) => {
|
|
553
553
|
return str.split("\n").map((line, i) => i === 0 ? line : indentText + line).join("\n");
|
|
554
554
|
};
|
|
555
|
+
var stripSymbolKeyedMembers = (text) => {
|
|
556
|
+
if (!text.includes("__@")) return text;
|
|
557
|
+
let result = text;
|
|
558
|
+
for (; ; ) {
|
|
559
|
+
const match = /__@\w+@\d+\??\s*:/.exec(result);
|
|
560
|
+
if (!match) break;
|
|
561
|
+
const symStart = match.index;
|
|
562
|
+
const lineStart = result.lastIndexOf("\n", symStart) + 1;
|
|
563
|
+
let i = symStart + match[0].length;
|
|
564
|
+
let depth = 0;
|
|
565
|
+
let end = result.length;
|
|
566
|
+
while (i < result.length) {
|
|
567
|
+
const ch = result[i];
|
|
568
|
+
if (ch === "{" || ch === "[" || ch === "(") {
|
|
569
|
+
depth++;
|
|
570
|
+
} else if (ch === "}" || ch === "]" || ch === ")") {
|
|
571
|
+
if (depth === 0) {
|
|
572
|
+
end = i;
|
|
573
|
+
break;
|
|
574
|
+
}
|
|
575
|
+
depth--;
|
|
576
|
+
} else if (ch === ";" && depth === 0) {
|
|
577
|
+
end = i + 1;
|
|
578
|
+
break;
|
|
579
|
+
}
|
|
580
|
+
i++;
|
|
581
|
+
}
|
|
582
|
+
if (result[end] === "\n") end += 1;
|
|
583
|
+
result = result.slice(0, lineStart) + result.slice(end);
|
|
584
|
+
}
|
|
585
|
+
return result;
|
|
586
|
+
};
|
|
555
587
|
var validateGeneratedTypeIdentifiers = (content) => {
|
|
556
588
|
const sourceFile = ts3.createSourceFile("apiTypes.generated.ts", content, ts3.ScriptTarget.Latest, true, ts3.ScriptKind.TS);
|
|
557
589
|
const knownSymbols = /* @__PURE__ */ new Set();
|
|
@@ -781,7 +813,7 @@ type _ProjectApiTypeMap = {
|
|
|
781
813
|
`;
|
|
782
814
|
content += ` input: ${indentStr(entry.input, " ")};
|
|
783
815
|
`;
|
|
784
|
-
content += ` output: ${indentStr(entry.output, " ")} | { status: 'error'; errorCode: string; [key: string]: unknown };
|
|
816
|
+
content += ` output: ${indentStr(entry.output, " ")} | { status: 'error'; errorCode: string; message?: string; errorParams?: { key: string; value: string | number | boolean }[]; httpStatus?: number; [key: string]: unknown };
|
|
785
817
|
`;
|
|
786
818
|
content += ` stream: ${indentStr(entry.stream, " ")};
|
|
787
819
|
`;
|
|
@@ -936,9 +968,9 @@ type _ProjectSyncTypeMap = {
|
|
|
936
968
|
`;
|
|
937
969
|
content += ` clientInput: ${indentStr(entry.clientInput, " ")};
|
|
938
970
|
`;
|
|
939
|
-
content += ` serverOutput: ${indentStr(entry.serverOutput, " ")} | { status: 'error'; errorCode: string; [key: string]: unknown };
|
|
971
|
+
content += ` serverOutput: ${indentStr(entry.serverOutput, " ")} | { status: 'error'; errorCode: string; message?: string; errorParams?: { key: string; value: string | number | boolean }[]; [key: string]: unknown };
|
|
940
972
|
`;
|
|
941
|
-
content += ` clientOutput: ${indentStr(entry.clientOutput, " ")} | { status: 'error'; errorCode: string; [key: string]: unknown };
|
|
973
|
+
content += ` clientOutput: ${indentStr(entry.clientOutput, " ")} | { status: 'error'; errorCode: string; message?: string; errorParams?: { key: string; value: string | number | boolean }[]; [key: string]: unknown };
|
|
942
974
|
`;
|
|
943
975
|
content += ` serverStream: ${indentStr(entry.serverStream, " ")};
|
|
944
976
|
`;
|
|
@@ -980,6 +1012,7 @@ declare module '@luckystack/core/typemap' {
|
|
|
980
1012
|
interface SyncTypeMap extends _ProjectSyncTypeMap {}
|
|
981
1013
|
}
|
|
982
1014
|
`;
|
|
1015
|
+
content = stripSymbolKeyedMembers(content);
|
|
983
1016
|
validateGeneratedTypeIdentifiers(content);
|
|
984
1017
|
const schemasContent = buildSchemasContent({ typesByPage });
|
|
985
1018
|
const fallbacks = collectFallbacks(typesByPage, syncTypesByPage);
|
|
@@ -1309,6 +1342,7 @@ var expandTypeDetailed = (type, checker, depth = 0, state) => {
|
|
|
1309
1342
|
const fields = [];
|
|
1310
1343
|
let unresolvedSymbols = [];
|
|
1311
1344
|
for (const prop of props) {
|
|
1345
|
+
if (prop.getName().startsWith("__@")) continue;
|
|
1312
1346
|
const propType = checker.getTypeOfSymbol(prop);
|
|
1313
1347
|
const literalType = getLiteralTypeFromPropertySymbol(prop, checker, depth + 1);
|
|
1314
1348
|
const isOptional = (prop.flags & ts4.SymbolFlags.Optional) !== 0;
|
|
@@ -1543,6 +1577,37 @@ var getInputTypeDetailsFromFile = (filePath) => {
|
|
|
1543
1577
|
return { text: DEFAULT, unresolvedSymbols: [] };
|
|
1544
1578
|
}
|
|
1545
1579
|
};
|
|
1580
|
+
var STREAM_EMITTER_TYPE_NAMES = /* @__PURE__ */ new Set([
|
|
1581
|
+
"ApiStreamEmitter",
|
|
1582
|
+
"SyncServerStreamEmitter",
|
|
1583
|
+
"SyncClientStreamEmitter",
|
|
1584
|
+
"SyncBroadcastStreamEmitter",
|
|
1585
|
+
"SyncStreamToEmitter"
|
|
1586
|
+
]);
|
|
1587
|
+
var extractDeclaredStreamTypeFromApiParams = (sourceFile, checker) => {
|
|
1588
|
+
let result = null;
|
|
1589
|
+
const visit = (node) => {
|
|
1590
|
+
if (result) return;
|
|
1591
|
+
if (ts5.isInterfaceDeclaration(node) && node.name.text.endsWith("Params")) {
|
|
1592
|
+
for (const member of node.members) {
|
|
1593
|
+
if (ts5.isPropertySignature(member) && ts5.isIdentifier(member.name) && member.name.text === "stream" && member.type && ts5.isTypeReferenceNode(member.type) && ts5.isIdentifier(member.type.typeName) && STREAM_EMITTER_TYPE_NAMES.has(member.type.typeName.text)) {
|
|
1594
|
+
const typeArg = member.type.typeArguments?.[0];
|
|
1595
|
+
if (typeArg) {
|
|
1596
|
+
const argType = checker.getNonNullableType(checker.getTypeFromTypeNode(typeArg));
|
|
1597
|
+
const expanded = expandTypeDetailed(argType, checker);
|
|
1598
|
+
if (expanded.text.trim().length > 0) {
|
|
1599
|
+
result = { text: expanded.text, unresolvedSymbols: expanded.unresolvedSymbols };
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
ts5.forEachChild(node, visit);
|
|
1607
|
+
};
|
|
1608
|
+
visit(sourceFile);
|
|
1609
|
+
return result;
|
|
1610
|
+
};
|
|
1546
1611
|
var getApiStreamPayloadTypeDetailsFromFile = (filePath) => {
|
|
1547
1612
|
const DEFAULT = "never";
|
|
1548
1613
|
try {
|
|
@@ -1553,7 +1618,10 @@ var getApiStreamPayloadTypeDetailsFromFile = (filePath) => {
|
|
|
1553
1618
|
const mainFn = findMainFunction(sourceFile);
|
|
1554
1619
|
if (!mainFn) return { text: DEFAULT, unresolvedSymbols: [] };
|
|
1555
1620
|
const details = collectStreamCallPayloadTypeDetails(mainFn, checker);
|
|
1556
|
-
|
|
1621
|
+
if (details.text !== "") return details;
|
|
1622
|
+
const declared = extractDeclaredStreamTypeFromApiParams(sourceFile, checker);
|
|
1623
|
+
if (declared) return declared;
|
|
1624
|
+
return { text: DEFAULT, unresolvedSymbols: [] };
|
|
1557
1625
|
} catch (error) {
|
|
1558
1626
|
console.error(`[TypeMapGenerator] Error extracting API stream payload type from ${filePath}:`, error);
|
|
1559
1627
|
return { text: DEFAULT, unresolvedSymbols: [] };
|
|
@@ -1732,8 +1800,7 @@ var sanitizeTypeAndCollectImports = ({
|
|
|
1732
1800
|
const { namedImports, defaultImports } = collectors;
|
|
1733
1801
|
return type.replaceAll(/\b([A-Z][a-zA-Z0-9_]*)(<[^>]+>)?(\[\])?\b/g, (match, typeName, _generics, isArray) => {
|
|
1734
1802
|
const builtins = ["Promise", "Date", "Function", "Array", "Record", "Partial", "Pick", "Omit", "Error", "Map", "Set", "Buffer", "Uint8Array", "Object"];
|
|
1735
|
-
|
|
1736
|
-
if (builtins.includes(typeName) || existingImports.includes(typeName) || knownGenerics.has(typeName)) {
|
|
1803
|
+
if (builtins.includes(typeName) || knownGenerics.has(typeName)) {
|
|
1737
1804
|
return match;
|
|
1738
1805
|
}
|
|
1739
1806
|
const importConfig = fileImports.get(typeName);
|
|
@@ -2159,6 +2226,13 @@ var normalizePath = (value) => {
|
|
|
2159
2226
|
return value.replaceAll("\\", "/");
|
|
2160
2227
|
};
|
|
2161
2228
|
var toRel = (absolute) => path8.relative(ROOT_DIR3, absolute).replaceAll("\\", "/");
|
|
2229
|
+
var isInsidePrivateRouteSubfolder = (fullPath) => {
|
|
2230
|
+
const { privateFolderPrefix, apiMarker, syncMarker } = getRoutingRules();
|
|
2231
|
+
const segments = normalizePath(fullPath).split("/");
|
|
2232
|
+
const markerIndex = segments.findIndex((segment) => segment === apiMarker || segment === syncMarker);
|
|
2233
|
+
if (markerIndex === -1) return false;
|
|
2234
|
+
return segments.slice(markerIndex + 1).some((segment) => segment.startsWith(privateFolderPrefix));
|
|
2235
|
+
};
|
|
2162
2236
|
var walkRouteFiles = (dir, results = []) => {
|
|
2163
2237
|
const entries = fs5.readdirSync(dir, { withFileTypes: true });
|
|
2164
2238
|
const apiSeg = apiMarkerSegment();
|
|
@@ -2168,6 +2242,7 @@ var walkRouteFiles = (dir, results = []) => {
|
|
|
2168
2242
|
const fullPath = path8.join(dir, entry.name);
|
|
2169
2243
|
const normalizedFullPath = normalizePath(fullPath);
|
|
2170
2244
|
if (ignore(toRel(fullPath))) continue;
|
|
2245
|
+
if (isInsidePrivateRouteSubfolder(fullPath)) continue;
|
|
2171
2246
|
if (entry.isDirectory()) {
|
|
2172
2247
|
if (entry.name.startsWith(".") || entry.name === "node_modules") {
|
|
2173
2248
|
continue;
|