firstly 0.3.0 → 0.4.1

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 (177) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/esm/SqlDatabase/FF_LogToConsole.js +9 -14
  3. package/esm/carbone/CarboneController.js +2 -1
  4. package/esm/changeLog/index.d.ts +0 -5
  5. package/esm/core/FF_Allow.d.ts +55 -0
  6. package/esm/core/FF_Allow.js +54 -0
  7. package/esm/core/FF_Filter.d.ts +55 -0
  8. package/esm/core/FF_Filter.js +57 -0
  9. package/esm/core/helper.d.ts +2 -0
  10. package/esm/core/helper.js +3 -0
  11. package/esm/core/index.d.ts +0 -0
  12. package/esm/core/index.js +5 -0
  13. package/esm/core/tailwind.d.ts +21 -0
  14. package/esm/core/tailwind.js +22 -0
  15. package/esm/core/tryCatch.d.ts +44 -0
  16. package/esm/core/tryCatch.js +34 -0
  17. package/esm/cron/server/index.js +1 -1
  18. package/esm/feedback/FeedbackController.js +3 -2
  19. package/esm/feedback/index.d.ts +7 -2
  20. package/esm/feedback/index.js +1 -2
  21. package/esm/feedback/server/index.d.ts +0 -5
  22. package/esm/feedback/server/index.js +1 -1
  23. package/esm/formats/strings.js +2 -2
  24. package/esm/index.d.ts +18 -0
  25. package/esm/index.js +15 -0
  26. package/esm/sqlAdmin/Roles_SqlAdmin.d.ts +3 -0
  27. package/esm/sqlAdmin/Roles_SqlAdmin.js +3 -0
  28. package/esm/sqlAdmin/SqlAdminController.d.ts +9 -0
  29. package/esm/sqlAdmin/SqlAdminController.js +23 -0
  30. package/esm/sqlAdmin/index.d.ts +6 -0
  31. package/esm/sqlAdmin/index.js +6 -0
  32. package/esm/sqlAdmin/server/index.d.ts +40 -0
  33. package/esm/sqlAdmin/server/index.js +40 -0
  34. package/esm/sqlAdmin/ui/SqlAdmin.svelte +146 -0
  35. package/esm/sqlAdmin/ui/SqlAdmin.svelte.d.ts +3 -0
  36. package/esm/svelte/FF_Repo.svelte.d.ts +0 -2
  37. package/esm/svelte/FF_Repo.svelte.js +1 -17
  38. package/esm/svelte/helpers/debounce.js +1 -1
  39. package/esm/svelte/index.d.ts +2 -24
  40. package/esm/svelte/index.js +2 -22
  41. package/esm/{ui → svelte/ui}/Icon.svelte +1 -1
  42. package/esm/virtual/StateDemoEnum.d.ts +3 -3
  43. package/esm/virtual/StateDemoEnum.js +3 -3
  44. package/esm/virtual/UIEntity.js +1 -2
  45. package/package.json +15 -24
  46. package/esm/bin/cmd.d.ts +0 -1
  47. package/esm/bin/cmd.js +0 -638
  48. package/esm/feedback/ui/DialogIssue.svelte +0 -149
  49. package/esm/feedback/ui/DialogIssue.svelte.d.ts +0 -22
  50. package/esm/feedback/ui/DialogIssues.svelte +0 -114
  51. package/esm/feedback/ui/DialogIssues.svelte.d.ts +0 -22
  52. package/esm/feedback/ui/DialogMilestones.svelte +0 -43
  53. package/esm/feedback/ui/DialogMilestones.svelte.d.ts +0 -20
  54. package/esm/feedback/ui/Feedback.svelte +0 -16
  55. package/esm/feedback/ui/Feedback.svelte.d.ts +0 -18
  56. package/esm/internals/FF_Entity.d.ts +0 -2
  57. package/esm/internals/FF_Fields.d.ts +0 -11
  58. package/esm/internals/FF_Fields.js +0 -144
  59. package/esm/internals/cellsBuildor.d.ts +0 -47
  60. package/esm/internals/cellsBuildor.js +0 -141
  61. package/esm/internals/helper.d.ts +0 -49
  62. package/esm/internals/helper.js +0 -162
  63. package/esm/internals/index.d.ts +0 -78
  64. package/esm/internals/index.js +0 -45
  65. package/esm/internals/storeItem.d.ts +0 -19
  66. package/esm/internals/storeItem.js +0 -190
  67. package/esm/internals/storeList.d.ts +0 -34
  68. package/esm/internals/storeList.js +0 -108
  69. package/esm/internals/theme.d.ts +0 -4
  70. package/esm/internals/theme.js +0 -4
  71. package/esm/server/index.d.ts +0 -52
  72. package/esm/server/index.js +0 -87
  73. package/esm/svelte/FF_Cell.svelte +0 -103
  74. package/esm/svelte/FF_Cell.svelte.d.ts +0 -33
  75. package/esm/svelte/FF_Cell_Caption.svelte +0 -20
  76. package/esm/svelte/FF_Cell_Caption.svelte.d.ts +0 -31
  77. package/esm/svelte/FF_Cell_Display.svelte +0 -61
  78. package/esm/svelte/FF_Cell_Display.svelte.d.ts +0 -29
  79. package/esm/svelte/FF_Cell_Edit.svelte +0 -104
  80. package/esm/svelte/FF_Cell_Edit.svelte.d.ts +0 -32
  81. package/esm/svelte/FF_Cell_Error.svelte +0 -20
  82. package/esm/svelte/FF_Cell_Error.svelte.d.ts +0 -31
  83. package/esm/svelte/FF_Cell_Hint.svelte +0 -20
  84. package/esm/svelte/FF_Cell_Hint.svelte.d.ts +0 -31
  85. package/esm/svelte/FF_Config.svelte +0 -29
  86. package/esm/svelte/FF_Config.svelte.d.ts +0 -9
  87. package/esm/svelte/FF_Form.svelte +0 -155
  88. package/esm/svelte/FF_Form.svelte.d.ts +0 -37
  89. package/esm/svelte/FF_Grid.svelte +0 -257
  90. package/esm/svelte/FF_Grid.svelte.d.ts +0 -37
  91. package/esm/svelte/FF_Layout.svelte +0 -62
  92. package/esm/svelte/FF_Layout.svelte.d.ts +0 -31
  93. package/esm/svelte/actions/intersection.d.ts +0 -6
  94. package/esm/svelte/actions/intersection.js +0 -17
  95. package/esm/svelte/customField.d.ts +0 -69
  96. package/esm/svelte/customField.js +0 -4
  97. package/esm/svelte/dialog/DialogManagement.svelte +0 -98
  98. package/esm/svelte/dialog/DialogManagement.svelte.d.ts +0 -18
  99. package/esm/svelte/dialog/DialogPrimitive.svelte +0 -156
  100. package/esm/svelte/dialog/DialogPrimitive.svelte.d.ts +0 -38
  101. package/esm/svelte/dialog/dialog.d.ts +0 -58
  102. package/esm/svelte/dialog/dialog.js +0 -130
  103. package/esm/svelte/ff_Config.svelte.d.ts +0 -91
  104. package/esm/svelte/ff_Config.svelte.js +0 -111
  105. package/esm/svelte/firstly.css +0 -14
  106. package/esm/svelte/helpers.d.ts +0 -30
  107. package/esm/svelte/helpers.js +0 -38
  108. package/esm/svelte/tryCatch.d.ts +0 -12
  109. package/esm/svelte/tryCatch.js +0 -18
  110. package/esm/sveltekit/server/index.d.ts +0 -5
  111. package/esm/sveltekit/server/index.js +0 -24
  112. package/esm/ui/Button.svelte +0 -90
  113. package/esm/ui/Button.svelte.d.ts +0 -11
  114. package/esm/ui/Clipboardable.svelte +0 -25
  115. package/esm/ui/Clipboardable.svelte.d.ts +0 -12
  116. package/esm/ui/Field.svelte +0 -391
  117. package/esm/ui/Field.svelte.d.ts +0 -40
  118. package/esm/ui/FieldGroup.svelte +0 -117
  119. package/esm/ui/FieldGroup.svelte.d.ts +0 -44
  120. package/esm/ui/Grid.svelte +0 -262
  121. package/esm/ui/Grid.svelte.d.ts +0 -57
  122. package/esm/ui/Grid2.svelte +0 -290
  123. package/esm/ui/Grid2.svelte.d.ts +0 -57
  124. package/esm/ui/GridLoading.svelte +0 -58
  125. package/esm/ui/GridLoading.svelte.d.ts +0 -23
  126. package/esm/ui/GridPaginate.svelte +0 -69
  127. package/esm/ui/GridPaginate.svelte.d.ts +0 -23
  128. package/esm/ui/GridPaginate2.svelte +0 -25
  129. package/esm/ui/GridPaginate2.svelte.d.ts +0 -7
  130. package/esm/ui/Loading.svelte +0 -16
  131. package/esm/ui/Loading.svelte.d.ts +0 -31
  132. package/esm/ui/Tooltip.svelte +0 -45
  133. package/esm/ui/Tooltip.svelte.d.ts +0 -32
  134. package/esm/ui/dialog/DialogForm.svelte +0 -76
  135. package/esm/ui/dialog/DialogForm.svelte.d.ts +0 -21
  136. package/esm/ui/dialog/DialogManagement.svelte +0 -96
  137. package/esm/ui/dialog/DialogManagement.svelte.d.ts +0 -26
  138. package/esm/ui/dialog/DialogPrimitive.svelte +0 -90
  139. package/esm/ui/dialog/DialogPrimitive.svelte.d.ts +0 -38
  140. package/esm/ui/dialog/FormEditAction.svelte +0 -62
  141. package/esm/ui/dialog/FormEditAction.svelte.d.ts +0 -31
  142. package/esm/ui/dialog/dialog.d.ts +0 -60
  143. package/esm/ui/dialog/dialog.js +0 -100
  144. package/esm/ui/index.d.ts +0 -6
  145. package/esm/ui/index.js +0 -20
  146. package/esm/ui/internals/FieldContainer.svelte +0 -39
  147. package/esm/ui/internals/FieldContainer.svelte.d.ts +0 -18
  148. package/esm/ui/internals/Input.svelte +0 -143
  149. package/esm/ui/internals/Input.svelte.d.ts +0 -37
  150. package/esm/ui/internals/Textarea.svelte +0 -66
  151. package/esm/ui/internals/Textarea.svelte.d.ts +0 -33
  152. package/esm/ui/internals/select/MultiSelectMelt.svelte +0 -260
  153. package/esm/ui/internals/select/MultiSelectMelt.svelte.d.ts +0 -32
  154. package/esm/ui/internals/select/Select2.svelte +0 -88
  155. package/esm/ui/internals/select/Select2.svelte.d.ts +0 -12
  156. package/esm/ui/internals/select/SelectMelt.svelte +0 -289
  157. package/esm/ui/internals/select/SelectMelt.svelte.d.ts +0 -40
  158. package/esm/ui/internals/select/SelectRadio.svelte +0 -43
  159. package/esm/ui/internals/select/SelectRadio.svelte.d.ts +0 -27
  160. package/esm/ui/link/Link.svelte +0 -33
  161. package/esm/ui/link/Link.svelte.d.ts +0 -33
  162. package/esm/ui/link/LinkPlus.svelte +0 -63
  163. package/esm/ui/link/LinkPlus.svelte.d.ts +0 -9
  164. package/esm/utils/tailwind.d.ts +0 -2
  165. package/esm/utils/tailwind.js +0 -3
  166. package/esm/utils/transition.d.ts +0 -9
  167. package/esm/utils/transition.js +0 -33
  168. /package/esm/{internals → core}/BaseEnum.d.ts +0 -0
  169. /package/esm/{internals → core}/BaseEnum.js +0 -0
  170. /package/esm/{internals → core}/FF_Entity.js +0 -0
  171. /package/esm/{internals → core}/common.d.ts +0 -0
  172. /package/esm/{internals → core}/common.js +0 -0
  173. /package/esm/{utils → core}/types.d.ts +0 -0
  174. /package/esm/{utils → core}/types.js +0 -0
  175. /package/esm/{ui → svelte/ui}/Icon.svelte.d.ts +0 -0
  176. /package/esm/{ui → svelte/ui}/LibIcon.d.ts +0 -0
  177. /package/esm/{ui → svelte/ui}/LibIcon.js +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,65 @@
