sera-2 1.26.2__py3-none-any.whl → 1.26.4__py3-none-any.whl

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.
@@ -1,23 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
- import re
4
- from typing import Any, Callable
5
-
6
- from codegen.models import AST, ImportHelper, PredefinedFn, Program, expr, stmt
3
+ from codegen.models import PredefinedFn, Program, expr, stmt
7
4
  from codegen.models.var import DeferredVar
8
5
  from loguru import logger
9
6
 
10
7
  from sera.make.ts_frontend.make_class_schema import make_class_schema
8
+ from sera.make.ts_frontend.make_draft_model import make_draft
11
9
  from sera.make.ts_frontend.make_query import make_query
12
- from sera.make.ts_frontend.misc import TS_GLOBAL_IDENTS, get_normalizer
13
- from sera.misc import (
14
- assert_isinstance,
15
- assert_not_null,
16
- identity,
17
- to_camel_case,
18
- to_pascal_case,
19
- to_snake_case,
20
- )
10
+ from sera.misc import assert_isinstance, assert_not_null, to_camel_case, to_snake_case
21
11
  from sera.models import (
22
12
  Class,
23
13
  DataProperty,
@@ -27,7 +17,6 @@ from sera.models import (
27
17
  Schema,
28
18
  TsTypeWithDep,
29
19
  )
30
- from sera.typing import is_set
31
20
 
32
21
 
33
22
  def make_typescript_data_model(schema: Schema, target_pkg: Package):
@@ -43,11 +32,6 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
43
32
  idprop.get_data_model_datatype().get_typescript_type()
44
33
  )
45
34
 
46
- def clone_prop(prop: DataProperty | ObjectProperty, value: expr.Expr):
47
- # detect all complex types is hard, we can assume that any update to this does not mutate
48
- # the original object, then it's okay.
49
- return value
50
-
51
35
  def get_normal_deser_args(
52
36
  prop: DataProperty | ObjectProperty,
53
37
  ) -> expr.Expr:
@@ -308,772 +292,6 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
308
292
 
309
293
  pkg.module(cls.name).write(program)
310
294
 
