inibase 3.6.0 → 3.7.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/dist/index.d.ts CHANGED
@@ -44,6 +44,14 @@ export interface TableConfig {
44
44
  cache?: boolean;
45
45
  prepend?: boolean;
46
46
  decodeID?: boolean;
47
+ /**
48
+ * Opt a table into the incremental update log (format 2): `put` appends a
49
+ * small frame instead of rewriting whole columns, and reads merge the log
50
+ * over the base columns. Requires a schema of simple top-level fields and
51
+ * is incompatible with `prepend`, transactions and DDL on the table
52
+ * (those need compaction, shipped in 3.9).
53
+ */
54
+ log?: boolean;
47
55
  }
48
56
  export interface TableObject {
49
57
  schema?: Schema;
@@ -88,7 +96,7 @@ declare global {
88
96
  entries<T extends object>(o: T): Entries<T>;
89
97
  }
90
98
  }
91
- export declare const ERROR_CODES: readonly ["GROUP_UNIQUE", "FIELD_UNIQUE", "FIELD_REQUIRED", "NO_SCHEMA", "TABLE_EMPTY", "INVALID_ID", "INVALID_TYPE", "INVALID_PARAMETERS", "NO_ENV", "TABLE_EXISTS", "TABLE_NOT_EXISTS", "INVALID_REGEX_MATCH", "INVALID_NAME", "COMPUTED_FIELD_SYNTAX", "COMPUTED_FIELD_UNKNOWN_FIELD", "COMPUTED_FIELD_INVALID_LINK", "COMPUTED_FIELD_INVALID_TARGET", "COMPUTED_FIELD_CONFLICT", "COMPUTED_FIELD_CYCLE", "COMPUTED_FIELD_SETTABLE", "COMPUTED_FIELD_DANGLING_LINK", "COMPUTED_FIELD_ARITHMETIC"];
99
+ export declare const ERROR_CODES: readonly ["GROUP_UNIQUE", "FIELD_UNIQUE", "FIELD_REQUIRED", "NO_SCHEMA", "TABLE_EMPTY", "INVALID_ID", "INVALID_TYPE", "INVALID_PARAMETERS", "NO_ENV", "TABLE_EXISTS", "TABLE_NOT_EXISTS", "INVALID_REGEX_MATCH", "INVALID_NAME", "COMPUTED_FIELD_SYNTAX", "COMPUTED_FIELD_UNKNOWN_FIELD", "COMPUTED_FIELD_INVALID_LINK", "COMPUTED_FIELD_INVALID_TARGET", "COMPUTED_FIELD_CONFLICT", "COMPUTED_FIELD_CYCLE", "COMPUTED_FIELD_SETTABLE", "COMPUTED_FIELD_DANGLING_LINK", "COMPUTED_FIELD_ARITHMETIC", "UPDATE_LOG_CORRUPT", "UPDATE_LOG_FRAME_TOO_LARGE", "MAINTENANCE_REQUIRED", "UNSUPPORTED_FORMAT"];
92
100
  export type ErrorCode = (typeof ERROR_CODES)[number];
93
101
  export type ErrorLang = "en" | "ar" | "fr" | "es";