1
1
  # firstly
2
2
 
3
+ ## 0.4.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#250](https://github.com/jycouet/firstly/pull/250) [`53916ad`](https://github.com/jycouet/firstly/commit/53916ad519835a47e3598afb68ce6d84356af944) Thanks [@jycouet](https://github.com/jycouet)! - Add `firstly/sqlAdmin` module: a drop-in `<SqlAdmin />` Svelte component plus a `BackendMethod` controller gated by `Roles_SqlAdmin.SqlAdmin_Admin` (or `FF_Role.FF_Role_Admin`). The component ships with prefilled queries (DB size, table sizes, indexes) and logs results as `for AI: <rows>` to the browser console for chrome-devtools / AI-agent inspection. `sqlAdmin({ path })` logs an AI hint on server start pointing to the page (default `/sql/admin`).
8
+
9
+ Add `FF_Allow` and `FF_Filter` helpers (exported from `firstly`) for owner-only / admin-or-owner row checks and prefilters - usable in `allowApi*` and `apiPrefilter`. Both accept an entity generic (`FF_Allow.owner<Task>('userId')`) for type-safe column names; `col` defaults to `'userId'` if omitted. The `ownerOr<T>({ col?, roles })` variants are shortcuts for the "admin (or any role) OR owner" pattern.
10
+
11
+ ## 0.4.0
12
+
13
+ ### Minor Changes
14
+
15
+ - [#239](https://github.com/jycouet/firstly/pull/239) [`b70967d`](https://github.com/jycouet/firstly/commit/b70967d0536298ec6659653da45eaa9e8b8e7fb0) Thanks [@jycouet](https://github.com/jycouet)! - BREAKING: Remove unused legacy components from `firstly/svelte`: `FF_Cell` (+ `FF_Cell_Caption/Display/Edit/Error/Hint`), `FF_Form`, `FF_Grid`, `FF_Layout`, `FF_Config` (+ `ff_Config.svelte.ts` with `daisyTheme`, `defaultTheme`, `emptyTheme`, `getTheme`, `setTheme`, `getDynamicCustomField`, `setDynamicCustomField`, `getClasses`, `FF_Theme`). Also removed `customField.ts` (`DynamicCustomField`, `CellMetadata`, `FieldGroup`, `getLayout`, and the related `FieldOptions.ui` / `EntityOptions.ui.getLayout` Remult module augmentations). Drops the `FF_Repo.getLayout()` instance method and the `internals/select/Select2.svelte` internal.
16
+
17
+ Also deletes the duplicate dead `svelte/dialog/` tree (the canonical `dialog()` API is exported from `firstly/internals` and backed by `ui/dialog/`).
18
+
19
+ Internal: removes the `task` demo module and its demo routes (`demo/FF_Cell`, `demo/FF_Form_Grid`, `demo/FF_Layout`, `demo/FF_Simple`).
20
+
21
+ - [#239](https://github.com/jycouet/firstly/pull/239) [`6589feb`](https://github.com/jycouet/firstly/commit/6589feba930250ffd842891f169faabfa498312b) Thanks [@jycouet](https://github.com/jycouet)! - Move `BaseEnum`, `FF_Entity`, `common` (`FF_Role`) source files from `lib/internals/` to `lib/core/`. The public export path is still `firstly/core`, which now also exports `FF_Role` (previously only available via `firstly/internals`) alongside the existing `BaseEnum`, `BaseEnumOptions`, `FF_Entity`, `isError`, `BaseItem`, `BaseItemLight`, `FF_Icon`. Consumers should migrate remaining `FF_Role` imports from `firstly/internals` to `firstly/core`.
22
+
23
+ - [#239](https://github.com/jycouet/firstly/pull/239) [`c4d2ee4`](https://github.com/jycouet/firstly/commit/c4d2ee4a4b7a1441c985c62b55f9fbacbfe02b9b) Thanks [@jycouet](https://github.com/jycouet)! - BREAKING: Fold `firstly/core` into the root `firstly` export.
24
+
25
+ `lib/utils/` (types + `tw`) moves into `lib/core/`. The `lib/core/index.ts` barrel is now empty — all core, frontend-safe primitives (`BaseEnum`, `BaseEnumOptions`, `BaseItem`, `BaseItemLight`, `FF_Icon`, `FF_Entity`, `FF_Role`, `isError`, `tryCatch`, `tryCatchSync`, `ResolvedType`, `UnArray`, `RecursivePartial`, `tw`) are re-exported from `lib/index.ts`, so consumers should import them from `'firstly'` directly.
26
+
27
+ The `./core` subpath is removed from `package.json` exports. Migrate `from 'firstly/core'` -> `from 'firstly'`.
28
+
29
+ Also moves the `RemultContext.feedbackOptions` module augmentation from `FeedbackController.ts` into `feedback/index.ts`, because some bundlers mangle `declare module 'remult'` inside a file that also has `@BackendMethod` decorators.
30
+
31
+ - [#239](https://github.com/jycouet/firstly/pull/239) [`b382619`](https://github.com/jycouet/firstly/commit/b3826192a26e529c45f78dffa59f9955e92f3257) Thanks [@jycouet](https://github.com/jycouet)! - BREAKING: Remove the `firstly` CLI.
32
+
33
+ The `bin` entry, the `./bin` export path, and `src/lib/bin/cmd.ts` are gone. Drops the `@clack/prompts` and `@kitql/internals` deps that only the CLI used.
34
+
35
+ - [#239](https://github.com/jycouet/firstly/pull/239) [`64c4644`](https://github.com/jycouet/firstly/commit/64c4644d8c75d7b757a2d4a4128cd0b4f5799c50) Thanks [@jycouet](https://github.com/jycouet)! - BREAKING: Remove the `firstly({ ... })` server wrapper and the `./server`, let's now just use remult Modules
36
+
37
+ - [#239](https://github.com/jycouet/firstly/pull/239) [`a784113`](https://github.com/jycouet/firstly/commit/a784113217ba477fc36de3fee7ba6e82c8248cf5) Thanks [@jycouet](https://github.com/jycouet)! - BREAKING: Remove `ModuleFF` and the `modulesFF` option.
38
+
39
+ Use `Module` from `remult/server` directly (it has all the same features: `key`, `priority`, `entities`, `controllers`, `initApi`, `initRequest`, nested `modules`). Remult also ships its own `modulesFlatAndOrdered`, so firstly's version is gone too. The `firstly({ ... })` wrapper now only accepts remult's native `modules` option.
40
+
41
+ Migration:
42
+
43
+ ```ts
44
+ // before
45
+ import { ModuleFF } from 'firstly/server'
46
+ new ModuleFF({ name: 'foo', modulesFF: [...] })
47
+
48
+ // after
49
+ import { Module } from 'remult/server'
50
+ new Module({ key: 'foo', modules: [...] })
51
+ ```
52
+
53
+ If you used `m.log`, create your own: `const log = new Log(key)` at module scope and reference it from `initApi` / `initRequest`.
54
+
55
+ - [#239](https://github.com/jycouet/firstly/pull/239) [`7e49152`](https://github.com/jycouet/firstly/commit/7e49152d3af4f2ca40635ec0e1112a7ffa0de786) Thanks [@jycouet](https://github.com/jycouet)! - BREAKING: Remove the deprecated internal `sveltekit` module and the `RemultContext.setHeaders` / `setCookie` / `deleteCookie` augmentation it set up.
56
+
57
+ Nothing consumed those context methods. The module itself was marked `@deprecated` and was only auto-wired through `firstly({ ... })`. `remult.context.request` (the `RequestEvent`) is still augmented, so headers and cookies can be set via `remult.context.request.setHeaders(...)` / `remult.context.request.cookies.set(...)` directly.
58
+
59
+ ### Patch Changes
60
+
61
+ - [#239](https://github.com/jycouet/firstly/pull/239) [`b70967d`](https://github.com/jycouet/firstly/commit/b70967d0536298ec6659653da45eaa9e8b8e7fb0) Thanks [@jycouet](https://github.com/jycouet)! - Bump deps: `nodemailer` 7 → 8 (+ `@types/nodemailer`), `tailwindcss` + `@tailwindcss/vite` 4.1.14 → 4.2.2, `vite-plugin-watch-and-run` 1.7.5 → 1.8.0, `@playwright/test` 1.58.2 → 1.59.1.
62
+
3
63
  ## 0.3.0
4
64
 
5
65
  ### Minor Changes
@@ -16,7 +16,7 @@ const typeQuery = new Map([
16
16
  ['REVOKE', '🟫'], // Used to take back permissions from a user.
17
17
  ]);
18
18
  const keys = ['FROM', 'WHERE', 'LIMIT', 'OFFSET'];
19
- const typeQueryKey = Array.from(typeQuery.keys());
19
+ const typeQueryKey = [...typeQuery.keys()];
20
20
  const log = new Log('');
21
21
  /**
22
22
  * @example
@@ -110,22 +110,17 @@ export const FF_LogToConsole = (duration, query, args, options) => {
110
110
  // final_s = final_s.replace(arg, yellow(args[arg]))
111
111
  // }
112
112
  const uniqueTables = [...new Set(tables)];
113
- const mainTable = uniqueTables[uniqueTables.length - 1];
113
+ const mainTable = uniqueTables.at(-1) ?? '';
114
114
  const subTables = uniqueTables.slice(0, -1);
115
115
  const time = ` ${bgCyan((duration * 1000).toFixed(0).padStart(3) + ' ms ')}`;
116
- let toLog = '';
117
116
  const withDetails = options?.withDetails === undefined ? true : options?.withDetails;
118
- if (withDetails) {
119
- toLog = `${typeQuery.get(first) || '💢'}` + time + ` ${final_s}`;
120
- }
121
- else {
122
- toLog =
123
- `${typeQuery.get(first) || '💢'}` +
124
- `${time}` +
125
- ` ${cyan(first)} ${green(mainTable?.replaceAll('"', ''))}` +
126
- `${listArgs.length > 0 ? ` { ${listArgs.join(', ')} }` : ``}` +
127
- `${subTables.length > 0 ? magenta(` (sub: ${subTables.join(', ')})`) : ``}`;
128
- }
117
+ const toLog = withDetails
118
+ ? `${typeQuery.get(first) || '💢'}` + time + ` ${final_s}`
119
+ : `${typeQuery.get(first) || '💢'}` +
120
+ `${time}` +
121
+ ` ${cyan(first)} ${green(mainTable?.replaceAll('"', ''))}` +
122
+ `${listArgs.length > 0 ? ` { ${listArgs.join(', ')} }` : ``}` +
123
+ `${subTables.length > 0 ? magenta(` (sub: ${subTables.join(', ')})`) : ``}`;
129
124
  log.info(toLog);
130
125
  if (options?.ending) {
131
126
  options.ending(duration, query, args, tables);
@@ -71,7 +71,7 @@ export class CarboneController {
71
71
  }
72
72
  static async deleteTemplate(config) {
73
73
  const { templateId } = config;
74
- const response = await CarboneController.server.fetch({
74
+ await CarboneController.server.fetch({
75
75
  api: `/template/${templateId}`,
76
76
  method: 'DELETE',
77
77
  });
@@ -84,6 +84,7 @@ export class CarboneController {
84
84
  static async render(config) {
85
85
  const { templateName, templateBase64, data, filename } = config;
86
86
  let { templateId, convertTo } = config;
87
+ // eslint-disable-next-line
87
88
  let mode = '';
88
89
  if (templateBase64) {
89
90
  mode = 'templateBase64';
@@ -46,9 +46,4 @@ export declare const withChangeLog: <entityType>(options?: EntityOptions<entityT
46
46
  apiRequireId?: import("remult").Allowed;
47
47
  dataProvider?: ((defaultDataProvider: import("remult").DataProvider) => import("remult").DataProvider | Promise<import("remult").DataProvider> | undefined | null) | undefined;
48
48
  changeLog?: false | ColumnDeciderArgs<entityType> | undefined;
49
- ui?: {
50
- getLayout?: import("../svelte/customField").getLayout<entityType> | undefined;
51
- } | undefined;
52
- searchableFind?: ((str: string) => import("remult").FindOptionsBase<entityType>) | undefined;
53
- displayValue?: ((item: entityType) => import("../internals").BaseItem) | undefined;
54
49
  };
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Row-level allow helpers (for `allowApiUpdate`, `allowApiDelete`, ...).
3
+ *
4
+ * Pair with `FF_Filter` (the equivalent for `apiPrefilter`).
5
+ *
6
+ * Pass the entity type as a generic (`FF_Allow.owner<Task>('userId')`) to get
7
+ * autocompletion and type-safety on the column name. Without a generic the
8
+ * column is just a `string`.
9
+ */
10
+ export declare const FF_Allow: {
11
+ /**
12
+ * Allow only when the row's `col` equals the current user id.
13
+ *
14
+ * `col` defaults to `'userId'` if omitted.
15
+ *
16
+ * @example
17
+ * Owner-only update / delete (typed):
18
+ * ```ts
19
+ * import { FF_Entity, FF_Allow } from '..'
20
+ *
21
+ * \@FF_Entity<Task>('tasks', {
22
+ * allowApiUpdate: FF_Allow.owner<Task>('userId'), // typed: 'userId' must be a key of Task
23
+ * allowApiDelete: FF_Allow.owner<Task>(), // defaults to 'userId'
24
+ * })
25
+ * export class Task { ... }
26
+ * ```
27
+ *
28
+ * For "admin OR owner", prefer `FF_Allow.ownerOr` instead of inlining the
29
+ * combination yourself.
30
+ */
31
+ owner: <T>(col?: keyof T & string) => (entity?: T) => boolean;
32
+ /**
33
+ * Allow when the current user has any of the given `roles`, OR when the
34
+ * row's `col` equals the current user id.
35
+ *
36
+ * `col` defaults to `'userId'` if omitted.
37
+ *
38
+ * @example
39
+ * Admin OR owner (typed):
40
+ * ```ts
41
+ * import { FF_Entity, FF_Allow } from '..'
42
+ * import { Roles } from '../roles'
43
+ *
44
+ * \@FF_Entity<Task>('tasks', {
45
+ * allowApiUpdate: FF_Allow.ownerOr<Task>({ roles: [Roles.Admin] }),
46
+ * allowApiDelete: FF_Allow.ownerOr<Task>({ col: 'createdBy', roles: [Roles.Admin] }),
47
+ * })
48
+ * export class Task { ... }
49
+ * ```
50
+ */
51
+ ownerOr: <T>({ col, roles }: {
52
+ col?: keyof T & string;
53
+ roles: string[];
54
+ }) => (entity?: T) => boolean;
55
+ };
@@ -0,0 +1,54 @@
1
+ import { remult } from 'remult';
2
+ /**
3
+ * Row-level allow helpers (for `allowApiUpdate`, `allowApiDelete`, ...).
4
+ *
5
+ * Pair with `FF_Filter` (the equivalent for `apiPrefilter`).
6
+ *
7
+ * Pass the entity type as a generic (`FF_Allow.owner<Task>('userId')`) to get
8
+ * autocompletion and type-safety on the column name. Without a generic the
9
+ * column is just a `string`.
10
+ */
11
+ export const FF_Allow = {
12
+ /**
13
+ * Allow only when the row's `col` equals the current user id.
14
+ *
15
+ * `col` defaults to `'userId'` if omitted.
16
+ *
17
+ * @example
18
+ * Owner-only update / delete (typed):
19
+ * ```ts
20
+ * import { FF_Entity, FF_Allow } from '..'
21
+ *
22
+ * \@FF_Entity<Task>('tasks', {
23
+ * allowApiUpdate: FF_Allow.owner<Task>('userId'), // typed: 'userId' must be a key of Task
24
+ * allowApiDelete: FF_Allow.owner<Task>(), // defaults to 'userId'
25
+ * })
26
+ * export class Task { ... }
27
+ * ```
28
+ *
29
+ * For "admin OR owner", prefer `FF_Allow.ownerOr` instead of inlining the
30
+ * combination yourself.
31
+ */
32
+ owner: (col = 'userId') => (entity) => !!remult.user?.id && entity?.[col] === remult.user.id,
33
+ /**
34
+ * Allow when the current user has any of the given `roles`, OR when the
35
+ * row's `col` equals the current user id.
36
+ *
37
+ * `col` defaults to `'userId'` if omitted.
38
+ *
39
+ * @example
40
+ * Admin OR owner (typed):
41
+ * ```ts
42
+ * import { FF_Entity, FF_Allow } from '..'
43
+ * import { Roles } from '../roles'
44
+ *
45
+ * \@FF_Entity<Task>('tasks', {
46
+ * allowApiUpdate: FF_Allow.ownerOr<Task>({ roles: [Roles.Admin] }),
47
+ * allowApiDelete: FF_Allow.ownerOr<Task>({ col: 'createdBy', roles: [Roles.Admin] }),
48
+ * })
49
+ * export class Task { ... }
50
+ * ```
51
+ */
52
+ ownerOr: ({ col = 'userId', roles }) => (entity) => roles.some((r) => remult.isAllowed(r)) ||
53
+ (!!remult.user?.id && entity?.[col] === remult.user.id),
54
+ };
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Prefilter helpers (for `apiPrefilter`, `backendPrefilter`).
3
+ *
4
+ * Pair with `FF_Allow` (the equivalent for `allowApi*` row checks).
5
+ *
6
+ * Pass the entity type as a generic (`FF_Filter.owner<Task>('userId')`) to get
7
+ * autocompletion and type-safety on the column name. Without a generic the
8
+ * column is just a `string`.
9
+ */
10
+ export declare const FF_Filter: {
11
+ /**
12
+ * Prefilter rows where `col` equals the current user id.
13
+ *
14
+ * `col` defaults to `'userId'` if omitted. When anonymous, yields
15
+ * `IN (NULL)` which matches nothing.
16
+ *
17
+ * @example
18
+ * Owner-only prefilter (typed):
19
+ * ```ts
20
+ * import { FF_Entity, FF_Filter } from '..'
21
+ *
22
+ * \@FF_Entity<Task>('tasks', {
23
+ * apiPrefilter: () => FF_Filter.owner<Task>('userId'),
24
+ * })
25
+ * export class Task { ... }
26
+ * ```
27
+ *
28
+ * For "admin sees all, others see only their own", prefer
29
+ * `FF_Filter.ownerOr` instead of inlining the combination yourself.
30
+ */
31
+ owner: <T>(col?: keyof T & string) => any;
32
+ /**
33
+ * Prefilter that returns `{}` (no filter) when the current user has any of
34
+ * the given `roles`, otherwise restricts to rows where `col` equals the
35
+ * current user id.
36
+ *
37
+ * `col` defaults to `'userId'` if omitted.
38
+ *
39
+ * @example
40
+ * Admin sees all, others only their own (typed):
41
+ * ```ts
42
+ * import { FF_Entity, FF_Filter } from '..'
43
+ * import { Roles } from '../roles'
44
+ *
45
+ * \@FF_Entity<Task>('tasks', {
46
+ * apiPrefilter: () => FF_Filter.ownerOr<Task>({ roles: [Roles.Admin] }),
47
+ * })
48
+ * export class Task { ... }
49
+ * ```
50
+ */
51
+ ownerOr: <T>({ col, roles, }: {
52
+ col?: keyof T & string;
53
+ roles: string[];
54
+ }) => any;
55
+ };
@@ -0,0 +1,57 @@
1
+ import { remult } from 'remult';
2
+ /**
3
+ * Prefilter helpers (for `apiPrefilter`, `backendPrefilter`).
4
+ *
5
+ * Pair with `FF_Allow` (the equivalent for `allowApi*` row checks).
6
+ *
7
+ * Pass the entity type as a generic (`FF_Filter.owner<Task>('userId')`) to get
8
+ * autocompletion and type-safety on the column name. Without a generic the
9
+ * column is just a `string`.
10
+ */
11
+ export const FF_Filter = {
12
+ /**
13
+ * Prefilter rows where `col` equals the current user id.
14
+ *
15
+ * `col` defaults to `'userId'` if omitted. When anonymous, yields
16
+ * `IN (NULL)` which matches nothing.
17
+ *
18
+ * @example
19
+ * Owner-only prefilter (typed):
20
+ * ```ts
21
+ * import { FF_Entity, FF_Filter } from '..'
22
+ *
23
+ * \@FF_Entity<Task>('tasks', {
24
+ * apiPrefilter: () => FF_Filter.owner<Task>('userId'),
25
+ * })
26
+ * export class Task { ... }
27
+ * ```
28
+ *
29
+ * For "admin sees all, others see only their own", prefer
30
+ * `FF_Filter.ownerOr` instead of inlining the combination yourself.
31
+ */
32
+ owner: (col = 'userId') => ({ [col]: [remult.user?.id] }),
33
+ /**
34
+ * Prefilter that returns `{}` (no filter) when the current user has any of
35
+ * the given `roles`, otherwise restricts to rows where `col` equals the
36
+ * current user id.
37
+ *
38
+ * `col` defaults to `'userId'` if omitted.
39
+ *
40
+ * @example
41
+ * Admin sees all, others only their own (typed):
42
+ * ```ts
43
+ * import { FF_Entity, FF_Filter } from '..'
44
+ * import { Roles } from '../roles'
45
+ *
46
+ * \@FF_Entity<Task>('tasks', {
47
+ * apiPrefilter: () => FF_Filter.ownerOr<Task>({ roles: [Roles.Admin] }),
48
+ * })
49
+ * export class Task { ... }
50
+ * ```
51
+ */
52
+ ownerOr: ({ col = 'userId', roles, }) => {
53
+ if (roles.some((r) => remult.isAllowed(r)))
54
+ return {};
55
+ return { [col]: [remult.user?.id] };
56
+ },
57
+ };
@@ -0,0 +1,2 @@
1
+ import type { ErrorInfo } from 'remult';
2
+ export declare function isError<T>(object: any): object is ErrorInfo<T>;
@@ -0,0 +1,3 @@
1
+ export function isError(object) {
2
+ return object;
3
+ }
File without changes
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ // Files under lib/core/ are the pure-TS, frontend-safe primitives.
3
+ // They are re-exported from lib/index.ts (the `firstly` root export).
4
+ // Keep this barrel empty — import sources directly from ./BaseEnum,
5
+ // ./FF_Entity, ./common, ./helper, ./tryCatch, ./types, ./tailwind.
@@ -0,0 +1,21 @@
1
+ import { type ClassValue } from 'clsx';
2
+ /**
3
+ * Merges Tailwind CSS classes using clsx and tailwind-merge.
4
+ * This utility helps combine conditional classes and resolves conflicts.
5
+ *
6
+ * @param inputs - Class values to merge (strings, objects, arrays, etc.)
7
+ * @returns A merged string of Tailwind classes
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import { tw } from './tailwind'
12
+ *
13
+ * const buttonClasses = tw(
14
+ * 'btn btn-primary',
15
+ * isDisabled && 'btn-disabled',
16
+ * size === 'lg' && 'btn-lg'
17
+ * )
18
+ * // Result: "btn btn-primary btn-disabled" (if isDisabled is true)
19
+ * ```
20
+ */
21
+ export declare const tw: (...inputs: ClassValue[]) => string;
@@ -0,0 +1,22 @@
1
+ import { clsx } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+ /**
4
+ * Merges Tailwind CSS classes using clsx and tailwind-merge.
5
+ * This utility helps combine conditional classes and resolves conflicts.
6
+ *
7
+ * @param inputs - Class values to merge (strings, objects, arrays, etc.)
8
+ * @returns A merged string of Tailwind classes
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { tw } from './tailwind'
13
+ *
14
+ * const buttonClasses = tw(
15
+ * 'btn btn-primary',
16
+ * isDisabled && 'btn-disabled',
17
+ * size === 'lg' && 'btn-lg'
18
+ * )
19
+ * // Result: "btn btn-primary btn-disabled" (if isDisabled is true)
20
+ * ```
21
+ */
22
+ export const tw = (...inputs) => twMerge(clsx(...inputs));
@@ -0,0 +1,44 @@
1
+ /**
2
+ * A successful result returned by `tryCatch` or `tryCatchSync`.
3
+ *
4
+ * @template T - The type of the resolved value.
5
+ */
6
+ type Success<T> = {
7
+ data: T;
8
+ error: null;
9
+ };
10
+ /**
11
+ * A failure result returned by `tryCatch` or `tryCatchSync`.
12
+ *
13
+ * @template E - The type of the thrown error.
14
+ */
15
+ type Failure<E> = {
16
+ data: null;
17
+ error: E;
18
+ };
19
+ /**
20
+ * A result wrapper that contains either a success value or an error.
21
+ *
22
+ * @template T - The type of the success value.
23
+ * @template E - The type of the error value.
24
+ */
25
+ type Result<T, E = Error> = Success<T> | Failure<E>;
26
+ /**
27
+ * Wraps an async promise and returns a `Result` object instead of throwing.
28
+ *
29
+ * @template T - The type of the resolved value.
30
+ * @template E - The type of the rejected error.
31
+ * @param promise - The promise to execute.
32
+ * @returns A result object with either `data` or `error`.
33
+ */
34
+ export declare function tryCatch<T, E = Error>(promise: Promise<T>): Promise<Result<T, E>>;
35
+ /**
36
+ * Wraps a synchronous function call and returns a `Result` object instead of throwing.
37
+ *
38
+ * @template T - The type of the returned value.
39
+ * @template E - The type of the thrown error.
40
+ * @param func - The synchronous function to execute.
41
+ * @returns A result object with either `data` or `error`.
42
+ */
43
+ export declare function tryCatchSync<T, E = Error>(func: () => T): Result<T, E>;
44
+ export {};
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Wraps an async promise and returns a `Result` object instead of throwing.
3
+ *
4
+ * @template T - The type of the resolved value.
5
+ * @template E - The type of the rejected error.
6
+ * @param promise - The promise to execute.
7
+ * @returns A result object with either `data` or `error`.
8
+ */
9
+ export async function tryCatch(promise) {
10
+ try {
11
+ const data = await promise;
12
+ return { data, error: null };
13
+ }
14
+ catch (error) {
15
+ return { data: null, error: error };
16
+ }
17
+ }
18
+ /**
19
+ * Wraps a synchronous function call and returns a `Result` object instead of throwing.
20
+ *
21
+ * @template T - The type of the returned value.
22
+ * @template E - The type of the thrown error.
23
+ * @param func - The synchronous function to execute.
24
+ * @returns A result object with either `data` or `error`.
25
+ */
26
+ export function tryCatchSync(func) {
27
+ try {
28
+ const data = func();
29
+ return { data, error: null };
30
+ }
31
+ catch (error) {
32
+ return { data: null, error: error };
33
+ }
34
+ }
@@ -98,7 +98,7 @@ export const cron = (jobsInfos) => {
98
98
  jobs[topic].concurrentInProgress = jobs[topic].concurrentInProgress - 1;
99
99
  }
100
100
  else {
101
- const rCron = await repo(Cron).insert({ topic, status: 'skipped' });
101
+ await repo(Cron).insert({ topic, status: 'skipped' });
102
102
  logJobs(topic, job, `skipped because of concurrent limit (${yellow(concurrentToUse.toString())})`, false, false);
103
103
  }
104
104
  };
@@ -204,8 +204,9 @@ repository(name: $repository, owner: $owner) {
204
204
  for (let i = 0; i < comments.length; i++) {
205
205
  if (comments[i].isMinimized) {
206
206
  const parsed = JSON.parse(comments[i].body.replaceAll('<pre>\n', '').replaceAll('\n</pre>', ''));
207
- items[items.length - 1].who = parsed?.author ?? '???';
208
- items[items.length - 1].public = true;
207
+ const last = items.at(-1);
208
+ last.who = parsed?.author ?? '???';
209
+ last.public = true;
209
210
  }
210
211
  else {
211
212
  const nbEye = comments[i].reactionGroups.find((c) => c.content === 'EYES')?.reactors.totalCount;
@@ -1,3 +1,8 @@
1
1
  import { FeedbackController } from './FeedbackController';
2
- import { default as Feedback } from './ui/Feedback.svelte';
3
- export { FeedbackController, Feedback };
2
+ import type { FeedbackOptions } from './types';
3
+ export { FeedbackController };
4
+ declare module 'remult' {
5
+ interface RemultContext {
6
+ feedbackOptions: FeedbackOptions;
7
+ }
8
+ }
@@ -1,3 +1,2 @@
1
1
  import { FeedbackController } from './FeedbackController';
2
- import { default as Feedback } from './ui/Feedback.svelte';
3
- export { FeedbackController, Feedback };
2
+ export { FeedbackController };
@@ -1,8 +1,3 @@
1
1
  import { Module } from 'remult/server';
2
2
  import type { FeedbackOptions } from '../types';
3
3
  export declare const feedback: (o: FeedbackOptions) => Module<unknown>;
4
- declare module 'remult' {
5
- interface RemultContext {
6
- feedbackOptions: FeedbackOptions;
7
- }
8
- }
@@ -5,7 +5,7 @@ export const feedback = (o) => {
5
5
  return new Module({
6
6
  key: 'feedback',
7
7
  controllers: [FeedbackController],
8
- initRequest: async (kitEvent, op) => {
8
+ initRequest: async () => {
9
9
  remult.context.feedbackOptions = o;
10
10
  },
11
11
  });
@@ -60,6 +60,7 @@ export const toTitleCase = (str) => {
60
60
  const rmvChars = (str, chars = ['<', '>', ',']) => {
61
61
  return chars.reduce((acc, char) => acc.replaceAll(char, ''), str).trim();
62
62
  };
63
+ const extractMailRegex = /(.+)\s(.+)\s<(.+)>/i;
63
64
  export const extractMailInfo = (mail, withThrow = true) => {
64
65
  if (!mail) {
65
66
  if (withThrow) {
@@ -71,8 +72,7 @@ export const extractMailInfo = (mail, withThrow = true) => {
71
72
  email: '',
72
73
  };
73
74
  }
74
- const regex = /(.+)\s(.+)\s<(.+)>/i;
75
- const match = mail.match(regex);
75
+ const match = mail.match(extractMailRegex);
76
76
  if (match) {
77
77
  const firstName = toTitleCase(rmvChars(match[1].trim())).replaceAll(' ', '-');
78
78
  const lastName = rmvChars(match[2].trim()).toUpperCase();
package/esm/index.d.ts CHANGED
@@ -3,3 +3,21 @@ export {
3
3
  /** alias for @kitql/helpers */
4
4
  h, };
5
5
  export declare const ff_Log: h.Log;
6
+ export { BaseEnum } from './core/BaseEnum.js';
7
+ export type { BaseEnumOptions, BaseItem, BaseItemLight, FF_Icon } from './core/BaseEnum.js';
8
+ export { FF_Entity } from './core/FF_Entity.js';
9
+ export { FF_Role } from './core/common.js';
10
+ export { FF_Allow } from './core/FF_Allow.js';
11
+ export { FF_Filter } from './core/FF_Filter.js';
12
+ export { isError } from './core/helper.js';
13
+ export { tryCatch, tryCatchSync } from './core/tryCatch.js';
14
+ export type { ResolvedType, UnArray, RecursivePartial } from './core/types.js';
15
+ export { tw } from './core/tailwind.js';
16
+ export { FF_LogToConsole } from './SqlDatabase/FF_LogToConsole.js';
17
+ export { FilterEntity } from './virtual/FilterEntity.js';
18
+ export { UIEntity } from './virtual/UIEntity.js';
19
+ declare module 'remult' {
20
+ interface FieldOptions<entityType, valueType> {
21
+ placeholder?: string;
22
+ }
23
+ }
package/esm/index.js CHANGED
@@ -3,3 +3,18 @@ export {
3
3
  /** alias for @kitql/helpers */
4
4
  h, };
5
5
  export const ff_Log = new h.Log('firstly');
6
+ // ******************************
7
+ // Core (pure TS, frontend-safe) - re-exported from lib/core/
8
+ // ******************************
9
+ export { BaseEnum } from './core/BaseEnum.js';
10
+ export { FF_Entity } from './core/FF_Entity.js';
11
+ export { FF_Role } from './core/common.js';
12
+ export { FF_Allow } from './core/FF_Allow.js';
13
+ export { FF_Filter } from './core/FF_Filter.js';
14
+ export { isError } from './core/helper.js';
15
+ export { tryCatch, tryCatchSync } from './core/tryCatch.js';
16
+ export { tw } from './core/tailwind.js';
17
+ // Misc primitives still exposed from the root.
18
+ export { FF_LogToConsole } from './SqlDatabase/FF_LogToConsole.js';
19
+ export { FilterEntity } from './virtual/FilterEntity.js';
20
+ export { UIEntity } from './virtual/UIEntity.js';
@@ -0,0 +1,3 @@
1
+ export declare const Roles_SqlAdmin: {
2
+ readonly SqlAdmin_Admin: "SqlAdmin.Admin";
3
+ };
@@ -0,0 +1,3 @@
1
+ export const Roles_SqlAdmin = {
2
+ SqlAdmin_Admin: 'SqlAdmin.Admin',
3
+ };
@@ -0,0 +1,9 @@
1
+ import { SqlDatabase } from 'remult';
2
+ export declare class SqlAdminController {
3
+ /** Optional override set by the `sqlAdmin()` module's `initApi`. Falls back to `SqlDatabase.getDb()`. */
4
+ static dp?: SqlDatabase;
5
+ static exec(cmd: string): Promise<{
6
+ r: import("remult").SqlResult;
7
+ took: number;
8
+ }>;
9
+ }