@softize/opus 21.0.0 → 21.0.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 CHANGED
@@ -7,6 +7,16 @@ Depois de qualquer bump, rode os gates (`typecheck` · `test` · `opus check` ·
7
7
  `opus copy --check` · `base copy check` · `manifest:check`) — eles apontam o que a
8
8
  mudança cobra do seu código.
9
9
 
10
+ ## 21.0.1 — 2026-09-26
11
+
12
+ **Correção.** O `status()` do driver BullMQ podia devolver `status: 'done'` com `data: null` para um
13
+ job que concluiu com resultado. O driver lia o job e depois o estado em duas consultas separadas;
14
+ quando o worker terminava entre as duas, o estado já era terminal e o job lido ainda era o do
15
+ processamento, sem resultado. Quem acompanhava o job por polling encerrava a espera sem o
16
+ resultado e não voltava a consultar. O mesmo valia para `failed` sem o motivo. Agora, quando o
17
+ estado é terminal e o job lido ainda não traz a conclusão, o driver relê o job antes de montar o
18
+ handle. Nenhuma mudança é necessária no seu código.
19
+
10
20
  ## 21.0.0 — 2026-09-26
11
21
 
12
22
  As reactions passam a fazer o que a declaração promete. `retry`, `timeout`, `dedup` e
@@ -16,6 +16,11 @@
16
16
  tem opinião.
17
17
  4. Com o tempo, a biblioteca inteira converge pra "ours" naturalmente, pelo uso — sem
18
18
  big-bang, sem cerimônia de virada.
19
+ 5. **O ledger acompanha o ejetado.** Editar um componente `ejected` continua livre, mas a
20
+ edição atualiza no mesmo commit o `localHash` e, quando muda o comportamento, o `delta`
21
+ do `registry.lock.json`. O `lock.test` confere o hash dos ejetados com esse propósito: não
22
+ impede a edição, impede que o registro de proveniência derive em silêncio (set/2026, depois
23
+ de doze ejetados ficarem com hash desatualizado sem nenhum gate perceber).
19
24
 
20
25
  ## Princípio irmão: sobrescrita sancionada
21
26
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softize/opus",
3
- "version": "21.0.0",
3
+ "version": "21.0.1",
4
4
  "description": "End-to-end action protocol for TypeScript. Single package with subpath exports (core + adapters).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -93,9 +93,18 @@ export function bullmqQueue(options: BullmqQueueOptions): QueueAdapter {
93
93
  },
94
94
 
95
95
  async status(jobId: string): Promise<JobHandle | null> {
96
- const job = await queue.getJob(jobId)
96
+ let job = await queue.getJob(jobId)
97
97
  if (job === undefined || job === null) return null
98
- const state = await job.getState()
98
+ let state = await job.getState()
99
+ // `getJob` e `getState` são leituras separadas: se o worker concluir entre as duas, o
100
+ // snapshot ainda é o do job ativo, sem `returnvalue`, e o estado já é terminal. O BullMQ
101
+ // grava resultado e `finishedOn` no mesmo passo atômico; estado terminal sem `finishedOn`
102
+ // denuncia snapshot anterior à conclusão, e uma releitura enxerga o registro completo.
103
+ if (isFinished(state) && job.finishedOn === undefined) {
104
+ job = await queue.getJob(jobId)
105
+ if (job === undefined || job === null) return null
106
+ state = await job.getState()
107
+ }
99
108
  const action = readAction(job)
100
109
  return jobToHandle(action, job, mapState(state))
101
110
  },
@@ -165,6 +174,10 @@ export function mapState(state: BullMQState): JobStatus {
165
174
  }
166
175
  }
167
176
 
177
+ function isFinished(state: BullMQState): boolean {
178
+ return state === 'completed' || state === 'failed'
179
+ }
180
+
168
181
  function jobToHandle(
169
182
  action: string,
170
183
  job: BullMQJob,