betterstart-cli 0.0.102 → 0.0.104

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.
Files changed (147) hide show
  1. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/index.ts +0 -5
  2. package/dist/assets/adapters/next/templates/init/lib/actions/webhooks/send-test-webhook.ts +1 -1
  3. package/dist/assets/adapters/next/templates/renderers/commands/init/banner-art.ts +7 -0
  4. package/dist/assets/adapters/next/templates/renderers/commands/init/verify-database-reachable.ts +30 -0
  5. package/dist/assets/adapters/next/templates/renderers/commands/seed/build-seed-script.ts +171 -0
  6. package/dist/assets/adapters/next/templates/renderers/generators/actions/action-helpers/build-authorship-alias-block.ts +4 -0
  7. package/dist/assets/adapters/next/templates/renderers/generators/actions/action-helpers/build-authorship-data-fields.ts +4 -0
  8. package/dist/assets/adapters/next/templates/renderers/generators/actions/action-helpers/build-format-action-date-source.ts +18 -0
  9. package/dist/assets/adapters/next/templates/renderers/generators/actions/action-helpers/build-read-select-option-type.ts +6 -0
  10. package/dist/assets/adapters/next/templates/renderers/generators/actions/action-helpers/build-relationship-data-types.ts +25 -0
  11. package/dist/assets/adapters/next/templates/renderers/generators/actions/action-helpers/build-result-mapping.ts +62 -0
  12. package/dist/assets/adapters/next/templates/renderers/generators/actions/action-helpers/generate-populate-list-rels-function.ts +50 -0
  13. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/build-restore-many-to-many-statements.ts +27 -0
  14. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/build-select-option-types.ts +37 -0
  15. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/gen-bulk-sort-order-content.ts +93 -0
  16. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/gen-create-content.ts +157 -0
  17. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/gen-create-select-option-content.ts +134 -0
  18. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/gen-delete-bulk-content.ts +85 -0
  19. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/gen-delete-content.ts +66 -0
  20. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/gen-distinct-files.ts +45 -0
  21. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/gen-get-by-id-content.ts +55 -0
  22. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/gen-get-by-slug-content.ts +55 -0
  23. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/gen-get-plural-content.ts +106 -0
  24. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/gen-get-select-options-content.ts +55 -0
  25. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/gen-get-versions-content.ts +19 -0
  26. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/gen-m2m-files.ts +151 -0
  27. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/gen-restore-content.ts +123 -0
  28. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/gen-update-content.ts +187 -0
  29. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/generate-resolve-media-function.ts +40 -0
  30. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/generate-slug-helpers.ts +73 -0
  31. package/dist/assets/adapters/next/templates/renderers/generators/actions/entity-file-contents/generate-version-snapshot-helpers.ts +104 -0
  32. package/dist/assets/adapters/next/templates/renderers/generators/actions/lifecycle-hook-emissions/lifecycle-context.ts +22 -0
  33. package/dist/assets/adapters/next/templates/renderers/generators/actions/lifecycle-hook-emissions/lifecycle-hook-import.ts +4 -0
  34. package/dist/assets/adapters/next/templates/renderers/generators/actions/markdown-renderer.ts +411 -0
  35. package/dist/assets/adapters/next/templates/renderers/generators/actions/select-fields-resolver.ts +99 -0
  36. package/dist/assets/adapters/next/templates/renderers/generators/actions/single-actions/generate-single-actions.ts +292 -0
  37. package/dist/assets/adapters/next/templates/renderers/generators/columns/column-defs/generate-authorship-value.ts +8 -0
  38. package/dist/assets/adapters/next/templates/renderers/generators/columns/column-defs/generate-column-def.ts +297 -0
  39. package/dist/assets/adapters/next/templates/renderers/generators/columns/column-defs/generate-first-column-def.ts +483 -0
  40. package/dist/assets/adapters/next/templates/renderers/generators/columns/column-defs/generate-image-cell-media.ts +38 -0
  41. package/dist/assets/adapters/next/templates/renderers/generators/columns/column-defs/generate-image-element.ts +22 -0
  42. package/dist/assets/adapters/next/templates/renderers/generators/columns/column-defs/generate-relationship-label.ts +26 -0
  43. package/dist/assets/adapters/next/templates/renderers/generators/columns/column-defs/generate-select-label.ts +17 -0
  44. package/dist/assets/adapters/next/templates/renderers/generators/columns/custom-cell.ts +24 -0
  45. package/dist/assets/adapters/next/templates/renderers/generators/columns/generate-columns.ts +55 -0
  46. package/dist/assets/adapters/next/templates/renderers/generators/create-page.ts +16 -0
  47. package/dist/assets/adapters/next/templates/renderers/generators/database/generate-database.ts +5 -0
  48. package/dist/assets/adapters/next/templates/renderers/generators/database/generate-junction-table.ts +27 -0
  49. package/dist/assets/adapters/next/templates/renderers/generators/database/generate-select-options-table.ts +35 -0
  50. package/dist/assets/adapters/next/templates/renderers/generators/database/generate-table-definition.ts +25 -0
  51. package/dist/assets/adapters/next/templates/renderers/generators/dev-mode-integration/build-entity-dev-mode-snippets.ts +180 -0
  52. package/dist/assets/adapters/next/templates/renderers/generators/dev-mode-integration/build-form-dev-mode-snippets.ts +97 -0
  53. package/dist/assets/adapters/next/templates/renderers/generators/dev-mode-integration/build-single-dev-mode-snippets.ts +60 -0
  54. package/dist/assets/adapters/next/templates/renderers/generators/dev-mode-integration/create-dev-mode-integration-artifact.ts +89 -0
  55. package/dist/assets/adapters/next/templates/renderers/generators/dev-mode-integration/lifecycle-hook-code.ts +55 -0
  56. package/dist/assets/adapters/next/templates/renderers/generators/edit-page.ts +33 -0
  57. package/dist/assets/adapters/next/templates/renderers/generators/email-template/generate-email-template.ts +248 -0
  58. package/dist/assets/adapters/next/templates/renderers/generators/email-template/generate-field-value.ts +31 -0
  59. package/dist/assets/adapters/next/templates/renderers/generators/emitted-helpers.ts +16 -0
  60. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx/generate-icon-postfix.ts +27 -0
  61. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx/render-boolean-field.ts +47 -0
  62. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx/render-creatable-select-field.ts +122 -0
  63. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx/render-date-field.ts +47 -0
  64. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx/render-default-field.ts +53 -0
  65. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx/render-gallery-field.ts +46 -0
  66. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx/render-group-field.ts +16 -0
  67. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx/render-icon-field.ts +31 -0
  68. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx/render-markdown-field.ts +50 -0
  69. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx/render-media-field.ts +47 -0
  70. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx/render-multi-relationship-field.ts +91 -0
  71. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx/render-multi-select-field.ts +219 -0
  72. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx/render-number-field.ts +48 -0
  73. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx/render-richtext-field.ts +50 -0
  74. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx/render-select-field.ts +61 -0
  75. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx/render-separator-field.ts +16 -0
  76. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx/render-single-relationship-field.ts +93 -0
  77. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx/render-textarea-field.ts +53 -0
  78. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx-nested/render-list-field.ts +163 -0
  79. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx-nested/render-nested-field.ts +164 -0
  80. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx-nested/render-nested-object-list-field.ts +59 -0
  81. package/dist/assets/adapters/next/templates/renderers/generators/form/field-jsx-nested/render-text-input-field.ts +27 -0
  82. package/dist/assets/adapters/next/templates/renderers/generators/form/form-entity/generate-form.ts +822 -0
  83. package/dist/assets/adapters/next/templates/renderers/generators/form/form-shared/build-default-value-from-sync-functions.ts +41 -0
  84. package/dist/assets/adapters/next/templates/renderers/generators/form/form-shared/build-ui-imports.ts +58 -0
  85. package/dist/assets/adapters/next/templates/renderers/generators/form/form-shared/generate-default-value.ts +67 -0
  86. package/dist/assets/adapters/next/templates/renderers/generators/form/form-shared/generate-nested-list-default-value.ts +13 -0
  87. package/dist/assets/adapters/next/templates/renderers/generators/form/form-shared/label-with-description.ts +14 -0
  88. package/dist/assets/adapters/next/templates/renderers/generators/form/form-shared/render-tabs-trigger.ts +18 -0
  89. package/dist/assets/adapters/next/templates/renderers/generators/form/form-shared/select-read-values-expression.ts +21 -0
  90. package/dist/assets/adapters/next/templates/renderers/generators/form/form-single/build-group-creatable-select-handlers.ts +67 -0
  91. package/dist/assets/adapters/next/templates/renderers/generators/form/form-single/build-group-creatable-select-state.ts +29 -0
  92. package/dist/assets/adapters/next/templates/renderers/generators/form/form-single/build-group-field-array-hooks.ts +13 -0
  93. package/dist/assets/adapters/next/templates/renderers/generators/form/form-single/build-group-fields-jsx.ts +30 -0
  94. package/dist/assets/adapters/next/templates/renderers/generators/form/form-single/generate-card-component.ts +129 -0
  95. package/dist/assets/adapters/next/templates/renderers/generators/form/form-single/generate-single-form.ts +66 -0
  96. package/dist/assets/adapters/next/templates/renderers/generators/form-admin/admin-columns/generate-column-def.ts +72 -0
  97. package/dist/assets/adapters/next/templates/renderers/generators/form-admin/admin-columns/generate-columns.ts +89 -0
  98. package/dist/assets/adapters/next/templates/renderers/generators/form-admin/admin-page/generate-page.ts +23 -0
  99. package/dist/assets/adapters/next/templates/renderers/generators/form-admin/admin-page-content.ts +246 -0
  100. package/dist/assets/adapters/next/templates/renderers/generators/form-admin/admin-submission-drawer.ts +216 -0
  101. package/dist/assets/adapters/next/templates/renderers/generators/form-admin/admin-table.ts +273 -0
  102. package/dist/assets/adapters/next/templates/renderers/generators/form-pipeline/form-actions/exclusive-date-upper-bound-helper.ts +11 -0
  103. package/dist/assets/adapters/next/templates/renderers/generators/form-pipeline/form-actions/generate-form-actions.ts +505 -0
  104. package/dist/assets/adapters/next/templates/renderers/generators/form-pipeline/form-component-multistep/build-component-source.ts +197 -0
  105. package/dist/assets/adapters/next/templates/renderers/generators/form-pipeline/form-component-shared/build-field-error-helper.ts +16 -0
  106. package/dist/assets/adapters/next/templates/renderers/generators/form-pipeline/form-component-shared/error-message-line.ts +13 -0
  107. package/dist/assets/adapters/next/templates/renderers/generators/form-pipeline/form-component-shared/generate-field-jsx.ts +226 -0
  108. package/dist/assets/adapters/next/templates/renderers/generators/form-pipeline/form-component-shared/generate-file-upload-field-jsx.ts +41 -0
  109. package/dist/assets/adapters/next/templates/renderers/generators/form-pipeline/form-component-shared/generate-list-field-jsx.ts +160 -0
  110. package/dist/assets/adapters/next/templates/renderers/generators/form-pipeline/form-component-shared/generate-multiselect-field-jsx.ts +40 -0
  111. package/dist/assets/adapters/next/templates/renderers/generators/form-pipeline/form-component-shared/generate-number-field-jsx.ts +41 -0
  112. package/dist/assets/adapters/next/templates/renderers/generators/form-pipeline/form-component-shared/generate-text-field-jsx.ts +39 -0
  113. package/dist/assets/adapters/next/templates/renderers/generators/form-pipeline/form-component-shared/label-with-description-line.ts +14 -0
  114. package/dist/assets/adapters/next/templates/renderers/generators/form-pipeline/form-component-single/build-single-step-form-component-source.ts +100 -0
  115. package/dist/assets/adapters/next/templates/renderers/generators/form-pipeline/form-database.ts +29 -0
  116. package/dist/assets/adapters/next/templates/renderers/generators/form-pipeline/form-hook.ts +142 -0
  117. package/dist/assets/adapters/next/templates/renderers/generators/hook/generate-hook.ts +169 -0
  118. package/dist/assets/adapters/next/templates/renderers/generators/hook/generate-single-hook.ts +30 -0
  119. package/dist/assets/adapters/next/templates/renderers/generators/navigation-extract.ts +19 -0
  120. package/dist/assets/adapters/next/templates/renderers/generators/page-content/generate-page-content.ts +419 -0
  121. package/dist/assets/adapters/next/templates/renderers/generators/page.ts +24 -0
  122. package/dist/assets/adapters/next/templates/renderers/generators/row-actions-emitters/build-delete-dialog-source.ts +108 -0
  123. package/dist/assets/adapters/next/templates/renderers/generators/row-actions-emitters/build-row-actions-source.ts +61 -0
  124. package/dist/assets/adapters/next/templates/renderers/generators/single-page.ts +59 -0
  125. package/dist/assets/adapters/next/templates/renderers/generators/sortable-header/build-sort-label-expr.ts +9 -0
  126. package/dist/assets/adapters/next/templates/renderers/generators/sortable-header/build-sortable-header-button-jsx.ts +20 -0
  127. package/dist/assets/adapters/next/templates/renderers/generators/sortable-header/build-sortable-header-def.ts +14 -0
  128. package/dist/assets/adapters/next/templates/renderers/generators/table.ts +284 -0
  129. package/dist/assets/adapters/next/templates/renderers/generators/thankyou-email-template.ts +110 -0
  130. package/dist/assets/adapters/next/templates/renderers/generators/webhook-events/render-extract.ts +14 -0
  131. package/dist/assets/adapters/next/templates/renderers/init/scaffolders/next-config/create-next-config-template.ts +48 -0
  132. package/dist/assets/adapters/next/templates/renderers/init/scaffolders/tailwind/admin-shadow-input-utility.ts +9 -0
  133. package/dist/assets/adapters/next/templates/renderers/init/scaffolders/tailwind/admin-theme-block.ts +42 -0
  134. package/dist/assets/adapters/next/templates/renderers/init/templates/app/admin.ts +12 -0
  135. package/dist/assets/adapters/next/templates/renderers/integration-runtime/render-core-email-provider.ts +27 -0
  136. package/dist/assets/adapters/next/templates/renderers/integration-runtime/render-core-storage-provider.ts +27 -0
  137. package/dist/assets/adapters/next/templates/renderers/snapshots/barrels/render-db-barrel.ts +8 -0
  138. package/dist/assets/adapters/next/templates/renderers/snapshots/barrels/render-navigation-barrel.ts +63 -0
  139. package/dist/assets/adapters/next/templates/renderers/snapshots/barrels/render-webhook-events-barrel.ts +16 -0
  140. package/dist/{chunk-TIJEHWRF.js → chunk-OYU73Q2W.js} +1 -50
  141. package/dist/chunk-OYU73Q2W.js.map +1 -0
  142. package/dist/cli.js +8903 -4025
  143. package/dist/cli.js.map +1 -1
  144. package/dist/{read-namespaced-template-UXU7OCY3.js → read-namespaced-template-EIH2NPDJ.js} +2 -2
  145. package/package.json +1 -1
  146. package/dist/chunk-TIJEHWRF.js.map +0 -1
  147. /package/dist/{read-namespaced-template-UXU7OCY3.js.map → read-namespaced-template-EIH2NPDJ.js.map} +0 -0
