@cosmicdrift/kumiko-framework 0.335.0 → 0.336.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.
Files changed (42) hide show
  1. package/dist/api/anonymous-live-entities.d.ts +2 -0
  2. package/dist/api/anonymous-live-entities.js +17 -0
  3. package/dist/api/index.d.ts +2 -1
  4. package/dist/api/index.js +1 -0
  5. package/dist/api/server.js +6 -3
  6. package/dist/api/sse-route.d.ts +4 -1
  7. package/dist/api/sse-route.js +38 -11
  8. package/dist/db/blind-index-cleanup.js +27 -16
  9. package/dist/db/event-store-executor-read.js +6 -2
  10. package/dist/engine/boot-validator/query-output-columns.js +12 -0
  11. package/dist/engine/boot-validator/screens.js +22 -13
  12. package/dist/engine/build-app-schema.js +3 -1
  13. package/dist/engine/create-app.js +10 -1
  14. package/dist/engine/extension-names.d.ts +4 -0
  15. package/dist/engine/extension-names.js +4 -0
  16. package/dist/engine/feature-ast/extractors/handlers.js +1 -0
  17. package/dist/engine/feature-entity-handlers.d.ts +1 -0
  18. package/dist/engine/feature-entity-handlers.js +2 -0
  19. package/dist/engine/registry-validate.d.ts +1 -0
  20. package/dist/engine/registry-validate.js +14 -0
  21. package/dist/engine/registry.js +2 -1
  22. package/dist/engine/steps/_event-match.js +12 -4
  23. package/dist/engine/system-user.d.ts +1 -0
  24. package/dist/engine/system-user.js +3 -0
  25. package/dist/env/dry-run.js +10 -3
  26. package/dist/event-store/event-store.js +4 -1
  27. package/dist/jobs/job-runner.js +29 -7
  28. package/dist/observability/index.d.ts +1 -1
  29. package/dist/observability/index.js +1 -1
  30. package/dist/observability/standard-metrics.d.ts +4 -0
  31. package/dist/observability/standard-metrics.js +16 -0
  32. package/dist/pipeline/event-dispatcher-delivery.js +5 -0
  33. package/dist/pipeline/event-dispatcher.js +4 -1
  34. package/dist/pipeline/system-hooks.js +10 -13
  35. package/dist/pipeline/write-origin.js +13 -6
  36. package/dist/stack/drain-event-consumers.js +3 -0
  37. package/dist/stack/drain-jobs.d.ts +3 -1
  38. package/dist/stack/drain-jobs.js +31 -1
  39. package/package.json +4 -4
  40. package/src/changes.json +58 -0
  41. package/src/scripts/codemod/pii-personal-migration.ts +56 -26
  42. package/src/scripts/codemod/unprocessable-error-details-reason.ts +61 -24
@@ -105,9 +105,25 @@ function resolveDetailsProperty(optsLiteral: ObjectLiteralExpression): DetailsRe
105
105
  /** True if removing this shorthand-bound identifier would leave its declaration with no other use. */
