@pattern-stack/codegen 0.22.0 → 0.23.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 (48) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/{chunk-NR7QQ6ZI.js → chunk-42763UEE.js} +2 -2
  3. package/dist/{chunk-6ECCJVYW.js → chunk-4M66MQYA.js} +44 -2
  4. package/dist/chunk-4M66MQYA.js.map +1 -0
  5. package/dist/{chunk-FNHNSFIJ.js → chunk-6XP2Q5SS.js} +2 -2
  6. package/dist/{chunk-VDL5CJ5C.js → chunk-7B7MMDOJ.js} +54 -1
  7. package/dist/chunk-7B7MMDOJ.js.map +1 -0
  8. package/dist/{chunk-NXHL5YII.js → chunk-7LKAMLV4.js} +4 -4
  9. package/dist/{chunk-6DQEIXYU.js → chunk-FIUC6QB5.js} +1 -1
  10. package/dist/chunk-FIUC6QB5.js.map +1 -0
  11. package/dist/{chunk-DB5UXJC3.js → chunk-PNCOUFFI.js} +4 -2
  12. package/dist/chunk-PNCOUFFI.js.map +1 -0
  13. package/dist/{chunk-QXVCRA23.js → chunk-SH76CFAY.js} +9 -4
  14. package/dist/chunk-SH76CFAY.js.map +1 -0
  15. package/dist/runtime/base-classes/index.js +17 -17
  16. package/dist/runtime/subsystems/auth/auth.module.js +3 -3
  17. package/dist/runtime/subsystems/auth/index.js +7 -7
  18. package/dist/runtime/subsystems/bridge/bridge.module.js +4 -4
  19. package/dist/runtime/subsystems/bridge/index.js +4 -4
  20. package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js +1 -1
  21. package/dist/runtime/subsystems/events/events.module.js +2 -2
  22. package/dist/runtime/subsystems/events/index.js +2 -2
  23. package/dist/runtime/subsystems/index.js +12 -12
  24. package/dist/runtime/subsystems/jobs/index.js +3 -3
  25. package/dist/runtime/subsystems/jobs/job-worker.d.ts +592 -4
  26. package/dist/runtime/subsystems/jobs/job-worker.js +3 -1
  27. package/dist/runtime/subsystems/jobs/job-worker.module.js +3 -3
  28. package/dist/runtime/subsystems/jobs/jobs-domain.module.d.ts +19 -0
  29. package/dist/runtime/subsystems/jobs/jobs-domain.module.js +1 -1
  30. package/dist/src/cli/index.js +211 -60
  31. package/dist/src/cli/index.js.map +1 -1
  32. package/dist/src/index.d.ts +477 -1
  33. package/dist/src/index.js +1 -1
  34. package/package.json +1 -1
  35. package/runtime/subsystems/events/event-bus.drizzle-backend.ts +23 -7
  36. package/runtime/subsystems/jobs/job-worker.module.ts +5 -0
  37. package/runtime/subsystems/jobs/job-worker.ts +126 -12
  38. package/runtime/subsystems/jobs/jobs-domain.module.ts +19 -0
  39. package/templates/entity/new/clean-lite-ps/prompt-extension.js +59 -10
  40. package/templates/subsystem/jobs-config/codegen-config-jobs-block.ejs.t +11 -0
  41. package/dist/chunk-6DQEIXYU.js.map +0 -1
  42. package/dist/chunk-6ECCJVYW.js.map +0 -1
  43. package/dist/chunk-DB5UXJC3.js.map +0 -1
  44. package/dist/chunk-QXVCRA23.js.map +0 -1
  45. package/dist/chunk-VDL5CJ5C.js.map +0 -1
  46. /package/dist/{chunk-NR7QQ6ZI.js.map → chunk-42763UEE.js.map} +0 -0
  47. /package/dist/{chunk-FNHNSFIJ.js.map → chunk-6XP2Q5SS.js.map} +0 -0
  48. /package/dist/{chunk-NXHL5YII.js.map → chunk-7LKAMLV4.js.map} +0 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,39 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ### Fixed
8
+
9
+ - **Jobs: claim heartbeat (CLAIM-HB-1) — long-running handlers are no longer
10
+ swept mid-flight.** The drizzle `JobWorker` stamped `claimed_at` once at claim
11
+ and never renewed it, while `sweepStaleClaims` reset any `running` row whose
12
+ `claimed_at` aged past `staleThresholdMs` (default 5 min) back to `pending`.
13
+ Consequence: ANY handler that legitimately ran longer than the threshold was
14
+ silently re-queued and re-claimed by a second worker, running CONCURRENTLY
15
+ with the still-live (uncancellable) original. Discovered by the swe-brain
16
+ dogfood: a 365-day Gmail backfill could never finish inside 5 min, so it
17
+ re-spawned a fresh concurrent mailbox walk every ~6 min for 5 days (writes
18
+ were idempotent upserts, so no corruption — but a non-idempotent handler would
19
+ have corrupted). Fix: a live worker now tracks its in-flight run IDs and bumps
20
+ `claimed_at = now()` for them every `claimHeartbeatIntervalMs` (new
21
+ `JobWorkerOptions` knob, default `staleThresholdMs / 3`). The sweeper now
22
+ fires only for genuinely dead workers (renewal stopped) — its documented
23
+ "stranded by a crashed worker" intent.
24
+
25
+ ### Added
26
+
27
+ - **Jobs: consumer-threadable lease tuning.** `jobs.extensions.drizzle` now
28
+ accepts `stale_threshold_ms`, `stale_sweeper_interval_ms`, and
29
+ `claim_heartbeat_interval_ms`, threaded through the subsystem barrel generator
30
+ into both `JobsDomainModule.forRoot` and `JobWorkerModule.forRoot` (camelCase
31
+ runtime keys). All optional; the worker defaults the heartbeat to a third of
32
+ the stale threshold.
33
+
34
+ > **Deferred (CLAIM-HB-1 follow-up):** fencing — a claim token on `job_run` so a
35
+ > swept-and-reclaimed run cannot be double-completed by a zombie attempt that
36
+ > finishes after the sweep. Needs a schema/migration change + write-site guards;
37
+ > tracked as issue #501. The heartbeat closes the practical
38
+ > re-claim-loop bug; fencing hardens the residual crash-recovery race.
39
+
7
40
  ## [0.21.0] — 2026-06-06
