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.
- package/CHANGELOG.md +15 -0
- package/LICENSE +18 -0
- package/README.md +12 -0
- package/esm/KitBaseEnum.d.ts +35 -0
- package/esm/KitBaseEnum.js +32 -0
- package/esm/KitEntity.d.ts +2 -0
- package/esm/KitEntity.js +24 -0
- package/esm/KitFields.d.ts +10 -0
- package/esm/KitFields.js +196 -0
- package/esm/ROUTES.d.ts +88 -0
- package/esm/ROUTES.js +98 -0
- package/esm/SqlDatabase/LogToConsoleCustom.d.ts +1 -0
- package/esm/SqlDatabase/LogToConsoleCustom.js +102 -0
- package/esm/api/index.d.ts +42 -0
- package/esm/api/index.js +97 -0
- package/esm/auth/Adapter.d.ts +10 -0
- package/esm/auth/Adapter.js +54 -0
- package/esm/auth/AuthController.d.ts +59 -0
- package/esm/auth/AuthController.js +434 -0
- package/esm/auth/Entities.d.ts +39 -0
- package/esm/auth/Entities.js +154 -0
- package/esm/auth/RoleController.d.ts +14 -0
- package/esm/auth/RoleController.js +57 -0
- package/esm/auth/helper.d.ts +1 -0
- package/esm/auth/helper.js +7 -0
- package/esm/auth/index.d.ts +153 -0
- package/esm/auth/index.js +280 -0
- package/esm/auth/providers/github.d.ts +25 -0
- package/esm/auth/providers/github.js +51 -0
- package/esm/auth/providers/index.d.ts +3 -0
- package/esm/auth/providers/index.js +26 -0
- package/esm/auth/providers/strava.d.ts +25 -0
- package/esm/auth/providers/strava.js +51 -0
- package/esm/auth/static/assets/Page-BMFREPjF.d.ts +5 -0
- package/esm/auth/static/assets/Page-BMFREPjF.js +18 -0
- package/esm/auth/static/assets/Page-BMOLAIFx.d.ts +5 -0
- package/esm/auth/static/assets/Page-BMOLAIFx.js +1 -0
- package/esm/auth/static/assets/Page-BwHye0GW.d.ts +5 -0
- package/esm/auth/static/assets/Page-BwHye0GW.js +1 -0
- package/esm/auth/static/assets/Page-gV58jf2r.css +1 -0
- package/esm/auth/static/assets/index-CKmKKRRL.d.ts +53 -0
- package/esm/auth/static/assets/index-CKmKKRRL.js +2 -0
- package/esm/auth/static/assets/index-R27C_TlP.css +4 -0
- package/esm/auth/static/favicon.svg +79 -0
- package/esm/auth/static/index.html +14 -0
- package/esm/auth/types.d.ts +33 -0
- package/esm/auth/types.js +1 -0
- package/esm/bin/cmd.d.ts +1 -0
- package/esm/bin/cmd.js +418 -0
- package/esm/changeLog/index.d.ts +55 -0
- package/esm/changeLog/index.js +179 -0
- package/esm/cron/index.d.ts +60 -0
- package/esm/cron/index.js +102 -0
- package/esm/feedback/FeedbackController.d.ts +30 -0
- package/esm/feedback/FeedbackController.js +313 -0
- package/esm/feedback/index.d.ts +18 -0
- package/esm/feedback/index.js +14 -0
- package/esm/feedback/ui/DialogIssue.svelte +102 -0
- package/esm/feedback/ui/DialogIssue.svelte.d.ts +20 -0
- package/esm/feedback/ui/DialogIssues.svelte +91 -0
- package/esm/feedback/ui/DialogIssues.svelte.d.ts +20 -0
- package/esm/feedback/ui/DialogMilestones.svelte +38 -0
- package/esm/feedback/ui/DialogMilestones.svelte.d.ts +18 -0
- package/esm/feedback/ui/Feedback.svelte +12 -0
- package/esm/feedback/ui/Feedback.svelte.d.ts +16 -0
- package/esm/formats/dates.d.ts +18 -0
- package/esm/formats/dates.js +35 -0
- package/esm/formats/index.d.ts +4 -0
- package/esm/formats/index.js +3 -0
- package/esm/formats/numbers.d.ts +4 -0
- package/esm/formats/numbers.js +34 -0
- package/esm/formats/strings.d.ts +11 -0
- package/esm/formats/strings.js +109 -0
- package/esm/handle/index.d.ts +7 -0
- package/esm/handle/index.js +40 -0
- package/esm/helper.d.ts +50 -0
- package/esm/helper.js +118 -0
- package/esm/index.d.ts +103 -0
- package/esm/index.js +42 -0
- package/esm/kitCellsBuildor.d.ts +45 -0
- package/esm/kitCellsBuildor.js +105 -0
- package/esm/kitStoreItem.d.ts +28 -0
- package/esm/kitStoreItem.js +170 -0
- package/esm/kitStoreList.d.ts +33 -0
- package/esm/kitStoreList.js +98 -0
- package/esm/mail/index.d.ts +11 -0
- package/esm/mail/index.js +51 -0
- package/esm/theme.d.ts +4 -0
- package/esm/theme.js +4 -0
- package/esm/ui/Button.svelte +102 -0
- package/esm/ui/Button.svelte.d.ts +27 -0
- package/esm/ui/Clipboardable.svelte +19 -0
- package/esm/ui/Clipboardable.svelte.d.ts +25 -0
- package/esm/ui/Field.svelte +288 -0
- package/esm/ui/Field.svelte.d.ts +29 -0
- package/esm/ui/FieldGroup.svelte +91 -0
- package/esm/ui/FieldGroup.svelte.d.ts +30 -0
- package/esm/ui/Grid.svelte +246 -0
- package/esm/ui/Grid.svelte.d.ts +46 -0
- package/esm/ui/GridLoading.svelte +32 -0
- package/esm/ui/GridLoading.svelte.d.ts +20 -0
- package/esm/ui/GridPaginate.svelte +66 -0
- package/esm/ui/GridPaginate.svelte.d.ts +22 -0
- package/esm/ui/Icon.svelte +86 -0
- package/esm/ui/Icon.svelte.d.ts +46 -0
- package/esm/ui/LibIcon.d.ts +23 -0
- package/esm/ui/LibIcon.js +28 -0
- package/esm/ui/Loading.svelte +11 -0
- package/esm/ui/Loading.svelte.d.ts +20 -0
- package/esm/ui/Tooltip.svelte +42 -0
- package/esm/ui/Tooltip.svelte.d.ts +22 -0
- package/esm/ui/dialog/DialogForm.svelte +70 -0
- package/esm/ui/dialog/DialogForm.svelte.d.ts +19 -0
- package/esm/ui/dialog/DialogManagement.svelte +87 -0
- package/esm/ui/dialog/DialogManagement.svelte.d.ts +25 -0
- package/esm/ui/dialog/DialogPrimitive.svelte +89 -0
- package/esm/ui/dialog/DialogPrimitive.svelte.d.ts +28 -0
- package/esm/ui/dialog/FormEditAction.svelte +54 -0
- package/esm/ui/dialog/FormEditAction.svelte.d.ts +24 -0
- package/esm/ui/dialog/dialog.d.ts +51 -0
- package/esm/ui/dialog/dialog.js +98 -0
- package/esm/ui/index.d.ts +5 -0
- package/esm/ui/index.js +19 -0
- package/esm/ui/internals/FieldContainer.svelte +22 -0
- package/esm/ui/internals/FieldContainer.svelte.d.ts +30 -0
- package/esm/ui/internals/Input.svelte +98 -0
- package/esm/ui/internals/Input.svelte.d.ts +35 -0
- package/esm/ui/internals/Textarea.svelte +61 -0
- package/esm/ui/internals/Textarea.svelte.d.ts +30 -0
- package/esm/ui/internals/select/MultiSelectMelt.svelte +217 -0
- package/esm/ui/internals/select/MultiSelectMelt.svelte.d.ts +30 -0
- package/esm/ui/internals/select/SelectMelt.svelte +238 -0
- package/esm/ui/internals/select/SelectMelt.svelte.d.ts +35 -0
- package/esm/ui/internals/select/SelectRadio.svelte +37 -0
- package/esm/ui/internals/select/SelectRadio.svelte.d.ts +25 -0
- package/esm/ui/link/Link.svelte +28 -0
- package/esm/ui/link/Link.svelte.d.ts +25 -0
- package/esm/ui/link/LinkPlus.svelte +44 -0
- package/esm/ui/link/LinkPlus.svelte.d.ts +21 -0
- package/esm/utils/tailwind.d.ts +2 -0
- package/esm/utils/tailwind.js +3 -0
- package/esm/utils/transition.d.ts +10 -0
- package/esm/utils/transition.js +33 -0
- package/esm/utils/types.d.ts +17 -0
- package/esm/utils/types.js +17 -0
- package/esm/virtual/Customer.d.ts +4 -0
- package/esm/virtual/Customer.js +24 -0
- package/esm/virtual/FilterEntity.d.ts +7 -0
- package/esm/virtual/FilterEntity.js +34 -0
- package/esm/virtual/StateDemoEnum.d.ts +9 -0
- package/esm/virtual/StateDemoEnum.js +42 -0
- package/esm/virtual/UIEntity.d.ts +16 -0
- package/esm/virtual/UIEntity.js +84 -0
- package/esm/vite/index.d.ts +8 -0
- package/esm/vite/index.js +47 -0
- 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;
|