@omnifyjp/ts 2.1.1 → 2.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,11 @@
1
1
  /**
2
- * Generates two-phase file cleanup commands:
3
- * - Phase 1 (CleanupExpiredFiles): Soft-delete expired temp records keeps physical files
4
- * - Phase 2 (PurgeDeletedFiles): Force-delete old soft-deleted records + remove physical files
2
+ * Generates a single file cleanup command with two phases:
3
+ * 1. Soft-delete expired temp records (keeps physical files)
4
+ * 2. Force-delete soft-deleted records past grace period + remove physical files
5
5
  *
6
6
  * Only generated when fileConfig.tempFlow is enabled.
7
7
  */
8
8
  import { SchemaReader } from './schema-reader.js';
9
9
  import type { GeneratedFile, PhpConfig } from './types.js';
10
- /** Generate the cleanup commands (only when tempFlow is enabled). */
10
+ /** Generate the cleanup command (only when tempFlow is enabled). */
11
11
  export declare function generateFileCleanup(reader: SchemaReader, config: PhpConfig): GeneratedFile[];
@@ -1,23 +1,21 @@
1
1
  /**
2
- * Generates two-phase file cleanup commands:
3
- * - Phase 1 (CleanupExpiredFiles): Soft-delete expired temp records keeps physical files
4
- * - Phase 2 (PurgeDeletedFiles): Force-delete old soft-deleted records + remove physical files
2
+ * Generates a single file cleanup command with two phases:
3
+ * 1. Soft-delete expired temp records (keeps physical files)
4
+ * 2. Force-delete soft-deleted records past grace period + remove physical files
5
5
  *
6
6
  * Only generated when fileConfig.tempFlow is enabled.
7
7
  */
8
8
  import { baseFile } from './types.js';
9
- /** Generate the cleanup commands (only when tempFlow is enabled). */
9
+ /** Generate the cleanup command (only when tempFlow is enabled). */
10
10
  export function generateFileCleanup(reader, config) {
11
11
  const fileConfig = reader.getFileConfig();
12
12
  if (!fileConfig?.tempFlow)
13
13
  return [];
14
14
  const modelNamespace = config.models.namespace;
15
- const files = [];
16
- files.push(generateCleanupCommand(modelNamespace));
17
- files.push(generatePurgeCommand(modelNamespace, fileConfig.purgeAfter ?? '72h'));
18
- return files;
15
+ const purgeHours = parseDurationHours(fileConfig.purgeAfter ?? '72h');
16
+ return [generateCommand(modelNamespace, purgeHours)];
19
17
  }