@@ -0,0 +1,55 @@
1
+ export function renderGenGetSelectOptionsContentServerActionTemplate({
2
+ ctxSelectOptionsTableVar,
3
+ ctxCacheReadImport,
4
+ ctxCamelSingular,
5
+ ctxSingular,
6
+ selectOptionFieldNames,
7
+ ctxCacheLifeLine,
8
+ ctxSingularText
9
+ }: {
10
+ ctxSelectOptionsTableVar: string
11
+ ctxCacheReadImport: string
12
+ ctxCamelSingular: string
13
+ ctxSingular: string
14
+ selectOptionFieldNames: string
15
+ ctxCacheLifeLine: string
16
+ ctxSingularText: string
17
+ }): string {
18
+ return `'use server'
19
+
20
+ import db from '@admin/db'
21
+ import { ${ctxSelectOptionsTableVar} } from '@admin/db/schema'
22
+ import { asc, eq } from 'drizzle-orm'
23
+ ${ctxCacheReadImport}
24
+ import { ${ctxCamelSingular}SelectOptionsCacheTags } from './types'
25
+ import type { ${ctxSingular}SelectOption, ${ctxSingular}SelectOptionFieldName } from './types'
26
+
27
+ const SELECT_OPTION_FIELDS = [${selectOptionFieldNames}] as const
28
+
29
+ function isSelectOptionFieldName(fieldName: string): fieldName is ${ctxSingular}SelectOptionFieldName {
30
+ return (SELECT_OPTION_FIELDS as readonly string[]).includes(fieldName)
31
+ }
32
+
33
+ export async function get${ctxSingular}SelectOptions(
34
+ fieldName: ${ctxSingular}SelectOptionFieldName
35
+ ): Promise<${ctxSingular}SelectOption[]> {
36
+ 'use cache'${ctxCacheLifeLine}
37
+ cacheTag(${ctxCamelSingular}SelectOptionsCacheTags.byField(fieldName))
38
+
39
+ if (!isSelectOptionFieldName(fieldName)) return []
40
+
41
+ try {
42
+ const rows = await db
43
+ .select()
44
+ .from(${ctxSelectOptionsTableVar})
45
+ .where(eq(${ctxSelectOptionsTableVar}.fieldName, fieldName))
46
+ .orderBy(asc(${ctxSelectOptionsTableVar}.label))
47
+
48
+ return rows as ${ctxSingular}SelectOption[]
49
+ } catch (error) {
50
+ console.error('Error fetching ${ctxSingularText} select options:', error)
51
+ return []
52
+ }
53
+ }
54
+ `
55
+ }
@@ -0,0 +1,19 @@
1
+ export function renderGenGetVersionsContentServerActionTemplate({
2
+ ctxSingular,
3
+ ctxPlural
4
+ }: {
5
+ ctxSingular: string
6
+ ctxPlural: string
7
+ }): string {
8
+ return `'use server'
9
+
10
+ import { getEntityVersions } from '@admin/actions/entity-versions'
11
+ import type { EntityVersionWithAuthor } from '@admin/actions/entity-versions'
12
+
13
+ export async function getVersionsBy${ctxSingular}Id(
14
+ id: string
15
+ ): Promise<EntityVersionWithAuthor[]> {
16
+ return getEntityVersions('${ctxPlural}', id)
17
+ }
18
+ `
19
+ }
@@ -0,0 +1,151 @@
1
+ export function renderGenM2MFilesServerActionTemplate({
2
+ junctionVar,
3
+ ctxCacheReadImport,
4
+ ctxCamelPlural,
5
+ relPascal,
6
+ ctxSingular,
7
+ ctxSingularText,
8
+ ctxCacheLifeLine,
9
+ relIdCol,
10
+ entityIdCol,
11
+ rel
12
+ }: {
13
+ junctionVar: string
14
+ ctxCacheReadImport: string
15
+ ctxCamelPlural: string
16
+ relPascal: string
17
+ ctxSingular: string
18
+ ctxSingularText: string
19
+ ctxCacheLifeLine: string
20
+ relIdCol: string
21
+ entityIdCol: string
22
+ rel: string
23
+ }): string {
24
+ return `'use server'
25
+
26
+ import db from '@admin/db'
27
+ import { ${junctionVar} } from '@admin/db/schema'
28
+ import { eq } from 'drizzle-orm'
29
+ ${ctxCacheReadImport}
30
+ import { ${ctxCamelPlural}CacheTags } from './types'
31
+
32
+ export async function get${relPascal}For${ctxSingular}(${ctxSingularText}Id: string): Promise<string[]> {
33
+ 'use cache'${ctxCacheLifeLine}
34
+ cacheTag(${ctxCamelPlural}CacheTags.byId(${ctxSingularText}Id))
35
+ cacheTag(${ctxCamelPlural}CacheTags.all)
36
+
37
+ try {
38
+ const results = await db
39
+ .select({ ${relIdCol}: ${junctionVar}.${relIdCol} })
40
+ .from(${junctionVar})
41
+ .where(eq(${junctionVar}.${entityIdCol}, ${ctxSingularText}Id))
42
+ return results.map((r) => r.${relIdCol})
43
+ } catch (error) {
44
+ console.error('Error fetching ${rel} for ${ctxSingularText}:', error)
45
+ return []
46
+ }
47
+ }
48
+ `
49
+ }
50
+
51
+ export function renderGenM2MFilesServerActionDetailsTemplate({
52
+ junctionVar,
53
+ ctxTableVar,
54
+ ctxCacheInvalidationFn,
55
+ ctxCamelPlural,
56
+ ctxSingular,
57
+ relPascal,
58
+ ctxSingularText,
59
+ rel,
60
+ fName,
61
+ ctxPlural,
62
+ entityIdCol,
63
+ relSingular,
64
+ relIdCol
65
+ }: {
66
+ junctionVar: string
67
+ ctxTableVar: string
68
+ ctxCacheInvalidationFn: string
69
+ ctxCamelPlural: string
70
+ ctxSingular: string
71
+ relPascal: string
72
+ ctxSingularText: string
73
+ rel: string
74
+ fName: string
75
+ ctxPlural: string
76
+ entityIdCol: string
77
+ relSingular: string
78
+ relIdCol: string
79
+ }): string {
80
+ return `'use server'
81
+
82
+ import db from '@admin/db'
83
+ import { requireRole, UserRole } from '@admin/auth/middleware'
84
+ import { ${junctionVar}, ${ctxTableVar} } from '@admin/db/schema'
85
+ import { eq } from 'drizzle-orm'
86
+ import { ${ctxCacheInvalidationFn} } from 'next/cache'
87
+ import { createEntityVersion, entityVersionsCacheTags } from '@admin/actions/entity-versions'
88
+ import { ${ctxCamelPlural}CacheTags } from './types'
89
+ import {
90
+ build${ctxSingular}VersionSnapshotWithRelationships,
91
+ has${ctxSingular}VersionChanges
92
+ } from './helpers'
93
+
94
+ export async function set${relPascal}For${ctxSingular}(
95
+ ${ctxSingularText}Id: string,
96
+ ${rel}Ids: string[]
97
+ ): Promise<{ success: boolean; error?: string }> {
98
+ const actor = await requireRole([UserRole.ADMIN, UserRole.EDITOR])
99
+
100
+ try {
101
+ await db.transaction(async (tx) => {
102
+ const [previousRow] = await tx
103
+ .select()
104
+ .from(${ctxTableVar})
105
+ .where(eq(${ctxTableVar}.id, ${ctxSingularText}Id))
106
+ .limit(1)
107
+
108
+ if (!previousRow) {
109
+ throw new Error('${ctxSingular} not found')
110
+ }
111
+
112
+ const previousSnapshot = await build${ctxSingular}VersionSnapshotWithRelationships(
113
+ previousRow as Record<string, unknown>,
114
+ tx
115
+ )
116
+ const nextSnapshot = { ...previousSnapshot, ${fName}: ${rel}Ids }
117
+ const shouldCreateVersion = has${ctxSingular}VersionChanges(previousSnapshot, nextSnapshot)
118
+
119
+ if (shouldCreateVersion) {
120
+ await createEntityVersion(tx, {
121
+ entityType: '${ctxPlural}',
122
+ entityId: ${ctxSingularText}Id,
123
+ data: previousSnapshot,
124
+ createdBy: actor.id
125
+ })
126
+ }
127
+
128
+ await tx.delete(${junctionVar}).where(eq(${junctionVar}.${entityIdCol}, ${ctxSingularText}Id))
129
+ if (${rel}Ids.length > 0) {
130
+ await tx.insert(${junctionVar}).values(
131
+ ${rel}Ids.map((${relSingular}Id) => ({
132
+ ${entityIdCol}: ${ctxSingularText}Id,
133
+ ${relIdCol}: ${relSingular}Id
134
+ }))
135
+ )
136
+ }
137
+ })
138
+ ${ctxCacheInvalidationFn}(${ctxCamelPlural}CacheTags.all)
139
+ ${ctxCacheInvalidationFn}(${ctxCamelPlural}CacheTags.byId(${ctxSingularText}Id))
140
+ ${ctxCacheInvalidationFn}(entityVersionsCacheTags.for('${ctxPlural}', ${ctxSingularText}Id))
141
+ return { success: true }
142
+ } catch (error) {
143
+ console.error('Error setting ${rel} for ${ctxSingularText}:', error)
144
+ return {
145
+ success: false,
146
+ error: error instanceof Error ? error.message : 'Failed to set ${rel}'
147
+ }
148
+ }
149
+ }
150
+ `
151
+ }
@@ -0,0 +1,123 @@
1
+ export function renderGenRestoreContentServerActionTemplate({
2
+ join,
3
+ ctxCacheInvalidationFn,
4
+ ctxCamelPlural,
5
+ ctxSingular,
6
+ kebabSingular,
7
+ snapshotImport,
8
+ lifecycleHookImport,
9
+ ctxPlural,
10
+ restoreContext,
11
+ beforeHook,
12
+ ctxTableVar,
13
+ currentSnapshot,
14
+ restoreM2MContent,
15
+ afterHookAfterRestore,
16
+ ctxCamelSingular,
17
+ ctxSingularText
18
+ }: {
19
+ join: string
20
+ ctxCacheInvalidationFn: string
21
+ ctxCamelPlural: string
22
+ ctxSingular: string
23
+ kebabSingular: string
24
+ snapshotImport: string
25
+ lifecycleHookImport: string
26
+ ctxPlural: string
27
+ restoreContext: string
28
+ beforeHook: string
29
+ ctxTableVar: string
30
+ currentSnapshot: string
31
+ restoreM2MContent: string
32
+ afterHookAfterRestore: string
33
+ ctxCamelSingular: string
34
+ ctxSingularText: string
35
+ }): string {
36
+ return `'use server'
37
+
38
+ import db from '@admin/db'
39
+ import { requireRole, UserRole } from '@admin/auth/middleware'
40
+ import { ${join} } from '@admin/db/schema'
41
+ import { and, eq } from 'drizzle-orm'
42
+ import { ${ctxCacheInvalidationFn} } from 'next/cache'
43
+ import { createEntityVersion, entityVersionsCacheTags } from '@admin/actions/entity-versions'
44
+ import { ${ctxCamelPlural}CacheTags } from './types'
45
+ import { get${ctxSingular}ById } from './get-${kebabSingular}-by-id'
46
+ import {
47
+ build${ctxSingular}RestoreData,
48
+ ${snapshotImport}
49
+ } from './helpers'
50
+ ${lifecycleHookImport}
51
+ import type { ${ctxSingular}UpdateResult, ${ctxSingular} } from './types'
52
+
53
+ export async function restore${ctxSingular}Version(
54
+ versionId: string
55
+ ): Promise<${ctxSingular}UpdateResult> {
56
+ const actor = await requireRole([UserRole.ADMIN, UserRole.EDITOR])
57
+
58
+ try {
59
+ const actorId = actor.id
60
+ const now = new Date().toISOString()
61
+
62
+ const [versionRow] = await db
63
+ .select()
64
+ .from(entityVersions)
65
+ .where(and(eq(entityVersions.id, versionId), eq(entityVersions.entityType, '${ctxPlural}')))
66
+ .limit(1)
67
+
68
+ if (!versionRow) throw new Error('${ctxSingular} version not found')
69
+
70
+ const versionData = versionRow.data as Record<string, unknown>
71
+ const restoreData = build${ctxSingular}RestoreData(versionData)
72
+ const auditRequestMetadata = await getAuditRequestMetadata()
73
+ const lifecycleContext = ${restoreContext}
74
+ ${beforeHook}
75
+
76
+ const result = await db.transaction(async (tx) => {
77
+ const [currentRow] = await tx
78
+ .select()
79
+ .from(${ctxTableVar})
80
+ .where(eq(${ctxTableVar}.id, versionRow.entityId))
81
+ .limit(1)
82
+
83
+ if (!currentRow) throw new Error('${ctxSingular} not found')
84
+
85
+ await createEntityVersion(tx, {
86
+ entityType: '${ctxPlural}',
87
+ entityId: versionRow.entityId,
88
+ data: ${currentSnapshot},
89
+ createdBy: actorId
90
+ })
91
+ ${restoreM2MContent}
92
+ return tx.update(${ctxTableVar})
93
+ .set({ ...restoreData, updatedAt: now, updatedBy: actorId })
94
+ .where(eq(${ctxTableVar}.id, versionRow.entityId))
95
+ .returning()
96
+ })
97
+
98
+ if (result.length === 0) throw new Error('${ctxSingular} not found')
99
+
100
+ ${ctxCacheInvalidationFn}(${ctxCamelPlural}CacheTags.all)
101
+ ${ctxCacheInvalidationFn}(${ctxCamelPlural}CacheTags.byId(versionRow.entityId))
102
+ ${ctxCacheInvalidationFn}(entityVersionsCacheTags.for('${ctxPlural}', versionRow.entityId))
103
+ const restored${ctxSingular} = await get${ctxSingular}ById(versionRow.entityId)
104
+ ${afterHookAfterRestore}
105
+
106
+ return {
107
+ success: true,
108
+ ${ctxCamelSingular}: restored${ctxSingular} ?? (result[0] as unknown as ${ctxSingular})
109
+ }
110
+ } catch (error) {
111
+ console.error('Error restoring ${ctxSingularText} version:', error)
112
+ throw new Error(error instanceof Error ? error.message : 'Failed to restore ${ctxSingularText} version')
113
+ }
114
+ }
115
+ `
116
+ }
117
+
118
+ export function renderGenRestoreContentTemplate({ ctxSingular }: { ctxSingular: string }): string {
119
+ return `{
120
+ ...lifecycleContext,
121
+ data: (restored${ctxSingular} ?? result[0]) as unknown as Record<string, unknown>
122
+ }`
123
+ }
@@ -0,0 +1,187 @@
1
+ export function renderGenUpdateContentServerActionTemplate({
2
+ ctxTableVar,
3
+ ctxCacheInvalidationFn,
4
+ ctxCamelPlural,
5
+ ctxSingular,
6
+ kebabSingular,
7
+ snapshotImport,
8
+ lifecycleHookImport,
9
+ markdownImport,
10
+ updateContext,
11
+ beforeHook,
12
+ updateBeforePublishBlock,
13
+ slugSourceValue,
14
+ toKebabCase,
15
+ ctxFieldMeta,
16
+ htmlUpdateBlock,
17
+ previousSnapshot,
18
+ ctxPlural,
19
+ updateResultContext,
20
+ updateAfterPublishBlock,
21
+ afterHook,
22
+ ctxCamelSingular,
23
+ ctxSingularText
24
+ }: {
25
+ ctxTableVar: string
26
+ ctxCacheInvalidationFn: string
27
+ ctxCamelPlural: string
28
+ ctxSingular: string
29
+ kebabSingular: string
30
+ snapshotImport: string
31
+ lifecycleHookImport: string
32
+ markdownImport: string
33
+ updateContext: string
34
+ beforeHook: string
35
+ updateBeforePublishBlock: string
36
+ slugSourceValue: string
37
+ toKebabCase: string
38
+ ctxFieldMeta: string
39
+ htmlUpdateBlock: string
40
+ previousSnapshot: string
41
+ ctxPlural: string
42
+ updateResultContext: string
43
+ updateAfterPublishBlock: string
44
+ afterHook: string
45
+ ctxCamelSingular: string
46
+ ctxSingularText: string
47
+ }): string {
48
+ return `'use server'
49
+
50
+ import db from '@admin/db'
51
+ import { requireRole, UserRole } from '@admin/auth/middleware'
52
+ import { ${ctxTableVar} } from '@admin/db/schema'
53
+ import { eq } from 'drizzle-orm'
54
+ import { ${ctxCacheInvalidationFn} } from 'next/cache'
55
+ import { createEntityVersion, entityVersionsCacheTags } from '@admin/actions/entity-versions'
56
+ import { ${ctxCamelPlural}CacheTags } from './types'
57
+ import { get${ctxSingular}ById } from './get-${kebabSingular}-by-id'
58
+ import { getChangedFieldNames } from '@admin/utils/audit/get-changed-field-names'
59
+ import {
60
+ build${ctxSingular}RestoreData,
61
+ get${ctxSingular}SlugRetryLimit,
62
+ is${ctxSingular}SlugConflict,
63
+ resolveUnique${ctxSingular}Slug,
64
+ ${snapshotImport},
65
+ has${ctxSingular}VersionChanges
66
+ } from './helpers'
67
+ ${lifecycleHookImport}
68
+ import type { ${ctxSingular}UpdateInput, ${ctxSingular}UpdateResult, ${ctxSingular} } from './types'${markdownImport}
69
+
70
+ export async function update${ctxSingular}(input: ${ctxSingular}UpdateInput): Promise<${ctxSingular}UpdateResult> {
71
+ const actor = await requireRole([UserRole.ADMIN, UserRole.EDITOR])
72
+
73
+ try {
74
+ const { id, ...updateData } = input
75
+ const actorId = actor.id
76
+ const now = new Date().toISOString()
77
+ const auditRequestMetadata = await getAuditRequestMetadata()
78
+ const [previousRow] = await db
79
+ .select()
80
+ .from(${ctxTableVar})
81
+ .where(eq(${ctxTableVar}.id, id))
82
+ .limit(1)
83
+
84
+ if (!previousRow) throw new Error('${ctxSingular} not found')
85
+
86
+ const lifecycleContext = ${updateContext}
87
+ ${beforeHook}
88
+ ${updateBeforePublishBlock}
89
+ const requestedUpdateSlug = updateData.slug
90
+ let changedFields: string[] = []
91
+ let result: (typeof ${ctxTableVar}.$inferSelect)[] | null = null
92
+ for (let attempt = 0; attempt < get${ctxSingular}SlugRetryLimit(); attempt += 1) {
93
+ try {
94
+ if (requestedUpdateSlug !== undefined) {
95
+ updateData.slug = await resolveUnique${ctxSingular}Slug(db, {
96
+ requestedSlug: requestedUpdateSlug,
97
+ sourceValue: ${slugSourceValue},
98
+ fallbackPrefix: '${toKebabCase}',
99
+ currentId: id
100
+ })
101
+ }
102
+
103
+ const fieldMeta = [
104
+ ${ctxFieldMeta}
105
+ ]
106
+
107
+ const processedData: Record<string, unknown> = {}
108
+ for (const [key, value] of Object.entries(updateData)) {
109
+ if (key === 'published') { processedData[key] = value; continue }
110
+ const field = fieldMeta.find(f => f.name === key)
111
+ if (!field) continue
112
+ if (field.type === 'list') {
113
+ processedData[key] = value || []
114
+ } else if (!field.required && ['number', 'decimal'].includes(field.type)) {
115
+ processedData[key] =
116
+ value === undefined ||
117
+ value === null ||
118
+ value === '' ||
119
+ (typeof value === 'number' && Number.isNaN(value))
120
+ ? null
121
+ : value
122
+ } else if (!field.required && ['date', 'timestamp', 'time', 'string', 'email', 'varchar', 'text', 'select', 'relationship'].includes(field.type)) {
123
+ processedData[key] = value && value !== '' ? value : null
124
+ } else {
125
+ processedData[key] = value
126
+ }
127
+ }
128
+ ${htmlUpdateBlock}
129
+ changedFields = getChangedFieldNames(
130
+ previousRow as unknown as Record<string, unknown>,
131
+ processedData
132
+ )
133
+ result = await db.transaction(async (tx) => {
134
+ const previousSnapshot = ${previousSnapshot}
135
+ const nextSnapshot = {
136
+ ...previousSnapshot,
137
+ ...build${ctxSingular}RestoreData(processedData)
138
+ }
139
+ const shouldCreateVersion = has${ctxSingular}VersionChanges(previousSnapshot, nextSnapshot)
140
+
141
+ if (shouldCreateVersion) {
142
+ await createEntityVersion(tx, {
143
+ entityType: '${ctxPlural}',
144
+ entityId: id,
145
+ data: previousSnapshot,
146
+ createdBy: actorId
147
+ })
148
+ }
149
+
150
+ return tx.update(${ctxTableVar})
151
+ .set({ ...processedData, updatedAt: now, updatedBy: actorId })
152
+ .where(eq(${ctxTableVar}.id, id))
153
+ .returning()
154
+ })
155
+ break
156
+ } catch (error) {
157
+ if (is${ctxSingular}SlugConflict(error) && attempt < get${ctxSingular}SlugRetryLimit() - 1) {
158
+ continue
159
+ }
160
+ throw error
161
+ }
162
+ }
163
+
164
+ if (!result) throw new Error('${ctxSingular} not found')
165
+
166
+ if (result.length === 0) throw new Error('${ctxSingular} not found')
167
+
168
+ ${ctxCacheInvalidationFn}(${ctxCamelPlural}CacheTags.all)
169
+ ${ctxCacheInvalidationFn}(${ctxCamelPlural}CacheTags.byId(id))
170
+ ${ctxCacheInvalidationFn}(entityVersionsCacheTags.for('${ctxPlural}', id))
171
+ const updated${ctxSingular} = await get${ctxSingular}ById(id)
172
+ const updated${ctxSingular}Data = updated${ctxSingular} ?? (result[0] as unknown as ${ctxSingular})
173
+ const updated${ctxSingular}Context = ${updateResultContext}
174
+ ${updateAfterPublishBlock}
175
+ ${afterHook}
176
+
177
+ return {
178
+ success: true,
179
+ ${ctxCamelSingular}: updated${ctxSingular}Data
180
+ }
181
+ } catch (error) {
182
+ console.error('Error updating ${ctxSingularText}:', error)
183
+ throw new Error(error instanceof Error ? error.message : 'Failed to update ${ctxSingularText}')
184
+ }
185
+ }
186
+ `
187
+ }
@@ -0,0 +1,40 @@
1
+ export function renderResolveMediaFunctionIdsTemplate({
2
+ ctxSingular,
3
+ collectSingle,
4
+ collectArrayContent,
5
+ map,
6
+ arrayFieldsContent,
7
+ assignSingle,
8
+ assignArrayContent
9
+ }: {
10
+ ctxSingular: string
11
+ collectSingle: string
12
+ collectArrayContent: string
13
+ map: string
14
+ arrayFieldsContent: string
15
+ assignSingle: string
16
+ assignArrayContent: string
17
+ }): string {
18
+ return `export async function resolve${ctxSingular}MediaFields(rows: ${ctxSingular}[]): Promise<${ctxSingular}[]> {
19
+ if (rows.length === 0) return rows
20
+
21
+ const ids = new Set<string>()
22
+ ${collectSingle}${collectArrayContent}
23
+
24
+ if (ids.size === 0) {
25
+ for (const row of rows) {
26
+ ${map}${arrayFieldsContent}
27
+ }
28
+ return rows
29
+ }
30
+
31
+ const mediaRows = await db.select().from(adminMedia).where(inArray(adminMedia.id, [...ids]))
32
+ const mediaMap = new Map<string, AdminMedia>(mediaRows.map(m => [m.id, m]))
33
+
34
+ for (const row of rows) {
35
+ ${assignSingle}${assignArrayContent}
36
+ }
37
+
38
+ return rows
39
+ }`
40
+ }
@@ -0,0 +1,73 @@
1
+ export function renderSlugHelpersComponentTemplate({
2
+ stringify,
3
+ ctxSingular,
4
+ ctxTableVar
5
+ }: {
6
+ stringify: string
7
+ ctxSingular: string
8
+ ctxTableVar: string
9
+ }): string {
10
+ return `const SLUG_CONSTRAINT_NAME = ${stringify}
11
+ const SLUG_RETRY_LIMIT = 5
12
+
13
+ type SlugLookupClient = typeof db | Parameters<Parameters<typeof db.transaction>[0]>[0]
14
+
15
+ interface Resolve${ctxSingular}SlugOptions {
16
+ requestedSlug?: unknown
17
+ sourceValue?: unknown
18
+ fallbackPrefix: string
19
+ currentId?: string
20
+ }
21
+
22
+ function toSlugBase(value: unknown): string {
23
+ if (typeof value !== 'string') return ''
24
+ return slugify(value)
25
+ }
26
+
27
+ export function get${ctxSingular}SlugRetryLimit(): number {
28
+ return SLUG_RETRY_LIMIT
29
+ }
30
+
31
+ export function is${ctxSingular}SlugConflict(error: unknown): boolean {
32
+ if (typeof error !== 'object' || error === null) return false
33
+ const record = error as Record<string, unknown>
34
+ if (record.code !== '23505') return false
35
+ const constraint = typeof record.constraint === 'string' ? record.constraint : ''
36
+ const message = typeof record.message === 'string' ? record.message : ''
37
+ return constraint === SLUG_CONSTRAINT_NAME || message.includes(SLUG_CONSTRAINT_NAME)
38
+ }
39
+
40
+ export async function resolveUnique${ctxSingular}Slug(
41
+ client: SlugLookupClient,
42
+ options: Resolve${ctxSingular}SlugOptions
43
+ ): Promise<string> {
44
+ const requestedBase = toSlugBase(options.requestedSlug)
45
+ const sourceBase = requestedBase ? '' : toSlugBase(options.sourceValue)
46
+ const usesFallback = !requestedBase && !sourceBase
47
+ const baseSlug = requestedBase || sourceBase || \`\${options.fallbackPrefix}-1\`
48
+ const rows = await client
49
+ .select({ id: ${ctxTableVar}.id, slug: ${ctxTableVar}.slug })
50
+ .from(${ctxTableVar})
51
+ const usedSlugs = new Set(
52
+ rows
53
+ .filter((row) => row.id !== options.currentId)
54
+ .map((row) => row.slug)
55
+ .filter((slug): slug is string => typeof slug === 'string' && slug.length > 0)
56
+ )
57
+
58
+ if (!usedSlugs.has(baseSlug)) return baseSlug
59
+
60
+ let suffix = 2
61
+ let candidate = usesFallback
62
+ ? \`\${options.fallbackPrefix}-\${suffix}\`
63
+ : \`\${baseSlug}-\${suffix}\`
64
+ while (usedSlugs.has(candidate)) {
65
+ suffix += 1
66
+ candidate = usesFallback
67
+ ? \`\${options.fallbackPrefix}-\${suffix}\`
68
+ : \`\${baseSlug}-\${suffix}\`
69
+ }
70
+
71
+ return candidate
72
+ }`
73
+ }