firstly 0.0.1 → 0.0.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.
Files changed (156) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/LICENSE +18 -0
  3. package/README.md +12 -0
  4. package/esm/KitBaseEnum.d.ts +35 -0
  5. package/esm/KitBaseEnum.js +32 -0
  6. package/esm/KitEntity.d.ts +2 -0
  7. package/esm/KitEntity.js +24 -0
  8. package/esm/KitFields.d.ts +10 -0
  9. package/esm/KitFields.js +196 -0
  10. package/esm/ROUTES.d.ts +88 -0
  11. package/esm/ROUTES.js +98 -0
  12. package/esm/SqlDatabase/LogToConsoleCustom.d.ts +1 -0
  13. package/esm/SqlDatabase/LogToConsoleCustom.js +102 -0
  14. package/esm/api/index.d.ts +42 -0
  15. package/esm/api/index.js +97 -0
  16. package/esm/auth/Adapter.d.ts +10 -0
  17. package/esm/auth/Adapter.js +54 -0
  18. package/esm/auth/AuthController.d.ts +59 -0
  19. package/esm/auth/AuthController.js +434 -0
  20. package/esm/auth/Entities.d.ts +39 -0
  21. package/esm/auth/Entities.js +154 -0
  22. package/esm/auth/RoleController.d.ts +14 -0
  23. package/esm/auth/RoleController.js +57 -0
  24. package/esm/auth/helper.d.ts +1 -0
  25. package/esm/auth/helper.js +7 -0
  26. package/esm/auth/index.d.ts +153 -0
  27. package/esm/auth/index.js +280 -0
  28. package/esm/auth/providers/github.d.ts +25 -0
  29. package/esm/auth/providers/github.js +51 -0
  30. package/esm/auth/providers/index.d.ts +3 -0
  31. package/esm/auth/providers/index.js +26 -0
  32. package/esm/auth/providers/strava.d.ts +25 -0
  33. package/esm/auth/providers/strava.js +51 -0
  34. package/esm/auth/static/assets/Page-BMFREPjF.d.ts +5 -0
  35. package/esm/auth/static/assets/Page-BMFREPjF.js +18 -0
  36. package/esm/auth/static/assets/Page-BMOLAIFx.d.ts +5 -0
  37. package/esm/auth/static/assets/Page-BMOLAIFx.js +1 -0
  38. package/esm/auth/static/assets/Page-BwHye0GW.d.ts +5 -0
  39. package/esm/auth/static/assets/Page-BwHye0GW.js +1 -0
  40. package/esm/auth/static/assets/Page-gV58jf2r.css +1 -0
  41. package/esm/auth/static/assets/index-CKmKKRRL.d.ts +53 -0
  42. package/esm/auth/static/assets/index-CKmKKRRL.js +2 -0
  43. package/esm/auth/static/assets/index-R27C_TlP.css +4 -0
  44. package/esm/auth/static/favicon.svg +79 -0
  45. package/esm/auth/static/index.html +14 -0
  46. package/esm/auth/types.d.ts +33 -0
  47. package/esm/auth/types.js +1 -0
  48. package/esm/bin/cmd.d.ts +1 -0
  49. package/esm/bin/cmd.js +418 -0
  50. package/esm/changeLog/index.d.ts +55 -0
  51. package/esm/changeLog/index.js +179 -0
  52. package/esm/cron/index.d.ts +60 -0
  53. package/esm/cron/index.js +102 -0
  54. package/esm/feedback/FeedbackController.d.ts +30 -0
  55. package/esm/feedback/FeedbackController.js +313 -0
  56. package/esm/feedback/index.d.ts +18 -0
  57. package/esm/feedback/index.js +14 -0
  58. package/esm/feedback/ui/DialogIssue.svelte +102 -0
  59. package/esm/feedback/ui/DialogIssue.svelte.d.ts +20 -0
  60. package/esm/feedback/ui/DialogIssues.svelte +91 -0
  61. package/esm/feedback/ui/DialogIssues.svelte.d.ts +20 -0
  62. package/esm/feedback/ui/DialogMilestones.svelte +38 -0
  63. package/esm/feedback/ui/DialogMilestones.svelte.d.ts +18 -0
  64. package/esm/feedback/ui/Feedback.svelte +12 -0
  65. package/esm/feedback/ui/Feedback.svelte.d.ts +16 -0
  66. package/esm/formats/dates.d.ts +18 -0
  67. package/esm/formats/dates.js +35 -0
  68. package/esm/formats/index.d.ts +4 -0
  69. package/esm/formats/index.js +3 -0
  70. package/esm/formats/numbers.d.ts +4 -0
  71. package/esm/formats/numbers.js +34 -0
  72. package/esm/formats/strings.d.ts +11 -0
  73. package/esm/formats/strings.js +109 -0
  74. package/esm/handle/index.d.ts +7 -0
  75. package/esm/handle/index.js +40 -0
  76. package/esm/helper.d.ts +50 -0
  77. package/esm/helper.js +118 -0
  78. package/esm/index.d.ts +103 -0
  79. package/esm/index.js +42 -0
  80. package/esm/kitCellsBuildor.d.ts +45 -0
  81. package/esm/kitCellsBuildor.js +105 -0
  82. package/esm/kitStoreItem.d.ts +28 -0
  83. package/esm/kitStoreItem.js +170 -0
  84. package/esm/kitStoreList.d.ts +33 -0
  85. package/esm/kitStoreList.js +98 -0
  86. package/esm/mail/index.d.ts +11 -0
  87. package/esm/mail/index.js +51 -0
  88. package/esm/theme.d.ts +4 -0
  89. package/esm/theme.js +4 -0
  90. package/esm/ui/Button.svelte +102 -0
  91. package/esm/ui/Button.svelte.d.ts +27 -0
  92. package/esm/ui/Clipboardable.svelte +19 -0
  93. package/esm/ui/Clipboardable.svelte.d.ts +25 -0
  94. package/esm/ui/Field.svelte +288 -0
  95. package/esm/ui/Field.svelte.d.ts +29 -0
  96. package/esm/ui/FieldGroup.svelte +91 -0
  97. package/esm/ui/FieldGroup.svelte.d.ts +30 -0
  98. package/esm/ui/Grid.svelte +246 -0
  99. package/esm/ui/Grid.svelte.d.ts +46 -0
  100. package/esm/ui/GridLoading.svelte +32 -0
  101. package/esm/ui/GridLoading.svelte.d.ts +20 -0
  102. package/esm/ui/GridPaginate.svelte +66 -0
  103. package/esm/ui/GridPaginate.svelte.d.ts +22 -0
  104. package/esm/ui/Icon.svelte +86 -0
  105. package/esm/ui/Icon.svelte.d.ts +46 -0
  106. package/esm/ui/LibIcon.d.ts +23 -0
  107. package/esm/ui/LibIcon.js +28 -0
  108. package/esm/ui/Loading.svelte +11 -0
  109. package/esm/ui/Loading.svelte.d.ts +20 -0
  110. package/esm/ui/Tooltip.svelte +42 -0
  111. package/esm/ui/Tooltip.svelte.d.ts +22 -0
  112. package/esm/ui/dialog/DialogForm.svelte +70 -0
  113. package/esm/ui/dialog/DialogForm.svelte.d.ts +19 -0
  114. package/esm/ui/dialog/DialogManagement.svelte +87 -0
  115. package/esm/ui/dialog/DialogManagement.svelte.d.ts +25 -0
  116. package/esm/ui/dialog/DialogPrimitive.svelte +89 -0
  117. package/esm/ui/dialog/DialogPrimitive.svelte.d.ts +28 -0
  118. package/esm/ui/dialog/FormEditAction.svelte +54 -0
  119. package/esm/ui/dialog/FormEditAction.svelte.d.ts +24 -0
  120. package/esm/ui/dialog/dialog.d.ts +51 -0
  121. package/esm/ui/dialog/dialog.js +98 -0
  122. package/esm/ui/index.d.ts +5 -0
  123. package/esm/ui/index.js +19 -0
  124. package/esm/ui/internals/FieldContainer.svelte +22 -0
  125. package/esm/ui/internals/FieldContainer.svelte.d.ts +30 -0
  126. package/esm/ui/internals/Input.svelte +98 -0
  127. package/esm/ui/internals/Input.svelte.d.ts +35 -0
  128. package/esm/ui/internals/Textarea.svelte +61 -0
  129. package/esm/ui/internals/Textarea.svelte.d.ts +30 -0
  130. package/esm/ui/internals/select/MultiSelectMelt.svelte +217 -0
  131. package/esm/ui/internals/select/MultiSelectMelt.svelte.d.ts +30 -0
  132. package/esm/ui/internals/select/SelectMelt.svelte +238 -0
  133. package/esm/ui/internals/select/SelectMelt.svelte.d.ts +35 -0
  134. package/esm/ui/internals/select/SelectRadio.svelte +37 -0
  135. package/esm/ui/internals/select/SelectRadio.svelte.d.ts +25 -0
  136. package/esm/ui/link/Link.svelte +28 -0
  137. package/esm/ui/link/Link.svelte.d.ts +25 -0
  138. package/esm/ui/link/LinkPlus.svelte +44 -0
  139. package/esm/ui/link/LinkPlus.svelte.d.ts +21 -0
  140. package/esm/utils/tailwind.d.ts +2 -0
  141. package/esm/utils/tailwind.js +3 -0
  142. package/esm/utils/transition.d.ts +10 -0
  143. package/esm/utils/transition.js +33 -0
  144. package/esm/utils/types.d.ts +17 -0
  145. package/esm/utils/types.js +17 -0
  146. package/esm/virtual/Customer.d.ts +4 -0
  147. package/esm/virtual/Customer.js +24 -0
  148. package/esm/virtual/FilterEntity.d.ts +7 -0
  149. package/esm/virtual/FilterEntity.js +34 -0
  150. package/esm/virtual/StateDemoEnum.d.ts +9 -0
  151. package/esm/virtual/StateDemoEnum.js +42 -0
  152. package/esm/virtual/UIEntity.d.ts +16 -0
  153. package/esm/virtual/UIEntity.js +84 -0
  154. package/esm/vite/index.d.ts +8 -0
  155. package/esm/vite/index.js +47 -0
  156. package/package.json +94 -10