311
- def make_draft(cls: Class, pkg: Package):
312
- if not cls.is_public:
313
- # skip classes that are not public
314
- return
315
-
316
- idprop = cls.get_id_property()
317
-
318
- draft_clsname = "Draft" + cls.name
319
- draft_validators = f"draft{cls.name}Validators"
320
-
321
- program = Program()
322
- program.import_(f"@.models.{pkg.dir.name}.{cls.name}.{cls.name}", True)
323
- program.import_("mobx.makeObservable", True)
324
- program.import_("mobx.observable", True)
325
- program.import_("mobx.action", True)
326
- program.import_("sera-db.validators", True)
327
-
328
- import_helper = ImportHelper(program, TS_GLOBAL_IDENTS)
329
-
330
- program.root(
331
- stmt.LineBreak(),
332
- stmt.TypescriptStatement(
333
- "const {getValidator, memoizeOneValidators} = validators;"
334
- ),
335
- stmt.LineBreak(),
336
- )
337
-
338
- # make sure that the property stale is not in existing properties
339
- if "stale" in cls.properties:
340
- raise ValueError(f"Class {cls.name} already has property stale")
341
-
342
- # information about class primary key
343
- cls_pk = None
344
- observable_args: list[tuple[expr.Expr, expr.ExprIdent]] = []
345
- prop_defs = []
346
- prop_validators: list[tuple[expr.ExprIdent, expr.Expr]] = []
347
- prop_constructor_assigns = []
348
- # attrs needed for the cls.create function
349
- create_args = []
350
- update_args = []
351
- ser_args = []
352
- to_record_args = []
353
- update_field_funcs: list[Callable[[AST], Any]] = []
354
-
355
- prop2tsname = {}
356
-
357
- for prop in cls.properties.values():
358
- # if prop.data.is_private:
359
- # # skip private fields as this is for APIs exchange
360
- # continue
361
-
362
- propname = to_camel_case(prop.name)
363
- if isinstance(prop, ObjectProperty) and prop.target.db is not None:
364
- propname = propname + "Id"
365
- prop2tsname[prop.name] = propname
366
-
367
- def _update_field_func(
368
- prop: DataProperty | ObjectProperty,
369
- propname: str,
370
- tstype: TsTypeWithDep,
371
- draft_clsname: str,
372
- ):
373
- return lambda ast: ast(
374
- stmt.LineBreak(),
375
- lambda ast01: ast01.func(
376
- f"update{to_pascal_case(prop.name)}",
377
- [
378
- DeferredVar.simple(
379
- "value",
380
- expr.ExprIdent(tstype.type),
381
- ),
382
- ],
383
- expr.ExprIdent(draft_clsname),
384
- comment=f"Update the `{prop.name}` field",
385
- )(
386
- stmt.AssignStatement(
387
- PredefinedFn.attr_getter(
388
- expr.ExprIdent("this"), expr.ExprIdent(propname)
389
- ),
390
- expr.ExprIdent("value"),
391
- ),
392
- stmt.AssignStatement(
393
- PredefinedFn.attr_getter(
394
- expr.ExprIdent("this"), expr.ExprIdent("stale")
395
- ),
396
- expr.ExprConstant(True),
397
- ),
398
- stmt.ReturnStatement(expr.ExprIdent("this")),
399
- ),
400
- )
401
-
402
- if isinstance(prop, DataProperty):
403
- tstype = prop.get_data_model_datatype().get_typescript_type()
404
- original_tstype = tstype
405
-
406
- if idprop is not None and prop.name == idprop.name:
407
- # use id type alias
408
- tstype = TsTypeWithDep(
409
- type=f"{cls.name}Id",
410
- spectype=tstype.spectype,
411
- deps=[f"@.models.{pkg.dir.name}.{cls.name}.{cls.name}Id"],
412
- )
413
- elif tstype.type not in schema.enums:
414
- # for none id & none enum properties, we need to include a type for "invalid" value
415
- tstype = _inject_type_for_invalid_value(tstype)
416
-
417
- if prop.is_optional:
418
- # convert type to optional
419
- tstype = tstype.as_optional_type()
420
- original_tstype = original_tstype.as_optional_type()
421
-
422
- for dep in tstype.deps:
423
- program.import_(dep, True)
424
-
425
- # however, if this is a primary key and auto-increment, we set a different default value
426
- # to be -1 to avoid start from 0
427
- if (
428
- prop.db is not None
429
- and prop.db.is_primary_key
430
- and prop.db.is_auto_increment
431
- ):
432
- create_propvalue = expr.ExprConstant(-1)
433
- elif is_set(prop.data.default_value):
434
- create_propvalue = expr.ExprConstant(prop.data.default_value)
435
- else:
436
- if tstype.type in idprop_aliases:
437
- create_propvalue = idprop_aliases[tstype.type].get_default()
438
- elif tstype.type in schema.enums:
439
- enum_value_name = next(
440
- iter(schema.enums[tstype.type].values.values())
441
- ).name
442
- assert isinstance(enum_value_name, str), enum_value_name
443
- create_propvalue = expr.ExprIdent(
444
- tstype.type + "." + enum_value_name
445
- )
446
- else:
447
- create_propvalue = tstype.get_default()
448
-
449
- prop_validators.append(
450
- (
451
- expr.ExprIdent(propname),
452
- expr.ExprFuncCall(
453
- expr.ExprIdent("getValidator"),
454
- [
455
- PredefinedFn.list(
456
- [
457
- expr.ExprConstant(
458
- constraint.get_typescript_constraint()
459
- )
460
- for constraint in prop.data.constraints
461
- ]
462
- ),
463
- ],
464
- ),
465
- )
466
- )
467
-
468
- if prop.db is not None and prop.db.is_primary_key:
469
- # for checking if the primary key is from the database or default (create_propvalue)
470
- cls_pk = (expr.ExprIdent(propname), create_propvalue)
471
-
472
- # if this field is private, we cannot get it from the normal record
473
- # we have to create a default value for it.
474
- if prop.data.is_private:
475
- update_propvalue = create_propvalue
476
- else:
477
- update_propvalue = PredefinedFn.attr_getter(
478
- expr.ExprIdent("record"), expr.ExprIdent(propname)
479
- )
480
-
481
- if (
482
- original_tstype.type != tstype.type
483
- and tstype.type != f"{cls.name}Id"
484
- ):
485
- norm_func = get_norm_func(original_tstype, import_helper)
486
- else:
487
- norm_func = identity
488
-
489
- ser_args.append(
490
- (
491
- expr.ExprIdent(prop.name),
492
- (
493
- expr.ExprTernary(
494
- PredefinedFn.attr_getter(
495
- expr.ExprFuncCall(
496
- PredefinedFn.attr_getter(
497
- expr.ExprIdent(draft_validators),
498
- expr.ExprIdent(propname),
499
- ),
500
- [
501
- PredefinedFn.attr_getter(
502
- expr.ExprIdent("this"),
503
- expr.ExprIdent(propname),
504
- )
505
- ],
506
- ),
507
- expr.ExprIdent("isValid"),
508
- ),
509
- original_tstype.get_json_ser_func(
510
- norm_func(
511
- PredefinedFn.attr_getter(
512
- expr.ExprIdent("this"),
513
- expr.ExprIdent(propname),
514
- )
515
- )
516
- ),
517
- expr.ExprIdent("undefined"),
518
- )
519
- if prop.is_optional
520
- else original_tstype.get_json_ser_func(
521
- norm_func(
522
- PredefinedFn.attr_getter(
523
- expr.ExprIdent("this"), expr.ExprIdent(propname)
524
- )
525
- )
526
- )
527
- ),
528
- )
529
- )
530
-
531
- if not prop.data.is_private:
532
- # private property does not include in the public record
533
- to_record_args.append(
534
- (
535
- expr.ExprIdent(propname),
536
- (
537
- expr.ExprTernary(
538
- PredefinedFn.attr_getter(
539
- expr.ExprFuncCall(
540
- PredefinedFn.attr_getter(
541
- expr.ExprIdent(draft_validators),
542
- expr.ExprIdent(propname),
543
- ),
544
- [
545
- PredefinedFn.attr_getter(
546
- expr.ExprIdent("this"),
547
- expr.ExprIdent(propname),
548
- )
549
- ],
550
- ),
551
- expr.ExprIdent("isValid"),
552
- ),
553
- norm_func(
554
- PredefinedFn.attr_getter(
555
- expr.ExprIdent("this"),
556
- expr.ExprIdent(propname),
557
- )
558
- ),
559
- expr.ExprIdent("undefined"),
560
- )
561
- if prop.is_optional
562
- else norm_func(
563
- PredefinedFn.attr_getter(
564
- expr.ExprIdent("this"), expr.ExprIdent(propname)
565
- )
566
- )
567
- ),
568
- )
569
- )
570
- if not (prop.db is not None and prop.db.is_primary_key):
571
- # skip observable for primary key as it is not needed
572
- observable_args.append(
573
- (
574
- expr.ExprIdent(propname),
575
- expr.ExprIdent("observable"),
576
- )
577
- )
578
- observable_args.append(
579
- (
580
- expr.ExprIdent(f"update{to_pascal_case(prop.name)}"),
581
- expr.ExprIdent("action"),
582
- )
583
- )
584
- else:
585
- assert isinstance(prop, ObjectProperty)
586
- if prop.target.db is not None:
587
- # this class is stored in the database, we store the id instead
588
- tstype = TsTypeWithDep(
589
- type=f"{prop.target.name}Id",
590
- spectype=assert_not_null(prop.target.get_id_property())
591
- .get_data_model_datatype()
592
- .get_typescript_type()
593
- .spectype,
594
- deps=[
595
- f"@.models.{prop.target.get_tsmodule_name()}.{prop.target.name}.{prop.target.name}Id"
596
- ],
597
- )
598
- if prop.cardinality.is_star_to_many():
599
- tstype = tstype.as_list_type()
600
- create_propvalue = expr.ExprConstant([])
601
- else:
602
- if prop.is_optional:
603
- # convert type to optional - for list type, we don't need to do this
604
- # as we will use empty list as no value
605
- tstype = tstype.as_optional_type()
606
- # if target class has an auto-increment primary key, we set a different default value
607
- # to be -1 to avoid start from 0
608
- target_idprop = prop.target.get_id_property()
609
- if (
610
- target_idprop is not None
611
- and target_idprop.db is not None
612
- and target_idprop.db.is_primary_key
613
- and target_idprop.db.is_auto_increment
614
- ):
615
- create_propvalue = expr.ExprConstant(-1)
616
- else:
617
- assert tstype.type in idprop_aliases
618
- create_propvalue = idprop_aliases[tstype.type].get_default()
619
-
620
- update_propvalue = PredefinedFn.attr_getter(
621
- expr.ExprIdent("record"), expr.ExprIdent(propname)
622
- )
623
- ser_args.append(
624
- (
625
- expr.ExprIdent(prop.name + "_id"),
626
- PredefinedFn.attr_getter(
627
- expr.ExprIdent("this"), expr.ExprIdent(propname)
628
- ),
629
- )
630
- )
631
-
632
- if not prop.data.is_private:
633
- # private property does not include in the public record
634
- to_record_args.append(
635
- (
636
- expr.ExprIdent(propname),
637
- PredefinedFn.attr_getter(
638
- expr.ExprIdent("this"), expr.ExprIdent(propname)
639
- ),
640
- )
641
- )
642
- else:
643
- # we are going to store the whole object
644
- tstype = TsTypeWithDep(
645
- type=f"Draft{prop.target.name}",
646
- spectype=f"Draft{prop.target.name}",
647
- deps=[
648
- f"@.models.{prop.target.get_tsmodule_name()}.Draft{prop.target.name}.Draft{prop.target.name}"
649
- ],
650
- )
651
- if prop.cardinality.is_star_to_many():
652
- create_propvalue = expr.ExprConstant([])
653
- update_propvalue = PredefinedFn.map_list(
654
- PredefinedFn.attr_getter(
655
- expr.ExprIdent("record"), expr.ExprIdent(propname)
656
- ),
657
- lambda item: expr.ExprMethodCall(
658
- expr.ExprIdent(tstype.type),
659
- "update",
660
- [item],
661
- ),
662
- )
663
- ser_args.append(
664
- (
665
- expr.ExprIdent(prop.name),
666
- PredefinedFn.map_list(
667
- PredefinedFn.attr_getter(
668
- expr.ExprIdent("this"), expr.ExprIdent(propname)
669
- ),
670
- lambda item: expr.ExprMethodCall(item, "ser", []),
671
- (
672
- (
673
- lambda item: PredefinedFn.attr_getter(
674
- expr.ExprFuncCall(
675
- PredefinedFn.attr_getter(
676
- expr.ExprIdent(
677
- draft_validators
678
- ),
679
- expr.ExprIdent(propname),
680
- ),
681
- [item],
682
- ),
683
- expr.ExprIdent("isValid"),
684
- )
685
- )
686
- if prop.is_optional
687
- else None
688
- ),
689
- ),
690
- )
691
- )
692
-
693
- if not prop.data.is_private:
694
- # private property does not include in the public record
695
- to_record_args.append(
696
- (
697
- expr.ExprIdent(propname),
698
- PredefinedFn.map_list(
699
- PredefinedFn.attr_getter(
700
- expr.ExprIdent("this"),
701
- expr.ExprIdent(propname),
702
- ),
703
- lambda item: expr.ExprMethodCall(
704
- item, "toRecord", []
705
- ),
706
- (
707
- (
708
- lambda item: PredefinedFn.attr_getter(
709
- expr.ExprFuncCall(
710
- PredefinedFn.attr_getter(
711
- expr.ExprIdent(
712
- draft_validators
713
- ),
714
- expr.ExprIdent(propname),
715
- ),
716
- [item],
717
- ),
718
- expr.ExprIdent("isValid"),
719
- )
720
- )
721
- if prop.is_optional
722
- else None
723
- ),
724
- ),
725
- )
726
- )
727
-
728
- tstype = tstype.as_list_type()
729
- else:
730
- create_propvalue = expr.ExprMethodCall(
731
- expr.ExprIdent(tstype.type),
732
- "create",
733
- [],
734
- )
735
- update_propvalue = expr.ExprMethodCall(
736
- expr.ExprIdent(tstype.type),
737
- "update",
738
- [
739
- PredefinedFn.attr_getter(
740
- expr.ExprIdent("record"), expr.ExprIdent(propname)
741
- ),
742
- ],
743
- )
744
-
745
- if prop.is_optional:
746
- ser_args.append(
747
- (
748
- expr.ExprIdent(prop.name),
749
- expr.ExprTernary(
750
- PredefinedFn.attr_getter(
751
- expr.ExprFuncCall(
752
- PredefinedFn.attr_getter(
753
- expr.ExprIdent(draft_validators),
754
- expr.ExprIdent(propname),
755
- ),
756
- [
757
- PredefinedFn.attr_getter(
758
- expr.ExprIdent("this"),
759
- expr.ExprIdent(propname),
760
- )
761
- ],
762
- ),
763
- expr.ExprIdent("isValid"),
764
- ),
765
- expr.ExprMethodCall(
766
- PredefinedFn.attr_getter(
767
- expr.ExprIdent("this"),
768
- expr.ExprIdent(propname),
769
- ),
770
- "ser",
771
- [],
772
- ),
773
- expr.ExprIdent("undefined"),
774
- ),
775
- )
776
- )
777
- if not prop.data.is_private:
778
- # private property does not include in the public record
779
- to_record_args.append(
780
- (
781
- expr.ExprIdent(propname),
782
- expr.ExprTernary(
783
- PredefinedFn.attr_getter(
784
- expr.ExprFuncCall(
785
- PredefinedFn.attr_getter(
786
- expr.ExprIdent(
787
- draft_validators
788
- ),
789
- expr.ExprIdent(propname),
790
- ),
791
- [
792
- PredefinedFn.attr_getter(
793
- expr.ExprIdent("this"),
794
- expr.ExprIdent(propname),
795
- )
796
- ],
797
- ),
798
- expr.ExprIdent("isValid"),
799
- ),
800
- expr.ExprMethodCall(
801
- PredefinedFn.attr_getter(
802
- expr.ExprIdent("this"),
803
- expr.ExprIdent(propname),
804
- ),
805
- "toRecord",
806
- [],
807
- ),
808
- expr.ExprIdent("undefined"),
809
- ),
810
- )
811
- )
812
- else:
813
- ser_args.append(
814
- (
815
- expr.ExprIdent(prop.name),
816
- expr.ExprMethodCall(
817
- PredefinedFn.attr_getter(
818
- expr.ExprIdent("this"),
819
- expr.ExprIdent(propname),
820
- ),
821
- "ser",
822
- [],
823
- ),
824
- )
825
- )
826
- if not prop.data.is_private:
827
- # private property does not include in the public record
828
- to_record_args.append(
829
- (
830
- expr.ExprIdent(propname),
831
- expr.ExprMethodCall(
832
- PredefinedFn.attr_getter(
833
- expr.ExprIdent("this"),
834
- expr.ExprIdent(propname),
835
- ),
836
- "toRecord",
837
- [],
838
- ),
839
- )
840
- )
841
-
842
- if prop.is_optional:
843
- # convert type to optional - for list type, we don't need to do this
844
- # as we will use empty list as no value
845
- tstype = tstype.as_optional_type()
846
-
847
- for dep in tstype.deps:
848
- program.import_(
849
- dep,
850
- True,
851
- )
852
-
853
- observable_args.append(
854
- (
855
- expr.ExprIdent(propname),
856
- expr.ExprIdent("observable"),
857
- )
858
- )
859
- observable_args.append(
860
- (
861
- expr.ExprIdent(f"update{to_pascal_case(prop.name)}"),
862
- expr.ExprIdent("action"),
863
- )
864
- )
865
-
866
- # TODO: fix me! fix me what?? next time give more context.
867
- prop_validators.append(
868
- (
869
- expr.ExprIdent(propname),
870
- expr.ExprFuncCall(
871
- expr.ExprIdent("getValidator"),
872
- [
873
- PredefinedFn.list(
874
- [
875
- expr.ExprConstant(
876
- constraint.get_typescript_constraint()
877
- )
878
- for constraint in prop.data.constraints
879
- ]
880
- ),
881
- ],
882
- ),
883
- )
884
- )
885
-
886
- prop_defs.append(stmt.DefClassVarStatement(propname, tstype.type))
887
- prop_constructor_assigns.append(
888
- stmt.AssignStatement(
889
- PredefinedFn.attr_getter(
890
- expr.ExprIdent("this"),
891
- expr.ExprIdent(propname),
892
- ),
893
- expr.ExprIdent("args." + propname),
894
- )
895
- )
896
- create_args.append((expr.ExprIdent(propname), create_propvalue))
897
- update_args.append(
898
- (
899
- expr.ExprIdent(propname),
900
- # if this is mutable property, we need to copy to make it immutable.
901
- clone_prop(prop, update_propvalue),
902
- )
903
- )
904
- update_field_funcs.append(
905
- _update_field_func(prop, propname, tstype, draft_clsname)
906
- )
907
-
908
- prop_defs.append(stmt.DefClassVarStatement("stale", "boolean"))
909
- prop_constructor_assigns.append(
910
- stmt.AssignStatement(
911
- PredefinedFn.attr_getter(
912
- expr.ExprIdent("this"), expr.ExprIdent("stale")
913
- ),
914
- expr.ExprIdent("args.stale"),
915
- )
916
- )
917
- observable_args.append(
918
- (
919
- expr.ExprIdent("stale"),
920
- expr.ExprIdent("observable"),
921
- )
922
- )
923
- create_args.append(
924
- (
925
- expr.ExprIdent("stale"),
926
- expr.ExprConstant(True),
927
- ),
928
- )
929
- update_args.append(
930
- (
931
- expr.ExprIdent("stale"),
932
- expr.ExprConstant(False),
933
- ),
934
- )
935
- observable_args.sort(key=lambda x: {"observable": 0, "action": 1}[x[1].ident])
936
-
937
- validators = expr.ExprFuncCall(
938
- expr.ExprIdent("memoizeOneValidators"), [PredefinedFn.dict(prop_validators)]
939
- )
940
-
941
- program.root(
942
- lambda ast00: ast00.class_like(
943
- "interface",
944
- draft_clsname + "ConstructorArgs",
945
- )(*prop_defs),
946
- stmt.LineBreak(),
947
- lambda ast10: ast10.class_(draft_clsname)(
948
- *prop_defs,
949
- stmt.LineBreak(),
950
- lambda ast10: ast10.func(
951
- "constructor",
952
- [
953
- DeferredVar.simple(
954
- "args",
955
- expr.ExprIdent(draft_clsname + "ConstructorArgs"),
956
- ),
957
- ],
958
- )(
959
- *prop_constructor_assigns,
960
- stmt.LineBreak(),
961
- stmt.SingleExprStatement(
962
- expr.ExprFuncCall(
963
- expr.ExprIdent("makeObservable"),
964
- [
965
- expr.ExprIdent("this"),
966
- PredefinedFn.dict(observable_args),
967
- ],
968
- )
969
- ),
970
- ),
971
- stmt.LineBreak(),
972
- lambda ast11: (
973
- ast11.func(
974
- "isNewRecord",
975
- [],
976
- expr.ExprIdent("boolean"),
977
- comment="Check if this draft is for creating a new record",
978
- )(
979
- stmt.ReturnStatement(
980
- expr.ExprEqual(
981
- PredefinedFn.attr_getter(
982
- expr.ExprIdent("this"), cls_pk[0]
983
- ),
984
- cls_pk[1],
985
- )
986
- )
987
- )
988
- if cls_pk is not None
989
- else None
990
- ),
991
- stmt.LineBreak(),
992
- lambda ast12: ast12.func(
993
- "create",
994
- [],
995
- expr.ExprIdent(draft_clsname),
996
- is_static=True,
997
- comment="Make a new draft for creating a new record",
998
- )(
999
- stmt.ReturnStatement(
1000
- expr.ExprNewInstance(
1001
- expr.ExprIdent(draft_clsname),
1002
- [PredefinedFn.dict(create_args)],
1003
- )
1004
- ),
1005
- ),
1006
- stmt.LineBreak(),
1007
- lambda ast13: ast13.func(
1008
- "update",
1009
- [DeferredVar.simple("record", expr.ExprIdent(cls.name))],
1010
- expr.ExprIdent(draft_clsname),
1011
- is_static=True,
1012
- comment="Make a new draft for updating an existing record",
1013
- )(
1014
- stmt.ReturnStatement(
1015
- expr.ExprNewInstance(
1016
- expr.ExprIdent(draft_clsname),
1017
- [PredefinedFn.dict(update_args)],
1018
- )
1019
- ),
1020
- ),
1021
- *update_field_funcs,
1022
- stmt.LineBreak(),
1023
- lambda ast14: ast14.func(
1024
- "isValid",
1025
- [],
1026
- expr.ExprIdent("boolean"),
1027
- comment="Check if the draft is valid (only check the required fields as the non-required fields if it's invalid will be set to undefined)",
1028
- )(
1029
- stmt.ReturnStatement(
1030
- expr.ExprRawTypescript(
1031
- " && ".join(
1032
- f"{draft_validators}.{prop2tsname[prop.name]}(this.{prop2tsname[prop.name]}).isValid"
1033
- for prop in cls.properties.values()
1034
- if not prop.is_optional
1035
- )
1036
- if any(
1037
- not prop.is_optional for prop in cls.properties.values()
1038
- )
1039
- else "true"
1040
- )
1041
- )
1042
- ),
1043
- stmt.LineBreak(),
1044
- lambda ast15: ast15.func(
1045
- "ser",
1046
- [],
1047
- expr.ExprIdent("any"),
1048
- comment="Serialize the draft to communicate with the server. `isValid` must be called first to ensure all data is valid",
1049
- )(
1050
- stmt.ReturnStatement(
1051
- PredefinedFn.dict(ser_args),
1052
- ),
1053
- ),
1054
- stmt.LineBreak(),
1055
- lambda ast16: ast16.func(
1056
- "toRecord",
1057
- [],
1058
- expr.ExprIdent(cls.name),
1059
- comment="Convert the draft to a normal record. `isValid` must be called first to ensure all data is valid",
1060
- )(
1061
- stmt.ReturnStatement(
1062
- expr.ExprNewInstance(
1063
- expr.ExprIdent(cls.name),
1064
- [PredefinedFn.dict(to_record_args)],
1065
- ),
1066
- )
1067
- ),
1068
- ),
1069
- stmt.LineBreak(),
1070
- stmt.TypescriptStatement(
1071
- f"export const {draft_validators} = " + validators.to_typescript() + ";"
1072
- ),
1073
- )
1074
-
1075
- pkg.module("Draft" + cls.name).write(program)
1076
-
1077
295
  def make_table(cls: Class, pkg: Package):
