@atscript/typescript 0.1.83 → 0.1.85

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 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
- async dbSync(configFile, dryRun, yes, force, safe) {
2699
- const config = await getConfig(configFile, this.logger);
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.logger.log(`No types with @db.table or @db.view found. Nothing to sync.`);
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
- if (plan.status === "up-to-date") {
2718
- this.printer.planUpToDate(plan);
2719
- return;
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
- const hasDestructive = plan.entries.some((e) => e.destructive);
2722
- const hasChanges = plan.entries.some((e) => e.hasChanges);
2723
- const hasErrors = plan.entries.some((e) => e.hasErrors);
2724
- this.printer.plan(plan);
2725
- if (hasErrors) {
2726
- this.logger.error(`Schema has errors. Fix the issues above before syncing.`);
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.logger.log(`Compiling .as files...`);
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.logger.log(`Resolving database space from callback...`);
2882
+ this.info(`Resolving database space from callback...`);
2799
2883
  return await dbConfig();
2800
2884
  }
2801
2885
  const { adapter, connection, options } = dbConfig;
2802
- this.logger.log(`Using adapter: ${"\x1B[36m"}${adapter}${"\x1B[39m"}`);
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.83",
3
+ "version": "0.1.85",
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.31",
67
- "moost": "^0.6.31"
66
+ "@moostjs/event-cli": "^0.6.33",
67
+ "moost": "^0.6.33"
68
68
  },
69
69
  "devDependencies": {
70
70
  "@atscript/db": "^0.1.97",