@atscript/typescript 0.1.83 → 0.1.84
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/cli.cjs +119 -18
- package/dist/utils.cjs +129 -0
- package/dist/utils.d.ts +34 -1
- package/dist/utils.mjs +128 -1
- package/package.json +3 -3
package/dist/cli.cjs
CHANGED
|
@@ -2623,9 +2623,16 @@ const PLAN_HEADER = [
|
|
|
2623
2623
|
DYE_COLORS.bold("╚══════════════════════════════════════╝"),
|
|
2624
2624
|
""
|
|
2625
2625
|
];
|
|
2626
|
+
function planFlags(plan) {
|
|
2627
|
+
return {
|
|
2628
|
+
destructive: plan.entries.some((e) => e.destructive),
|
|
2629
|
+
hasChanges: plan.entries.some((e) => e.hasChanges),
|
|
2630
|
+
hasErrors: plan.entries.some((e) => e.hasErrors)
|
|
2631
|
+
};
|
|
2632
|
+
}
|
|
2626
2633
|
var DbSyncPrinter = class {
|
|
2627
2634
|
log(...args) {
|
|
2628
|
-
console.log(...args);
|
|
2635
|
+
if (!this.muted) console.log(...args);
|
|
2629
2636
|
}
|
|
2630
2637
|
banner() {
|
|
2631
2638
|
this.log(BANNER);
|
|
@@ -2650,6 +2657,60 @@ var DbSyncPrinter = class {
|
|
|
2650
2657
|
this.printGrouped(result.entries, "result");
|
|
2651
2658
|
this.log("");
|
|
2652
2659
|
}
|
|
2660
|
+
/** Renders the plan as a JSON document for CI consumption. */ renderJson(plan) {
|
|
2661
|
+
const doc = {
|
|
2662
|
+
status: plan.status,
|
|
2663
|
+
schemaHash: plan.schemaHash,
|
|
2664
|
+
...planFlags(plan),
|
|
2665
|
+
entries: plan.entries.map((e) => ({
|
|
2666
|
+
name: e.name,
|
|
2667
|
+
kind: e.viewType ? "view" : "table",
|
|
2668
|
+
viewType: e.viewType,
|
|
2669
|
+
status: e.status,
|
|
2670
|
+
destructive: e.destructive,
|
|
2671
|
+
syncMethod: e.syncMethod,
|
|
2672
|
+
renamedFrom: e.renamedFrom,
|
|
2673
|
+
columnsToAdd: e.columnsToAdd.map((c) => ({
|
|
2674
|
+
path: c.path,
|
|
2675
|
+
physicalName: c.physicalName,
|
|
2676
|
+
designType: c.designType,
|
|
2677
|
+
optional: c.optional,
|
|
2678
|
+
isPrimaryKey: c.isPrimaryKey,
|
|
2679
|
+
storage: c.storage
|
|
2680
|
+
})),
|
|
2681
|
+
columnsToRename: e.columnsToRename,
|
|
2682
|
+
typeChanges: e.typeChanges,
|
|
2683
|
+
nullableChanges: e.nullableChanges,
|
|
2684
|
+
defaultChanges: e.defaultChanges,
|
|
2685
|
+
columnsToDrop: e.columnsToDrop,
|
|
2686
|
+
optionChanges: e.optionChanges,
|
|
2687
|
+
fkAdded: e.fkAdded,
|
|
2688
|
+
fkRemoved: e.fkRemoved,
|
|
2689
|
+
fkChanged: e.fkChanged,
|
|
2690
|
+
errors: e.errors
|
|
2691
|
+
}))
|
|
2692
|
+
};
|
|
2693
|
+
return `${JSON.stringify(doc, undefined, 2)}\n`;
|
|
2694
|
+
}
|
|
2695
|
+
/** Renders the plan as a Markdown document (e.g. for PR comments). */ renderMarkdown(plan) {
|
|
2696
|
+
const { destructive } = planFlags(plan);
|
|
2697
|
+
const lines = [
|
|
2698
|
+
"# Schema Sync Plan",
|
|
2699
|
+
"",
|
|
2700
|
+
`- **Status:** ${plan.status}${destructive ? " (destructive)" : ""}`,
|
|
2701
|
+
`- **Schema hash:** \`${plan.schemaHash}\``,
|
|
2702
|
+
""
|
|
2703
|
+
];
|
|
2704
|
+
const tables = plan.entries.filter((e) => !e.viewType);
|
|
2705
|
+
const views = plan.entries.filter((e) => e.viewType);
|
|
2706
|
+
for (const [title, entries] of [["Tables", tables], ["Views", views]]) {
|
|
2707
|
+
if (entries.length === 0) continue;
|
|
2708
|
+
lines.push(`## ${title}`, "", "```");
|
|
2709
|
+
for (const e of entries) for (const line of e.print("plan")) lines.push(line);
|
|
2710
|
+
lines.push("```", "");
|
|
2711
|
+
}
|
|
2712
|
+
return `${lines.join("\n")}\n`;
|
|
2713
|
+
}
|
|
2653
2714
|
printGrouped(entries, mode) {
|
|
2654
2715
|
const tables = entries.filter((e) => !e.viewType);
|
|
2655
2716
|
const views = entries.filter((e) => e.viewType);
|
|
@@ -2665,6 +2726,7 @@ var DbSyncPrinter = class {
|
|
|
2665
2726
|
}
|
|
2666
2727
|
constructor() {
|
|
2667
2728
|
_define_property$1(this, "colors", DYE_COLORS);
|
|
2729
|
+
/** When true, all pretty-print output is suppressed (structured stdout mode). */ _define_property$1(this, "muted", false);
|
|
2668
2730
|
}
|
|
2669
2731
|
};
|
|
2670
2732
|
|
|
@@ -2695,15 +2757,25 @@ function _ts_param(paramIndex, decorator) {
|
|
|
2695
2757
|
};
|
|
2696
2758
|
}
|
|
2697
2759
|
var DbSyncController = class {
|
|
2698
|
-
|
|
2699
|
-
|
|
2760
|
+
info(message) {
|
|
2761
|
+
if (!this.quiet) this.logger.log(message);
|
|
2762
|
+
}
|
|
2763
|
+
async dbSync(configFile, dryRun, yes, force, safe, check, format, out) {
|
|
2764
|
+
if (format && format !== "json" && format !== "markdown") {
|
|
2765
|
+
this.logger.error(`${"\x1B[31m"}Unknown --format "${format}". Use "json" or "markdown".${"\x1B[39m"}`);
|
|
2766
|
+
process.exit(1);
|
|
2767
|
+
}
|
|
2768
|
+
this.quiet = Boolean(format) && !out;
|
|
2769
|
+
this.printer.muted = this.quiet;
|
|
2770
|
+
const cfgLogger = this.quiet ? Object.create(this.logger, { log: { value: () => {} } }) : this.logger;
|
|
2771
|
+
const config = await getConfig(configFile, cfgLogger);
|
|
2700
2772
|
if (!config.db) {
|
|
2701
2773
|
this.logger.error(`${"\x1B[31m"}No "db" field in atscript config. ` + `Add a db configuration to use schema sync.${"\x1B[39m"}`);
|
|
2702
2774
|
process.exit(1);
|
|
2703
2775
|
}
|
|
2704
2776
|
const [dbSpace, dbTypes] = await Promise.all([this.resolveDbSpace(config.db), this.compileAndLoadTypes(config)]);
|
|
2705
2777
|
if (dbTypes.length === 0) {
|
|
2706
|
-
this.
|
|
2778
|
+
this.info(`No types with @db.table or @db.view found. Nothing to sync.`);
|
|
2707
2779
|
return;
|
|
2708
2780
|
}
|
|
2709
2781
|
this.printer.typeCount(dbTypes.length);
|
|
@@ -2714,18 +2786,30 @@ var DbSyncController = class {
|
|
|
2714
2786
|
force,
|
|
2715
2787
|
safe
|
|
2716
2788
|
});
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2789
|
+
const { destructive: hasDestructive, hasChanges, hasErrors } = planFlags(plan);
|
|
2790
|
+
if (format) {
|
|
2791
|
+
const doc = format === "json" ? this.printer.renderJson(plan) : this.printer.renderMarkdown(plan);
|
|
2792
|
+
if (out) {
|
|
2793
|
+
(0, fs.writeFileSync)(out, doc);
|
|
2794
|
+
this.info(`Plan written to ${out}`);
|
|
2795
|
+
} else process.stdout.write(doc);
|
|
2796
|
+
}
|
|
2797
|
+
if (plan.status === "up-to-date") this.printer.planUpToDate(plan);
|
|
2798
|
+
else {
|
|
2799
|
+
this.printer.plan(plan);
|
|
2800
|
+
if (hasErrors) {
|
|
2801
|
+
this.logger.error(`Schema has errors. Fix the issues above before syncing.`);
|
|
2802
|
+
process.exit(1);
|
|
2803
|
+
}
|
|
2720
2804
|
}
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
process.exit(1);
|
|
2805
|
+
if (check || format) {
|
|
2806
|
+
if (check && hasChanges) {
|
|
2807
|
+
this.logger.error(hasDestructive ? `Schema changes needed (includes destructive operations).` : `Schema changes needed.`);
|
|
2808
|
+
process.exit(hasDestructive ? 2 : 1);
|
|
2809
|
+
}
|
|
2810
|
+
return;
|
|
2728
2811
|
}
|
|
2812
|
+
if (plan.status === "up-to-date") return;
|
|
2729
2813
|
if (!hasChanges) {
|
|
2730
2814
|
if (!dryRun) await sync.run(dbTypes, {
|
|
2731
2815
|
force: true,
|
|
@@ -2760,7 +2844,7 @@ var DbSyncController = class {
|
|
|
2760
2844
|
if (dbConfig.exclude) buildConfig.exclude = dbConfig.exclude;
|
|
2761
2845
|
}
|
|
2762
2846
|
buildConfig.format = "js";
|
|
2763
|
-
this.
|
|
2847
|
+
this.info(`Compiling .as files...`);
|
|
2764
2848
|
const builder = await (0, __atscript_core.build)(buildConfig);
|
|
2765
2849
|
const outputs = await builder.generate(buildConfig);
|
|
2766
2850
|
const jsOutputs = outputs.filter((o) => o.fileName.endsWith(".js"));
|
|
@@ -2795,11 +2879,11 @@ var DbSyncController = class {
|
|
|
2795
2879
|
}
|
|
2796
2880
|
async resolveDbSpace(dbConfig) {
|
|
2797
2881
|
if (typeof dbConfig === "function") {
|
|
2798
|
-
this.
|
|
2882
|
+
this.info(`Resolving database space from callback...`);
|
|
2799
2883
|
return await dbConfig();
|
|
2800
2884
|
}
|
|
2801
2885
|
const { adapter, connection, options } = dbConfig;
|
|
2802
|
-
this.
|
|
2886
|
+
this.info(`Using adapter: ${"\x1B[36m"}${adapter}${"\x1B[39m"}`);
|
|
2803
2887
|
let mod;
|
|
2804
2888
|
try {
|
|
2805
2889
|
mod = await this.importFromCwd(adapter);
|
|
@@ -2841,7 +2925,9 @@ var DbSyncController = class {
|
|
|
2841
2925
|
constructor(logger) {
|
|
2842
2926
|
_define_property(this, "logger", void 0);
|
|
2843
2927
|
_define_property(this, "printer", void 0);
|
|
2928
|
+
/** Muted when structured --format output goes to stdout, to keep it parseable. */ _define_property(this, "quiet", void 0);
|
|
2844
2929
|
this.logger = logger;
|
|
2930
|
+
this.quiet = false;
|
|
2845
2931
|
this.printer = new DbSyncPrinter();
|
|
2846
2932
|
}
|
|
2847
2933
|
};
|
|
@@ -2853,6 +2939,9 @@ _ts_decorate([
|
|
|
2853
2939
|
(0, __moostjs_event_cli.CliExample)("db sync --yes", "Skip confirmation prompt (CI mode)"),
|
|
2854
2940
|
(0, __moostjs_event_cli.CliExample)("db sync --force", "Re-sync even if schema hash matches"),
|
|
2855
2941
|
(0, __moostjs_event_cli.CliExample)("db sync --safe", "Skip destructive operations (column/table drops)"),
|
|
2942
|
+
(0, __moostjs_event_cli.CliExample)("db sync --check", "Exit 1 when changes are needed, 2 when destructive (CI gate)"),
|
|
2943
|
+
(0, __moostjs_event_cli.CliExample)("db sync --format json", "Emit the plan as JSON to stdout, do not apply"),
|
|
2944
|
+
(0, __moostjs_event_cli.CliExample)("db sync --format markdown --out plan.md", "Write the plan as Markdown to a file"),
|
|
2856
2945
|
_ts_param(0, (0, __moostjs_event_cli.CliOption)("c", "config")),
|
|
2857
2946
|
_ts_param(0, (0, moost.Optional)()),
|
|
2858
2947
|
_ts_param(0, (0, moost.Description)("Path to config file")),
|
|
@@ -2868,13 +2957,25 @@ _ts_decorate([
|
|
|
2868
2957
|
_ts_param(4, (0, __moostjs_event_cli.CliOption)("safe")),
|
|
2869
2958
|
_ts_param(4, (0, moost.Optional)()),
|
|
2870
2959
|
_ts_param(4, (0, moost.Description)("Skip destructive operations (column/table drops)")),
|
|
2960
|
+
_ts_param(5, (0, __moostjs_event_cli.CliOption)("check")),
|
|
2961
|
+
_ts_param(5, (0, moost.Optional)()),
|
|
2962
|
+
_ts_param(5, (0, moost.Description)("Plan only; exit 1 when changes are needed, 2 when destructive changes are present")),
|
|
2963
|
+
_ts_param(6, (0, __moostjs_event_cli.CliOption)("f", "format")),
|
|
2964
|
+
_ts_param(6, (0, moost.Optional)()),
|
|
2965
|
+
_ts_param(6, (0, moost.Description)("Emit the plan as \"json\" or \"markdown\" instead of applying (plan-only mode)")),
|
|
2966
|
+
_ts_param(7, (0, __moostjs_event_cli.CliOption)("out")),
|
|
2967
|
+
_ts_param(7, (0, moost.Optional)()),
|
|
2968
|
+
_ts_param(7, (0, moost.Description)("Write --format output to a file instead of stdout")),
|
|
2871
2969
|
_ts_metadata("design:type", Function),
|
|
2872
2970
|
_ts_metadata("design:paramtypes", [
|
|
2873
2971
|
String,
|
|
2874
2972
|
Boolean,
|
|
2875
2973
|
Boolean,
|
|
2876
2974
|
Boolean,
|
|
2877
|
-
Boolean
|
|
2975
|
+
Boolean,
|
|
2976
|
+
Boolean,
|
|
2977
|
+
String,
|
|
2978
|
+
String
|
|
2878
2979
|
]),
|
|
2879
2980
|
_ts_metadata("design:returntype", Promise)
|
|
2880
2981
|
], DbSyncController.prototype, "dbSync", null);
|
package/dist/utils.cjs
CHANGED
|
@@ -1,6 +1,133 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const require_json_schema = require('./json-schema-3mti4G4J.cjs');
|
|
3
3
|
|
|
4
|
+
//#region packages/typescript/src/runtime/coerce.ts
|
|
5
|
+
/**
|
|
6
|
+
* Internal sentinel returned when a value cannot be coerced to a type node.
|
|
7
|
+
* Drives union branch selection without allocating per-node result objects.
|
|
8
|
+
*/ const NO_MATCH = Symbol("atscript.coerce.no-match");
|
|
9
|
+
function coerceForType(def, value) {
|
|
10
|
+
const result = coerce(def, value);
|
|
11
|
+
return result === NO_MATCH ? value : result;
|
|
12
|
+
}
|
|
13
|
+
function coerceScalar(designType, value) {
|
|
14
|
+
const result = scalar(designType, value);
|
|
15
|
+
return result === NO_MATCH ? value : result;
|
|
16
|
+
}
|
|
17
|
+
/** Returns the coerced value, or NO_MATCH when the value can't fit this node. */ function coerce(def, value) {
|
|
18
|
+
if (value === undefined || value === null) return def.optional === true ? value : NO_MATCH;
|
|
19
|
+
return require_json_schema.forAnnotatedType(def, {
|
|
20
|
+
final: (d) => {
|
|
21
|
+
const result = scalar(d.type.designType, value);
|
|
22
|
+
if (result !== NO_MATCH && d.type.value !== undefined && result !== d.type.value) return NO_MATCH;
|
|
23
|
+
return result;
|
|
24
|
+
},
|
|
25
|
+
phantom: () => value,
|
|
26
|
+
object: (d) => {
|
|
27
|
+
if (!isPlainObject(value)) return NO_MATCH;
|
|
28
|
+
return coerceProps(d, value);
|
|
29
|
+
},
|
|
30
|
+
array: (d) => Array.isArray(value) ? coerceItems(value, d.type.of) : NO_MATCH,
|
|
31
|
+
tuple: (d) => Array.isArray(value) && value.length === d.type.items.length ? coerceItems(value, undefined, d.type.items) : NO_MATCH,
|
|
32
|
+
union: (d) => {
|
|
33
|
+
for (const item of d.type.items) {
|
|
34
|
+
const result = coerce(item, value);
|
|
35
|
+
if (result !== NO_MATCH) return result;
|
|
36
|
+
}
|
|
37
|
+
return NO_MATCH;
|
|
38
|
+
},
|
|
39
|
+
intersection: (d) => {
|
|
40
|
+
let current = value;
|
|
41
|
+
for (const item of d.type.items) {
|
|
42
|
+
const result = coerce(item, current);
|
|
43
|
+
if (result === NO_MATCH) return NO_MATCH;
|
|
44
|
+
current = result;
|
|
45
|
+
}
|
|
46
|
+
return current;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
function scalar(designType, value) {
|
|
51
|
+
switch (designType) {
|
|
52
|
+
case "string":
|
|
53
|
+
case "decimal": return typeof value === "string" ? value : NO_MATCH;
|
|
54
|
+
case "number": {
|
|
55
|
+
if (typeof value === "number") return value;
|
|
56
|
+
if (typeof value === "string") {
|
|
57
|
+
const trimmed = value.trim();
|
|
58
|
+
if (trimmed.length > 0) {
|
|
59
|
+
const parsed = Number(trimmed);
|
|
60
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return NO_MATCH;
|
|
64
|
+
}
|
|
65
|
+
case "boolean": {
|
|
66
|
+
if (typeof value === "boolean") return value;
|
|
67
|
+
if (value === "true" || value === "1") return true;
|
|
68
|
+
if (value === "false" || value === "0") return false;
|
|
69
|
+
return NO_MATCH;
|
|
70
|
+
}
|
|
71
|
+
case "object": return typeof value === "object" ? value : NO_MATCH;
|
|
72
|
+
case "any":
|
|
73
|
+
case "phantom": return value;
|
|
74
|
+
default: return NO_MATCH;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Coerces the props of a plain object. Copy-on-first-change: allocates the
|
|
79
|
+
* output object only when some prop actually coerced, else returns the input.
|
|
80
|
+
*/ function coerceProps(def, value) {
|
|
81
|
+
const keys = Object.keys(value);
|
|
82
|
+
let out;
|
|
83
|
+
for (let i = 0; i < keys.length; i++) {
|
|
84
|
+
const key = keys[i];
|
|
85
|
+
const input = value[key];
|
|
86
|
+
let propDef = def.type.props.get(key);
|
|
87
|
+
if (!propDef) {
|
|
88
|
+
for (const { pattern, def: patternDef } of def.type.propsPatterns) if (pattern.test(key)) {
|
|
89
|
+
propDef = patternDef;
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
let coerced = input;
|
|
94
|
+
if (propDef) {
|
|
95
|
+
const result = coerce(propDef, input);
|
|
96
|
+
if (result !== NO_MATCH) coerced = result;
|
|
97
|
+
}
|
|
98
|
+
if (out) out[key] = coerced;
|
|
99
|
+
else if (coerced !== input) {
|
|
100
|
+
out = {};
|
|
101
|
+
for (let j = 0; j < i; j++) out[keys[j]] = value[keys[j]];
|
|
102
|
+
out[key] = coerced;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return out ?? value;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Coerces array/tuple items against `of` (arrays) or `items[i]` (tuples).
|
|
109
|
+
* Copy-on-first-change, same as {@link coerceProps}.
|
|
110
|
+
*/ function coerceItems(value, of, items) {
|
|
111
|
+
let out;
|
|
112
|
+
for (let i = 0; i < value.length; i++) {
|
|
113
|
+
const input = value[i];
|
|
114
|
+
const result = coerce(of ?? items[i], input);
|
|
115
|
+
const coerced = result === NO_MATCH ? input : result;
|
|
116
|
+
if (out) out.push(coerced);
|
|
117
|
+
else if (coerced !== input) {
|
|
118
|
+
out = value.slice(0, i);
|
|
119
|
+
out.push(coerced);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return out ?? value;
|
|
123
|
+
}
|
|
124
|
+
function isPlainObject(value) {
|
|
125
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
126
|
+
const proto = Object.getPrototypeOf(value);
|
|
127
|
+
return proto === Object.prototype || proto === null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
//#endregion
|
|
4
131
|
//#region packages/typescript/src/runtime/default-value.ts
|
|
5
132
|
/**
|
|
6
133
|
* Attempts to resolve a value from the mode for the given annotated type.
|
|
@@ -487,6 +614,8 @@ exports.ValidatorError = require_json_schema.ValidatorError
|
|
|
487
614
|
exports.annotate = require_json_schema.annotate
|
|
488
615
|
exports.buildJsonSchema = require_json_schema.buildJsonSchema
|
|
489
616
|
exports.cloneRefProp = require_json_schema.cloneRefProp
|
|
617
|
+
exports.coerceForType = coerceForType
|
|
618
|
+
exports.coerceScalar = coerceScalar
|
|
490
619
|
exports.createAnnotatedTypeNode = require_json_schema.createAnnotatedTypeNode
|
|
491
620
|
exports.createDataFromAnnotatedType = createDataFromAnnotatedType
|
|
492
621
|
exports.defineAnnotatedType = require_json_schema.defineAnnotatedType
|
package/dist/utils.d.ts
CHANGED
|
@@ -290,6 +290,39 @@ declare function isPhantomType(def: TAtscriptAnnotatedType): boolean;
|
|
|
290
290
|
*/
|
|
291
291
|
declare function isAnnotatedTypeOfPrimitive(t: TAtscriptAnnotatedType): boolean;
|
|
292
292
|
|
|
293
|
+
/**
|
|
294
|
+
* Coerces string-transport input (route params, query strings) toward the
|
|
295
|
+
* scalar shapes an annotated type expects.
|
|
296
|
+
*
|
|
297
|
+
* Pure and non-throwing: when a value can't be coerced it is returned
|
|
298
|
+
* untouched, leaving the {@link Validator} to report the proper error.
|
|
299
|
+
* Coercion converts representation only — constraint checks (`@expect.int`,
|
|
300
|
+
* `@expect.min`, patterns) remain the validator's job.
|
|
301
|
+
*
|
|
302
|
+
* Rules:
|
|
303
|
+
* - Strings targeting `number` are trimmed and parsed via `Number()`; only
|
|
304
|
+
* finite results are accepted.
|
|
305
|
+
* - Strings targeting `boolean` accept `"true"/"1"` and `"false"/"0"`.
|
|
306
|
+
* - Unions try each branch in declared order; the first successful parse wins.
|
|
307
|
+
* - Objects recurse into props when the input is a plain object (covers
|
|
308
|
+
* `@Query()` DTOs where every field arrives as a string).
|
|
309
|
+
* - Arrays and tuples coerce items; intersections apply each member in order.
|
|
310
|
+
* - `string` / `decimal` targets never change string input.
|
|
311
|
+
*
|
|
312
|
+
* @example
|
|
313
|
+
* ```ts
|
|
314
|
+
* coerceForType(KafkaOffset, '42') // → 42
|
|
315
|
+
* coerceForType(KafkaOffset, 'abc') // → 'abc' (validator reports the error)
|
|
316
|
+
* ```
|
|
317
|
+
*/
|
|
318
|
+
declare function coerceForType(def: TAtscriptAnnotatedType, value: unknown): unknown;
|
|
319
|
+
/**
|
|
320
|
+
* Coerces a single scalar value toward a design type, using the same rules
|
|
321
|
+
* as {@link coerceForType}. Returns the input untouched when it can't coerce.
|
|
322
|
+
* Useful when only a design type is known (e.g. `design:paramtypes` fallbacks).
|
|
323
|
+
*/
|
|
324
|
+
declare function coerceScalar(designType: TAtscriptTypeFinal['designType'], value: unknown): unknown;
|
|
325
|
+
|
|
293
326
|
/** A JSON Schema object (draft-compatible). */
|
|
294
327
|
type TJsonSchema = Record<string, any>;
|
|
295
328
|
/**
|
|
@@ -649,5 +682,5 @@ type NavPropsOf<T> = T extends {
|
|
|
649
682
|
__navProps: infer N extends Record<string, unknown>;
|
|
650
683
|
} ? N : Record<string, never>;
|
|
651
684
|
|
|
652
|
-
export { SERIALIZE_VERSION, Validator, ValidatorError, annotate, buildJsonSchema, cloneRefProp, createAnnotatedTypeNode, createDataFromAnnotatedType, defineAnnotatedType, deserializeAnnotatedType, detectDiscriminator, flattenAnnotatedType, forAnnotatedType, fromJsonSchema, isAnnotatedType, isAnnotatedTypeOfPrimitive, isPhantomType, mergeJsonSchemas, serializeAnnotatedType, throwFeatureDisabled };
|
|
685
|
+
export { SERIALIZE_VERSION, Validator, ValidatorError, annotate, buildJsonSchema, cloneRefProp, coerceForType, coerceScalar, createAnnotatedTypeNode, createDataFromAnnotatedType, defineAnnotatedType, deserializeAnnotatedType, detectDiscriminator, flattenAnnotatedType, forAnnotatedType, fromJsonSchema, isAnnotatedType, isAnnotatedTypeOfPrimitive, isPhantomType, mergeJsonSchemas, serializeAnnotatedType, throwFeatureDisabled };
|
|
653
686
|
export type { AtscriptQueryComparison, AtscriptQueryFieldRef, AtscriptQueryNode, AtscriptRef, FlatOf, InferDataType, NavPropsOf, OwnPropsOf, PrimaryKeyOf, TAnnotatedTypeHandle, TAtscriptAnnotatedType, TAtscriptAnnotatedTypeConstructor, TAtscriptDataType, TAtscriptTypeArray, TAtscriptTypeComplex, TAtscriptTypeDef, TAtscriptTypeFinal, TAtscriptTypeObject, TCreateDataOptions, TFlattenOptions, TJsonSchema, TMetadataMap, TProcessAnnotationContext, TSerializeOptions, TSerializedAnnotatedType, TSerializedAnnotatedTypeInner, TSerializedTypeArray, TSerializedTypeComplex, TSerializedTypeDef, TSerializedTypeFinal, TSerializedTypeObject, TUnionDiscriminator, TValidatorOptions, TValidatorPlugin, TValidatorPluginContext, TValueResolver };
|
package/dist/utils.mjs
CHANGED
|
@@ -1,5 +1,132 @@
|
|
|
1
1
|
import { Validator, ValidatorError, annotate, buildJsonSchema, cloneRefProp, createAnnotatedTypeNode, defineAnnotatedType, detectDiscriminator, forAnnotatedType, fromJsonSchema, isAnnotatedType, isAnnotatedTypeOfPrimitive, isPhantomType, mergeJsonSchemas } from "./json-schema-B64qclVa.mjs";
|
|
2
2
|
|
|
3
|
+
//#region packages/typescript/src/runtime/coerce.ts
|
|
4
|
+
/**
|
|
5
|
+
* Internal sentinel returned when a value cannot be coerced to a type node.
|
|
6
|
+
* Drives union branch selection without allocating per-node result objects.
|
|
7
|
+
*/ const NO_MATCH = Symbol("atscript.coerce.no-match");
|
|
8
|
+
function coerceForType(def, value) {
|
|
9
|
+
const result = coerce(def, value);
|
|
10
|
+
return result === NO_MATCH ? value : result;
|
|
11
|
+
}
|
|
12
|
+
function coerceScalar(designType, value) {
|
|
13
|
+
const result = scalar(designType, value);
|
|
14
|
+
return result === NO_MATCH ? value : result;
|
|
15
|
+
}
|
|
16
|
+
/** Returns the coerced value, or NO_MATCH when the value can't fit this node. */ function coerce(def, value) {
|
|
17
|
+
if (value === undefined || value === null) return def.optional === true ? value : NO_MATCH;
|
|
18
|
+
return forAnnotatedType(def, {
|
|
19
|
+
final: (d) => {
|
|
20
|
+
const result = scalar(d.type.designType, value);
|
|
21
|
+
if (result !== NO_MATCH && d.type.value !== undefined && result !== d.type.value) return NO_MATCH;
|
|
22
|
+
return result;
|
|
23
|
+
},
|
|
24
|
+
phantom: () => value,
|
|
25
|
+
object: (d) => {
|
|
26
|
+
if (!isPlainObject(value)) return NO_MATCH;
|
|
27
|
+
return coerceProps(d, value);
|
|
28
|
+
},
|
|
29
|
+
array: (d) => Array.isArray(value) ? coerceItems(value, d.type.of) : NO_MATCH,
|
|
30
|
+
tuple: (d) => Array.isArray(value) && value.length === d.type.items.length ? coerceItems(value, undefined, d.type.items) : NO_MATCH,
|
|
31
|
+
union: (d) => {
|
|
32
|
+
for (const item of d.type.items) {
|
|
33
|
+
const result = coerce(item, value);
|
|
34
|
+
if (result !== NO_MATCH) return result;
|
|
35
|
+
}
|
|
36
|
+
return NO_MATCH;
|
|
37
|
+
},
|
|
38
|
+
intersection: (d) => {
|
|
39
|
+
let current = value;
|
|
40
|
+
for (const item of d.type.items) {
|
|
41
|
+
const result = coerce(item, current);
|
|
42
|
+
if (result === NO_MATCH) return NO_MATCH;
|
|
43
|
+
current = result;
|
|
44
|
+
}
|
|
45
|
+
return current;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function scalar(designType, value) {
|
|
50
|
+
switch (designType) {
|
|
51
|
+
case "string":
|
|
52
|
+
case "decimal": return typeof value === "string" ? value : NO_MATCH;
|
|
53
|
+
case "number": {
|
|
54
|
+
if (typeof value === "number") return value;
|
|
55
|
+
if (typeof value === "string") {
|
|
56
|
+
const trimmed = value.trim();
|
|
57
|
+
if (trimmed.length > 0) {
|
|
58
|
+
const parsed = Number(trimmed);
|
|
59
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return NO_MATCH;
|
|
63
|
+
}
|
|
64
|
+
case "boolean": {
|
|
65
|
+
if (typeof value === "boolean") return value;
|
|
66
|
+
if (value === "true" || value === "1") return true;
|
|
67
|
+
if (value === "false" || value === "0") return false;
|
|
68
|
+
return NO_MATCH;
|
|
69
|
+
}
|
|
70
|
+
case "object": return typeof value === "object" ? value : NO_MATCH;
|
|
71
|
+
case "any":
|
|
72
|
+
case "phantom": return value;
|
|
73
|
+
default: return NO_MATCH;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Coerces the props of a plain object. Copy-on-first-change: allocates the
|
|
78
|
+
* output object only when some prop actually coerced, else returns the input.
|
|
79
|
+
*/ function coerceProps(def, value) {
|
|
80
|
+
const keys = Object.keys(value);
|
|
81
|
+
let out;
|
|
82
|
+
for (let i = 0; i < keys.length; i++) {
|
|
83
|
+
const key = keys[i];
|
|
84
|
+
const input = value[key];
|
|
85
|
+
let propDef = def.type.props.get(key);
|
|
86
|
+
if (!propDef) {
|
|
87
|
+
for (const { pattern, def: patternDef } of def.type.propsPatterns) if (pattern.test(key)) {
|
|
88
|
+
propDef = patternDef;
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
let coerced = input;
|
|
93
|
+
if (propDef) {
|
|
94
|
+
const result = coerce(propDef, input);
|
|
95
|
+
if (result !== NO_MATCH) coerced = result;
|
|
96
|
+
}
|
|
97
|
+
if (out) out[key] = coerced;
|
|
98
|
+
else if (coerced !== input) {
|
|
99
|
+
out = {};
|
|
100
|
+
for (let j = 0; j < i; j++) out[keys[j]] = value[keys[j]];
|
|
101
|
+
out[key] = coerced;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return out ?? value;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Coerces array/tuple items against `of` (arrays) or `items[i]` (tuples).
|
|
108
|
+
* Copy-on-first-change, same as {@link coerceProps}.
|
|
109
|
+
*/ function coerceItems(value, of, items) {
|
|
110
|
+
let out;
|
|
111
|
+
for (let i = 0; i < value.length; i++) {
|
|
112
|
+
const input = value[i];
|
|
113
|
+
const result = coerce(of ?? items[i], input);
|
|
114
|
+
const coerced = result === NO_MATCH ? input : result;
|
|
115
|
+
if (out) out.push(coerced);
|
|
116
|
+
else if (coerced !== input) {
|
|
117
|
+
out = value.slice(0, i);
|
|
118
|
+
out.push(coerced);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return out ?? value;
|
|
122
|
+
}
|
|
123
|
+
function isPlainObject(value) {
|
|
124
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
125
|
+
const proto = Object.getPrototypeOf(value);
|
|
126
|
+
return proto === Object.prototype || proto === null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
//#endregion
|
|
3
130
|
//#region packages/typescript/src/runtime/default-value.ts
|
|
4
131
|
/**
|
|
5
132
|
* Attempts to resolve a value from the mode for the given annotated type.
|
|
@@ -480,4 +607,4 @@ function deserializeTypeDef(t, resolved) {
|
|
|
480
607
|
}
|
|
481
608
|
|
|
482
609
|
//#endregion
|
|
483
|
-
export { SERIALIZE_VERSION, Validator, ValidatorError, annotate, buildJsonSchema, cloneRefProp, createAnnotatedTypeNode, createDataFromAnnotatedType, defineAnnotatedType, deserializeAnnotatedType, detectDiscriminator, flattenAnnotatedType, forAnnotatedType, fromJsonSchema, isAnnotatedType, isAnnotatedTypeOfPrimitive, isPhantomType, mergeJsonSchemas, serializeAnnotatedType, throwFeatureDisabled };
|
|
610
|
+
export { SERIALIZE_VERSION, Validator, ValidatorError, annotate, buildJsonSchema, cloneRefProp, coerceForType, coerceScalar, createAnnotatedTypeNode, createDataFromAnnotatedType, defineAnnotatedType, deserializeAnnotatedType, detectDiscriminator, flattenAnnotatedType, forAnnotatedType, fromJsonSchema, isAnnotatedType, isAnnotatedTypeOfPrimitive, isPhantomType, mergeJsonSchemas, serializeAnnotatedType, throwFeatureDisabled };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atscript/typescript",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.84",
|
|
4
4
|
"description": "Atscript: typescript-gen support.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"annotations",
|
|
@@ -63,8 +63,8 @@
|
|
|
63
63
|
"./package.json": "./package.json"
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
|
-
"@moostjs/event-cli": "^0.6.
|
|
67
|
-
"moost": "^0.6.
|
|
66
|
+
"@moostjs/event-cli": "^0.6.32",
|
|
67
|
+
"moost": "^0.6.32"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
70
|
"@atscript/db": "^0.1.97",
|