94
102
  export declare const globalConfig: {
@@ -121,6 +129,15 @@ export default class Inibase {
121
129
  * changes: see the invalidation in `getTable`'s reload branch,
122
130
  * `createTable` and `updateTableLocked`. Bounded by the number of tables. */
123
131
  private readonly computedPlanCache;
132
+ /** Per-table `.format` marker + live update-log overlays (format 2 tables).
133
+ * Keyed by `databasePath\0tableName` so two databases in one process never
134
+ * share state. Populated lazily by `getOverlay`. */
135
+ private readonly tableFormats;
136
+ private readonly overlays;
137
+ /** Last observed identity of each table's `.format` file (`mtime:size:ino`),
138
+ * used to detect a compaction by another process without re-reading the
139
+ * marker on every access. */
140
+ private readonly formatStamps;
124
141
  private databasePath;
125
142
  private uniqueMap;
126
143
  private schemaFileExtension;
@@ -176,6 +193,74 @@ export default class Inibase {
176
193
  * @return {*} {Promise<TableObject | undefined>}
177
194
  */
178
195
  getTable(tableName: string): Promise<TableObject | undefined>;
196
+ private overlayKey;
197
+ /** True when the table opted into the incremental update log. */
198
+ private isLogTable;
199
+ /**
200
+ * Resolve a table's format marker, revalidating a cached *log* table when
201
+ * its `.format` file changed underneath us. A compaction by another process
202
+ * bumps `baseGen` and empties the log; without re-reading the marker this
203
+ * process would scan the new (empty) log with the old `baseGen`, and once a
204
+ * post-compaction frame appears the generation gap would look like
205
+ * corruption. Legacy tables never change format implicitly, so they skip
206
+ * the stat entirely and keep the pre-3.8 read path cost.
207
+ */
208
+ /**
209
+ * Read a table's `.format` marker and refuse a table written by a newer
210
+ * engine (§4.1) *before* any column is read: a higher `format`, or a
211
+ * `minReader` above this build, throws UNSUPPORTED_FORMAT rather than being
212
+ * silently mis-read as format 2/legacy.
213
+ */
214
+ private readValidatedFormat;
215
+ private currentFormat;
216
+ /**
217
+ * Load (or refresh) a table's live overlay. Returns null for legacy tables
218
+ * (no `.format` marker) so every existing code path stays untouched. With
219
+ * `forWrite`, repairs a torn tail and rebuilds to the committed boundary;
220
+ * the caller must already hold the table writer lock.
221
+ */
222
+ private getOverlay;
223
+ private stampFormat;
224
+ /**
225
+ * The format-2 update log stores one encoded cell per changed *top-level
226
+ * simple* column; nested (object/array/table) and computed fields are not
227
+ * representable without a compaction step (3.9). A log table whose schema
228
+ * violates this is rejected at DDL time so the write path can always take
229
+ * the log and never needs to interleave a whole-column rewrite.
230
+ */
231
+ private assertLogCompatibleSchema;
232
+ private idFieldOf;
233
+ /** Read the internal numeric id for each requested line (stable overlay key). */
234
+ private overlayLineIds;
235
+ /** Internal id -> current line number(s), built from the id column. */
236
+ private overlayIdToLines;
237
+ /**
238
+ * Per-line raw-cell overrides for one column, from the overlay. Used by
239
+ * `File.search` so criteria, uniqueness and aggregate scans see the merged
240
+ * (un-compacted) values. Returns undefined when the table has no overlay or
241
+ * the column is never overlaid.
242
+ */
243
+ private overlayColumnOverrides;
244
+ /**
245
+ * Fold every logged update into the base columns and reset the log.
246
+ *
247
+ * Safe to call on any table: legacy tables (no `.format` marker) are a
248
+ * no-op. Semantically neutral — it only materializes already-acknowledged
249
+ * updates, so a failure never makes an acknowledged `put` disappear.
250
+ * Returns the new `baseGen` for a compacted table, or `null` when there was
251
+ * nothing to fold.
252
+ */
253
+ compact(tableName: string): Promise<number | null>;
254
+ /**
255
+ * Compaction core. The caller must hold the table writer lock. Streams each
256
+ * overlaid base column through `File.replace` (which handles compression),
257
+ * then publishes the new columns, an empty `updates.log`, and a `.format`
258
+ * marker with the bumped generation in one journaled commit, so a crash
259
+ * either rolls every file back (old base + old log + old `baseGen`) or
260
+ * forward (new base + empty log + new `baseGen`). No acknowledged update is
261
+ * ever lost.
262
+ */
263
+ private compactLogTable;
179
264
  getTableSchema(tableName: string): Promise<Schema>;
180
265
  private throwErrorIfTableEmpty;
181
266
  validateData(data: Data | Data[], schema: Schema, skipRequiredField?: boolean): void;
@@ -190,6 +275,17 @@ export default class Inibase {
190
275
  private joinPathesContents;
191
276
  private _processSchemaDataHelper;
192
277
  private processSchemaData;
278
+ /**
279
+ * Fold the table's un-compacted update-log overlay onto decoded rows.
280
+ *
281
+ * `RETURN` is keyed by line number and its `id` is the user-facing id, so
282
+ * the overlay (keyed by the internal numeric id) is matched through a fresh
283
+ * id-column read of just the requested lines. Only columns present in
284
+ * `schema` (which is already narrowed to the requested `columns`) are
285
+ * written, so a partial projection never grows extra keys. Legacy tables
286
+ * (no `.format` marker) return immediately.
287
+ */
288
+ private mergeOverlayIntoRows;
193
289
  /**
194
290
  * Extract the raw expression from a field's `computed` property (string or
195
291
  * persisted `{ expr, ast }` spec).
@@ -252,14 +348,6 @@ export default class Inibase {
252
348
  private sameCellValue;
253
349
  private isSimpleField;
254
350
  private processSimpleField;
255
- /**
256
- * Batched read for top-level simple columns: a single `paste | sed` child
257
- * process returns every requested line of every column at once instead of
258
- * spawning one `sed`/`gunzip|sed` child process per column. Returning null
259
- * (e.g. when any column file is missing, or the shell command fails) makes
260
- * the caller fall back to the existing per-column reads.
261
- */
262
- private processSimpleFieldsBatch;
263
351
  private isArrayField;
264
352
  private processArrayField;
265
353
  private isObjectField;
@@ -390,6 +478,13 @@ export default class Inibase {
390
478
  get<_TData extends Record<string, any> & Partial<Data>>(tableName: string, where: string | number | (string | number)[] | Criteria | undefined, options: Options | undefined, onlyOne: true, onlyLinesNumbers: true, _whereIsLinesNumbers?: boolean): Promise<number | null>;
391
479
  private _get;
392
480
  private getOnce;
481
+ /**
482
+ * In-process sort fallback for log tables with a non-empty overlay: the
483
+ * shell `paste | sort | awk` path reads base cells and would miss puts.
484
+ * Reads the merged rows for the (optionally filtered) lines, sorts them by
485
+ * the requested keys with type-aware comparison, then paginates.
486
+ */
487
+ private getOnceSortedWithOverlay;
393
488
  /**
394
489
  * Create new item(s) in a table
395
490
  *
@@ -422,6 +517,16 @@ export default class Inibase {
422
517
  put<TData extends Record<string, any> & Partial<Data>>(tableName: string, data: Data & TData, where: number | string | (number | string)[] | Criteria | undefined, options: Options | undefined, returnUpdatedData: true | boolean, _whereIsLinesNumbers?: boolean): Promise<(Data & TData) | null>;
423
518
  put<TData extends Record<string, any> & Partial<Data>>(tableName: string, data: (Data & TData)[], where: number | string | (number | string)[] | Criteria | undefined, options: Options | undefined, returnUpdatedData: true | boolean, _whereIsLinesNumbers?: boolean): Promise<(Data & TData)[] | null>;
424
519
  put<TData extends Record<string, any> & Partial<Data>>(tableName: string, data: (Data & TData) | (Data & TData)[], where: number | string | (number | string)[] | Criteria | undefined, options: Options | undefined, returnUpdatedData: true | boolean, _whereIsLinesNumbers?: boolean): Promise<(Data & TData) | (Data & TData)[] | null>;
520
+ /**
521
+ * Incremental `put` for format-2 (log) tables.
522
+ *
523
+ * Resolves the target rows to their stable internal ids, validates the
524
+ * payload against the merged view (uniqueness included), and appends one
525
+ * bounded frame instead of rewriting whole columns. The base files are left
526
+ * untouched; readers merge the overlay. Returns `undefined` when nothing
527
+ * matches, matching the legacy `put` contract.
528
+ */
529
+ private putViaLog;
425
530
  private _put;
426
531
  /**
427
532
  * Delete item(s) in a table