tina4-nodejs 3.13.104 → 3.13.105

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.
@@ -297,7 +297,16 @@ const MIGRATION_TABLE = "tina4_migration";
297
297
  * sql_mode, so they are always correct there.
298
298
  */
299
299
  function mt(db: DatabaseAdapter): string {
300
- return engineOf(db) === "mysql" ? `\`${MIGRATION_TABLE}\`` : `"${MIGRATION_TABLE}"`;
300
+ const engine = engineOf(db);
301
+
302
+ // Firebird: leave it UNQUOTED so it folds to the upper-case TINA4_MIGRATION
303
+ // that the PHP and Python masters create. A quoted lower-case identifier is a
304
+ // DIFFERENT, case-sensitive table there, so a quoted spelling cannot see a
305
+ // ledger written by another Tina4 language — while tableExists() matches
306
+ // case-insensitively and reports it present, so the INSERT fails alone.
307
+ if (engine === "firebird") return MIGRATION_TABLE;
308
+
309
+ return engine === "mysql" ? `\`${MIGRATION_TABLE}\`` : `"${MIGRATION_TABLE}"`;
301
310
  }
302
311
 
303
312
  /**
@@ -347,9 +356,9 @@ async function ensureMigrationTableOn(db: DatabaseAdapter): Promise<void> {
347
356
  id INTEGER NOT NULL PRIMARY KEY,
348
357
  migration_name VARCHAR(500) NOT NULL UNIQUE,
349
358
  description VARCHAR(500),
350
- batch INTEGER NOT NULL DEFAULT 1,
359
+ batch INTEGER DEFAULT 1 NOT NULL,
351
360
  executed_at VARCHAR(50) NOT NULL,
352
- passed INTEGER NOT NULL DEFAULT 1
361
+ passed INTEGER DEFAULT 1 NOT NULL
353
362
  )`);
354
363
  } else {
355
364
  // Engine-aware bookkeeping DDL (non-Firebird). Each engine spells an
@@ -517,11 +526,11 @@ async function recordApplied(
517
526
 
518
527
  if (isFirebirdAdapter(db)) {
519
528
  // Firebird: generate the id from the sequence.
520
- const rows = await adapterQuery<{ NEXT_ID: number }>(db,
529
+ const rows = await adapterQuery<{ next_id: number }>(db,
521
530
  "SELECT GEN_ID(GEN_TINA4_MIGRATION_ID, 1) AS NEXT_ID FROM RDB$DATABASE",
522
531
  );
523
532
  insertCols.unshift("id");
524
- values.unshift(rows[0]?.NEXT_ID ?? 1);
533
+ values.unshift(rows[0]?.next_id ?? 1);
525
534
  }
526
535
 
527
536
  const placeholders = insertCols.map(() => "?").join(", ");
@@ -74,7 +74,13 @@ export interface QueueBackendInterface {
74
74
  close(): void;
75
75
  complete?(queue: string, id: string): void;
76
76
  fail?(queue: string, id: string, error: string, maxRetries: number, retryBackoff: number): void;
77
- retry?(queue: string, id: string, delaySeconds?: number): void;
77
+ /**
78
+ * Explicit manual re-queue: returns true if the id was found and revived,
79
+ * false otherwise (parity with Python's backend.retry_job()). A backend may
80
+ * legacy-return void; callers coerce a void return to true so nothing that
81
+ * used to be reported as success silently flips to failure.
82
+ */
83
+ retry?(queue: string, id: string, delaySeconds?: number): boolean | void;
78
84
  deadLetters?(queue: string, maxRetries?: number): QueueJob[];
79
85
  failed?(queue: string, maxRetries?: number): QueueJob[];
80
86
  retryFailed?(queue: string, maxRetries?: number): number;
