@luckystack/devkit 0.9.1 → 0.10.0

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 CHANGED
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.9.2] - 2026-09-01
11
+
12
+ ### Fixed
13
+
14
+ - A sync route without the optional `_client` handler is no longer reported as
15
+ degraded type extraction. `apiTypeDiagnostics.generated.json` matched on the
16
+ fallback TEXT alone, and an absent side produces the same `{ }` as a side that
17
+ is present but declares no shape — so every deliberately server-only sync
18
+ landed in `fallbackCount` as a `default-fallback`. In a real project that was
19
+ 44 of 46 reported fallbacks, and the only way to silence them was to add the
20
+ no-op `_client` handlers `ARCHITECTURE_SYNC.md` tells you not to write (they
21
+ cost one execution per recipient). The generator now carries which sides were
22
+ actually found and suppresses only the missing side's own field. A thrown
23
+ extraction is still checked first and always reported, and a side that IS
24
+ present keeps falling back exactly as before.
25
+
10
26
  ## [0.8.7] - 2026-08-18
11
27
 
12
28
  ### Fixed
package/CLAUDE.md CHANGED
@@ -101,6 +101,8 @@ Object literal types → `.strict()` (unknown keys rejected, fail-closed). This
101
101
 
102
102
  Every `generateTypeMapFile()` run emits `apiTypeDiagnostics.generated.json` alongside `apiDocs.generated.json`. It lists every route whose type extraction degraded to a fallback (source file miss, thrown API/sync input/output/**stream** extraction, or `z.any()` in the Zod schema). Shape: `{ generatedAt, totalRoutes, fallbackCount, fallbacks: DiagnosticsEntry[] }`. CI can fail on non-zero `fallbackCount` to enforce type-fidelity. The file path is `<generatedApiDocs dir>/apiTypeDiagnostics.generated.json`.
103
103
 
104
+ One case is deliberately NOT a fallback: a sync side that does not exist on disk. `_client` is optional by contract (`/docs/ARCHITECTURE_SYNC.md` — a pass-through sync must not have one, because a no-op handler costs an execution per recipient), and an absent side yields the same `{ }` text as a present side that declares no shape. The generator therefore carries per-entry presence flags so the emitter can tell the two apart, and suppresses only the field belonging to the missing side. A THROWN extraction is still checked first and always reported, so this can never hide a real failure — do not widen the exception past the missing side's own field.
105
+
104
106
  **D4 — transport-safe route contracts**
105
107
 
106
108
  Output extraction projects `JSON.stringify` semantics recursively: `Date` becomes `string`; `toJSON()` becomes its return type; omitted object values (`undefined`, symbol, every callable value) disappear or make a union-valued property optional; those values become `null` in arrays/tuples. Binary values are rejected because HTTP JSON and Socket.io binary delivery cannot share one truthful output type. Input extraction does not project declarations, but rejects `Date`: JSON can only deliver an ISO string, so route authors must declare `string`, validate it, and convert explicitly before using Date methods.
package/dist/index.js CHANGED
@@ -1030,14 +1030,16 @@ var validateGeneratedTypeIdentifiers = (content) => {
1030
1030
  };
1031
1031
  var collectFallbacks = (typesByPage, syncTypesByPage) => {
1032
1032
  const entries = [];
1033
- const flagField = (route, kind, field, value, checkZod = false) => {
1033
+ const flagField = (route, kind, field, value, checkZod = false, intentionalDefault = false) => {
1034
1034
  const extractionError = findExtractionFailure(route, kind, field);
1035
1035
  if (extractionError !== void 0) {
1036
1036
  entries.push({ route, kind, field, fallback: value, reason: "extraction-error", detail: extractionError });
1037
1037
  return;
1038
1038
  }
1039
1039
  if (value === "{ }" || value === "{ status: string }") {
1040
- entries.push({ route, kind, field, fallback: value, reason: "default-fallback" });
1040
+ if (!intentionalDefault) {
1041
+ entries.push({ route, kind, field, fallback: value, reason: "default-fallback" });
1042
+ }
1041
1043
  return;
1042
1044
  }
1043
1045
  const zodSrc = checkZod ? typeTextToZodSource(value) : null;
@@ -1059,8 +1061,8 @@ var collectFallbacks = (typesByPage, syncTypesByPage) => {
1059
1061
  const { name, version } = splitVersionedKey(syncKey);
1060
1062
  const route = `${pagePath}/${name}@${version}`;
1061
1063
  flagField(route, "sync", "clientInput", entry.clientInput);
1062
- flagField(route, "sync", "serverOutput", entry.serverOutput);
1063
- flagField(route, "sync", "clientOutput", entry.clientOutput);
1064
+ flagField(route, "sync", "serverOutput", entry.serverOutput, false, !entry.hasServer);
1065
+ flagField(route, "sync", "clientOutput", entry.clientOutput, false, !entry.hasClient);
1064
1066
  flagField(route, "sync", "serverStream", entry.serverStream);
1065
1067
  flagField(route, "sync", "clientStream", entry.clientStream);
1066
1068
  }
@@ -2967,6 +2969,8 @@ var collectSyncTypes = (syncServerFiles, syncClientFiles, collectors) => {
2967
2969
  serverStream: serverStreamType,
2968
2970
  clientStream: clientStreamType,
2969
2971
  version: syncVersion,
2972
+ hasServer: serverFile !== void 0,
2973
+ hasClient: clientFile !== void 0,
2970
2974
  ...syncMeta ? { meta: syncMeta } : {}
2971
2975
  });
2972
2976
  }