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