1078
296
  if not cls.is_public or cls.db is None:
1079
297
  # skip classes that are not public and not stored in the database
@@ -1201,7 +419,7 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
1201
419
  for cls in schema.topological_sort():
1202
420
  pkg = target_pkg.pkg(cls.get_tsmodule_name())
1203
421
  make_normal(cls, pkg)
1204
- make_draft(cls, pkg)
422
+ make_draft(schema, cls, pkg, idprop_aliases)
1205
423
  make_query(schema, cls, pkg)
1206
424
  make_table(cls, pkg)
1207
425
  make_class_schema(schema, cls, pkg)
@@ -1245,75 +463,3 @@ def make_typescript_enum(schema: Schema, target_pkg: Package):
1245
463
  ),
1246
464
  )
1247
465
  enum_pkg.module("index").write(program)
1248
-
1249
-
1250
- def _inject_type_for_invalid_value(tstype: TsTypeWithDep) -> TsTypeWithDep:
1251
- """
1252
- Inject a type for "invalid" values into the given TypeScript type. For context, see the discussion in Data Modeling Problems:
1253
- What would be an appropriate type for an invalid value? Since it's user input, it will be a string type.
1254
-
1255
- However, there are some exceptions such as boolean type, which will always be valid and do not need injection.
1256
-
1257
- If the type already includes `string` type, no changes are needed. Otherwise, we add `string` to the type. For example:
1258
- - (number | undefined) -> (number | undefined | string)
1259
- - number | undefined -> number | undefined | string
1260
- - number[] -> (number | string)[]
1261
- - (number | undefined)[] -> (number | undefined | string)[]
1262
- """
1263
- if tstype.type == "boolean":
1264
- return tstype
1265
-
1266
- # TODO: fix me and make it more robust!
1267
- m = re.match(r"(\(?[a-zA-Z \|]+\)?)(\[\])", tstype.type)
1268
- if m is not None:
1269
- # This is an array type, add string to the inner type
1270
- inner_type = m.group(1)
1271
- inner_spectype = assert_not_null(
1272
- re.match(r"(\(?[a-zA-Z \|]+\)?)(\[\])", tstype.spectype)
1273
- ).group(1)
1274
- if "string" not in inner_type:
1275
- if inner_type.startswith("(") and inner_type.endswith(")"):
1276
- # Already has parentheses
1277
- inner_type = f"{inner_type[:-1]} | string)"
1278
- inner_spectype = f"{inner_spectype[:-1]} | string)"
1279
- else:
1280
- # Need to add parentheses
1281
- inner_type = f"({inner_type} | string)"
1282
- inner_spectype = f"({inner_spectype} | string)"
1283
- return TsTypeWithDep(inner_type + "[]", inner_spectype + "[]", tstype.deps)
1284
-
1285
- m = re.match(r"^\(?[a-zA-Z \|]+\)?$", tstype.type)
1286
- if m is not None:
1287
- if "string" not in tstype.type:
1288
- if tstype.type.startswith("(") and tstype.type.endswith(")"):
1289
- # Already has parentheses
1290
- new_type = f"{tstype.type[:-1]} | string)"
1291
- new_spectype = f"{tstype.spectype[:-1]} | string)"
1292
- else:
1293
- # Needs parentheses for clarity
1294
- new_type = f"({tstype.type} | string)"
1295
- new_spectype = f"({tstype.spectype} | string)"
1296
- return TsTypeWithDep(new_type, new_spectype, tstype.deps)
1297
- return tstype
1298
-
1299
- raise NotImplementedError(tstype.type)
1300
-
1301
-
1302
- def get_norm_func(
1303
- tstype: TsTypeWithDep, import_helper: ImportHelper
1304
- ) -> Callable[[expr.Expr], expr.Expr]:
1305
- """
1306
- Get the normalizer function for the given TypeScript type.
1307
- If no normalizer is available, return None.
1308
- """
1309
- norm_func = get_normalizer(tstype, import_helper)
1310
- if norm_func is not None:
1311
-
1312
- def modify_expr(value: expr.Expr) -> expr.Expr:
1313
- return expr.ExprFuncCall(
1314
- norm_func,
1315
- [value],
1316
- )
1317
-
1318
- return modify_expr
1319
- return identity # Return the value as is if no normalizer is available