@forzalabs/remora 2.3.0 → 2.5.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.
@@ -81,7 +81,7 @@
81
81
  "properties": {
82
82
  "key": {
83
83
  "type": "string",
84
- "description": "The dimension/measure of the producer, OR '*' for every dimension of one producer, OR a pattern holding a '*' (e.g. 'addr*Street') that selects every matching field — each keeping the rest of this field's configuration, so one entry can transform or validate a whole family of columns."
84
+ "description": "The dimension of the producer, OR '*' for every dimension of one producer, OR a pattern holding a '*' (e.g. 'addr*Street') that selects every matching field — each keeping the rest of this field's configuration, so one entry can transform or validate a whole family of columns."
85
85
  },
86
86
  "description": {
87
87
  "type": "string",
@@ -290,6 +290,46 @@
290
290
  "copyFrom": {
291
291
  "type": "string",
292
292
  "description": "If set, this field will be added as new to the consumer dataset and will be a copy of the specified field. Use the alias if set, otherwise the key. The source field should come before this field in the fields list."
293
+ },
294
+ "position": {
295
+ "type": "object",
296
+ "description": "'FIXED' output only, and required on every visible field of one: where this field sits on the positional line and how its value is filled out to that width. Column order is field order and the offsets are computed from the widths, so no column number is transcribed by hand. A value longer than 'width' fails the run rather than being cut.",
297
+ "additionalProperties": false,
298
+ "required": ["width"],
299
+ "properties": {
300
+ "width": {
301
+ "type": "integer",
302
+ "minimum": 1,
303
+ "description": "How many characters the field occupies on the line."
304
+ },
305
+ "align": {
306
+ "type": "string",
307
+ "enum": ["left", "right"],
308
+ "description": "Which end of the field the value sits at, the other end taking the padding. Defaults by the field's type: 'left' for a string, 'right' for a number."
309
+ },
310
+ "pad": {
311
+ "type": "string",
312
+ "minLength": 1,
313
+ "maxLength": 1,
314
+ "description": "The single character the field is filled out with. Defaults by the field's type: a space for a string, '0' for a number."
315
+ },
316
+ "nullFill": {
317
+ "type": "string",
318
+ "minLength": 1,
319
+ "maxLength": 1,
320
+ "description": "The single character the full width of a NULL field is filled with. Defaults to a space, not to 'pad': zero-filling an empty numeric field writes a real value where there was none. An empty string counts as a null here."
321
+ },
322
+ "overflow": {
323
+ "type": "string",
324
+ "enum": ["fail", "truncate"],
325
+ "description": "What a value longer than 'width' does. 'fail' (the default) stops the run naming the field, the value and both widths; 'truncate' cuts the value to 'width', for the layouts that genuinely specify truncation."
326
+ },
327
+ "start": {
328
+ "type": "integer",
329
+ "minimum": 1,
330
+ "description": "Optional 1-based column the field is expected to start at. Not a second source of truth — the offsets always come from the widths — but an assertion that compile recomputes and fails on, so a forgotten filler is caught where it is written."
331
+ }
332
+ }
293
333
  }
294
334
  },
295
335
  "required": [
@@ -301,7 +341,7 @@
301
341
  },
302
342
  "outputs": {
303
343
  "type": "array",
304
- "description": "Output formats and configurations for the consumer",
344
+ "description": "Where and how the consumer exports its data. Exactly one: a run stages its records in one encoding and moves that file to the destination, so a second output has nothing left to write.",
305
345
  "items": {
306
346
  "type": "object",
307
347
  "properties": {
@@ -310,10 +350,12 @@
310
350
  "enum": [
311
351
  "API",
312
352
  "CSV",
353
+ "FIXED",
313
354
  "JSON",
355
+ "PARQUET",
314
356
  "TXT"
315
357
  ],
316
- "description": "The output format of the consumer. JSON/CSV/TXT export to a file destination; API exposes the result over the API."
358
+ "description": "The output format of the consumer. JSON/CSV/TXT/PARQUET/FIXED export to a file destination; API exposes the result over the API. PARQUET writes a typed, SNAPPY-compressed columnar file whose schema comes from the consumer's dimension types. FIXED writes a positional flat file — no delimiter, no quotes, no header — where each visible field declares its own 'position'."
317
359
  },
318
360
  "exportDestination": {
319
361
  "type": "string",
@@ -325,15 +367,90 @@
325
367
  },
326
368
  "delimiter": {
327
369
  "type": "string",
328
- "description": "Only used when the format is 'TXT'. The single-character delimiter used between fields in the exported file. Defaults to a tab if not set."
370
+ "description": "Only used when the format is 'TXT'. The single-character delimiter used between fields in the exported file. Defaults to a tab if not set. Ignored by every other format, PARQUET included."
329
371
  },
330
372
  "header": {
331
373
  "type": "boolean",
332
- "description": "'CSV'/'TXT' only. Writes the field names as the first line of each exported file, quoted and delimited exactly as the rows under them. Defaults to true; set it to false for a destination that reads the first line as data."
374
+ "description": "'CSV'/'TXT' only. Writes the field names as the first line of each exported file, quoted and delimited exactly as the rows under them. Defaults to true; set it to false for a destination that reads the first line as data. Rejected on PARQUET, which carries its own schema."
333
375
  },
334
376
  "omitNull": {
335
377
  "type": "boolean",
336
- "description": "'JSON'/'API' only. Omits the fields whose value is null from each exported record instead of writing them as \"field\": null — worth it on a wide, sparse record. Records then legitimately differ in shape, so it is rejected on a positional format (CSV/TXT), and on a JSON output whose consumer also writes one, since every output is serialized from the same record."
378
+ "description": "'JSON'/'API' only. Omits the fields whose value is null from each exported record instead of writing them as \"field\": null — worth it on a wide, sparse record. Records then legitimately differ in shape, so it is rejected on a positional format (CSV/TXT/PARQUET), and on a JSON output whose consumer also writes one, since every output is serialized from the same record."
379
+ },
380
+ "recordLength": {
381
+ "type": "integer",
382
+ "minimum": 1,
383
+ "description": "'FIXED' only, and not alongside 'layouts', where each layout declares its own. The declared width of one record, which compile checks the field widths sum to and the export checks every composed line against. It is the length of the record as written, not the length of the copybook it came from: the fields past the cut are simply not declared rather than padded to. Every record is exactly this long, including one whose last field is null — that field is written full width in 'nullFill' and the line is never cut short."
384
+ },
385
+ "defaults": {
386
+ "type": "object",
387
+ "description": "'FIXED' only. The fill rules every field of one type takes unless its own 'position' overrides them, keyed by the field's type. A wide layout declares 'numbers are right-aligned and zero-padded' once instead of on each of 90 fields.",
388
+ "additionalProperties": false,
389
+ "properties": {
390
+ "string": { "$ref": "#/definitions/fixedFieldFill" },
391
+ "number": { "$ref": "#/definitions/fixedFieldFill" },
392
+ "datetime": { "$ref": "#/definitions/fixedFieldFill" },
393
+ "boolean": { "$ref": "#/definitions/fixedFieldFill" }
394
+ }
395
+ },
396
+ "lineEnding": {
397
+ "type": "string",
398
+ "enum": ["lf", "crlf"],
399
+ "description": "'FIXED' only. What ends every record, the last one included. Defaults to 'lf'. A real positional feed picks one and holds it — the NASCO POD files are CRLF on every line. A file with no terminator at all is fixed record framing (RECFM=FB) and is not expressible here."
400
+ },
401
+ "layouts": {
402
+ "type": "object",
403
+ "description": "'FIXED' only. Several record layouts in one file, picked per record by the value of one field — the write-side mirror of a producer's 'recordSelector'. The records come out grouped, one contiguous block per value in 'order', keeping within each block the order they were read in. Rejected together with a top-level 'recordLength': each layout declares its own. Without it a FIXED output writes every visible field as one layout.",
404
+ "additionalProperties": false,
405
+ "required": ["discriminator", "order", "values"],
406
+ "properties": {
407
+ "discriminator": {
408
+ "type": "string",
409
+ "description": "The consumer field whose value picks the layout, by the name it is exported under (the alias where one is set). It does not have to be written by any of the layouts, though it usually is."
410
+ },
411
+ "order": {
412
+ "type": "array",
413
+ "description": "The discriminator values in the order their blocks are written. The complete list: a record carrying a value that is not here has no block to go in and fails the run.",
414
+ "items": { "type": "string" },
415
+ "minItems": 1,
416
+ "uniqueItems": true
417
+ },
418
+ "values": {
419
+ "type": "object",
420
+ "description": "The layout each discriminator value is written with. Every value in 'order' needs an entry.",
421
+ "minProperties": 1,
422
+ "additionalProperties": {
423
+ "type": "object",
424
+ "additionalProperties": false,
425
+ "required": ["fields"],
426
+ "properties": {
427
+ "fields": {
428
+ "type": "array",
429
+ "description": "The fields this layout writes, in column order, by their exported names. A subset of the consumer's visible fields: the consumer declares the union of every layout's fields and each layout names the ones its records carry.",
430
+ "items": { "type": "string" },
431
+ "minItems": 1,
432
+ "uniqueItems": true
433
+ },
434
+ "recordLength": {
435
+ "type": "integer",
436
+ "minimum": 1,
437
+ "description": "As the output's 'recordLength', for this layout alone. A multi-layout output has no single one."
438
+ },
439
+ "defaults": {
440
+ "type": "object",
441
+ "description": "As the output's 'defaults', applied over them for this layout's fields alone.",
442
+ "additionalProperties": false,
443
+ "properties": {
444
+ "string": { "$ref": "#/definitions/fixedFieldFill" },
445
+ "number": { "$ref": "#/definitions/fixedFieldFill" },
446
+ "datetime": { "$ref": "#/definitions/fixedFieldFill" },
447
+ "boolean": { "$ref": "#/definitions/fixedFieldFill" }
448
+ }
449
+ }
450
+ }
451
+ }
452
+ }
453
+ }
337
454
  },
338
455
  "trigger": {
339
456
  "type": "object",
@@ -443,7 +560,8 @@
443
560
  ],
444
561
  "additionalProperties": false
445
562
  },
446
- "minItems": 1
563
+ "minItems": 1,
564
+ "maxItems": 1
447
565
  },
448
566
  "filters": {
449
567
  "type": "array",
@@ -765,6 +883,35 @@
765
883
  ],
766
884
  "additionalProperties": false,
767
885
  "definitions": {
886
+ "fixedFieldFill": {
887
+ "type": "object",
888
+ "description": "How a value is filled out to the width of its fixed-width field, set once for every field of one type. A field's own 'position' wins where both set the same rule.",
889
+ "additionalProperties": false,
890
+ "properties": {
891
+ "align": {
892
+ "type": "string",
893
+ "enum": ["left", "right"],
894
+ "description": "Which end of the field the value sits at, the other end taking the padding. Defaults by the field's type: 'left' for a string, 'right' for a number."
895
+ },
896
+ "pad": {
897
+ "type": "string",
898
+ "minLength": 1,
899
+ "maxLength": 1,
900
+ "description": "The single character the field is filled out with. Defaults by the field's type: a space for a string, '0' for a number."
901
+ },
902
+ "nullFill": {
903
+ "type": "string",
904
+ "minLength": 1,
905
+ "maxLength": 1,
906
+ "description": "The single character the full width of a NULL field is filled with. Defaults to a space, not to 'pad': zero-filling an empty numeric field writes a real value where there was none. An empty string counts as a null here."
907
+ },
908
+ "overflow": {
909
+ "type": "string",
910
+ "enum": ["fail", "truncate"],
911
+ "description": "What a value longer than 'width' does. 'fail' (the default) stops the run naming the field, the value and both widths; 'truncate' cuts the value to 'width', for the layouts that genuinely specify truncation."
912
+ }
913
+ }
914
+ },
768
915
  "singleTransformation": {
769
916
  "oneOf": [
770
917
  {
@@ -1875,9 +2022,6 @@
1875
2022
  }
1876
2023
  ],
1877
2024
  "outputs": [
1878
- {
1879
- "format": "API"
1880
- },
1881
2025
  {
1882
2026
  "format": "JSON",
1883
2027
  "exportDestination": "Data Lake",
@@ -26,32 +26,6 @@
26
26
  "items": { "$ref": "#/definitions/dimension" },
27
27
  "minItems": 1
28
28
  },
29
- "measures": {
30
- "type": "array",
31
- "description": "Optional measures for the producer",
32
- "items": {
33
- "type": "object",
34
- "properties": {
35
- "name": {
36
- "type": "string",
37
- "description": "The name of the measure"
38
- },
39
- "description": {
40
- "type": "string",
41
- "description": "Optional description of the measure"
42
- },
43
- "sql": {
44
- "type": "string",
45
- "description": "SQL command used to create a metric"
46
- }
47
- },
48
- "required": [
49
- "name",
50
- "sql"
51
- ],
52
- "additionalProperties": false
53
- }
54
- },
55
29
  "settings": {
56
30
  "type": "object",
57
31
  "description": "Settings for the producer",
@@ -515,18 +489,6 @@
515
489
  "type": "datetime"
516
490
  }
517
491
  ],
518
- "measures": [
519
- {
520
- "name": "total_orders",
521
- "description": "Total number of orders by this customer",
522
- "sql": "COUNT(order_id)"
523
- },
524
- {
525
- "name": "total_spent",
526
- "description": "Total amount spent by this customer",
527
- "sql": "SUM(order_amount)"
528
- }
529
- ],
530
492
  "settings": {
531
493
  "sqlTable": "customers"
532
494
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forzalabs/remora",
3
- "version": "2.3.0",
3
+ "version": "2.5.0",
4
4
  "description": "A powerful CLI tool for seamless data translation.",
5
5
  "main": "index.js",
6
6
  "private": false,
@@ -14,8 +14,9 @@
14
14
  "sync:drivers": "cd ../.. && npm run build --workspace=@remora/app-drivers",
15
15
  "watch:drivers": "cd ../.. && npm run dev --workspace=@remora/app-drivers",
16
16
  "sync:internal": "cd ../.. && npm run sync:internal",
17
+ "sync:preview": "cd ../.. && npm run build:preview",
17
18
  "watch:internal": "cd ../.. && npm run watch:internal",
18
- "ts-check": "npx tsc --noemit",
19
+ "ts-check": "npm run sync:preview && npx tsc --noemit",
19
20
  "perf": "npm run sync:internal && npm run fast-build && npm run copy-static-file && npx tsx ./scripts/perf/RunPerfBaseline.ts",
20
21
  "perf:quick": "npx tsx ./scripts/perf/RunPerfBaseline.ts",
21
22
  "init": "npm run sync:internal && npx tsx ./src/index.ts init",
@@ -64,6 +65,7 @@
64
65
  "fast-xml-parser": "^5.3.5",
65
66
  "fs-extra": "^11.1.0",
66
67
  "hyparquet": "^1.17.4",
68
+ "hyparquet-writer": "^0.16.12",
67
69
  "inquirer": "^8.2.5",
68
70
  "json-schema": "^0.4.0",
69
71
  "jsonwebtoken": "^9.0.2",