@@ -137,7 +143,19 @@ export declare class Queue {
137
143
  */
138
144
  process(handler: (job: QueueJob | QueueJob[]) => Promise<void> | void, options?: ProcessOptions): void;
139
145
  /**
140
- * Count jobs filtered by status. Defaults to "pending".
146
+ * Count jobs by status. Defaults to "pending".
147
+ *
148
+ * ``"pending"`` counts jobs waiting to be popped -- INCLUDES retryable-
149
+ * but-attempted ones, because they live in the pending queue under the
150
+ * auto-retry lifecycle (see failed()).
151
+ * ``"reserved"`` counts jobs a consumer has popped but not yet
152
+ * completed/failed (in-flight against the visibility timeout).
153
+ * ``"completed"`` counts jobs the consumer has finished successfully.
154
+ * ``"failed"``, ``"dead"``, ``"dead_letter"`` are ALIASES that all count
155
+ * the dead-letter store -- jobs whose attempts >= maxRetries and that
156
+ * have given up. Use deadLetters() to list them. Retryable-but-attempted
157
+ * jobs are NOT counted by size("failed"); use failed() to list them or
158
+ * size("pending") to include them in a total.
141
159
  */
142
160
  size(status?: string): number;
143
161
  /**
@@ -172,7 +190,11 @@ export declare class Queue {
172
190
  /**
173
191
  * Get jobs that failed at least once but are still being retried
174
192
  * (0 < attempts < maxRetries). These live in the pending queue under the
175
- * auto-retry lifecycle; dead-lettered jobs are returned by deadLetters().
193
+ * auto-retry lifecycle (fail() re-queues them with an incremented attempts
194
+ * count and a retryBackoff delay) so pop() picks them up again. They are
195
+ * NOT counted by size("failed") -- that alias counts the dead-letter store,
196
+ * matching deadLetters(). To include retryable-failed jobs in a total, use
197
+ * size("pending"). Terminal failures are returned by deadLetters().
176
198
  */
177
199
  failed(): QueueJob[];
178
200
  /**
@@ -184,7 +206,22 @@ export declare class Queue {
184
206
  */
185
207
  retry(jobId?: string, delaySeconds?: number): boolean;
186
208
  /**
187
- * Get dead letter jobs failed jobs that exceeded max retries.
209
+ * Get jobs that exceeded max_retries -- terminal failures.
210
+ *
211
+ * Same set counted by size("failed") / size("dead") / size("dead_letter")
212
+ * (three aliases for the dead-letter store). To LIST retryable-but-
213
+ * attempted jobs (attempts > 0 AND attempts < maxRetries) that are still
214
+ * being auto-retried, use failed() -- those live in the pending queue and
215
+ * are NOT dead letters.
216
+ *
217
+ * Returns Job objects with the failure reason on ``.error`` (not raw dicts)
218
+ * so callers can iterate uniformly with the rest of the queue API and, in
219
+ * particular, call ``.retry()`` on each to manually revive it:
220
+ *
221
+ * for (const job of queue.deadLetters()) {
222
+ * Log.warn(`revived ${job.id}: ${job.error}`);
223
+ * job.retry();
224
+ * }
188
225
  */
189
226
  deadLetters(maxRetries?: number): QueueJob[];
190
227
  /**
@@ -122,7 +122,13 @@ export declare class LiteBackend {
122
122
  * Explicit re-queue requested by the caller (job.retry()).
123
123
  *
124
124
  * Always re-enqueues regardless of the retry limit — manual override,
125
- * distinct from the automatic failJob() path.
125
+ * distinct from the automatic failJob() path. Cleans up BOTH the
126
+ * reservation record AND any dead-letter file for this id, so a caller
127
+ * that iterates deadLetters() and calls .retry() on each doesn't leave
128
+ * the failed/ directory carrying duplicates (PY-12-05, 3.13.105).
129
+ * Aligns with retry(queue, jobId) which had always unlinked the
130
+ * dead-letter file -- two spellings of the same intent that previously
131
+ * diverged.
126
132
  */
127
133
  retryJob(queue: string, job: QueueJob, delaySeconds?: number): void;
128
134
  }
@@ -87,8 +87,13 @@ export declare class MongoBackend implements QueueBackend {
87
87
  * retries remain, else dead-letter. Mirrors the file/lite backend.
88
88
  */
89
89
  fail(queue: string, id: string, error: string, maxRetries: number, retryBackoff?: number): void;
90
- /** Explicit manual re-queue (always re-enqueues regardless of the retry limit). */
91
- retry(queue: string, id: string, delaySeconds?: number): void;
90
+ /**
91
+ * Revive a specific dead-letter job by id. Returns true if the DL was found
92
+ * and revived, false otherwise (parity with LiteBackend.retry(queue, id)
93
+ * and Python's mongo_backend.retry_job()). Pre-3.13.105 this returned void
94
+ * and Queue.retry(id) reported success for every call, even for unknown ids.
95
+ */
96
+ retry(queue: string, id: string, delaySeconds?: number): boolean;
92
97
  /** Jobs that exceeded max retries (the `<queue>.dead_letter` collection topic). */
93
98
  deadLetters(queue: string, maxRetries?: number): QueueJob[];
94
99
  /** Jobs that failed but are still eligible for retry (status=failed, attempts < max). */
@@ -339,11 +339,16 @@ export declare class BaseModel {
339
339
  /**
340
340
  * Invalidate every cached query that touches this model's table.
341
341
  *
342
- * Tag-scoped, NOT a wholesale flush: a cached JOIN on another model that reads
343
- * this table is busted too (it carries this table's tag), while a query that
344
- * never touches this table is left intact. Called after every ORM write
345
- * (save/delete/forceDelete/restore) so a read-after-write never serves a
346
- * stale/deleted row (CACHE-DEC-01).
342
+ * Tag-scoped in the ORM layer (a cached JOIN on another model that reads
343
+ * this table is busted too because it carries this table's tag; a query
344
+ * that never touches this table is left intact), then cascaded to the
345
+ * DB layer on this model's bound connection so an out-of-band write /
346
+ * deliberate refresh / race-with-another-process cannot leave stale rows
347
+ * in db.fetch()'s persistent cache. Called after every ORM write
348
+ * (save/delete/forceDelete/restore) so a read-after-write never serves
349
+ * a stale/deleted row (CACHE-DEC-01). PY-06-22 (3.13.105) added the
350
+ * DB-layer cascade -- previously the two cache layers disagreed under
351
+ * TINA4_AUTO_CACHING=true + TINA4_DB_CACHE=true.
347
352
  */
348
353
  static clearCache(): void;
349
354
  /**