package/esm/bin/cmd.js ADDED
@@ -0,0 +1,418 @@
1
+ import * as p from '@clack/prompts';
2
+ import { bold, cyan, gray, green, italic, Log } from '@kitql/helpers';
3
+ import { read, write } from '@kitql/internals';
4
+ // Need this trick to be be replaced by the real lib alias here ;)
5
+ const libAlias = '$' + 'lib';
6
+ const pkgFirstly = JSON.parse(read('./node_modules/firstly/package.json') ?? '{}');
7
+ const versionFirstly = pkgFirstly?.peerDependencies?.['remult'] ?? 'latest';
8
+ const pkg = JSON.parse(read('./package.json') ?? '{}');
9
+ const version = pkg.devDependencies?.['firstly'] ?? pkg.dependencies?.['firstly'] ?? '???';
10
+ console.info('');
11
+ p.intro(`${green(`⚡️`)} Welcome to firstly world! ${gray(` - v${version}`)}`);
12
+ const keys = ['all', 'module-demo', 'dependencies'];
13
+ const options = [
14
+ {
15
+ value: 'all',
16
+ label: 'All',
17
+ hint: 'If you are starting a new project, this is for you!',
18
+ },
19
+ {
20
+ value: 'module-demo',
21
+ label: 'module tasks',
22
+ hint: 'A default module with a task entity and a controller (you can rename the folder and make it yours)',
23
+ },
24
+ {
25
+ value: 'dependencies',
26
+ label: 'dependencies',
27
+ hint: 'Add all dependencies that make sense to use with firstly',
28
+ },
29
+ ];
30
+ const res = (await p.multiselect({
31
+ message: 'You can generate different things here',
32
+ options,
33
+ }));
34
+ const devDependenciesPrepare = {
35
+ '@kitql/eslint-config': '0.3.2',
36
+ '@kitql/helpers': '0.8.9',
37
+ remult: versionFirstly,
38
+ pg: '8.11.3',
39
+ ...pkg.devDependencies,
40
+ };
41
+ // sort by name
42
+ const devDependenciesSorted = Object.keys(devDependenciesPrepare)
43
+ .sort()
44
+ .reduce((acc, key) => {
45
+ acc[key] = devDependenciesPrepare[key];
46
+ return acc;
47
+ }, {});
48
+ pkg.devDependencies = devDependenciesSorted;
49
+ pkg.scripts = {
50
+ ...pkg.scripts,
51
+ '//// ---- BEST PRACTICES ---- ////': '',
52
+ lint: 'kitql-lint',
53
+ format: 'kitql-lint -f',
54
+ };
55
+ if (res.includes('all') || res.includes('dependencies')) {
56
+ write('./package.json', [JSON.stringify(pkg, null, 2)]);
57
+ }
58
+ const obj = {
59
+ './.eslintrc.cjs': [
60
+ `module.exports = {
61
+ extends: ['@kitql'],
62
+ rules: {
63
+ // Your overrides here
64
+ }
65
+ }
66
+ `,
67
+ ],
68
+ './.prettierignore': [
69
+ `node_modules/
70
+ dist/
71
+ build
72
+ .vs
73
+ .vscode
74
+ .bob/
75
+ .next/
76
+ .idea/
77
+ .svelte-kit/
78
+ .husky/_/
79
+ .changeset/
80
+ .DS_Store
81
+ coverage/
82
+ package.json
83
+ pnpm-lock.yaml
84
+ README.md
85
+
86
+ db/
87
+ src/lib/ROUTES.ts
88
+ `,
89
+ ],
90
+ './.prettierrc.cjs': [
91
+ `const {
92
+ //plugins,
93
+ ...prettierConfig
94
+ } = require('@kitql/eslint-config/.prettierrc.cjs')
95
+
96
+ module.exports = {
97
+ ...prettierConfig,
98
+ // Your overrides here
99
+ }`,
100
+ ],
101
+ '.env.example': [
102
+ `# Enable some roles
103
+ # KIT_ADMIN = 'JYC'
104
+ # KIT_AUTH_ADMIN = ''
105
+
106
+ # Enable GitHub login
107
+ GITHUB_CLIENT_ID = ''
108
+ GITHUB_CLIENT_SECRET = ''
109
+ `,
110
+ ],
111
+ './src/lib/firstly/index.ts': [
112
+ `import { firstly } from 'firstly/api'
113
+ import { auth } from 'firstly/auth'
114
+ // import { github } from 'firstly/auth/providers'
115
+ // import { GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET } from '$env/static/private'
116
+ import { Log } from '@kitql/helpers'
117
+
118
+ import { tasks } from './modules/tasks'
119
+
120
+ // When you will want to use postgres, create a .env file with DATABASE_URL
121
+ // import { createPostgresConnection } from 'remult/postgres'
122
+ // import { DATABASE_URL } from '$env/static/private'
123
+
124
+ /** Define your roles here and use them in your app */
125
+ export const Role = {
126
+ ADMIN: 'admin',
127
+ SUPER_ADMIN: 'super_admin',
128
+ }
129
+
130
+ /** Define your log instance and user it accross your all app */
131
+ export const log = new Log('${pkg.name}')
132
+
133
+ export const api = firstly({
134
+ // dataProvider: await createPostgresConnection({
135
+ // connectionString: DATABASE_URL,
136
+ // }),
137
+ modules: [
138
+ // core module: auth
139
+ auth({
140
+ providers: {
141
+ demo: [
142
+ { name: 'Ermin' },
143
+ { name: 'JYC', roles: [Role.ADMIN] },
144
+ { name: 'Noam', roles: [Role.SUPER_ADMIN] },
145
+ ],
146
+
147
+ // password: {},
148
+
149
+ // otp: {},
150
+
151
+ oAuths: [
152
+ // To enable GitHub auth,
153
+ // 1/ Add your GitHub credentials to .env file (example in .env.example)
154
+ // 2/ uncomment imports & github() call below
155
+ // 3/ under a button click call something like this:
156
+ // async function oauth() {
157
+ // window.location.href = await AuthController.signInOAuthGetUrl({ provider: 'github', redirect: window.location.pathname })
158
+ // }
159
+ // github( { GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET } )
160
+ ],
161
+ },
162
+ }),
163
+
164
+ // example of a userland module
165
+ tasks({ specialInfo: 'hello from userland' }),
166
+
167
+ // example of a userland inline module
168
+ {
169
+ name: 'app',
170
+ entities: [],
171
+ controllers: [],
172
+ initApi: async () => {
173
+ log.success('App is ready! 🚀')
174
+ },
175
+ },
176
+ ],
177
+ })
178
+ `,
179
+ ],
180
+ './src/hooks.server.ts': [
181
+ `import { sequence } from '@sveltejs/kit/hooks'
182
+
183
+ import { firstly } from 'firstly/handle'
184
+
185
+ import { api } from '${libAlias}/firstly'
186
+
187
+ export const handle = sequence(firstly(api))
188
+ `,
189
+ ],
190
+ './src/routes/api/[...remult]/+server.ts': [
191
+ `import { api } from '${libAlias}/firstly'
192
+
193
+ export const GET = api.server.GET
194
+ export const POST = api.server.POST
195
+ export const PUT = api.server.PUT
196
+ export const DELETE = api.server.DELETE
197
+ `,
198
+ ],
199
+ './src/routes/+page.svelte': [`Home 👋`, ``],
200
+ './src/routes/+layout.server.ts': [
201
+ `import { remult } from 'remult'
202
+
203
+ import type { LayoutServerLoad } from './$types'
204
+
205
+ export const load = (async () => {
206
+ return { user: remult.user }
207
+ }) satisfies LayoutServerLoad
208
+ `,
209
+ ],
210
+ './src/routes/+layout.svelte': [
211
+ `<script lang="ts">
212
+ import { remult } from 'remult'
213
+ import { isError } from 'firstly'
214
+ import { AuthController } from 'firstly/auth'
215
+
216
+ import { invalidateAll } from '$app/navigation'
217
+
218
+ import { route } from '${libAlias}/ROUTES'
219
+
220
+ import type { LayoutData } from './$types'
221
+
222
+ const login = async (identif: string) => {
223
+ try {
224
+ await AuthController.signInDemo(identif)
225
+ invalidateAll()
226
+ } catch (error) {
227
+ if (isError(error)) {
228
+ alert(error.message)
229
+ }
230
+ }
231
+ }
232
+
233
+ const logout = async () => {
234
+ try {
235
+ await AuthController.signOut()
236
+ invalidateAll()
237
+ } catch (error) {
238
+ if (isError(error)) {
239
+ alert(error.message)
240
+ }
241
+ }
242
+ }
243
+
244
+ export let data: LayoutData
245
+ $: remult.user = data.user
246
+ </script>
247
+
248
+ <svelte:head>
249
+ <title>${pkg.name}</title>
250
+ <link
251
+ rel="icon"
252
+ href="https://raw.githubusercontent.com/jycouet/firstly/main/assets/firstly.png"
253
+ />
254
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/dark.css" />
255
+ </svelte:head>
256
+
257
+ <h1>${pkg.name}</h1>
258
+
259
+ {#if remult.authenticated()}
260
+ <button style="float:right;" on:click={logout}>Logout</button>
261
+ <span>{remult.user?.name} ({remult.user?.roles})<br /><br /></span>
262
+ {:else}
263
+ <button on:click={() => login('Ermin')}>Login as Ermin</button>
264
+ <button on:click={() => login('JYC')}>Login as JYC</button>
265
+ <button on:click={() => login('Noam')}>Login as Noam</button>
266
+ {/if}
267
+
268
+ <hr />
269
+
270
+ <slot />
271
+
272
+ <hr />
273
+
274
+ <a href={route('github', { owner: 'jycouet', repo: 'firstly' })} target="_blank">
275
+ ⭐️ firstly
276
+ </a>
277
+ |
278
+ <a href={route('github', { owner: 'remult', repo: 'remult' })} target="_blank">⭐️ remult</a>
279
+ `,
280
+ ],
281
+ './tsconfig.json': [
282
+ `{
283
+ "extends": "./.svelte-kit/tsconfig.json",
284
+ "compilerOptions": {
285
+ "experimentalDecorators": true,
286
+ "allowJs": true,
287
+ "checkJs": true,
288
+ "esModuleInterop": true,
289
+ "forceConsistentCasingInFileNames": true,
290
+ "resolveJsonModule": true,
291
+ "skipLibCheck": true,
292
+ "sourceMap": true,
293
+ "strict": true,
294
+ "moduleResolution": "bundler"
295
+ }
296
+ // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
297
+ //
298
+ // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
299
+ // from the referenced tsconfig.json - TypeScript does not merge them in
300
+ }
301
+ `,
302
+ ],
303
+ './vite.config.ts': [
304
+ `import { sveltekit } from '@sveltejs/kit/vite'
305
+ import { defineConfig } from 'vite'
306
+
307
+ import { firstly } from 'firstly/vite'
308
+
309
+ import type { KIT_ROUTES } from '${libAlias}/ROUTES'
310
+
311
+ export default defineConfig({
312
+ plugins: [
313
+ firstly<KIT_ROUTES>({
314
+ kitRoutes: {
315
+ LINKS: { github: 'https://github.com/[owner]/[repo]' },
316
+ }
317
+ }),
318
+ sveltekit(),
319
+ ],
320
+ })
321
+ `,
322
+ ],
323
+ './src/lib/firstly/modules/tasks/index.ts': [
324
+ `import type { Module } from 'firstly/api'
325
+
326
+ import { log } from '${libAlias}/firstly'
327
+
328
+ import { Task } from './Task'
329
+ import { TaskController } from './TaskController'
330
+
331
+ export const tasks: (o: { specialInfo: string }) => Module = ({ specialInfo }) => {
332
+ return {
333
+ name: 'task',
334
+ entities: [Task],
335
+ controllers: [TaskController],
336
+ initApi: async () => {
337
+ log.success(\`Task module is ready! 🚀 (specialInfo: \${specialInfo})\`)
338
+ },
339
+ }
340
+ }`,
341
+ ],
342
+ './src/lib/firstly/modules/tasks/Task.ts': [
343
+ `import { Entity, Field, Fields, ValueListFieldType } from 'remult'
344
+ import { KitBaseEnum, LibIcon_Add, LibIcon_Delete, type KitBaseEnumOptions } from 'firstly'
345
+
346
+ @Entity('tasks', {
347
+ allowApiCrud: true,
348
+ })
349
+ export class Task {
350
+ @Fields.cuid()
351
+ id!: string
352
+
353
+ @Fields.createdAt()
354
+ createdAt?: Date
355
+
356
+ @Fields.string<Task>({
357
+ validate: (task) => {
358
+ if (task.title.length < 3) throw 'The title must be at least 3 characters long'
359
+ },
360
+ })
361
+ title: string = ''
362
+
363
+ @Fields.boolean()
364
+ completed: boolean = false
365
+
366
+ @Field(() => TypeOfTaskEnum, { inputType: 'selectEnum' })
367
+ typeOfTask = TypeOfTaskEnum.EASY
368
+ }
369
+
370
+ @ValueListFieldType()
371
+ export class TypeOfTaskEnum extends KitBaseEnum {
372
+ static EASY = new TypeOfTaskEnum('EASY', {
373
+ caption: 'Easy',
374
+ icon: { data: LibIcon_Add },
375
+ })
376
+ static HARD = new TypeOfTaskEnum('HARD', {
377
+ caption: 'Hard',
378
+ icon: { data: LibIcon_Delete },
379
+ })
380
+ constructor(id: string, o?: KitBaseEnumOptions<TypeOfTaskEnum>) {
381
+ super(id, o)
382
+ }
383
+ }
384
+ `,
385
+ ],
386
+ './src/lib/firstly/modules/tasks/TaskController.ts': [
387
+ `import { BackendMethod } from 'remult'
388
+
389
+ import { log } from '${libAlias}/firstly'
390
+
391
+ /**
392
+ * await TaskController.sayHiFromTask("JYC")
393
+ */
394
+ export class TaskController {
395
+ @BackendMethod({ allowed: true })
396
+ static async sayHiFromTask(name: string) {
397
+ log.info(\`hello \${name} 👋\`)
398
+ }
399
+ }
400
+ `,
401
+ ],
402
+ };
403
+ for (const [path, content] of Object.entries(obj)) {
404
+ if (res.includes('all')) {
405
+ write(path, content);
406
+ }
407
+ else {
408
+ if (res.includes('module-demo')) {
409
+ if (path.startsWith('./src/lib/firstly/modules/tasks')) {
410
+ write(path, content);
411
+ }
412
+ }
413
+ }
414
+ }
415
+ p.outro(`🎉 Everything is ok, happy coding!`);
416
+ new Log('').info(gray(italic(`${bold('❔ More help')} ` +
417
+ `at ${cyan('https://github.com/jycouet/firstly')} ` +
418
+ `(📄 Docs, ⭐ Github, 📣 Discord, ...)\n`)));
@@ -0,0 +1,55 @@
1
+ import { type FieldRef, type FieldsRef, type LifecycleEvent } from 'remult';
2
+ import type { Module } from '../api';
3
+ export declare class WithChangeLogs {
4
+ }
5
+ /**
6
+ * in an entity, add these 2 functions:
7
+ * ```ts
8
+ *
9
+ * \@Entity<Task>('tasks', {
10
+ * saved: async (entity, e) => {
11
+ * await recordSaved(entity, e)
12
+ * },
13
+ * deleted: async (entity, e) => {
14
+ * await recordDeleted(entity, e)
15
+ * },
16
+ * })
17
+ *
18
+ * ```
19
+ */
20
+ export declare const changeLog: () => Module;
21
+ export declare class ChangeLog {
22
+ id: string;
23
+ entity: string;
24
+ entityId: string;
25
+ changeDate: Date;
26
+ userId: string;
27
+ changes: change[];
28
+ newRow: boolean;
29
+ deleted: boolean;
30
+ }
31
+ export interface changeEvent {
32
+ date: Date;
33
+ userId: string;
34
+ changes: change[];
35
+ }
36
+ export interface change {
37
+ key: string;
38
+ oldValue: string;
39
+ newValue: string;
40
+ }
41
+ export declare function recordSaved<entityType>(entity: entityType, e: LifecycleEvent<entityType>, options?: ColumnDeciderArgs<entityType>): Promise<void>;
42
+ export declare function recordDeleted<entityType>(entity: entityType, e: LifecycleEvent<entityType>, options?: ColumnDeciderArgs<entityType>): Promise<void>;
43
+ interface ColumnDeciderArgs<entityType> {
44
+ excludeColumns?: (e: FieldsRef<entityType>) => FieldRef<entityType, any>[];
45
+ excludeValues?: (e: FieldsRef<entityType>) => FieldRef<entityType, any>[];
46
+ forceDate?: Date;
47
+ forceNew?: boolean;
48
+ }
49
+ export declare class FieldDecider<entityType> {
50
+ fields: FieldRef<entityType>[];
51
+ excludedFields: FieldRef<entityType>[];
52
+ excludedValues: FieldRef<entityType>[];
53
+ constructor(entity: entityType, options?: ColumnDeciderArgs<entityType>);
54
+ }
55
+ export {};
@@ -0,0 +1,179 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { Entity, Fields, getEntityRef, IdEntity, isBackend, remult, } from 'remult';
8
+ let WithChangeLogs = class WithChangeLogs {
9
+ };
10
+ WithChangeLogs = __decorate([
11
+ Entity('change_logs', {
12
+ saved: async (entity, e) => {
13
+ await recordSaved(entity, e);
14
+ },
15
+ deleted: async (entity, e) => {
16
+ await recordDeleted(entity, e);
17
+ },
18
+ })
19
+ ], WithChangeLogs);
20
+ export { WithChangeLogs };
21
+ /**
22
+ * in an entity, add these 2 functions:
23
+ * ```ts
24
+ *
25
+ * \@Entity<Task>('tasks', {
26
+ * saved: async (entity, e) => {
27
+ * await recordSaved(entity, e)
28
+ * },
29
+ * deleted: async (entity, e) => {
30
+ * await recordDeleted(entity, e)
31
+ * },
32
+ * })
33
+ *
34
+ * ```
35
+ */
36
+ export const changeLog = () => {
37
+ return {
38
+ name: 'changeLog',
39
+ entities: [ChangeLog],
40
+ };
41
+ };
42
+ let ChangeLog = class ChangeLog {
43
+ id = '';
44
+ entity = '';
45
+ entityId = '';
46
+ changeDate = new Date();
47
+ userId = '';
48
+ changes = [];
49
+ newRow = false;
50
+ deleted = false;
51
+ };
52
+ __decorate([
53
+ Fields.cuid()
54
+ ], ChangeLog.prototype, "id", void 0);
55
+ __decorate([
56
+ Fields.string()
57
+ ], ChangeLog.prototype, "entity", void 0);
58
+ __decorate([
59
+ Fields.string()
60
+ ], ChangeLog.prototype, "entityId", void 0);
61
+ __decorate([
62
+ Fields.date()
63
+ ], ChangeLog.prototype, "changeDate", void 0);
64
+ __decorate([
65
+ Fields.string()
66
+ ], ChangeLog.prototype, "userId", void 0);
67
+ __decorate([
68
+ Fields.json({ dbName: 'changesJson' })
69
+ ], ChangeLog.prototype, "changes", void 0);
70
+ __decorate([
71
+ Fields.boolean()
72
+ ], ChangeLog.prototype, "newRow", void 0);
73
+ __decorate([
74
+ Fields.boolean()
75
+ ], ChangeLog.prototype, "deleted", void 0);
76
+ ChangeLog = __decorate([
77
+ Entity('change_logs', {
78
+ caption: 'Change Logs',
79
+ allowApiCrud: false,
80
+ defaultOrderBy: {
81
+ changeDate: 'desc',
82
+ },
83
+ })
84
+ ], ChangeLog);
85
+ export { ChangeLog };
86
+ export async function recordSaved(entity, e, options) {
87
+ if (isBackend()) {
88
+ const changes = [];
89
+ const decider = new FieldDecider(entity, options);
90
+ const isNew = options?.forceNew || e.isNew;
91
+ const changeDate = options?.forceDate || new Date();
92
+ for (const c of decider.fields.filter((c) => c.valueChanged() || (isNew && c.value))) {
93
+ try {
94
+ const noVal = decider.excludedValues.includes(c);
95
+ changes.push({
96
+ key: c.metadata.key,
97
+ newValue: noVal
98
+ ? '***'
99
+ : c.value instanceof IdEntity
100
+ ? c.value.id
101
+ : c.metadata.options.valueConverter.toJson(c.value),
102
+ oldValue: e.isNew
103
+ ? '---'
104
+ : noVal
105
+ ? '***'
106
+ : c.originalValue instanceof IdEntity
107
+ ? c.originalValue.id
108
+ : c.metadata.options.valueConverter.toJson(c.originalValue),
109
+ });
110
+ }
111
+ catch (err) {
112
+ console.error(c);
113
+ throw err;
114
+ }
115
+ }
116
+ if (changes.length > 0) {
117
+ await remult.repo(ChangeLog).insert({
118
+ changeDate,
119
+ changes,
120
+ entity: e.metadata.key,
121
+ entityId: e.metadata.idMetadata.getId(entity),
122
+ userId: remult.user?.id || '',
123
+ newRow: isNew,
124
+ });
125
+ }
126
+ }
127
+ }
128
+ export async function recordDeleted(entity, e, options) {
129
+ const changes = [];
130
+ const decider = new FieldDecider(entity, options);
131
+ const changeDate = options?.forceDate || new Date();
132
+ for (const c of decider.fields) {
133
+ try {
134
+ const noVal = decider.excludedValues.includes(c);
135
+ changes.push({
136
+ key: c.metadata.key,
137
+ newValue: noVal ? '***' : '---',
138
+ oldValue: noVal
139
+ ? '***'
140
+ : c.originalValue instanceof IdEntity
141
+ ? c.originalValue.id
142
+ : c.metadata.options.valueConverter.toJson(c.originalValue),
143
+ });
144
+ }
145
+ catch (err) {
146
+ console.error(c);
147
+ throw err;
148
+ }
149
+ }
150
+ await remult.repo(ChangeLog).insert({
151
+ changeDate,
152
+ changes,
153
+ entity: e.metadata.key,
154
+ entityId: e.metadata.idMetadata.getId(entity),
155
+ userId: remult.user?.id || '',
156
+ deleted: true,
157
+ });
158
+ }
159
+ export class FieldDecider {
160
+ fields;
161
+ excludedFields;
162
+ excludedValues;
163
+ constructor(entity, options) {
164
+ const meta = getEntityRef(entity);
165
+ if (!options?.excludeColumns)
166
+ this.excludedFields = [];
167
+ // @ts-ignore
168
+ else
169
+ this.excludedFields = options.excludeColumns(meta.fields);
170
+ if (!options?.excludeValues)
171
+ this.excludedValues = [];
172
+ // @ts-ignore
173
+ else
174
+ this.excludedValues = options.excludeValues(meta.fields);
175
+ this.excludedFields.push(...meta.fields.toArray().filter((c) => c.metadata.options.serverExpression));
176
+ this.excludedFields.push(...meta.fields.toArray().filter((c) => c.metadata.options.sqlExpression));
177
+ this.fields = meta.fields.toArray().filter((f) => !this.excludedFields.includes(f));
178
+ }
179
+ }
@@ -0,0 +1,60 @@
1
+ import { CronJob } from 'cron';
2
+ import type { Module } from '../api';
3
+ export declare const jobs: Record<string, {
4
+ job: CronJob<null, unknown> | null;
5
+ concurrentInProgress: number;
6
+ }>;
7
+ /**
8
+ * Link to a nice Cheatsheet TODO
9
+ */
10
+ export declare const cronTime: {
11
+ /**
12
+ * Every morning is actually at 4 am and 7 minutes. (because I like this number!)
13
+ */
14
+ every_morning: string;
15
+ /**
16
+ * Every second
17
+ */
18
+ every_second: string;
19
+ /**
20
+ * Every minute
21
+ */
22
+ every_minute: string;
23
+ /**
24
+ * Every 10 minute
25
+ */
26
+ every_10_minute: string;
27
+ /**
28
+ * Every friday at 5:11 am
29
+ */
30
+ every_friday_morning: string;
31
+ };
32
+ /**
33
+ * usage:
34
+ *
35
+ * ```ts
36
+ * import { cron, cronTime } from 'firstly/cron'
37
+ *
38
+ * export const api = firstly({
39
+ * modules: [
40
+ * cron([{
41
+ * topic: 'first_cron',
42
+ * cronTime: cronTime.every_second,
43
+ * onTick: () => { console.log('hello') },
44
+ * start: !dev, // Start in production
45
+ * // runOnInit: dev, // nice in dev environement
46
+ * }])
47
+ * ]
48
+ * })
49
+ * ```
50
+ *
51
+ * using [cron](https://www.npmjs.com/package/cron) library under the hood
52
+ */
53
+ export declare const cron: (jobsInfos: (Parameters<typeof CronJob.from>[0] & {
54
+ topic: string;
55
+ concurrent?: number;
56
+ logs?: {
57
+ starting?: boolean;
58
+ ended?: boolean;
59
+ };
60
+ })[]) => Module;