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