106
106
  function wouldLeaveUnusedVariable(nameNode: Identifier): boolean {
107
107
  try {
108
- const refs = nameNode.findReferencesAsNodes();
109
- const others = refs.filter((r) => r !== nameNode);
110
- return others.length === 0;
108
+ // The shorthand name resolves to the object-literal property symbol too, and
109
+ // a declaration counts as a reference, so look up the variable's own uses.
110
+ const shorthand = nameNode.getParent();
111
+ const variableSymbol = Node.isShorthandPropertyAssignment(shorthand)
112
+ ? shorthand.getValueSymbol()
113
+ : undefined;
114
+ const declarations = variableSymbol?.getDeclarations() ?? [];
115
+ const declarationNames = declarations.flatMap((declaration) =>
116
+ Node.isVariableDeclaration(declaration) || Node.isParameterDeclaration(declaration)
117
+ ? [declaration.getNameNode()]
118
+ : [],
119
+ );
120
+ // No resolvable declaration: can't verify, treat as unsafe.
121
+ if (declarationNames.length === 0) return true;
122
+ return declarationNames
123
+ .flatMap((declarationName) =>
124
+ Node.isIdentifier(declarationName) ? declarationName.findReferencesAsNodes() : [],
125
+ )
126
+ .every((usage) => usage === nameNode);
111
127
  } catch {
112
128
  // Can't verify — treat as unsafe rather than guess.
113
129
  return true;
@@ -122,12 +138,11 @@ type RemovalResult =
122
138
  /** Removes the `reason` property from a `details` object literal, in place. */
123
139
  function removeReasonProperty(detailsLiteral: ObjectLiteralExpression): RemovalResult {
124
140
  const properties = detailsLiteral.getProperties();
125
- if (properties.some((p) => Node.isSpreadAssignment(p))) {
126
- return {
127
- kind: "skip",
128
- note: "details object literal contains a spread — not statically decidable",
129
- };
130
- }
141
+ const hasSpread = properties.some((p) => Node.isSpreadAssignment(p));
142
+ const spreadSkip: RemovalResult = {
143
+ kind: "skip",
144
+ note: "details object literal has a spread next to an explicit reason — not statically decidable",
145
+ };
131
146
 
132
147
  for (const prop of properties) {
133
148
  if (Node.isPropertyAssignment(prop)) {
@@ -145,12 +160,14 @@ function removeReasonProperty(detailsLiteral: ObjectLiteralExpression): RemovalR
145
160
  ? nameNode.getLiteralText()
146
161
  : undefined;
147
162
  if (propName === "reason") {
163
+ if (hasSpread) return spreadSkip;
148
164
  prop.remove();
149
165
  return { kind: "removed" };
150
166
  }
151
167
  continue;
152
168
  }
153
169
  if (Node.isShorthandPropertyAssignment(prop) && prop.getName() === "reason") {
170
+ if (hasSpread) return spreadSkip;
154
171
  if (wouldLeaveUnusedVariable(prop.getNameNode())) {
155
172
  return {
156
173
  kind: "skip",
@@ -166,13 +183,20 @@ function removeReasonProperty(detailsLiteral: ObjectLiteralExpression): RemovalR
166
183
 
167
184
  type CallOutcome = "changed" | "skipped" | "not-a-match";
168
185
 
169
- function processNewExpression(args: readonly Node[], report: (note: string) => void): CallOutcome {
186
+ // "manual": a concrete `reason` the codemod found but cannot remove safely.
187
+ // "unverified": opts/details not statically analyzable — may well be fine, so
188
+ // it is listed apart to keep the actionable list short.
189
+ type SkipKind = "manual" | "unverified";
190
+ type ReportSkip = (note: string, kind: SkipKind) => void;
191
+
192
+ function processNewExpression(args: readonly Node[], report: ReportSkip): CallOutcome {
170
193
  const optsArg = args[1];
171
194
  if (!optsArg) return "not-a-match";
172
195
 
173
196
  if (!Node.isObjectLiteralExpression(optsArg)) {
174
197
  report(
175
198
  "second argument (opts) is not a statically-analyzable object literal — cannot verify details.reason",
199
+ "unverified",
176
200
  );
177
201
  return "skipped";
178
202
  }
@@ -180,14 +204,14 @@ function processNewExpression(args: readonly Node[], report: (note: string) => v
180
204
  const detailsRes = resolveDetailsProperty(optsArg);
181
205
  if (detailsRes.kind === "absent") return "not-a-match";
182
206
  if (detailsRes.kind === "unresolvable") {
183
- report(detailsRes.note);
207
+ report(detailsRes.note, "unverified");
184
208
  return "skipped";
185
209
  }
186
210
 
187
211
  const removal = removeReasonProperty(detailsRes.literal);
188
212
  if (removal.kind === "absent") return "not-a-match";
189
213
  if (removal.kind === "skip") {
190
- report(removal.note);
214
+ report(removal.note, "manual");
191
215
  return "skipped";
192
216
  }
193
217
  return "changed";
@@ -195,16 +219,14 @@ function processNewExpression(args: readonly Node[], report: (note: string) => v
195
219
 
196
220
  // `failUnprocessable(reason, details)` takes the details literal directly as
197
221
  // its second argument (no `{ details }` wrapper like the ctor's opts).
198
- function processFailUnprocessableCall(
199
- args: readonly Node[],
200
- report: (note: string) => void,
201
- ): CallOutcome {
222
+ function processFailUnprocessableCall(args: readonly Node[], report: ReportSkip): CallOutcome {
202
223
  const detailsArg = args[1];
203
224
  if (!detailsArg) return "not-a-match";
204
225
 
205
226
  if (!Node.isObjectLiteralExpression(detailsArg)) {
206
227
  report(
207
228
  "second argument (details) is not a statically-analyzable object literal — cannot verify details.reason",
229
+ "unverified",
208
230
  );
209
231
  return "skipped";
210
232
  }
@@ -212,18 +234,23 @@ function processFailUnprocessableCall(
212
234
  const removal = removeReasonProperty(detailsArg);
213
235
  if (removal.kind === "absent") return "not-a-match";
214
236
  if (removal.kind === "skip") {
215
- report(removal.note);
237
+ report(removal.note, "manual");
216
238
  return "skipped";
217
239
  }
218
240
  return "changed";
219
241
  }
220
242
 
221
- type SkipEntry = { readonly file: string; readonly line: number; readonly note: string };
243
+ type SkipEntry = {
244
+ readonly file: string;
245
+ readonly line: number;
246
+ readonly note: string;
247
+ readonly kind: SkipKind;
248
+ };
222
249
 
223
250
  type MigratableCall = {
224
251
  readonly exportName: "UnprocessableError" | "failUnprocessable";
225
252
  readonly localKind: "class" | "function";
226
- readonly process: (args: readonly Node[], report: (note: string) => void) => CallOutcome;
253
+ readonly process: (args: readonly Node[], report: ReportSkip) => CallOutcome;
227
254
  };
228
255
 
229
256
  const UNPROCESSABLE_ERROR_CTOR: MigratableCall = {
@@ -256,7 +283,7 @@ function migrateCallSite(
256
283
  const note = binding
257
284
  ? `${target.exportName} is imported from "${binding.moduleSpecifier}", not a @cosmicdrift/kumiko-* package`
258
285
  : `${target.exportName} has no @cosmicdrift/kumiko-* import in this file (likely a locally defined ${target.localKind})`;
259
- skips.push({ file, line, note });
286
+ skips.push({ file, line, note, kind: "manual" });
260
287
  return false;
261
288
  }
262
289
  if (!KUMIKO_MODULE_RE.test(binding.moduleSpecifier)) {
@@ -264,11 +291,14 @@ function migrateCallSite(
264
291
  file,
265
292
  line,
266
293
  note: `${target.exportName} is imported from "${binding.moduleSpecifier}", not a @cosmicdrift/kumiko-* package`,
294
+ kind: "manual",
267
295
  });
268
296
  return false;
269
297
  }
270
298
 
271
- const outcome = target.process(site.getArguments(), (note) => skips.push({ file, line, note }));
299
+ const outcome = target.process(site.getArguments(), (note, kind) =>
300
+ skips.push({ file, line, note, kind }),
301
+ );
272
302
  return outcome === "changed";
273
303
  }
274
304
 
@@ -320,9 +350,16 @@ async function main(): Promise<void> {
320
350
  console.log(
321
351
  `Touched ${touchedFiles} files, removed ${removedTotal} redundant "reason" propert(y/ies).`,
322
352
  );
323
- console.log(`Skipped ${skips.length} site(s):`);
324
- for (const s of skips) {
325
- console.log(` ${relative(rootDir, s.file)}:${s.line} — ${s.note}`);
353
+ for (const kind of ["manual", "unverified"] as const) {
354
+ const ofKind = skips.filter((s) => s.kind === kind);
355
+ console.log(
356
+ kind === "manual"
357
+ ? `Skipped ${ofKind.length} site(s) needing manual work:`
358
+ : `Unverified ${ofKind.length} site(s) (opts/details not statically analyzable, may be fine):`,
359
+ );
360
+ for (const s of ofKind) {
361
+ console.log(` ${relative(rootDir, s.file)}:${s.line} — ${s.note}`);
362
+ }
326
363
  }
327
364
  console.log();
328
365
  }