@pattern-stack/codegen 0.21.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.
- package/CHANGELOG.md +33 -0
- package/README.md +5 -1
- package/dist/{chunk-3A34R6CI.js → chunk-3VEVGL74.js} +4 -4
- package/dist/{chunk-G3IKPDTP.js → chunk-42763UEE.js} +2 -2
- package/dist/{chunk-524YKITE.js → chunk-4M66MQYA.js} +50 -6
- package/dist/chunk-4M66MQYA.js.map +1 -0
- package/dist/{chunk-EEJC66ZF.js → chunk-6XP2Q5SS.js} +3 -3
- package/dist/{chunk-6CJRZHV4.js → chunk-7B7MMDOJ.js} +57 -4
- package/dist/chunk-7B7MMDOJ.js.map +1 -0
- package/dist/{chunk-NXHL5YII.js → chunk-7LKAMLV4.js} +4 -4
- package/dist/{chunk-7625PLY7.js → chunk-COGHTKXY.js} +4 -4
- package/dist/{chunk-GV337QP3.js → chunk-E5FJWOMP.js} +7 -7
- package/dist/{chunk-TKU6VYG3.js → chunk-E6PLM6QG.js} +6 -6
- package/dist/{chunk-GMRTI7AK.js → chunk-FIUC6QB5.js} +3 -3
- package/dist/chunk-FIUC6QB5.js.map +1 -0
- package/dist/{chunk-YXI7K4MJ.js → chunk-PNCOUFFI.js} +7 -5
- package/dist/chunk-PNCOUFFI.js.map +1 -0
- package/dist/{chunk-MBFSG4KQ.js → chunk-SH76CFAY.js} +9 -4
- package/dist/chunk-SH76CFAY.js.map +1 -0
- package/dist/runtime/subsystems/auth/auth.module.js +3 -3
- package/dist/runtime/subsystems/auth/index.js +7 -7
- package/dist/runtime/subsystems/bridge/bridge-delivery.drizzle-backend.js +1 -1
- package/dist/runtime/subsystems/bridge/bridge-outbox-drain-hook.js +4 -4
- package/dist/runtime/subsystems/bridge/bridge.module.js +10 -10
- package/dist/runtime/subsystems/bridge/index.js +13 -13
- package/dist/runtime/subsystems/cache/cache.module.js +2 -2
- package/dist/runtime/subsystems/cache/index.js +4 -4
- package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js +2 -2
- package/dist/runtime/subsystems/events/events.module.js +3 -3
- package/dist/runtime/subsystems/events/index.js +5 -5
- package/dist/runtime/subsystems/index.js +48 -48
- package/dist/runtime/subsystems/jobs/index.js +18 -18
- package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js +5 -5
- package/dist/runtime/subsystems/jobs/job-orchestrator.drizzle-backend.js +2 -2
- package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.js +2 -2
- package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js +3 -3
- package/dist/runtime/subsystems/jobs/job-worker.d.ts +592 -4
- package/dist/runtime/subsystems/jobs/job-worker.js +4 -2
- package/dist/runtime/subsystems/jobs/job-worker.module.js +6 -6
- package/dist/runtime/subsystems/jobs/jobs-domain.module.d.ts +19 -0
- package/dist/runtime/subsystems/jobs/jobs-domain.module.js +4 -4
- package/dist/runtime/subsystems/storage/index.js +1 -1
- package/dist/runtime/subsystems/storage/storage.module.js +1 -1
- package/dist/src/cli/index.js +226 -65
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/index.d.ts +477 -1
- package/dist/src/index.js +1 -1
- package/package.json +1 -1
- package/runtime/subsystems/events/event-bus.drizzle-backend.ts +23 -7
- package/runtime/subsystems/jobs/job-worker.module.ts +5 -0
- package/runtime/subsystems/jobs/job-worker.ts +126 -12
- package/runtime/subsystems/jobs/jobs-domain.module.ts +19 -0
- package/templates/entity/new/clean-lite-ps/prompt-extension.js +59 -10
- package/templates/subsystem/jobs-config/codegen-config-jobs-block.ejs.t +11 -0
- package/dist/chunk-524YKITE.js.map +0 -1
- package/dist/chunk-6CJRZHV4.js.map +0 -1
- package/dist/chunk-GMRTI7AK.js.map +0 -1
- package/dist/chunk-MBFSG4KQ.js.map +0 -1
- package/dist/chunk-YXI7K4MJ.js.map +0 -1
- /package/dist/{chunk-3A34R6CI.js.map → chunk-3VEVGL74.js.map} +0 -0
- /package/dist/{chunk-G3IKPDTP.js.map → chunk-42763UEE.js.map} +0 -0
- /package/dist/{chunk-EEJC66ZF.js.map → chunk-6XP2Q5SS.js.map} +0 -0
- /package/dist/{chunk-NXHL5YII.js.map → chunk-7LKAMLV4.js.map} +0 -0
- /package/dist/{chunk-7625PLY7.js.map → chunk-COGHTKXY.js.map} +0 -0
- /package/dist/{chunk-GV337QP3.js.map → chunk-E5FJWOMP.js.map} +0 -0
- /package/dist/{chunk-TKU6VYG3.js.map → chunk-E6PLM6QG.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
|
package/README.md
CHANGED
|
@@ -209,11 +209,15 @@ frontend:
|
|
|
209
209
|
columnMapperNeedsCall: true # call the mapper (fn()) vs reference (fn)
|
|
210
210
|
apiBaseUrlImport: null # when set, import API_BASE_URL from it as baseURL
|
|
211
211
|
apiUrl: /api # REST base path when no apiBaseUrlImport
|
|
212
|
+
fields:
|
|
213
|
+
textareaThreshold: 500 # string→textarea cutoff (strict >); null DISABLES
|
|
212
214
|
```
|
|
213
215
|
|
|
214
216
|
`null`-disables convention: an **absent** `auth.function` defaults to
|
|
215
217
|
`getAuthorizationHeader`; an **explicit `null`** disables it entirely (no header
|
|
216
|
-
lines emitted). Likewise `sync.columnMapper: null` omits the Electric mapper
|
|
218
|
+
lines emitted). Likewise `sync.columnMapper: null` omits the Electric mapper, and
|
|
219
|
+
`fields.textareaThreshold: null` disables the string→textarea heuristic entirely
|
|
220
|
+
(bounded strings always render as `text` unless the author sets `ui_type: textarea`).
|
|
217
221
|
|
|
218
222
|
### Per-entity sync mode (`entity.sync`)
|
|
219
223
|
|
|
@@ -7,13 +7,13 @@ import {
|
|
|
7
7
|
import {
|
|
8
8
|
MissingTenantIdError
|
|
9
9
|
} from "./chunk-T4BIIU5E.js";
|
|
10
|
-
import {
|
|
11
|
-
jobRuns
|
|
12
|
-
} from "./chunk-OKXZ63IA.js";
|
|
13
10
|
import {
|
|
14
11
|
JOBS_MULTI_TENANT,
|
|
15
12
|
JOB_ORCHESTRATOR
|
|
16
13
|
} from "./chunk-ZPL74UQN.js";
|
|
14
|
+
import {
|
|
15
|
+
jobRuns
|
|
16
|
+
} from "./chunk-OKXZ63IA.js";
|
|
17
17
|
import {
|
|
18
18
|
DRIZZLE
|
|
19
19
|
} from "./chunk-U64T4YZE.js";
|
|
@@ -198,4 +198,4 @@ DrizzleJobRunService = __decorateClass([
|
|
|
198
198
|
export {
|
|
199
199
|
DrizzleJobRunService
|
|
200
200
|
};
|
|
201
|
-
//# sourceMappingURL=chunk-
|
|
201
|
+
//# sourceMappingURL=chunk-3VEVGL74.js.map
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from "./chunk-DUUCU77W.js";
|
|
8
8
|
import {
|
|
9
9
|
DrizzleEventBus
|
|
10
|
-
} from "./chunk-
|
|
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-
|
|
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
|
|
@@ -4072,11 +4114,12 @@ registerLibraryPattern(MetadataPattern);
|
|
|
4072
4114
|
registerLibraryPattern(JunctionPattern);
|
|
4073
4115
|
|
|
4074
4116
|
// src/emitters/frontend/field-meta.ts
|
|
4117
|
+
var DEFAULT_TEXTAREA_THRESHOLD = 500;
|
|
4075
4118
|
var CAMEL = (s) => s.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
4076
4119
|
function formatLabel(fieldName) {
|
|
4077
4120
|
return fieldName.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
4078
4121
|
}
|
|
4079
|
-
function inferUiType(field) {
|
|
4122
|
+
function inferUiType(field, opts = {}) {
|
|
4080
4123
|
if (field.ui.type) return field.ui.type;
|
|
4081
4124
|
if (Array.isArray(field.choices) && field.choices.length > 0) return "enum";
|
|
4082
4125
|
if (field.foreignKey) return "reference";
|
|
@@ -4090,9 +4133,10 @@ function inferUiType(field) {
|
|
|
4090
4133
|
if (nameLower.includes("percent") || nameLower.includes("rate")) {
|
|
4091
4134
|
return "percentage";
|
|
4092
4135
|
}
|
|
4136
|
+
const threshold = opts.textareaThreshold === void 0 ? DEFAULT_TEXTAREA_THRESHOLD : opts.textareaThreshold;
|
|
4093
4137
|
switch (field.type) {
|
|
4094
4138
|
case "string":
|
|
4095
|
-
return field.constraints.maxLength && field.constraints.maxLength >
|
|
4139
|
+
return threshold !== null && field.constraints.maxLength && field.constraints.maxLength > threshold ? "textarea" : "text";
|
|
4096
4140
|
case "integer":
|
|
4097
4141
|
case "decimal":
|
|
4098
4142
|
return "number";
|
|
@@ -4125,12 +4169,12 @@ function isEntityRefField(field) {
|
|
|
4125
4169
|
if (field.type === "entity_ref") return true;
|
|
4126
4170
|
return field.name.endsWith("_entity_type") || field.name.endsWith("_entity_id");
|
|
4127
4171
|
}
|
|
4128
|
-
function deriveFieldMeta(field, defaults = {}) {
|
|
4172
|
+
function deriveFieldMeta(field, defaults = {}, opts = {}) {
|
|
4129
4173
|
const hasChoices = Array.isArray(field.choices) && field.choices.length > 0;
|
|
4130
4174
|
const meta = {
|
|
4131
4175
|
field: CAMEL(field.name),
|
|
4132
4176
|
label: field.ui.label ?? formatLabel(field.name),
|
|
4133
|
-
type: inferUiType(field),
|
|
4177
|
+
type: inferUiType(field, opts),
|
|
4134
4178
|
importance: inferUiImportance(field),
|
|
4135
4179
|
sortable: field.ui.sortable ?? false,
|
|
4136
4180
|
filterable: field.ui.filterable ?? false
|
|
@@ -4303,4 +4347,4 @@ export {
|
|
|
4303
4347
|
analyzeDomain,
|
|
4304
4348
|
validateEntities
|
|
4305
4349
|
};
|
|
4306
|
-
//# sourceMappingURL=chunk-
|
|
4350
|
+
//# sourceMappingURL=chunk-4M66MQYA.js.map
|