firstly 0.0.1 → 0.0.2

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 +8 -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 +279 -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 +408 -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,408 @@
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 pkg = JSON.parse(read('./package.json') ?? '{}');
7
+ const version = pkg.devDependencies?.['firstly'] ?? pkg.dependencies?.['firstly'] ?? '???';
8
+ console.info('');
9
+ p.intro(`${green(`⚡️`)} Welcome to firstly world! ${gray(` - v${version}`)}`);
10
+ const keys = ['all', 'module-demo', 'dependencies'];
11
+ const options = [
12
+ {
13
+ value: 'all',
14
+ label: 'All',
15
+ hint: 'If you are starting a new project, this is for you!',
16
+ },
17
+ {
18
+ value: 'module-demo',
19
+ label: 'module tasks',
20
+ hint: 'A default module with a task entity and a controller (you can rename the folder and make it yours)',
21
+ },
22
+ {
23
+ value: 'dependencies',
24
+ label: 'dependencies',
25
+ hint: 'Add all dependencies that make sense to use with firstly',
26
+ },
27
+ ];
28
+ const res = (await p.multiselect({
29
+ message: 'You can generate different things here',
30
+ options,
31
+ }));
32
+ pkg.devDependencies = {
33
+ '@kitql/eslint-config': '0.3.2',
34
+ '@kitql/helpers': '0.8.9',
35
+ remult: '0.26.14',
36
+ pg: '8.11.3',
37
+ ...pkg.devDependencies,
38
+ };
39
+ pkg.scripts = {
40
+ ...pkg.scripts,
41
+ '//// ---- BEST PRACTICES ---- ////': '',
42
+ lint: 'kitql-lint',
43
+ format: 'kitql-lint -f',
44
+ };
45
+ if (res.includes('all') || res.includes('dependencies')) {
46
+ write('./package.json', [JSON.stringify(pkg, null, 2)]);
47
+ }
48
+ const obj = {
49
+ './.eslintrc.cjs': [
50
+ `module.exports = {
51
+ extends: ['@kitql'],
52
+ rules: {
53
+ // Your overrides here
54
+ }
55
+ }
56
+ `,
57
+ ],
58
+ './.prettierignore': [
59
+ `node_modules/
60
+ dist/
61
+ build
62
+ .vs
63
+ .vscode
64
+ .bob/
65
+ .next/
66
+ .idea/
67
+ .svelte-kit/
68
+ .husky/_/
69
+ .changeset/
70
+ .DS_Store
71
+ coverage/
72
+ package.json
73
+ pnpm-lock.yaml
74
+ README.md
75
+
76
+ db/
77
+ src/lib/ROUTES.ts
78
+ `,
79
+ ],
80
+ './.prettierrc.cjs': [
81
+ `const {
82
+ //plugins,
83
+ ...prettierConfig
84
+ } = require('@kitql/eslint-config/.prettierrc.cjs')
85
+
86
+ module.exports = {
87
+ ...prettierConfig,
88
+ // Your overrides here
89
+ }`,
90
+ ],
91
+ '.env.example': [
92
+ `# Enable some roles
93
+ # KIT_ADMIN = 'JYC'
94
+ # KIT_AUTH_ADMIN = ''
95
+
96
+ # Enable GitHub login
97
+ GITHUB_CLIENT_ID = ''
98
+ GITHUB_CLIENT_SECRET = ''
99
+ `,
100
+ ],
101
+ './src/lib/firstly/index.ts': [
102
+ `import { firstly } from 'firstly/api'
103
+ import { auth } from 'firstly/auth'
104
+ // import { github } from 'firstly/auth/providers'
105
+ // import { GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET } from '$env/static/private'
106
+ import { Log } from '@kitql/helpers'
107
+
108
+ import { tasks } from './modules/tasks'
109
+
110
+ // When you will want to use postgres, create a .env file with DATABASE_URL
111
+ // import { createPostgresConnection } from 'remult/postgres'
112
+ // import { DATABASE_URL } from '$env/static/private'
113
+
114
+ /** Define your roles here and use them in your app */
115
+ export const Role = {
116
+ ADMIN: 'admin',
117
+ SUPER_ADMIN: 'super_admin',
118
+ }
119
+
120
+ /** Define your log instance and user it accross your all app */
121
+ export const log = new Log('${pkg.name}')
122
+
123
+ export const api = firstly({
124
+ // dataProvider: await createPostgresConnection({
125
+ // connectionString: DATABASE_URL,
126
+ // }),
127
+ modules: [
128
+ // core module: auth
129
+ auth({
130
+ providers: {
131
+ demo: [
132
+ { name: 'Ermin' },
133
+ { name: 'JYC', roles: [Role.ADMIN] },
134
+ { name: 'Noam', roles: [Role.SUPER_ADMIN] },
135
+ ],
136
+
137
+ // password: {},
138
+
139
+ // otp: {},
140
+
141
+ oAuths: [
142
+ // To enable GitHub auth,
143
+ // 1/ Add your GitHub credentials to .env file (example in .env.example)
144
+ // 2/ uncomment imports & github() call below
145
+ // 3/ under a button click call something like this:
146
+ // async function oauth() {
147
+ // window.location.href = await AuthController.signInOAuthGetUrl({ provider: 'github', redirect: window.location.pathname })
148
+ // }
149
+ // github( { GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET } )
150
+ ],
151
+ },
152
+ }),
153
+
154
+ // example of a userland module
155
+ tasks({ specialInfo: 'hello from userland' }),
156
+
157
+ // example of a userland inline module
158
+ {
159
+ name: 'app',
160
+ entities: [],
161
+ controllers: [],
162
+ initApi: async () => {
163
+ log.success('App is ready! 🚀')
164
+ },
165
+ },
166
+ ],
167
+ })
168
+ `,
169
+ ],
170
+ './src/hooks.server.ts': [
171
+ `import { sequence } from '@sveltejs/kit/hooks'
172
+
173
+ import { firstly } from 'firstly/handle'
174
+
175
+ import { api } from '${libAlias}/firstly'
176
+
177
+ export const handle = sequence(firstly(api))
178
+ `,
179
+ ],
180
+ './src/routes/api/[...remult]/+server.ts': [
181
+ `import { api } from '${libAlias}/firstly'
182
+
183
+ export const GET = api.server.GET
184
+ export const POST = api.server.POST
185
+ export const PUT = api.server.PUT
186
+ export const DELETE = api.server.DELETE
187
+ `,
188
+ ],
189
+ './src/routes/+page.svelte': [`Home 👋`, ``],
190
+ './src/routes/+layout.server.ts': [
191
+ `import { remult } from 'remult'
192
+
193
+ import type { LayoutServerLoad } from './$types'
194
+
195
+ export const load = (async () => {
196
+ return { user: remult.user }
197
+ }) satisfies LayoutServerLoad
198
+ `,
199
+ ],
200
+ './src/routes/+layout.svelte': [
201
+ `<script lang="ts">
202
+ import { remult } from 'remult'
203
+ import { isError } from 'firstly'
204
+ import { AuthController } from 'firstly/auth'
205
+
206
+ import { invalidateAll } from '$app/navigation'
207
+
208
+ import { route } from '${libAlias}/ROUTES'
209
+
210
+ import type { LayoutData } from './$types'
211
+
212
+ const login = async (identif: string) => {
213
+ try {
214
+ await AuthController.signInDemo(identif)
215
+ invalidateAll()
216
+ } catch (error) {
217
+ if (isError(error)) {
218
+ alert(error.message)
219
+ }
220
+ }
221
+ }
222
+
223
+ const logout = async () => {
224
+ try {
225
+ await AuthController.signOut()
226
+ invalidateAll()
227
+ } catch (error) {
228
+ if (isError(error)) {
229
+ alert(error.message)
230
+ }
231
+ }
232
+ }
233
+
234
+ export let data: LayoutData
235
+ $: remult.user = data.user
236
+ </script>
237
+
238
+ <svelte:head>
239
+ <title>${pkg.name}</title>
240
+ <link
241
+ rel="icon"
242
+ href="https://raw.githubusercontent.com/jycouet/firstly/main/assets/firstly.png"
243
+ />
244
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/dark.css" />
245
+ </svelte:head>
246
+
247
+ <h1>${pkg.name}</h1>
248
+
249
+ {#if remult.authenticated()}
250
+ <button style="float:right;" on:click={logout}>Logout</button>
251
+ <span>{remult.user?.name} ({remult.user?.roles})<br /><br /></span>
252
+ {:else}
253
+ <button on:click={() => login('Ermin')}>Login as Ermin</button>
254
+ <button on:click={() => login('JYC')}>Login as JYC</button>
255
+ <button on:click={() => login('Noam')}>Login as Noam</button>
256
+ {/if}
257
+
258
+ <hr />
259
+
260
+ <slot />
261
+
262
+ <hr />
263
+
264
+ <a href={route('github', { owner: 'jycouet', repo: 'firstly' })} target="_blank">
265
+ ⭐️ firstly
266
+ </a>
267
+ |
268
+ <a href={route('github', { owner: 'remult', repo: 'remult' })} target="_blank">⭐️ remult</a>
269
+ `,
270
+ ],
271
+ './tsconfig.json': [
272
+ `{
273
+ "extends": "./.svelte-kit/tsconfig.json",
274
+ "compilerOptions": {
275
+ "experimentalDecorators": true,
276
+ "allowJs": true,
277
+ "checkJs": true,
278
+ "esModuleInterop": true,
279
+ "forceConsistentCasingInFileNames": true,
280
+ "resolveJsonModule": true,
281
+ "skipLibCheck": true,
282
+ "sourceMap": true,
283
+ "strict": true,
284
+ "moduleResolution": "bundler"
285
+ }
286
+ // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
287
+ //
288
+ // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
289
+ // from the referenced tsconfig.json - TypeScript does not merge them in
290
+ }
291
+ `,
292
+ ],
293
+ './vite.config.ts': [
294
+ `import { sveltekit } from '@sveltejs/kit/vite'
295
+ import { defineConfig } from 'vite'
296
+
297
+ import { firstly } from 'firstly/vite'
298
+
299
+ import type { KIT_ROUTES } from '${libAlias}/ROUTES'
300
+
301
+ export default defineConfig({
302
+ plugins: [
303
+ firstly<KIT_ROUTES>({
304
+ kitRoutes: {
305
+ LINKS: { github: 'https://github.com/[owner]/[repo]' },
306
+ }
307
+ }),
308
+ sveltekit(),
309
+ ],
310
+ })
311
+ `,
312
+ ],
313
+ './src/lib/firstly/modules/tasks/index.ts': [
314
+ `import type { Module } from 'firstly/api'
315
+
316
+ import { log } from '${libAlias}/firstly'
317
+
318
+ import { Task } from './Task'
319
+ import { TaskController } from './TaskController'
320
+
321
+ export const tasks: (o: { specialInfo: string }) => Module = ({ specialInfo }) => {
322
+ return {
323
+ name: 'task',
324
+ entities: [Task],
325
+ controllers: [TaskController],
326
+ initApi: async () => {
327
+ log.success(\`Task module is ready! 🚀 (specialInfo: \${specialInfo})\`)
328
+ },
329
+ }
330
+ }`,
331
+ ],
332
+ './src/lib/firstly/modules/tasks/Task.ts': [
333
+ `import { Entity, Field, Fields, ValueListFieldType } from 'remult'
334
+ import { KitBaseEnum, LibIcon_Add, LibIcon_Delete, type KitBaseEnumOptions } from 'firstly'
335
+
336
+ @Entity('tasks', {
337
+ allowApiCrud: true,
338
+ })
339
+ export class Task {
340
+ @Fields.cuid()
341
+ id!: string
342
+
343
+ @Fields.createdAt()
344
+ createdAt?: Date
345
+
346
+ @Fields.string<Task>({
347
+ validate: (task) => {
348
+ if (task.title.length < 3) throw 'The title must be at least 3 characters long'
349
+ },
350
+ })
351
+ title: string = ''
352
+
353
+ @Fields.boolean()
354
+ completed: boolean = false
355
+
356
+ @Field(() => TypeOfTaskEnum, { inputType: 'selectEnum' })
357
+ typeOfTask = TypeOfTaskEnum.EASY
358
+ }
359
+
360
+ @ValueListFieldType()
361
+ export class TypeOfTaskEnum extends KitBaseEnum {
362
+ static EASY = new TypeOfTaskEnum('EASY', {
363
+ caption: 'Easy',
364
+ icon: { data: LibIcon_Add },
365
+ })
366
+ static HARD = new TypeOfTaskEnum('HARD', {
367
+ caption: 'Hard',
368
+ icon: { data: LibIcon_Delete },
369
+ })
370
+ constructor(id: string, o?: KitBaseEnumOptions<TypeOfTaskEnum>) {
371
+ super(id, o)
372
+ }
373
+ }
374
+ `,
375
+ ],
376
+ './src/lib/firstly/modules/tasks/TaskController.ts': [
377
+ `import { BackendMethod } from 'remult'
378
+
379
+ import { log } from '${libAlias}/firstly'
380
+
381
+ /**
382
+ * await TaskController.sayHiFromTask("JYC")
383
+ */
384
+ export class TaskController {
385
+ @BackendMethod({ allowed: true })
386
+ static async sayHiFromTask(name: string) {
387
+ log.info(\`hello \${name} 👋\`)
388
+ }
389
+ }
390
+ `,
391
+ ],
392
+ };
393
+ for (const [path, content] of Object.entries(obj)) {
394
+ if (res.includes('all')) {
395
+ write(path, content);
396
+ }
397
+ else {
398
+ if (res.includes('module-demo')) {
399
+ if (path.startsWith('./src/lib/firstly/modules/tasks')) {
400
+ write(path, content);
401
+ }
402
+ }
403
+ }
404
+ }
405
+ p.outro(`🎉 Everything is ok, happy coding!`);
406
+ new Log('').info(gray(italic(`${bold('❔ More help')} ` +
407
+ `at ${cyan('https://github.com/jycouet/firstly')} ` +
408
+ `(📄 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;