@famgia/omnify-laravel 0.0.78 → 0.0.80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -2571,6 +2571,951 @@ function getFactoryPath(factory) {
2571
2571
  // src/plugin.ts
2572
2572
  var import_node_fs = require("fs");
2573
2573
  var import_node_path = require("path");
2574
+
2575
+ // src/request/generator.ts
2576
+ var import_omnify_types3 = require("@famgia/omnify-types");
2577
+ var DEFAULT_OPTIONS2 = {
2578
+ baseRequestNamespace: "App\\Http\\Requests\\OmnifyBase",
2579
+ requestNamespace: "App\\Http\\Requests",
2580
+ baseRequestPath: "app/Http/Requests/OmnifyBase",
2581
+ requestPath: "app/Http/Requests",
2582
+ modelNamespace: "App\\Models",
2583
+ customTypes: /* @__PURE__ */ new Map(),
2584
+ locale: "en"
2585
+ };
2586
+ var SKIP_FIELDS = /* @__PURE__ */ new Set([
2587
+ "id",
2588
+ "created_at",
2589
+ "updated_at",
2590
+ "deleted_at",
2591
+ "remember_token",
2592
+ "email_verified_at"
2593
+ ]);
2594
+ function resolveOptions3(options) {
2595
+ return {
2596
+ baseRequestNamespace: options?.baseRequestNamespace ?? DEFAULT_OPTIONS2.baseRequestNamespace,
2597
+ requestNamespace: options?.requestNamespace ?? DEFAULT_OPTIONS2.requestNamespace,
2598
+ baseRequestPath: options?.baseRequestPath ?? DEFAULT_OPTIONS2.baseRequestPath,
2599
+ requestPath: options?.requestPath ?? DEFAULT_OPTIONS2.requestPath,
2600
+ modelNamespace: options?.modelNamespace ?? DEFAULT_OPTIONS2.modelNamespace,
2601
+ customTypes: options?.customTypes ?? /* @__PURE__ */ new Map(),
2602
+ locale: options?.locale ?? DEFAULT_OPTIONS2.locale
2603
+ };
2604
+ }
2605
+ function escapePhpString2(str) {
2606
+ return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
2607
+ }
2608
+ function getDisplayName(displayName, locale, fallback) {
2609
+ if (!displayName) return fallback;
2610
+ if (typeof displayName === "string") return displayName;
2611
+ if ((0, import_omnify_types3.isLocaleMap)(displayName)) {
2612
+ return displayName[locale] ?? displayName["en"] ?? fallback;
2613
+ }
2614
+ return fallback;
2615
+ }
2616
+ function getModuleName(schema) {
2617
+ if (schema.module) {
2618
+ return schema.module;
2619
+ }
2620
+ return "";
2621
+ }
2622
+ function generateStoreRules(propName, propDef, schema, schemas, options) {
2623
+ const rules = [];
2624
+ const snakeName = toSnakeCase(propName);
2625
+ const prop = propDef;
2626
+ const tableName = schema.options?.tableName ?? pluralize(toSnakeCase(schema.name));
2627
+ const isNullable2 = prop.nullable === true;
2628
+ if (!isNullable2) {
2629
+ rules.push("'required'");
2630
+ } else {
2631
+ rules.push("'nullable'");
2632
+ }
2633
+ switch (propDef.type) {
2634
+ case "String":
2635
+ case "Email":
2636
+ case "Password":
2637
+ rules.push("'string'");
2638
+ const length = prop.length ?? 255;
2639
+ rules.push(`'max:${length}'`);
2640
+ if (propDef.type === "Email") {
2641
+ rules.push("'email'");
2642
+ }
2643
+ break;
2644
+ case "Text":
2645
+ case "MediumText":
2646
+ case "LongText":
2647
+ rules.push("'string'");
2648
+ break;
2649
+ case "TinyInt":
2650
+ case "Int":
2651
+ case "BigInt":
2652
+ rules.push("'integer'");
2653
+ if (prop.min !== void 0) {
2654
+ rules.push(`'min:${prop.min}'`);
2655
+ }
2656
+ if (prop.max !== void 0) {
2657
+ rules.push(`'max:${prop.max}'`);
2658
+ }
2659
+ break;
2660
+ case "Float":
2661
+ case "Decimal":
2662
+ rules.push("'numeric'");
2663
+ if (prop.min !== void 0) {
2664
+ rules.push(`'min:${prop.min}'`);
2665
+ }
2666
+ if (prop.max !== void 0) {
2667
+ rules.push(`'max:${prop.max}'`);
2668
+ }
2669
+ break;
2670
+ case "Boolean":
2671
+ rules.push("'boolean'");
2672
+ break;
2673
+ case "Date":
2674
+ rules.push("'date'");
2675
+ break;
2676
+ case "DateTime":
2677
+ case "Timestamp":
2678
+ rules.push("'date'");
2679
+ break;
2680
+ case "Json":
2681
+ rules.push("'array'");
2682
+ break;
2683
+ case "Enum":
2684
+ case "EnumRef":
2685
+ rules.push("'string'");
2686
+ if (prop.enum && Array.isArray(prop.enum)) {
2687
+ const values = prop.enum.map((v) => `'${v}'`).join(", ");
2688
+ rules.push(`Rule::in([${values}])`);
2689
+ }
2690
+ break;
2691
+ case "Association":
2692
+ const assoc = propDef;
2693
+ if (assoc.relation === "ManyToOne" || assoc.relation === "OneToOne") {
2694
+ if (assoc.target) {
2695
+ const targetSchema = schemas[assoc.target];
2696
+ const targetTable = targetSchema?.options?.tableName ?? pluralize(toSnakeCase(assoc.target));
2697
+ rules.push("'integer'");
2698
+ rules.push(`'exists:${targetTable},id'`);
2699
+ }
2700
+ }
2701
+ break;
2702
+ default:
2703
+ const customType = options.customTypes.get(propDef.type);
2704
+ if (customType && !customType.compound) {
2705
+ if (propDef.type === "JapanesePhone") {
2706
+ rules.push("'string'");
2707
+ rules.push("'max:15'");
2708
+ rules.push("'regex:/^\\d{2,4}-\\d{2,4}-\\d{4}$/'");
2709
+ } else if (propDef.type === "JapanesePostalCode") {
2710
+ rules.push("'string'");
2711
+ rules.push("'max:8'");
2712
+ rules.push("'regex:/^\\d{3}-\\d{4}$/'");
2713
+ } else {
2714
+ const sqlType = customType.sql?.sqlType ?? "VARCHAR";
2715
+ const sqlLength = customType.sql?.length ?? 255;
2716
+ if (sqlType === "VARCHAR" || sqlType === "TEXT") {
2717
+ rules.push("'string'");
2718
+ if (sqlType === "VARCHAR") {
2719
+ rules.push(`'max:${sqlLength}'`);
2720
+ }
2721
+ } else if (sqlType === "INT" || sqlType === "TINYINT" || sqlType === "BIGINT") {
2722
+ rules.push("'integer'");
2723
+ } else if (sqlType === "DECIMAL" || sqlType === "FLOAT") {
2724
+ rules.push("'numeric'");
2725
+ }
2726
+ }
2727
+ }
2728
+ break;
2729
+ }
2730
+ if (prop.unique === true) {
2731
+ rules.push(`'unique:${tableName}'`);
2732
+ }
2733
+ return rules;
2734
+ }
2735
+ function generateUpdateRules(propName, propDef, schema, schemas, options) {
2736
+ const rules = [];
2737
+ const snakeName = toSnakeCase(propName);
2738
+ const prop = propDef;
2739
+ const tableName = schema.options?.tableName ?? pluralize(toSnakeCase(schema.name));
2740
+ const modelVar = toSnakeCase(schema.name);
2741
+ rules.push("'sometimes'");
2742
+ switch (propDef.type) {
2743
+ case "String":
2744
+ case "Email":
2745
+ case "Password":
2746
+ rules.push("'string'");
2747
+ const length = prop.length ?? 255;
2748
+ rules.push(`'max:${length}'`);
2749
+ if (propDef.type === "Email") {
2750
+ rules.push("'email'");
2751
+ }
2752
+ break;
2753
+ case "Text":
2754
+ case "MediumText":
2755
+ case "LongText":
2756
+ rules.push("'string'");
2757
+ break;
2758
+ case "TinyInt":
2759
+ case "Int":
2760
+ case "BigInt":
2761
+ rules.push("'integer'");
2762
+ if (prop.min !== void 0) {
2763
+ rules.push(`'min:${prop.min}'`);
2764
+ }
2765
+ if (prop.max !== void 0) {
2766
+ rules.push(`'max:${prop.max}'`);
2767
+ }
2768
+ break;
2769
+ case "Float":
2770
+ case "Decimal":
2771
+ rules.push("'numeric'");
2772
+ if (prop.min !== void 0) {
2773
+ rules.push(`'min:${prop.min}'`);
2774
+ }
2775
+ if (prop.max !== void 0) {
2776
+ rules.push(`'max:${prop.max}'`);
2777
+ }
2778
+ break;
2779
+ case "Boolean":
2780
+ rules.push("'boolean'");
2781
+ break;
2782
+ case "Date":
2783
+ rules.push("'date'");
2784
+ break;
2785
+ case "DateTime":
2786
+ case "Timestamp":
2787
+ rules.push("'date'");
2788
+ break;
2789
+ case "Json":
2790
+ rules.push("'array'");
2791
+ break;
2792
+ case "Enum":
2793
+ case "EnumRef":
2794
+ rules.push("'string'");
2795
+ if (prop.enum && Array.isArray(prop.enum)) {
2796
+ const values = prop.enum.map((v) => `'${v}'`).join(", ");
2797
+ rules.push(`Rule::in([${values}])`);
2798
+ }
2799
+ break;
2800
+ case "Association":
2801
+ const assoc = propDef;
2802
+ if (assoc.relation === "ManyToOne" || assoc.relation === "OneToOne") {
2803
+ if (assoc.target) {
2804
+ const targetSchema = schemas[assoc.target];
2805
+ const targetTable = targetSchema?.options?.tableName ?? pluralize(toSnakeCase(assoc.target));
2806
+ rules.push("'integer'");
2807
+ rules.push(`'exists:${targetTable},id'`);
2808
+ }
2809
+ }
2810
+ break;
2811
+ default:
2812
+ const customType = options.customTypes.get(propDef.type);
2813
+ if (customType && !customType.compound) {
2814
+ if (propDef.type === "JapanesePhone") {
2815
+ rules.push("'string'");
2816
+ rules.push("'max:15'");
2817
+ rules.push("'regex:/^\\d{2,4}-\\d{2,4}-\\d{4}$/'");
2818
+ } else if (propDef.type === "JapanesePostalCode") {
2819
+ rules.push("'string'");
2820
+ rules.push("'max:8'");
2821
+ rules.push("'regex:/^\\d{3}-\\d{4}$/'");
2822
+ } else {
2823
+ const sqlType = customType.sql?.sqlType ?? "VARCHAR";
2824
+ const sqlLength = customType.sql?.length ?? 255;
2825
+ if (sqlType === "VARCHAR" || sqlType === "TEXT") {
2826
+ rules.push("'string'");
2827
+ if (sqlType === "VARCHAR") {
2828
+ rules.push(`'max:${sqlLength}'`);
2829
+ }
2830
+ } else if (sqlType === "INT" || sqlType === "TINYINT" || sqlType === "BIGINT") {
2831
+ rules.push("'integer'");
2832
+ } else if (sqlType === "DECIMAL" || sqlType === "FLOAT") {
2833
+ rules.push("'numeric'");
2834
+ }
2835
+ }
2836
+ }
2837
+ break;
2838
+ }
2839
+ if (prop.unique === true) {
2840
+ rules.push(`Rule::unique('${tableName}')->ignore($this->route('${modelVar}'))`);
2841
+ }
2842
+ return rules;
2843
+ }
2844
+ function getCompoundFieldRules(typeName, suffix, field, fieldOverride) {
2845
+ const rules = [];
2846
+ const sql = field.sql;
2847
+ const length = fieldOverride?.length ?? sql?.length ?? 255;
2848
+ switch (typeName) {
2849
+ case "JapaneseName":
2850
+ rules.push("'string'");
2851
+ rules.push(`'max:${length}'`);
2852
+ if (suffix === "KanaLastname" || suffix === "KanaFirstname") {
2853
+ rules.push("'regex:/^[\\x{30A0}-\\x{30FF}\\x{3000}-\\x{303F}\\x{FF00}-\\x{FF9F}\\s]+$/u'");
2854
+ }
2855
+ break;
2856
+ case "JapaneseAddress":
2857
+ if (suffix === "PostalCode") {
2858
+ rules.push("'string'");
2859
+ rules.push("'max:8'");
2860
+ rules.push("'regex:/^\\d{3}-\\d{4}$/'");
2861
+ } else if (suffix === "PrefectureId") {
2862
+ rules.push("'integer'");
2863
+ rules.push("'between:1,47'");
2864
+ } else {
2865
+ rules.push("'string'");
2866
+ rules.push(`'max:${length}'`);
2867
+ }
2868
+ break;
2869
+ case "JapaneseBankAccount":
2870
+ if (suffix === "BankCode") {
2871
+ rules.push("'string'");
2872
+ rules.push("'size:4'");
2873
+ rules.push("'regex:/^\\d{4}$/'");
2874
+ } else if (suffix === "BranchCode") {
2875
+ rules.push("'string'");
2876
+ rules.push("'size:3'");
2877
+ rules.push("'regex:/^\\d{3}$/'");
2878
+ } else if (suffix === "AccountType") {
2879
+ rules.push("'string'");
2880
+ rules.push("Rule::in(['1', '2', '4'])");
2881
+ } else if (suffix === "AccountNumber") {
2882
+ rules.push("'string'");
2883
+ rules.push("'max:7'");
2884
+ rules.push("'regex:/^\\d{1,7}$/'");
2885
+ } else if (suffix === "AccountHolder") {
2886
+ rules.push("'string'");
2887
+ rules.push(`'max:${length}'`);
2888
+ rules.push("'regex:/^[\\x{30A0}-\\x{30FF}\\x{3000}-\\x{303F}\\x{FF00}-\\x{FF9F}\\s]+$/u'");
2889
+ }
2890
+ break;
2891
+ default:
2892
+ if (sql?.sqlType === "TINYINT" || sql?.sqlType === "INT" || sql?.sqlType === "BIGINT") {
2893
+ rules.push("'integer'");
2894
+ if (sql?.unsigned) {
2895
+ rules.push("'min:0'");
2896
+ }
2897
+ } else {
2898
+ rules.push("'string'");
2899
+ rules.push(`'max:${length}'`);
2900
+ }
2901
+ break;
2902
+ }
2903
+ return rules;
2904
+ }
2905
+ function expandCompoundTypeFields(propName, propDef, options) {
2906
+ const typeDef = options.customTypes.get(propDef.type);
2907
+ if (!typeDef || !typeDef.compound || !typeDef.expand) {
2908
+ return [];
2909
+ }
2910
+ const snakeName = toSnakeCase(propName);
2911
+ const prop = propDef;
2912
+ const isNullable2 = prop.nullable === true;
2913
+ const fields = [];
2914
+ for (const field of typeDef.expand) {
2915
+ const suffixSnake = toSnakeCase(field.suffix);
2916
+ const fieldName = `${snakeName}_${suffixSnake}`;
2917
+ const fieldOverride = prop.fields?.[field.suffix];
2918
+ const fieldDefNullable = field.sql?.nullable ?? false;
2919
+ const fieldNullable = fieldOverride?.nullable ?? fieldDefNullable ?? isNullable2;
2920
+ const rules = [];
2921
+ if (!fieldNullable) {
2922
+ rules.push("'required'");
2923
+ } else {
2924
+ rules.push("'nullable'");
2925
+ }
2926
+ const typeRules = getCompoundFieldRules(propDef.type, field.suffix, field, fieldOverride);
2927
+ rules.push(...typeRules);
2928
+ const needsRuleImport = rules.some((r) => r.includes("Rule::"));
2929
+ fields.push({ fieldName, rules, needsRuleImport });
2930
+ }
2931
+ return fields;
2932
+ }
2933
+ function generateStoreRequestBase(schema, schemas, options) {
2934
+ const className = toPascalCase(schema.name);
2935
+ const module2 = getModuleName(schema);
2936
+ const namespaceModule = module2 ? `\\${module2}` : "";
2937
+ const namespace = `${options.baseRequestNamespace}${namespaceModule}`;
2938
+ const properties = schema.properties ?? {};
2939
+ const rulesLines = [];
2940
+ const attributeLines = [];
2941
+ const fieldList = [];
2942
+ let needsRuleImport = false;
2943
+ for (const [propName, propDef] of Object.entries(properties)) {
2944
+ const snakeName = toSnakeCase(propName);
2945
+ if (SKIP_FIELDS.has(snakeName)) continue;
2946
+ if (propDef.type === "Association") {
2947
+ const assoc = propDef;
2948
+ if (assoc.relation !== "ManyToOne" && assoc.relation !== "OneToOne") {
2949
+ continue;
2950
+ }
2951
+ const fkName = `${snakeName}_id`;
2952
+ const rules2 = generateStoreRules(propName, propDef, schema, schemas, options);
2953
+ if (rules2.some((r) => r.includes("Rule::"))) needsRuleImport = true;
2954
+ rulesLines.push(` '${fkName}' => [${rules2.join(", ")}],`);
2955
+ fieldList.push(fkName);
2956
+ const displayName2 = getDisplayName(propDef.displayName, options.locale, propName);
2957
+ attributeLines.push(` '${fkName}' => '${escapePhpString2(displayName2)}',`);
2958
+ continue;
2959
+ }
2960
+ const expandedFields = expandCompoundTypeFields(propName, propDef, options);
2961
+ if (expandedFields.length > 0) {
2962
+ for (const field of expandedFields) {
2963
+ if (field.needsRuleImport) needsRuleImport = true;
2964
+ rulesLines.push(` '${field.fieldName}' => [${field.rules.join(", ")}],`);
2965
+ fieldList.push(field.fieldName);
2966
+ attributeLines.push(` '${field.fieldName}' => '${escapePhpString2(field.fieldName)}',`);
2967
+ }
2968
+ continue;
2969
+ }
2970
+ const rules = generateStoreRules(propName, propDef, schema, schemas, options);
2971
+ if (rules.some((r) => r.includes("Rule::"))) needsRuleImport = true;
2972
+ rulesLines.push(` '${snakeName}' => [${rules.join(", ")}],`);
2973
+ fieldList.push(snakeName);
2974
+ const displayName = getDisplayName(propDef.displayName, options.locale, propName);
2975
+ attributeLines.push(` '${snakeName}' => '${escapePhpString2(displayName)}',`);
2976
+ }
2977
+ const ruleImport = needsRuleImport ? "\nuse Illuminate\\Validation\\Rule;" : "";
2978
+ const content = `<?php
2979
+
2980
+ /**
2981
+ * AUTO-GENERATED BY OMNIFY - DO NOT EDIT!
2982
+ *
2983
+ * This file is generated from Omnify schema: ${schema.name}
2984
+ * Re-run \`npx omnify generate\` to update.
2985
+ *
2986
+ * @generated
2987
+ */
2988
+
2989
+ namespace ${namespace};
2990
+
2991
+ use Illuminate\\Foundation\\Http\\FormRequest;${ruleImport}
2992
+
2993
+ abstract class ${className}StoreRequestBase extends FormRequest
2994
+ {
2995
+ /**
2996
+ * Validation rules generated from Omnify schema.
2997
+ *
2998
+ * Generated fields: ${fieldList.join(", ")}
2999
+ *
3000
+ * @return array<string, array<int, mixed>>
3001
+ */
3002
+ protected function schemaRules(): array
3003
+ {
3004
+ return [
3005
+ ${rulesLines.join("\n")}
3006
+ ];
3007
+ }
3008
+
3009
+ /**
3010
+ * Get custom attributes for validator errors.
3011
+ *
3012
+ * @return array<string, string>
3013
+ */
3014
+ protected function schemaAttributes(): array
3015
+ {
3016
+ return [
3017
+ ${attributeLines.join("\n")}
3018
+ ];
3019
+ }
3020
+ }
3021
+ `;
3022
+ const modulePath = module2 ? `/${module2}` : "";
3023
+ return {
3024
+ path: `${options.baseRequestPath}${modulePath}/${className}StoreRequestBase.php`,
3025
+ content,
3026
+ type: "store-base",
3027
+ overwrite: true,
3028
+ schemaName: schema.name,
3029
+ module: module2
3030
+ };
3031
+ }
3032
+ function generateUpdateRequestBase(schema, schemas, options) {
3033
+ const className = toPascalCase(schema.name);
3034
+ const module2 = getModuleName(schema);
3035
+ const namespaceModule = module2 ? `\\${module2}` : "";
3036
+ const namespace = `${options.baseRequestNamespace}${namespaceModule}`;
3037
+ const properties = schema.properties ?? {};
3038
+ const rulesLines = [];
3039
+ const attributeLines = [];
3040
+ let needsRuleImport = false;
3041
+ for (const [propName, propDef] of Object.entries(properties)) {
3042
+ const snakeName = toSnakeCase(propName);
3043
+ if (SKIP_FIELDS.has(snakeName)) continue;
3044
+ if (propDef.type === "Association") {
3045
+ const assoc = propDef;
3046
+ if (assoc.relation !== "ManyToOne" && assoc.relation !== "OneToOne") {
3047
+ continue;
3048
+ }
3049
+ const fkName = `${snakeName}_id`;
3050
+ const rules2 = generateUpdateRules(propName, propDef, schema, schemas, options);
3051
+ if (rules2.some((r) => r.includes("Rule::") || r.includes("Rule::"))) needsRuleImport = true;
3052
+ rulesLines.push(` '${fkName}' => [${rules2.join(", ")}],`);
3053
+ const displayName2 = getDisplayName(propDef.displayName, options.locale, propName);
3054
+ attributeLines.push(` '${fkName}' => '${escapePhpString2(displayName2)}',`);
3055
+ continue;
3056
+ }
3057
+ const expandedFields = expandCompoundTypeFields(propName, propDef, options);
3058
+ if (expandedFields.length > 0) {
3059
+ for (const field of expandedFields) {
3060
+ if (field.needsRuleImport) needsRuleImport = true;
3061
+ const updateRules = field.rules.map((r) => r === "'required'" ? "'sometimes'" : r);
3062
+ rulesLines.push(` '${field.fieldName}' => [${updateRules.join(", ")}],`);
3063
+ attributeLines.push(` '${field.fieldName}' => '${escapePhpString2(field.fieldName)}',`);
3064
+ }
3065
+ continue;
3066
+ }
3067
+ const rules = generateUpdateRules(propName, propDef, schema, schemas, options);
3068
+ if (rules.some((r) => r.includes("Rule::"))) needsRuleImport = true;
3069
+ rulesLines.push(` '${snakeName}' => [${rules.join(", ")}],`);
3070
+ const displayName = getDisplayName(propDef.displayName, options.locale, propName);
3071
+ attributeLines.push(` '${snakeName}' => '${escapePhpString2(displayName)}',`);
3072
+ }
3073
+ const ruleImport = needsRuleImport ? "\nuse Illuminate\\Validation\\Rule;" : "";
3074
+ const content = `<?php
3075
+
3076
+ /**
3077
+ * AUTO-GENERATED BY OMNIFY - DO NOT EDIT!
3078
+ *
3079
+ * This file is generated from Omnify schema: ${schema.name}
3080
+ * Re-run \`npx omnify generate\` to update.
3081
+ *
3082
+ * @generated
3083
+ */
3084
+
3085
+ namespace ${namespace};
3086
+
3087
+ use Illuminate\\Foundation\\Http\\FormRequest;${ruleImport}
3088
+
3089
+ abstract class ${className}UpdateRequestBase extends FormRequest
3090
+ {
3091
+ /**
3092
+ * Validation rules generated from Omnify schema.
3093
+ * All fields use 'sometimes' for partial updates.
3094
+ *
3095
+ * @return array<string, array<int, mixed>>
3096
+ */
3097
+ protected function schemaRules(): array
3098
+ {
3099
+ return [
3100
+ ${rulesLines.join("\n")}
3101
+ ];
3102
+ }
3103
+
3104
+ /**
3105
+ * Get custom attributes for validator errors.
3106
+ *
3107
+ * @return array<string, string>
3108
+ */
3109
+ protected function schemaAttributes(): array
3110
+ {
3111
+ return [
3112
+ ${attributeLines.join("\n")}
3113
+ ];
3114
+ }
3115
+ }
3116
+ `;
3117
+ const modulePath = module2 ? `/${module2}` : "";
3118
+ return {
3119
+ path: `${options.baseRequestPath}${modulePath}/${className}UpdateRequestBase.php`,
3120
+ content,
3121
+ type: "update-base",
3122
+ overwrite: true,
3123
+ schemaName: schema.name,
3124
+ module: module2
3125
+ };
3126
+ }
3127
+ function generateStoreRequest(schema, options) {
3128
+ const className = toPascalCase(schema.name);
3129
+ const module2 = getModuleName(schema);
3130
+ const namespaceModule = module2 ? `\\${module2}` : "";
3131
+ const namespace = `${options.requestNamespace}${namespaceModule}`;
3132
+ const baseNamespace = `${options.baseRequestNamespace}${namespaceModule}`;
3133
+ const content = `<?php
3134
+
3135
+ /**
3136
+ * ${className} Store Request
3137
+ *
3138
+ * SAFE TO EDIT - This file is never overwritten by Omnify.
3139
+ */
3140
+
3141
+ namespace ${namespace};
3142
+
3143
+ use ${baseNamespace}\\${className}StoreRequestBase;
3144
+
3145
+ class ${className}StoreRequest extends ${className}StoreRequestBase
3146
+ {
3147
+ /**
3148
+ * Determine if the user is authorized to make this request.
3149
+ */
3150
+ public function authorize(): bool
3151
+ {
3152
+ return true;
3153
+ }
3154
+
3155
+ /**
3156
+ * Get the validation rules that apply to the request.
3157
+ *
3158
+ * @return array<string, array<int, mixed>>
3159
+ */
3160
+ public function rules(): array
3161
+ {
3162
+ return array_merge($this->schemaRules(), [
3163
+ // Custom/override rules here
3164
+ ]);
3165
+ }
3166
+
3167
+ /**
3168
+ * Get custom attributes for validator errors.
3169
+ *
3170
+ * @return array<string, string>
3171
+ */
3172
+ public function attributes(): array
3173
+ {
3174
+ return array_merge($this->schemaAttributes(), [
3175
+ // Custom attributes here
3176
+ ]);
3177
+ }
3178
+
3179
+ /**
3180
+ * Get custom messages for validator errors.
3181
+ *
3182
+ * @return array<string, string>
3183
+ */
3184
+ public function messages(): array
3185
+ {
3186
+ return [
3187
+ // Custom messages here
3188
+ ];
3189
+ }
3190
+ }
3191
+ `;
3192
+ const modulePath = module2 ? `/${module2}` : "";
3193
+ return {
3194
+ path: `${options.requestPath}${modulePath}/${className}StoreRequest.php`,
3195
+ content,
3196
+ type: "store",
3197
+ overwrite: false,
3198
+ schemaName: schema.name,
3199
+ module: module2
3200
+ };
3201
+ }
3202
+ function generateUpdateRequest(schema, options) {
3203
+ const className = toPascalCase(schema.name);
3204
+ const module2 = getModuleName(schema);
3205
+ const namespaceModule = module2 ? `\\${module2}` : "";
3206
+ const namespace = `${options.requestNamespace}${namespaceModule}`;
3207
+ const baseNamespace = `${options.baseRequestNamespace}${namespaceModule}`;
3208
+ const content = `<?php
3209
+
3210
+ /**
3211
+ * ${className} Update Request
3212
+ *
3213
+ * SAFE TO EDIT - This file is never overwritten by Omnify.
3214
+ */
3215
+
3216
+ namespace ${namespace};
3217
+
3218
+ use ${baseNamespace}\\${className}UpdateRequestBase;
3219
+
3220
+ class ${className}UpdateRequest extends ${className}UpdateRequestBase
3221
+ {
3222
+ /**
3223
+ * Determine if the user is authorized to make this request.
3224
+ */
3225
+ public function authorize(): bool
3226
+ {
3227
+ return true;
3228
+ }
3229
+
3230
+ /**
3231
+ * Get the validation rules that apply to the request.
3232
+ *
3233
+ * @return array<string, array<int, mixed>>
3234
+ */
3235
+ public function rules(): array
3236
+ {
3237
+ return array_merge($this->schemaRules(), [
3238
+ // Custom/override rules here
3239
+ ]);
3240
+ }
3241
+
3242
+ /**
3243
+ * Get custom attributes for validator errors.
3244
+ *
3245
+ * @return array<string, string>
3246
+ */
3247
+ public function attributes(): array
3248
+ {
3249
+ return array_merge($this->schemaAttributes(), [
3250
+ // Custom attributes here
3251
+ ]);
3252
+ }
3253
+
3254
+ /**
3255
+ * Get custom messages for validator errors.
3256
+ *
3257
+ * @return array<string, string>
3258
+ */
3259
+ public function messages(): array
3260
+ {
3261
+ return [
3262
+ // Custom messages here
3263
+ ];
3264
+ }
3265
+ }
3266
+ `;
3267
+ const modulePath = module2 ? `/${module2}` : "";
3268
+ return {
3269
+ path: `${options.requestPath}${modulePath}/${className}UpdateRequest.php`,
3270
+ content,
3271
+ type: "update",
3272
+ overwrite: false,
3273
+ schemaName: schema.name,
3274
+ module: module2
3275
+ };
3276
+ }
3277
+ function generateRequests(schemas, options) {
3278
+ const resolved = resolveOptions3(options);
3279
+ const requests = [];
3280
+ for (const schema of Object.values(schemas)) {
3281
+ if (schema.kind === "enum") continue;
3282
+ if (schema.options?.hidden === true) continue;
3283
+ requests.push(generateStoreRequestBase(schema, schemas, resolved));
3284
+ requests.push(generateUpdateRequestBase(schema, schemas, resolved));
3285
+ requests.push(generateStoreRequest(schema, resolved));
3286
+ requests.push(generateUpdateRequest(schema, resolved));
3287
+ }
3288
+ return requests;
3289
+ }
3290
+ function getRequestPath(request) {
3291
+ return request.path;
3292
+ }
3293
+
3294
+ // src/resource/generator.ts
3295
+ var import_omnify_types4 = require("@famgia/omnify-types");
3296
+ var DEFAULT_OPTIONS3 = {
3297
+ baseResourceNamespace: "App\\Http\\Resources\\OmnifyBase",
3298
+ resourceNamespace: "App\\Http\\Resources",
3299
+ baseResourcePath: "app/Http/Resources/OmnifyBase",
3300
+ resourcePath: "app/Http/Resources",
3301
+ customTypes: /* @__PURE__ */ new Map(),
3302
+ locale: "en"
3303
+ };
3304
+ var SKIP_FIELDS2 = /* @__PURE__ */ new Set([
3305
+ "password",
3306
+ "remember_token"
3307
+ ]);
3308
+ function resolveOptions4(options) {
3309
+ return {
3310
+ baseResourceNamespace: options?.baseResourceNamespace ?? DEFAULT_OPTIONS3.baseResourceNamespace,
3311
+ resourceNamespace: options?.resourceNamespace ?? DEFAULT_OPTIONS3.resourceNamespace,
3312
+ baseResourcePath: options?.baseResourcePath ?? DEFAULT_OPTIONS3.baseResourcePath,
3313
+ resourcePath: options?.resourcePath ?? DEFAULT_OPTIONS3.resourcePath,
3314
+ customTypes: options?.customTypes ?? /* @__PURE__ */ new Map(),
3315
+ locale: options?.locale ?? DEFAULT_OPTIONS3.locale
3316
+ };
3317
+ }
3318
+ function getModuleName2(schema) {
3319
+ if (schema.module) {
3320
+ return schema.module;
3321
+ }
3322
+ return "";
3323
+ }
3324
+ function getPropertyOutput(propName, propDef, schemas, options) {
3325
+ const snakeName = toSnakeCase(propName);
3326
+ const lines = [];
3327
+ if (SKIP_FIELDS2.has(snakeName)) {
3328
+ return lines;
3329
+ }
3330
+ if (propDef.type === "Association") {
3331
+ const assoc = propDef;
3332
+ const targetClass = assoc.target ? toPascalCase(assoc.target) : "";
3333
+ switch (assoc.relation) {
3334
+ case "ManyToOne":
3335
+ case "OneToOne":
3336
+ lines.push(` '${snakeName}_id' => $this->${snakeName}_id,`);
3337
+ lines.push(` '${snakeName}' => $this->whenLoaded('${toCamelCase(propName)}', fn() => new ${targetClass}Resource($this->${toCamelCase(propName)})),`);
3338
+ break;
3339
+ case "OneToMany":
3340
+ case "ManyToMany":
3341
+ lines.push(` '${snakeName}' => $this->whenLoaded('${toCamelCase(propName)}', fn() => ${targetClass}Resource::collection($this->${toCamelCase(propName)})),`);
3342
+ break;
3343
+ case "MorphTo":
3344
+ lines.push(` '${snakeName}_type' => $this->${snakeName}_type,`);
3345
+ lines.push(` '${snakeName}_id' => $this->${snakeName}_id,`);
3346
+ lines.push(` '${snakeName}' => $this->whenLoaded('${toCamelCase(propName)}'),`);
3347
+ break;
3348
+ case "MorphOne":
3349
+ case "MorphMany":
3350
+ lines.push(` '${snakeName}' => $this->whenLoaded('${toCamelCase(propName)}', fn() => ${targetClass}Resource::collection($this->${toCamelCase(propName)})),`);
3351
+ break;
3352
+ }
3353
+ return lines;
3354
+ }
3355
+ const typeDef = options.customTypes.get(propDef.type);
3356
+ if (typeDef?.compound && typeDef.expand) {
3357
+ for (const field of typeDef.expand) {
3358
+ const suffixSnake = toSnakeCase(field.suffix);
3359
+ const fieldName = `${snakeName}_${suffixSnake}`;
3360
+ lines.push(` '${fieldName}' => $this->${fieldName},`);
3361
+ }
3362
+ if (typeDef.accessors) {
3363
+ for (const accessor of typeDef.accessors) {
3364
+ const accessorName = `${snakeName}_${toSnakeCase(accessor.name)}`;
3365
+ lines.push(` '${accessorName}' => $this->${accessorName},`);
3366
+ }
3367
+ }
3368
+ return lines;
3369
+ }
3370
+ lines.push(` '${snakeName}' => $this->${snakeName},`);
3371
+ return lines;
3372
+ }
3373
+ function generateResourceBase(schema, schemas, options) {
3374
+ const className = toPascalCase(schema.name);
3375
+ const module2 = getModuleName2(schema);
3376
+ const namespaceModule = module2 ? `\\${module2}` : "";
3377
+ const namespace = `${options.baseResourceNamespace}${namespaceModule}`;
3378
+ const properties = schema.properties ?? {};
3379
+ const outputLines = [];
3380
+ const imports = /* @__PURE__ */ new Set();
3381
+ if (schema.options?.id !== false) {
3382
+ outputLines.push(` 'id' => $this->id,`);
3383
+ }
3384
+ for (const [propName, propDef] of Object.entries(properties)) {
3385
+ const lines = getPropertyOutput(propName, propDef, schemas, options);
3386
+ outputLines.push(...lines);
3387
+ if (propDef.type === "Association") {
3388
+ const assoc = propDef;
3389
+ if (assoc.target) {
3390
+ const targetModule = getModuleName2(schemas[assoc.target] ?? schema);
3391
+ const targetModuleNs = targetModule ? `\\${targetModule}` : "";
3392
+ imports.add(`use ${options.resourceNamespace}${targetModuleNs}\\${toPascalCase(assoc.target)}Resource;`);
3393
+ }
3394
+ }
3395
+ }
3396
+ if (schema.options?.timestamps !== false) {
3397
+ outputLines.push(` 'created_at' => $this->created_at?->toISOString(),`);
3398
+ outputLines.push(` 'updated_at' => $this->updated_at?->toISOString(),`);
3399
+ }
3400
+ if (schema.options?.softDelete) {
3401
+ outputLines.push(` 'deleted_at' => $this->deleted_at?->toISOString(),`);
3402
+ }
3403
+ const importLines = Array.from(imports).sort().join("\n");
3404
+ const importBlock = importLines ? `
3405
+ ${importLines}` : "";
3406
+ const content = `<?php
3407
+
3408
+ /**
3409
+ * AUTO-GENERATED BY OMNIFY - DO NOT EDIT!
3410
+ *
3411
+ * This file is generated from Omnify schema: ${schema.name}
3412
+ * Re-run \`npx omnify generate\` to update.
3413
+ *
3414
+ * @generated
3415
+ */
3416
+
3417
+ namespace ${namespace};
3418
+
3419
+ use Illuminate\\Http\\Request;
3420
+ use Illuminate\\Http\\Resources\\Json\\JsonResource;${importBlock}
3421
+
3422
+ class ${className}ResourceBase extends JsonResource
3423
+ {
3424
+ /**
3425
+ * Transform the resource into an array.
3426
+ *
3427
+ * @return array<string, mixed>
3428
+ */
3429
+ protected function schemaArray(Request $request): array
3430
+ {
3431
+ return [
3432
+ ${outputLines.join("\n")}
3433
+ ];
3434
+ }
3435
+ }
3436
+ `;
3437
+ const modulePath = module2 ? `/${module2}` : "";
3438
+ return {
3439
+ path: `${options.baseResourcePath}${modulePath}/${className}ResourceBase.php`,
3440
+ content,
3441
+ type: "base",
3442
+ overwrite: true,
3443
+ schemaName: schema.name,
3444
+ module: module2
3445
+ };
3446
+ }
3447
+ function generateResource(schema, options) {
3448
+ const className = toPascalCase(schema.name);
3449
+ const module2 = getModuleName2(schema);
3450
+ const namespaceModule = module2 ? `\\${module2}` : "";
3451
+ const namespace = `${options.resourceNamespace}${namespaceModule}`;
3452
+ const baseNamespace = `${options.baseResourceNamespace}${namespaceModule}`;
3453
+ const content = `<?php
3454
+
3455
+ /**
3456
+ * ${className} Resource
3457
+ *
3458
+ * SAFE TO EDIT - This file is never overwritten by Omnify.
3459
+ */
3460
+
3461
+ namespace ${namespace};
3462
+
3463
+ use Illuminate\\Http\\Request;
3464
+ use ${baseNamespace}\\${className}ResourceBase;
3465
+
3466
+ class ${className}Resource extends ${className}ResourceBase
3467
+ {
3468
+ /**
3469
+ * Transform the resource into an array.
3470
+ *
3471
+ * @return array<string, mixed>
3472
+ */
3473
+ public function toArray(Request $request): array
3474
+ {
3475
+ return array_merge($this->schemaArray($request), [
3476
+ // Custom fields here
3477
+ ]);
3478
+ }
3479
+
3480
+ /**
3481
+ * Get additional data that should be returned with the resource array.
3482
+ *
3483
+ * @return array<string, mixed>
3484
+ */
3485
+ public function with(Request $request): array
3486
+ {
3487
+ return [
3488
+ // Additional metadata here
3489
+ ];
3490
+ }
3491
+ }
3492
+ `;
3493
+ const modulePath = module2 ? `/${module2}` : "";
3494
+ return {
3495
+ path: `${options.resourcePath}${modulePath}/${className}Resource.php`,
3496
+ content,
3497
+ type: "user",
3498
+ overwrite: false,
3499
+ schemaName: schema.name,
3500
+ module: module2
3501
+ };
3502
+ }
3503
+ function generateResources(schemas, options) {
3504
+ const resolved = resolveOptions4(options);
3505
+ const resources = [];
3506
+ for (const schema of Object.values(schemas)) {
3507
+ if (schema.kind === "enum") continue;
3508
+ if (schema.options?.hidden === true) continue;
3509
+ resources.push(generateResourceBase(schema, schemas, resolved));
3510
+ resources.push(generateResource(schema, resolved));
3511
+ }
3512
+ return resources;
3513
+ }
3514
+ function getResourcePath(resource) {
3515
+ return resource.path;
3516
+ }
3517
+
3518
+ // src/plugin.ts
2574
3519
  function getExistingMigrationTables(migrationsDir) {
2575
3520
  const existingTables = /* @__PURE__ */ new Set();
2576
3521
  if (!(0, import_node_fs.existsSync)(migrationsDir)) {
@@ -2654,10 +3599,58 @@ var LARAVEL_CONFIG_SCHEMA = {
2654
3599
  description: "Laravel database connection name (optional)",
2655
3600
  placeholder: "mysql",
2656
3601
  group: "options"
3602
+ },
3603
+ {
3604
+ key: "requestsPath",
3605
+ type: "path",
3606
+ label: "Requests Path",
3607
+ description: "Directory for user-editable FormRequest files",
3608
+ default: "app/Http/Requests",
3609
+ group: "output"
3610
+ },
3611
+ {
3612
+ key: "baseRequestsPath",
3613
+ type: "path",
3614
+ label: "Base Requests Path",
3615
+ description: "Directory for auto-generated base FormRequest files",
3616
+ default: "app/Http/Requests/OmnifyBase",
3617
+ group: "output"
3618
+ },
3619
+ {
3620
+ key: "generateRequests",
3621
+ type: "boolean",
3622
+ label: "Generate Requests",
3623
+ description: "Generate Laravel FormRequest classes for validation",
3624
+ default: false,
3625
+ group: "options"
3626
+ },
3627
+ {
3628
+ key: "resourcesPath",
3629
+ type: "path",
3630
+ label: "Resources Path",
3631
+ description: "Directory for user-editable API Resource files",
3632
+ default: "app/Http/Resources",
3633
+ group: "output"
3634
+ },
3635
+ {
3636
+ key: "baseResourcesPath",
3637
+ type: "path",
3638
+ label: "Base Resources Path",
3639
+ description: "Directory for auto-generated base API Resource files",
3640
+ default: "app/Http/Resources/OmnifyBase",
3641
+ group: "output"
3642
+ },
3643
+ {
3644
+ key: "generateResources",
3645
+ type: "boolean",
3646
+ label: "Generate Resources",
3647
+ description: "Generate Laravel API Resource classes",
3648
+ default: false,
3649
+ group: "options"
2657
3650
  }
2658
3651
  ]
2659
3652
  };
2660
- function resolveOptions3(options) {
3653
+ function resolveOptions5(options) {
2661
3654
  return {
2662
3655
  migrationsPath: options?.migrationsPath ?? "database/migrations/omnify",
2663
3656
  modelsPath: options?.modelsPath ?? "app/Models",
@@ -2670,11 +3663,21 @@ function resolveOptions3(options) {
2670
3663
  generateFactories: options?.generateFactories ?? true,
2671
3664
  fakerLocale: options?.fakerLocale ?? "en_US",
2672
3665
  connection: options?.connection,
2673
- timestamp: options?.timestamp
3666
+ timestamp: options?.timestamp,
3667
+ requestsPath: options?.requestsPath ?? "app/Http/Requests",
3668
+ baseRequestsPath: options?.baseRequestsPath ?? "app/Http/Requests/OmnifyBase",
3669
+ requestNamespace: options?.requestNamespace ?? "App\\Http\\Requests",
3670
+ baseRequestNamespace: options?.baseRequestNamespace ?? "App\\Http\\Requests\\OmnifyBase",
3671
+ generateRequests: options?.generateRequests ?? false,
3672
+ resourcesPath: options?.resourcesPath ?? "app/Http/Resources",
3673
+ baseResourcesPath: options?.baseResourcesPath ?? "app/Http/Resources/OmnifyBase",
3674
+ resourceNamespace: options?.resourceNamespace ?? "App\\Http\\Resources",
3675
+ baseResourceNamespace: options?.baseResourceNamespace ?? "App\\Http\\Resources\\OmnifyBase",
3676
+ generateResources: options?.generateResources ?? false
2674
3677
  };
2675
3678
  }
2676
3679
  function laravelPlugin(options) {
2677
- const resolved = resolveOptions3(options);
3680
+ const resolved = resolveOptions5(options);
2678
3681
  const migrationGenerator = {
2679
3682
  name: "laravel-migrations",
2680
3683
  description: "Generate Laravel migration files",
@@ -2846,6 +3849,59 @@ function laravelPlugin(options) {
2846
3849
  }));
2847
3850
  }
2848
3851
  };
3852
+ const requestGenerator = {
3853
+ name: "laravel-requests",
3854
+ description: "Generate Laravel FormRequest classes for validation",
3855
+ generate: async (ctx) => {
3856
+ const requestOptions = {
3857
+ requestNamespace: resolved.requestNamespace,
3858
+ baseRequestNamespace: resolved.baseRequestNamespace,
3859
+ requestPath: resolved.requestsPath,
3860
+ baseRequestPath: resolved.baseRequestsPath,
3861
+ modelNamespace: resolved.modelNamespace,
3862
+ customTypes: ctx.customTypes
3863
+ };
3864
+ const requests = generateRequests(ctx.schemas, requestOptions);
3865
+ return requests.map((request) => ({
3866
+ path: getRequestPath(request),
3867
+ content: request.content,
3868
+ type: "other",
3869
+ // Skip writing user requests if they already exist
3870
+ skipIfExists: !request.overwrite,
3871
+ metadata: {
3872
+ requestType: request.type,
3873
+ schemaName: request.schemaName,
3874
+ module: request.module
3875
+ }
3876
+ }));
3877
+ }
3878
+ };
3879
+ const resourceGenerator = {
3880
+ name: "laravel-resources",
3881
+ description: "Generate Laravel API Resource classes",
3882
+ generate: async (ctx) => {
3883
+ const resourceOptions = {
3884
+ resourceNamespace: resolved.resourceNamespace,
3885
+ baseResourceNamespace: resolved.baseResourceNamespace,
3886
+ resourcePath: resolved.resourcesPath,
3887
+ baseResourcePath: resolved.baseResourcesPath,
3888
+ customTypes: ctx.customTypes
3889
+ };
3890
+ const resources = generateResources(ctx.schemas, resourceOptions);
3891
+ return resources.map((resource) => ({
3892
+ path: getResourcePath(resource),
3893
+ content: resource.content,
3894
+ type: "other",
3895
+ // Skip writing user resources if they already exist
3896
+ skipIfExists: !resource.overwrite,
3897
+ metadata: {
3898
+ resourceType: resource.type,
3899
+ schemaName: resource.schemaName,
3900
+ module: resource.module
3901
+ }
3902
+ }));
3903
+ }
3904
+ };
2849
3905
  const generators = [migrationGenerator];
2850
3906
  if (resolved.generateModels) {
2851
3907
  generators.push(modelGenerator);
@@ -2853,6 +3909,12 @@ function laravelPlugin(options) {
2853
3909
  if (resolved.generateFactories) {
2854
3910
  generators.push(factoryGenerator);
2855
3911
  }
3912
+ if (resolved.generateRequests) {
3913
+ generators.push(requestGenerator);
3914
+ }
3915
+ if (resolved.generateResources) {
3916
+ generators.push(resourceGenerator);
3917
+ }
2856
3918
  return {
2857
3919
  name: "@famgia/omnify-laravel",
2858
3920
  version: "0.0.14",