20
- /** Parse a duration string like "72h" to hours. */
18
+ /** Parse a duration string like "72h" or "3d" to hours. */
21
19
  function parseDurationHours(duration) {
22
20
  const match = duration.match(/^(\d+)(h|d)$/);
23
21
  if (!match)
@@ -26,14 +24,21 @@ function parseDurationHours(duration) {
26
24
  const unit = match[2];
27
25
  return unit === 'd' ? value * 24 : value;
28
26
  }
29
- function generateCleanupCommand(modelNamespace) {
27
+ function generateCommand(modelNamespace, purgeHours) {
30
28
  const content = `<?php
31
29
 
32
30
  namespace App\\Console\\Commands;
33
31
 
34
32
  /**
35
- * Phase 1: Soft-delete expired temporary file records.
36
- * Physical files are kept on disk for the grace period.
33
+ * Two-phase file cleanup:
34
+ * Phase 1: Soft-delete expired temp records (physical files stay on disk)
35
+ * Phase 2: Force-delete soft-deleted records past grace period + remove physical files
36
+ *
37
+ * Usage:
38
+ * php artisan omnify:cleanup-files # run both phases
39
+ * php artisan omnify:cleanup-files --phase=1 # soft-delete only
40
+ * php artisan omnify:cleanup-files --phase=2 # purge only
41
+ * php artisan omnify:cleanup-files --dry-run # preview without changes
37
42
  *
38
43
  * DO NOT EDIT - This file is auto-generated by Omnify.
39
44
  * Any changes will be overwritten on next generation.
@@ -42,101 +47,82 @@ namespace App\\Console\\Commands;
42
47
  */
43
48
 
44
49
  use Illuminate\\Console\\Command;
50
+ use Illuminate\\Support\\Facades\\Storage;
45
51
  use ${modelNamespace}\\File;
46
52
 
47
- class CleanupExpiredFiles extends Command
53
+ class CleanupFiles extends Command
48
54
  {
49
- /**
50
- * The name and signature of the console command.
51
- */
52
- protected $signature = 'omnify:cleanup-expired-files
53
- {--dry-run : Show what would be deleted without actually deleting}';
55
+ protected $signature = 'omnify:cleanup-files
56
+ {--phase= : Run specific phase only (1=soft-delete, 2=purge)}
57
+ {--purge-hours=${purgeHours} : Grace period in hours before physical deletion}
58
+ {--dry-run : Preview what would happen without making changes}';
54
59
 
55
- /**
56
- * The console command description.
57
- */
58
- protected $description = 'Soft-delete expired temporary file records (keeps physical files)';
60
+ protected $description = 'Clean up expired and deleted file attachments';
61
+
62
+ public function handle(): int
63
+ {
64
+ $phase = $this->option('phase');
65
+ $dryRun = (bool) $this->option('dry-run');
66
+
67
+ $result = self::SUCCESS;
68
+
69
+ if ($phase === null || $phase === '1') {
70
+ $r = $this->phase1SoftDelete($dryRun);
71
+ if ($r !== self::SUCCESS) $result = $r;
72
+ }
73
+
74
+ if ($phase === null || $phase === '2') {
75
+ $r = $this->phase2Purge($dryRun);
76
+ if ($r !== self::SUCCESS) $result = $r;
77
+ }
78
+
79
+ return $result;
80
+ }
59
81
 
60
82
  /**
61
- * Execute the console command.
83
+ * Phase 1: Soft-delete expired temporary file records.
84
+ * Physical files remain on disk for the grace period.
62
85
  */
63
- public function handle(): int
86
+ private function phase1SoftDelete(bool $dryRun): int
64
87
  {
65
88
  $query = File::expired();
66
89
  $count = $query->count();
67
90
 
68
91
  if ($count === 0) {
69
- $this->info('No expired files found.');
92
+ $this->info('[Phase 1] No expired temp files.');
70
93
  return self::SUCCESS;
71
94
  }
72
95
 
73
- if ($this->option('dry-run')) {
74
- $this->info("Would soft-delete {$count} expired file record(s).");
96
+ if ($dryRun) {
97
+ $this->info("[Phase 1] Would soft-delete {$count} expired file(s).");
75
98
  return self::SUCCESS;
76
99
  }
77
100
 
78
- // Soft-delete only — physical files stay on disk
79
101
  $deleted = $query->delete();
102
+ $this->info("[Phase 1] Soft-deleted {$deleted} expired file(s). Physical files retained.");
80
103
 
81
- $this->info("Soft-deleted {$deleted} expired file record(s). Physical files retained.");
82
104
  return self::SUCCESS;
83
105
  }
84
- }
85
- `;
86
- return baseFile('app/Console/Commands/CleanupExpiredFiles.php', content);
87
- }
88
- function generatePurgeCommand(modelNamespace, purgeAfter) {
89
- const hours = parseDurationHours(purgeAfter);
90
- const content = `<?php
91
-
92
- namespace App\\Console\\Commands;
93
-
94
- /**
95
- * Phase 2: Force-delete soft-deleted file records older than the grace period
96
- * and remove their physical files from storage.
97
- *
98
- * DO NOT EDIT - This file is auto-generated by Omnify.
99
- * Any changes will be overwritten on next generation.
100
- *
101
- * @generated by omnify
102
- */
103
-
104
- use Illuminate\\Console\\Command;
105
- use Illuminate\\Support\\Facades\\Storage;
106
- use ${modelNamespace}\\File;
107
-
108
- class PurgeDeletedFiles extends Command
109
- {
110
- /**
111
- * The name and signature of the console command.
112
- */
113
- protected $signature = 'omnify:purge-deleted-files
114
- {--hours=${hours} : Grace period in hours before physical deletion}
115
- {--dry-run : Show what would be purged without actually purging}';
116
106
 
117
107
  /**
118
- * The console command description.
108
+ * Phase 2: Force-delete soft-deleted records past grace period
109
+ * and remove physical files from storage.
119
110
  */
120
- protected $description = 'Force-delete old soft-deleted file records and remove physical files from disk';
121
-
122
- /**
123
- * Execute the console command.
124
- */
125
- public function handle(): int
111
+ private function phase2Purge(bool $dryRun): int
126
112
  {
127
- $hours = (int) $this->option('hours');
113
+ $hours = (int) $this->option('purge-hours');
128
114
  $cutoff = now()->subHours($hours);
129
115
 
130
116
  $query = File::onlyTrashed()->where('deleted_at', '<', $cutoff);
131
117
  $count = $query->count();
132
118
 
133
119
  if ($count === 0) {
134
- $this->info("No soft-deleted files older than {$hours}h found.");
120
+ $this->info("[Phase 2] No soft-deleted files older than {$hours}h.");
135
121
  return self::SUCCESS;
136
122
  }
137
123
 
138
- if ($this->option('dry-run')) {
139
- $this->info("Would purge {$count} file(s) older than {$hours}h.");
124
+ if ($dryRun) {
125
+ $this->info("[Phase 2] Would purge {$count} file(s) older than {$hours}h.");
140
126
  return self::SUCCESS;
141
127
  }
142
128
 
@@ -146,11 +132,9 @@ class PurgeDeletedFiles extends Command
146
132
  $query->chunkById(100, function ($files) use (&$purged, &$errors) {
147
133
  foreach ($files as $file) {
148
134
  try {
149
- // Remove physical file from storage
150
135
  if ($file->path && Storage::disk($file->disk)->exists($file->path)) {
151
136
  Storage::disk($file->disk)->delete($file->path);
152
137
  }
153
- // Force-delete the database record
154
138
  $file->forceDelete();
155
139
  $purged++;
156
140
  } catch (\\Throwable $e) {
@@ -160,7 +144,7 @@ class PurgeDeletedFiles extends Command
160
144
  }
161
145
  });
162
146
 
163
- $this->info("Purged {$purged} file(s). Physical files removed from disk.");
147
+ $this->info("[Phase 2] Purged {$purged} file(s). Physical files removed.");
164
148
  if ($errors > 0) {
165
149
  $this->warn("{$errors} file(s) failed to purge.");
166
150
  }
@@ -169,5 +153,5 @@ class PurgeDeletedFiles extends Command
169
153
  }
170
154
  }
171
155
  `;
172
- return baseFile('app/Console/Commands/PurgeDeletedFiles.php', content);
156
+ return baseFile('app/Console/Commands/CleanupFiles.php', content);
173
157
  }
@@ -276,6 +276,13 @@ function buildDocProperties(properties, expandedProperties, propertyOrder) {
276
276
  const phpType = nullable ? 'int|null' : 'int';
277
277
  lines.push(` * @property ${phpType} $${snakeName}`);
278
278
  }
279
+ else if (relation === 'MorphTo') {
280
+ const morphName = prop['morphName'] || toSnakeCase(propName);
281
+ const nullable = prop['nullable'] ?? true;
282
+ const nullSuffix = nullable ? '|null' : '';
283
+ lines.push(` * @property string${nullSuffix} $${morphName}_type`);
284
+ lines.push(` * @property string${nullSuffix} $${morphName}_id`);
285
+ }
279
286
  continue;
280
287
  }
281
288
  if (expandedProperties[propName]) {
@@ -329,6 +336,11 @@ function buildFillable(properties, expandedProperties, propertyOrder) {
329
336
  if (relation === 'ManyToOne') {
330
337
  fields.push(toSnakeCase(propName) + '_id');
331
338
  }
339
+ else if (relation === 'MorphTo') {
340
+ const morphName = prop['morphName'] || toSnakeCase(propName);
341
+ fields.push(morphName + '_type');
342
+ fields.push(morphName + '_id');
343
+ }
332
344
  continue;
333
345
  }
334
346
  if (expandedProperties[propName]) {
@@ -73,17 +73,34 @@ function generateBaseRequest(name, schema, reader, config, action) {
73
73
  }
74
74
  continue;
75
75
  }
76
- // Skip non-fillable associations except ManyToOne
76
+ // Association handling
77
77
  if (type === 'Association') {
78
78
  const relation = prop['relation'] ?? '';
79
- if (relation !== 'ManyToOne')
80
- continue;
81
- const snakeName = toSnakeCase(propName) + '_id';
82
- const rules = isUpdate
83
- ? toUpdateRules(prop, tableName, modelRouteParam)
84
- : toStoreRules(prop, tableName);
85
- rulesLines.push(` '${snakeName}' => ${formatRules(rules)},`);
86
- attributeKeys.push(snakeName);
79
+ if (relation === 'ManyToOne') {
80
+ const snakeName = toSnakeCase(propName) + '_id';
81
+ const rules = isUpdate
82
+ ? toUpdateRules(prop, tableName, modelRouteParam)
83
+ : toStoreRules(prop, tableName);
84
+ rulesLines.push(` '${snakeName}' => ${formatRules(rules)},`);
85
+ attributeKeys.push(snakeName);
86
+ }
87
+ else if (relation === 'MorphTo') {
88
+ const morphName = prop['morphName'] || toSnakeCase(propName);
89
+ const nullable = prop['nullable'] ?? true;
90
+ const requiredRule = nullable ? 'nullable' : 'required';
91
+ const targets = prop['targets'] ?? [];
92
+ // {morphName}_type rules
93
+ const typeRules = [requiredRule, 'string'];
94
+ if (targets.length > 0) {
95
+ typeRules.push(`in:${targets.join(',')}`);
96
+ }
97
+ rulesLines.push(` '${morphName}_type' => [${typeRules.map(r => `'${r}'`).join(', ')}],`);
98
+ attributeKeys.push(morphName + '_type');
99
+ // {morphName}_id rules
100
+ const idRules = [requiredRule, 'string'];
101
+ rulesLines.push(` '${morphName}_id' => [${idRules.map(r => `'${r}'`).join(', ')}],`);
102
+ attributeKeys.push(morphName + '_id');
103
+ }
87
104
  continue;
88
105
  }
89
106
  // File type: generate array item rules for multiple files
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnifyjp/ts",
3
- "version": "2.1.1",
3
+ "version": "2.1.3",
4
4
  "description": "TypeScript model type generator from Omnify schemas.json",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",