8
41
 
9
42
  **FieldMeta enrichment (ADR-040, Phase A of type-aware rendering).** The
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-DUUCU77W.js";
8
8
  import {
9
9
  DrizzleEventBus
10
- } from "./chunk-DB5UXJC3.js";
10
+ } from "./chunk-PNCOUFFI.js";
11
11
  import {
12
12
  MemoryEventBus
13
13
  } from "./chunk-GOO5ZMYO.js";
@@ -200,4 +200,4 @@ export {
200
200
  EventSchedulerLifecycle,
201
201
  EventsModule
202
202
  };
203
- //# sourceMappingURL=chunk-NR7QQ6ZI.js.map
203
+ //# sourceMappingURL=chunk-42763UEE.js.map
@@ -396,9 +396,15 @@ var ProviderIntegrationSchema = z.object({
396
396
  field_mapping: z.record(z.string(), z.string()).optional(),
397
397
  read_only_fields: z.array(z.string()).optional()
398
398
  });
399
+ var SinkPolicySchema = z.object({
400
+ delete: z.enum(["soft", "tombstone", "noop"]).optional(),
401
+ // NO .default() — see default fence in spec §Goal
402
+ exclude_fields: z.array(z.string()).optional()
403
+ }).strict();
399
404
  var IntegrationConfigSchema = z.object({
400
405
  electric: z.boolean().optional().default(false),
401
- providers: z.record(z.string(), ProviderIntegrationSchema).optional()
406
+ providers: z.record(z.string(), ProviderIntegrationSchema).optional(),
407
+ sink: SinkPolicySchema.optional()
402
408
  });
403
409
  var EventDeclarationSchema = z.object({
404
410
  name: z.string().regex(/^[a-z][a-z0-9_]*$/, "Event name must be snake_case"),
@@ -567,6 +573,42 @@ var EntityDefinitionSchema = z.object({
567
573
  });
568
574
  }
569
575
  }
576
+ }).superRefine((entity, ctx) => {
577
+ const excludeFields = entity.integration?.sink?.exclude_fields;
578
+ if (!excludeFields || excludeFields.length === 0) return;
579
+ const declaredFields = new Set(Object.keys(entity.fields ?? {}));
580
+ const fkColumns = /* @__PURE__ */ new Set();
581
+ for (const rel of Object.values(entity.relationships ?? {})) {
582
+ if (rel.type === "belongs_to" && typeof rel.foreign_key === "string") {
583
+ fkColumns.add(rel.foreign_key);
584
+ }
585
+ }
586
+ for (let i = 0; i < excludeFields.length; i++) {
587
+ const name = excludeFields[i];
588
+ if (!declaredFields.has(name)) {
589
+ ctx.addIssue({
590
+ code: "custom",
591
+ path: ["integration", "sink", "exclude_fields", i],
592
+ message: `exclude_fields: '${name}' is not a declared field. Declared fields: ${[...declaredFields].join(", ")}`
593
+ });
594
+ continue;
595
+ }
596
+ if (fkColumns.has(name)) {
597
+ ctx.addIssue({
598
+ code: "custom",
599
+ path: ["integration", "sink", "exclude_fields", i],
600
+ message: `exclude_fields: '${name}' is a FK column (belongs_to foreign_key). Excluding FK columns corrupts the FK-resolver path \u2014 exclude FK columns is not supported. Declare it in exclude_fields only for copy-through scalars.`
601
+ });
602
+ continue;
603
+ }
604
+ if (name === "user_id") {
605
+ ctx.addIssue({
606
+ code: "custom",
607
+ path: ["integration", "sink", "exclude_fields", i],
608
+ message: `exclude_fields: 'user_id' cannot be excluded. It is used for user-scoping and EAV dual-write; excluding it would break those mechanisms.`
609
+ });
610
+ }
611
+ }
570
612
  });
571
613
 
572
614
  // src/schema/event-definition.schema.ts
@@ -4305,4 +4347,4 @@ export {
4305
4347
  analyzeDomain,
4306
4348
  validateEntities
4307
4349
  };
4308
- //# sourceMappingURL=chunk-6ECCJVYW.js.map
4350
+ //# sourceMappingURL=chunk-4M66MQYA.js.map