@open-mercato/shared 0.4.2-canary-c02407ff85
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/build.mjs +101 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +7 -0
- package/dist/lib/api/crud.js +47 -0
- package/dist/lib/api/crud.js.map +7 -0
- package/dist/lib/api/scoped.js +140 -0
- package/dist/lib/api/scoped.js.map +7 -0
- package/dist/lib/auth/jwt.js +34 -0
- package/dist/lib/auth/jwt.js.map +7 -0
- package/dist/lib/auth/server.js +157 -0
- package/dist/lib/auth/server.js.map +7 -0
- package/dist/lib/boolean.js +22 -0
- package/dist/lib/boolean.js.map +7 -0
- package/dist/lib/bootstrap/appResolver.js +43 -0
- package/dist/lib/bootstrap/appResolver.js.map +7 -0
- package/dist/lib/bootstrap/dynamicLoader.js +108 -0
- package/dist/lib/bootstrap/dynamicLoader.js.map +7 -0
- package/dist/lib/bootstrap/factory.js +59 -0
- package/dist/lib/bootstrap/factory.js.map +7 -0
- package/dist/lib/bootstrap/index.js +11 -0
- package/dist/lib/bootstrap/index.js.map +7 -0
- package/dist/lib/bootstrap/types.js +1 -0
- package/dist/lib/bootstrap/types.js.map +7 -0
- package/dist/lib/cache/segments.js +36 -0
- package/dist/lib/cache/segments.js.map +7 -0
- package/dist/lib/cli/progress.js +46 -0
- package/dist/lib/cli/progress.js.map +7 -0
- package/dist/lib/commands/command-bus.js +285 -0
- package/dist/lib/commands/command-bus.js.map +7 -0
- package/dist/lib/commands/customFieldSnapshots.js +66 -0
- package/dist/lib/commands/customFieldSnapshots.js.map +7 -0
- package/dist/lib/commands/helpers.js +98 -0
- package/dist/lib/commands/helpers.js.map +7 -0
- package/dist/lib/commands/index.js +8 -0
- package/dist/lib/commands/index.js.map +7 -0
- package/dist/lib/commands/operationMetadata.js +32 -0
- package/dist/lib/commands/operationMetadata.js.map +7 -0
- package/dist/lib/commands/registry.js +43 -0
- package/dist/lib/commands/registry.js.map +7 -0
- package/dist/lib/commands/scope.js +44 -0
- package/dist/lib/commands/scope.js.map +7 -0
- package/dist/lib/commands/types.js +8 -0
- package/dist/lib/commands/types.js.map +7 -0
- package/dist/lib/crud/cache-stats.js +98 -0
- package/dist/lib/crud/cache-stats.js.map +7 -0
- package/dist/lib/crud/cache.js +175 -0
- package/dist/lib/crud/cache.js.map +7 -0
- package/dist/lib/crud/custom-fields-client.js +52 -0
- package/dist/lib/crud/custom-fields-client.js.map +7 -0
- package/dist/lib/crud/custom-fields.js +467 -0
- package/dist/lib/crud/custom-fields.js.map +7 -0
- package/dist/lib/crud/errors.js +24 -0
- package/dist/lib/crud/errors.js.map +7 -0
- package/dist/lib/crud/exporters.js +154 -0
- package/dist/lib/crud/exporters.js.map +7 -0
- package/dist/lib/crud/factory.js +1311 -0
- package/dist/lib/crud/factory.js.map +7 -0
- package/dist/lib/crud/types.js +1 -0
- package/dist/lib/crud/types.js.map +7 -0
- package/dist/lib/custom-fields/normalize.js +36 -0
- package/dist/lib/custom-fields/normalize.js.map +7 -0
- package/dist/lib/data/engine.js +396 -0
- package/dist/lib/data/engine.js.map +7 -0
- package/dist/lib/db/escapeLikePattern.js +5 -0
- package/dist/lib/db/escapeLikePattern.js.map +7 -0
- package/dist/lib/db/mikro.js +82 -0
- package/dist/lib/db/mikro.js.map +7 -0
- package/dist/lib/di/container.js +94 -0
- package/dist/lib/di/container.js.map +7 -0
- package/dist/lib/email/send.js +12 -0
- package/dist/lib/email/send.js.map +7 -0
- package/dist/lib/encryption/aes.js +58 -0
- package/dist/lib/encryption/aes.js.map +7 -0
- package/dist/lib/encryption/customFieldValues.js +49 -0
- package/dist/lib/encryption/customFieldValues.js.map +7 -0
- package/dist/lib/encryption/entityFields.js +26 -0
- package/dist/lib/encryption/entityFields.js.map +7 -0
- package/dist/lib/encryption/entityIds.js +80 -0
- package/dist/lib/encryption/entityIds.js.map +7 -0
- package/dist/lib/encryption/find.js +45 -0
- package/dist/lib/encryption/find.js.map +7 -0
- package/dist/lib/encryption/indexDoc.js +69 -0
- package/dist/lib/encryption/indexDoc.js.map +7 -0
- package/dist/lib/encryption/kms.js +282 -0
- package/dist/lib/encryption/kms.js.map +7 -0
- package/dist/lib/encryption/subscriber.js +330 -0
- package/dist/lib/encryption/subscriber.js.map +7 -0
- package/dist/lib/encryption/tenantDataEncryptionService.js +252 -0
- package/dist/lib/encryption/tenantDataEncryptionService.js.map +7 -0
- package/dist/lib/encryption/toggles.js +18 -0
- package/dist/lib/encryption/toggles.js.map +7 -0
- package/dist/lib/entities/naming.js +9 -0
- package/dist/lib/entities/naming.js.map +7 -0
- package/dist/lib/entities/system-entities.js +43 -0
- package/dist/lib/entities/system-entities.js.map +7 -0
- package/dist/lib/frontend/organizationEvents.js +41 -0
- package/dist/lib/frontend/organizationEvents.js.map +7 -0
- package/dist/lib/frontend/useOrganizationScope.js +32 -0
- package/dist/lib/frontend/useOrganizationScope.js.map +7 -0
- package/dist/lib/hotkeys/index.js +128 -0
- package/dist/lib/hotkeys/index.js.map +7 -0
- package/dist/lib/i18n/app-dictionaries.js +17 -0
- package/dist/lib/i18n/app-dictionaries.js.map +7 -0
- package/dist/lib/i18n/config.js +7 -0
- package/dist/lib/i18n/config.js.map +7 -0
- package/dist/lib/i18n/context.js +50 -0
- package/dist/lib/i18n/context.js.map +7 -0
- package/dist/lib/i18n/server.js +68 -0
- package/dist/lib/i18n/server.js.map +7 -0
- package/dist/lib/i18n/translate.js +45 -0
- package/dist/lib/i18n/translate.js.map +7 -0
- package/dist/lib/indexers/error-log.js +82 -0
- package/dist/lib/indexers/error-log.js.map +7 -0
- package/dist/lib/indexers/status-log.js +80 -0
- package/dist/lib/indexers/status-log.js.map +7 -0
- package/dist/lib/lib/auth/jwt.js +34 -0
- package/dist/lib/lib/auth/jwt.js.map +7 -0
- package/dist/lib/lib/auth/server.js +77 -0
- package/dist/lib/lib/auth/server.js.map +7 -0
- package/dist/lib/lib/email/send.js +12 -0
- package/dist/lib/lib/email/send.js.map +7 -0
- package/dist/lib/lib/i18n/config.js +7 -0
- package/dist/lib/lib/i18n/config.js.map +7 -0
- package/dist/lib/lib/i18n/context.js +31 -0
- package/dist/lib/lib/i18n/context.js.map +7 -0
- package/dist/lib/lib/utils.js +9 -0
- package/dist/lib/lib/utils.js.map +7 -0
- package/dist/lib/location/countries.js +68 -0
- package/dist/lib/location/countries.js.map +7 -0
- package/dist/lib/modules/index.js +6 -0
- package/dist/lib/modules/index.js.map +7 -0
- package/dist/lib/modules/registry.js +18 -0
- package/dist/lib/modules/registry.js.map +7 -0
- package/dist/lib/openapi/crud.js +137 -0
- package/dist/lib/openapi/crud.js.map +7 -0
- package/dist/lib/openapi/generator.js +1131 -0
- package/dist/lib/openapi/generator.js.map +7 -0
- package/dist/lib/openapi/index.js +10 -0
- package/dist/lib/openapi/index.js.map +7 -0
- package/dist/lib/openapi/sanitize.js +110 -0
- package/dist/lib/openapi/sanitize.js.map +7 -0
- package/dist/lib/openapi/types.js +1 -0
- package/dist/lib/openapi/types.js.map +7 -0
- package/dist/lib/profiler/index.js +258 -0
- package/dist/lib/profiler/index.js.map +7 -0
- package/dist/lib/query/engine.js +729 -0
- package/dist/lib/query/engine.js.map +7 -0
- package/dist/lib/query/join-utils.js +195 -0
- package/dist/lib/query/join-utils.js.map +7 -0
- package/dist/lib/query/types.js +9 -0
- package/dist/lib/query/types.js.map +7 -0
- package/dist/lib/search/config.js +32 -0
- package/dist/lib/search/config.js.map +7 -0
- package/dist/lib/search/tokenize.js +34 -0
- package/dist/lib/search/tokenize.js.map +7 -0
- package/dist/lib/slugify.js +24 -0
- package/dist/lib/slugify.js.map +7 -0
- package/dist/lib/testing/bootstrap.js +51 -0
- package/dist/lib/testing/bootstrap.js.map +7 -0
- package/dist/lib/testing/index.js +17 -0
- package/dist/lib/testing/index.js.map +7 -0
- package/dist/lib/testing/renderWithProviders.js +15 -0
- package/dist/lib/testing/renderWithProviders.js.map +7 -0
- package/dist/lib/url.js +12 -0
- package/dist/lib/url.js.map +7 -0
- package/dist/lib/utils.js +13 -0
- package/dist/lib/utils.js.map +7 -0
- package/dist/lib/version.js +7 -0
- package/dist/lib/version.js.map +7 -0
- package/dist/modules/dashboard/widgets.js +1 -0
- package/dist/modules/dashboard/widgets.js.map +7 -0
- package/dist/modules/dsl.js +30 -0
- package/dist/modules/dsl.js.map +7 -0
- package/dist/modules/entities/kinds.js +22 -0
- package/dist/modules/entities/kinds.js.map +7 -0
- package/dist/modules/entities/options.js +26 -0
- package/dist/modules/entities/options.js.map +7 -0
- package/dist/modules/entities/validation.js +102 -0
- package/dist/modules/entities/validation.js.map +7 -0
- package/dist/modules/entities/validators.js +88 -0
- package/dist/modules/entities/validators.js.map +7 -0
- package/dist/modules/entities.js +1 -0
- package/dist/modules/entities.js.map +7 -0
- package/dist/modules/navigation/sidebarPreferences.js +50 -0
- package/dist/modules/navigation/sidebarPreferences.js.map +7 -0
- package/dist/modules/perspectives/types.js +1 -0
- package/dist/modules/perspectives/types.js.map +7 -0
- package/dist/modules/registry.js +96 -0
- package/dist/modules/registry.js.map +7 -0
- package/dist/modules/search.js +15 -0
- package/dist/modules/search.js.map +7 -0
- package/dist/modules/vector.js +1 -0
- package/dist/modules/vector.js.map +7 -0
- package/dist/modules/widgets/injection-loader.js +180 -0
- package/dist/modules/widgets/injection-loader.js.map +7 -0
- package/dist/modules/widgets/injection.js +1 -0
- package/dist/modules/widgets/injection.js.map +7 -0
- package/dist/security/features.js +23 -0
- package/dist/security/features.js.map +7 -0
- package/dist/types/pg.d.js +1 -0
- package/dist/types/pg.d.js.map +7 -0
- package/dist/types/react-email.d.js +1 -0
- package/dist/types/react-email.d.js.map +7 -0
- package/dist/types/resend.d.js +1 -0
- package/dist/types/resend.d.js.map +7 -0
- package/jest.config.cjs +22 -0
- package/package.json +88 -0
- package/src/index.ts +0 -0
- package/src/lib/api/__tests__/scoped.test.ts +38 -0
- package/src/lib/api/crud.ts +59 -0
- package/src/lib/api/scoped.ts +239 -0
- package/src/lib/auth/jwt.ts +39 -0
- package/src/lib/auth/server.ts +199 -0
- package/src/lib/boolean.ts +17 -0
- package/src/lib/bootstrap/appResolver.ts +85 -0
- package/src/lib/bootstrap/dynamicLoader.ts +177 -0
- package/src/lib/bootstrap/factory.ts +108 -0
- package/src/lib/bootstrap/index.ts +23 -0
- package/src/lib/bootstrap/types.ts +31 -0
- package/src/lib/cache/segments.ts +56 -0
- package/src/lib/cli/progress.ts +55 -0
- package/src/lib/commands/__tests__/command-bus.test.ts +84 -0
- package/src/lib/commands/__tests__/helpers.test.ts +42 -0
- package/src/lib/commands/command-bus.ts +349 -0
- package/src/lib/commands/customFieldSnapshots.ts +86 -0
- package/src/lib/commands/helpers.ts +143 -0
- package/src/lib/commands/index.ts +4 -0
- package/src/lib/commands/operationMetadata.ts +40 -0
- package/src/lib/commands/registry.ts +46 -0
- package/src/lib/commands/scope.ts +59 -0
- package/src/lib/commands/types.ts +63 -0
- package/src/lib/crud/__tests__/crud-factory.test.ts +333 -0
- package/src/lib/crud/__tests__/custom-fields.test.ts +150 -0
- package/src/lib/crud/cache-stats.ts +127 -0
- package/src/lib/crud/cache.ts +205 -0
- package/src/lib/crud/custom-fields-client.ts +54 -0
- package/src/lib/crud/custom-fields.ts +607 -0
- package/src/lib/crud/errors.ts +23 -0
- package/src/lib/crud/exporters.ts +188 -0
- package/src/lib/crud/factory.ts +1622 -0
- package/src/lib/crud/types.ts +29 -0
- package/src/lib/custom-fields/normalize.ts +45 -0
- package/src/lib/data/engine.ts +562 -0
- package/src/lib/db/escapeLikePattern.ts +2 -0
- package/src/lib/db/mikro.ts +100 -0
- package/src/lib/di/container.ts +105 -0
- package/src/lib/email/send.ts +18 -0
- package/src/lib/encryption/__tests__/customFieldValues.test.ts +63 -0
- package/src/lib/encryption/__tests__/indexDoc.test.ts +115 -0
- package/src/lib/encryption/aes.ts +64 -0
- package/src/lib/encryption/customFieldValues.ts +67 -0
- package/src/lib/encryption/entityFields.ts +39 -0
- package/src/lib/encryption/entityIds.ts +107 -0
- package/src/lib/encryption/find.ts +81 -0
- package/src/lib/encryption/indexDoc.ts +104 -0
- package/src/lib/encryption/kms.ts +337 -0
- package/src/lib/encryption/subscriber.ts +416 -0
- package/src/lib/encryption/tenantDataEncryptionService.ts +313 -0
- package/src/lib/encryption/toggles.ts +15 -0
- package/src/lib/entities/naming.ts +6 -0
- package/src/lib/entities/system-entities.ts +43 -0
- package/src/lib/frontend/organizationEvents.ts +55 -0
- package/src/lib/frontend/useOrganizationScope.ts +30 -0
- package/src/lib/hotkeys/index.ts +168 -0
- package/src/lib/i18n/app-dictionaries.ts +18 -0
- package/src/lib/i18n/config.ts +4 -0
- package/src/lib/i18n/context.tsx +66 -0
- package/src/lib/i18n/server.ts +74 -0
- package/src/lib/i18n/translate.ts +54 -0
- package/src/lib/indexers/error-log.ts +106 -0
- package/src/lib/indexers/status-log.ts +119 -0
- package/src/lib/lib/auth/jwt.ts +39 -0
- package/src/lib/lib/auth/server.ts +94 -0
- package/src/lib/lib/email/send.ts +18 -0
- package/src/lib/lib/i18n/config.ts +4 -0
- package/src/lib/lib/i18n/context.tsx +38 -0
- package/src/lib/lib/utils.ts +6 -0
- package/src/lib/location/countries.ts +97 -0
- package/src/lib/modules/index.ts +1 -0
- package/src/lib/modules/registry.ts +18 -0
- package/src/lib/openapi/crud.ts +218 -0
- package/src/lib/openapi/generator.ts +1311 -0
- package/src/lib/openapi/index.ts +4 -0
- package/src/lib/openapi/sanitize.ts +137 -0
- package/src/lib/openapi/types.ts +79 -0
- package/src/lib/profiler/index.ts +371 -0
- package/src/lib/query/__tests__/engine.test.ts +274 -0
- package/src/lib/query/engine.ts +837 -0
- package/src/lib/query/join-utils.ts +238 -0
- package/src/lib/query/types.ts +121 -0
- package/src/lib/search/config.ts +49 -0
- package/src/lib/search/tokenize.ts +45 -0
- package/src/lib/slugify.ts +28 -0
- package/src/lib/testing/bootstrap.ts +124 -0
- package/src/lib/testing/index.ts +15 -0
- package/src/lib/testing/renderWithProviders.tsx +31 -0
- package/src/lib/url.ts +12 -0
- package/src/lib/utils.ts +17 -0
- package/src/lib/version.ts +5 -0
- package/src/modules/__tests__/dsl.test.ts +35 -0
- package/src/modules/__tests__/registry.test.ts +300 -0
- package/src/modules/dashboard/widgets.ts +57 -0
- package/src/modules/dsl.ts +32 -0
- package/src/modules/entities/__tests__/validation.test.ts +52 -0
- package/src/modules/entities/kinds.ts +20 -0
- package/src/modules/entities/options.ts +36 -0
- package/src/modules/entities/validation.ts +118 -0
- package/src/modules/entities/validators.ts +93 -0
- package/src/modules/entities.ts +102 -0
- package/src/modules/navigation/sidebarPreferences.ts +62 -0
- package/src/modules/perspectives/types.ts +40 -0
- package/src/modules/registry.ts +249 -0
- package/src/modules/search.ts +325 -0
- package/src/modules/vector.ts +122 -0
- package/src/modules/widgets/__tests__/injection.test.ts +48 -0
- package/src/modules/widgets/injection-loader.ts +235 -0
- package/src/modules/widgets/injection.ts +120 -0
- package/src/security/features.ts +22 -0
- package/src/types/pg.d.ts +2 -0
- package/src/types/react-email.d.ts +2 -0
- package/src/types/resend.d.ts +2 -0
- package/tsconfig.build.json +11 -0
- package/tsconfig.json +9 -0
- package/watch.mjs +6 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { CUSTOM_FIELD_KINDS } from './kinds'
|
|
3
|
+
import { validationRulesArraySchema } from './validation'
|
|
4
|
+
|
|
5
|
+
export const entityIdRegex = /^[a-z0-9_]+:[a-z0-9_]+$/
|
|
6
|
+
export const fieldsetCodeRegex = /^[a-z0-9_\-]+$/
|
|
7
|
+
|
|
8
|
+
export const upsertCustomEntitySchema = z.object({
|
|
9
|
+
entityId: z.string().regex(
|
|
10
|
+
entityIdRegex,
|
|
11
|
+
'Enter the entity id in the format: module_name:entity_id with your prefered entity and module names'
|
|
12
|
+
),
|
|
13
|
+
label: z.string().min(1).max(200),
|
|
14
|
+
description: z.string().max(2000).optional().nullable(),
|
|
15
|
+
labelField: z.string().min(1).max(100).regex(/^[a-zA-Z_][a-zA-Z0-9_]*$/).optional(),
|
|
16
|
+
defaultEditor: z.enum(['markdown','simpleMarkdown','htmlRichText']).optional(),
|
|
17
|
+
showInSidebar: z.boolean().default(false),
|
|
18
|
+
isActive: z.boolean().optional(),
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
export const upsertCustomFieldDefSchema = z.object({
|
|
22
|
+
entityId: z.string().regex(entityIdRegex),
|
|
23
|
+
key: z.string().min(1).max(100).regex(/^[a-z0-9_]+$/, 'snake_case only'),
|
|
24
|
+
kind: z.enum(CUSTOM_FIELD_KINDS),
|
|
25
|
+
configJson: z
|
|
26
|
+
.object({
|
|
27
|
+
// optional UI/behavioral hints
|
|
28
|
+
label: z.string().max(200).optional(),
|
|
29
|
+
description: z.string().max(2000).optional(),
|
|
30
|
+
options: z.array(z.union([z.string(), z.number()])).optional(),
|
|
31
|
+
optionsUrl: z.string().url().optional(),
|
|
32
|
+
multi: z.boolean().optional(),
|
|
33
|
+
editor: z.string().optional(),
|
|
34
|
+
input: z.string().optional(),
|
|
35
|
+
filterable: z.boolean().optional(),
|
|
36
|
+
formEditable: z.boolean().optional(),
|
|
37
|
+
listVisible: z.boolean().optional(),
|
|
38
|
+
priority: z.number().optional(),
|
|
39
|
+
encrypted: z.boolean().optional(),
|
|
40
|
+
relatedEntityId: z.string().optional(),
|
|
41
|
+
dictionaryId: z.string().uuid().optional(),
|
|
42
|
+
dictionaryInlineCreate: z.boolean().optional(),
|
|
43
|
+
// validation rules
|
|
44
|
+
validation: validationRulesArraySchema.optional(),
|
|
45
|
+
fieldset: z.string().regex(fieldsetCodeRegex).optional(),
|
|
46
|
+
group: z
|
|
47
|
+
.object({
|
|
48
|
+
code: z.string().regex(fieldsetCodeRegex),
|
|
49
|
+
title: z.string().max(200).optional(),
|
|
50
|
+
hint: z.string().max(500).optional(),
|
|
51
|
+
})
|
|
52
|
+
.optional(),
|
|
53
|
+
})
|
|
54
|
+
.passthrough()
|
|
55
|
+
.optional(),
|
|
56
|
+
isActive: z.boolean().optional(),
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
export const customFieldsetGroupSchema = z.object({
|
|
60
|
+
code: z.string().regex(fieldsetCodeRegex),
|
|
61
|
+
title: z.string().max(200).optional(),
|
|
62
|
+
hint: z.string().max(500).optional(),
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
export const customFieldsetSchema = z.object({
|
|
66
|
+
code: z.string().regex(fieldsetCodeRegex),
|
|
67
|
+
label: z.string().min(1).max(255),
|
|
68
|
+
icon: z.string().max(100).optional(),
|
|
69
|
+
description: z.string().max(2000).optional(),
|
|
70
|
+
groups: z.array(customFieldsetGroupSchema).optional(),
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
export const customFieldEntityConfigSchema = z.object({
|
|
74
|
+
fieldsets: z.array(customFieldsetSchema).max(20).optional(),
|
|
75
|
+
singleFieldsetPerRecord: z.boolean().optional(),
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
export const encryptionFieldRuleSchema = z.object({
|
|
79
|
+
field: z.string().min(1).max(200),
|
|
80
|
+
hashField: z.string().min(1).max(200).optional().nullable(),
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
export const upsertEncryptionMapSchema = z.object({
|
|
84
|
+
entityId: z.string().regex(entityIdRegex),
|
|
85
|
+
tenantId: z.string().uuid().nullable().optional(),
|
|
86
|
+
organizationId: z.string().uuid().nullable().optional(),
|
|
87
|
+
fields: z.array(encryptionFieldRuleSchema).min(1),
|
|
88
|
+
isActive: z.boolean().optional(),
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
export type UpsertCustomEntityInput = z.infer<typeof upsertCustomEntitySchema>
|
|
92
|
+
export type UpsertCustomFieldDefInput = z.infer<typeof upsertCustomFieldDefSchema>
|
|
93
|
+
export type UpsertEncryptionMapInput = z.infer<typeof upsertEncryptionMapSchema>
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// Shared entity/extension/custom-field types used by generators and DI
|
|
2
|
+
|
|
3
|
+
export type EntityId = string // format: '<module>:<entity>' e.g., 'auth:user'
|
|
4
|
+
|
|
5
|
+
export type EntityExtension = {
|
|
6
|
+
// Base entity to extend, e.g., 'auth:user'
|
|
7
|
+
base: EntityId
|
|
8
|
+
// The extension entity that holds extra columns/relations, defined by the extending module
|
|
9
|
+
// Usually one-to-one keyed by base PK; other cardinalities allowed via explicit join keys
|
|
10
|
+
extension: EntityId
|
|
11
|
+
// Join description for query builder to link base <-> extension
|
|
12
|
+
join: {
|
|
13
|
+
baseKey: string // column name on base (e.g., 'id')
|
|
14
|
+
extensionKey: string // column name on extension (e.g., 'user_id')
|
|
15
|
+
}
|
|
16
|
+
cardinality?: 'one-to-one' | 'one-to-many' | 'many-to-one' | 'many-to-many'
|
|
17
|
+
required?: boolean
|
|
18
|
+
description?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type CustomFieldKind =
|
|
22
|
+
| 'text'
|
|
23
|
+
| 'multiline'
|
|
24
|
+
| 'integer'
|
|
25
|
+
| 'float'
|
|
26
|
+
| 'boolean'
|
|
27
|
+
| 'select'
|
|
28
|
+
| 'currency'
|
|
29
|
+
| 'relation'
|
|
30
|
+
| 'attachment'
|
|
31
|
+
| 'dictionary'
|
|
32
|
+
|
|
33
|
+
export type CustomFieldDefinition = {
|
|
34
|
+
id?: string // stable id; generated if omitted
|
|
35
|
+
key: string // unique within entity (snake_case)
|
|
36
|
+
kind: CustomFieldKind
|
|
37
|
+
label?: string
|
|
38
|
+
description?: string
|
|
39
|
+
fieldset?: string
|
|
40
|
+
group?: {
|
|
41
|
+
code: string
|
|
42
|
+
title?: string
|
|
43
|
+
hint?: string
|
|
44
|
+
}
|
|
45
|
+
required?: boolean
|
|
46
|
+
multi?: boolean // allow multiple values
|
|
47
|
+
options?: Array<
|
|
48
|
+
string | number | boolean | { value: string | number | boolean; label?: string | null }
|
|
49
|
+
>
|
|
50
|
+
// Optional dynamic options source for selects/tags relations
|
|
51
|
+
optionsUrl?: string
|
|
52
|
+
defaultValue?: string | number | boolean | null
|
|
53
|
+
filterable?: boolean
|
|
54
|
+
// whether field should be editable in generated CRUD forms
|
|
55
|
+
formEditable?: boolean
|
|
56
|
+
indexed?: boolean
|
|
57
|
+
listVisible?: boolean
|
|
58
|
+
// Optional UI hints for generated forms/filters
|
|
59
|
+
// Editors for multiline-rich text fields:
|
|
60
|
+
// - 'markdown' -> UIW Markdown editor
|
|
61
|
+
// - 'simpleMarkdown' -> minimal toolbar markdown
|
|
62
|
+
// - 'htmlRichText' -> contenteditable rich text
|
|
63
|
+
editor?: 'markdown' | 'simpleMarkdown' | 'htmlRichText'
|
|
64
|
+
// Input hint for plain text fields (e.g., tags input when multi=true)
|
|
65
|
+
// Allow additional custom renderers (e.g., listbox from modules)
|
|
66
|
+
input?: string
|
|
67
|
+
// Relation helper metadata
|
|
68
|
+
relatedEntityId?: string
|
|
69
|
+
// Backed by global dictionaries module
|
|
70
|
+
dictionaryId?: string
|
|
71
|
+
dictionaryInlineCreate?: boolean
|
|
72
|
+
// Advanced validation rules applied in UI + API
|
|
73
|
+
validation?: Array<{ rule: string; param?: unknown; message?: string }>
|
|
74
|
+
// Attachments config passthrough (handled by attachments module)
|
|
75
|
+
maxAttachmentSizeMb?: number
|
|
76
|
+
acceptExtensions?: string[]
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export type CustomFieldSet = {
|
|
80
|
+
entity: EntityId
|
|
81
|
+
fields: CustomFieldDefinition[]
|
|
82
|
+
// Optional: module id or other provenance
|
|
83
|
+
source?: string
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export type EntityRegistrySpec = {
|
|
87
|
+
// Static, per-module declared extensions
|
|
88
|
+
extensions?: EntityExtension[]
|
|
89
|
+
// Static, per-module declared custom fields (seeded via migrations/CLI)
|
|
90
|
+
customFieldSets?: CustomFieldSet[]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export type CustomEntitySpec = {
|
|
94
|
+
id: EntityId
|
|
95
|
+
label?: string
|
|
96
|
+
description?: string
|
|
97
|
+
labelField?: string
|
|
98
|
+
defaultEditor?: string
|
|
99
|
+
showInSidebar?: boolean
|
|
100
|
+
global?: boolean
|
|
101
|
+
fields?: CustomFieldDefinition[]
|
|
102
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { slugify } from '../../lib/slugify'
|
|
2
|
+
|
|
3
|
+
export const SIDEBAR_PREFERENCES_VERSION = 2
|
|
4
|
+
|
|
5
|
+
export type SidebarPreferencesSettings = {
|
|
6
|
+
version: number
|
|
7
|
+
groupOrder?: string[]
|
|
8
|
+
groupLabels?: Record<string, string>
|
|
9
|
+
itemLabels?: Record<string, string>
|
|
10
|
+
hiddenItems?: string[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type SidebarPreferencesPayload = {
|
|
14
|
+
locale: string
|
|
15
|
+
settings: SidebarPreferencesSettings
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function normalizeSidebarSettings(settings?: SidebarPreferencesSettings | null): SidebarPreferencesSettings {
|
|
19
|
+
if (!settings || typeof settings !== 'object') {
|
|
20
|
+
return { version: SIDEBAR_PREFERENCES_VERSION, groupOrder: [], groupLabels: {}, itemLabels: {}, hiddenItems: [] }
|
|
21
|
+
}
|
|
22
|
+
const version = typeof settings.version === 'number' ? settings.version : SIDEBAR_PREFERENCES_VERSION
|
|
23
|
+
const groupOrder = Array.isArray(settings.groupOrder) ? settings.groupOrder.filter((v): v is string => typeof v === 'string') : []
|
|
24
|
+
const groupLabels = normalizeRecord(settings.groupLabels)
|
|
25
|
+
const itemLabels = normalizeRecord(settings.itemLabels)
|
|
26
|
+
const hiddenItems = normalizeStringArray(settings.hiddenItems)
|
|
27
|
+
return {
|
|
28
|
+
version,
|
|
29
|
+
groupOrder,
|
|
30
|
+
groupLabels,
|
|
31
|
+
itemLabels,
|
|
32
|
+
hiddenItems,
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function normalizeRecord(record: Record<string, unknown> | undefined): Record<string, string> {
|
|
37
|
+
if (!record || typeof record !== 'object') return {}
|
|
38
|
+
const out: Record<string, string> = {}
|
|
39
|
+
for (const [key, value] of Object.entries(record)) {
|
|
40
|
+
if (typeof value !== 'string') continue
|
|
41
|
+
out[key] = value
|
|
42
|
+
}
|
|
43
|
+
return out
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function normalizeStringArray(values: unknown): string[] {
|
|
47
|
+
if (!Array.isArray(values)) return []
|
|
48
|
+
const seen = new Set<string>()
|
|
49
|
+
const out: string[] = []
|
|
50
|
+
for (const value of values) {
|
|
51
|
+
if (typeof value !== 'string') continue
|
|
52
|
+
const trimmed = value.trim()
|
|
53
|
+
if (!trimmed || seen.has(trimmed)) continue
|
|
54
|
+
seen.add(trimmed)
|
|
55
|
+
out.push(trimmed)
|
|
56
|
+
}
|
|
57
|
+
return out
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function slugifySidebarId(source: string): string {
|
|
61
|
+
return slugify(source, { allowedChars: '' }) || 'group'
|
|
62
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export type PerspectiveSettings = {
|
|
2
|
+
columnOrder?: string[]
|
|
3
|
+
columnVisibility?: Record<string, boolean>
|
|
4
|
+
filters?: Record<string, unknown>
|
|
5
|
+
sorting?: Array<{ id: string; desc?: boolean }>
|
|
6
|
+
pageSize?: number
|
|
7
|
+
searchValue?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type PerspectiveDto = {
|
|
11
|
+
id: string
|
|
12
|
+
name: string
|
|
13
|
+
tableId: string
|
|
14
|
+
settings: PerspectiveSettings
|
|
15
|
+
isDefault: boolean
|
|
16
|
+
createdAt: string
|
|
17
|
+
updatedAt?: string | null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type RolePerspectiveDto = PerspectiveDto & {
|
|
21
|
+
roleId: string
|
|
22
|
+
tenantId: string | null
|
|
23
|
+
organizationId: string | null
|
|
24
|
+
roleName?: string | null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type PerspectivesIndexResponse = {
|
|
28
|
+
tableId: string
|
|
29
|
+
perspectives: PerspectiveDto[]
|
|
30
|
+
defaultPerspectiveId: string | null
|
|
31
|
+
rolePerspectives: RolePerspectiveDto[]
|
|
32
|
+
roles: Array<{ id: string; name: string; hasPerspective: boolean; hasDefault: boolean }>
|
|
33
|
+
canApplyToRoles: boolean
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type PerspectiveSaveResponse = {
|
|
37
|
+
perspective: PerspectiveDto
|
|
38
|
+
rolePerspectives: RolePerspectiveDto[]
|
|
39
|
+
clearedRoleIds: string[]
|
|
40
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
import type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi/types'
|
|
3
|
+
import type { DashboardWidgetModule } from './dashboard/widgets'
|
|
4
|
+
import type { InjectionWidgetModule, ModuleInjectionTable } from './widgets/injection'
|
|
5
|
+
|
|
6
|
+
// Context passed to dynamic metadata guards
|
|
7
|
+
export type RouteVisibilityContext = { path?: string; auth?: any }
|
|
8
|
+
|
|
9
|
+
// Metadata you can export from page.meta.ts or directly from a server page
|
|
10
|
+
export type PageMetadata = {
|
|
11
|
+
requireAuth?: boolean
|
|
12
|
+
requireRoles?: readonly string[]
|
|
13
|
+
// Optional fine-grained feature requirements
|
|
14
|
+
requireFeatures?: readonly string[]
|
|
15
|
+
// Titles and grouping (aliases supported)
|
|
16
|
+
title?: string
|
|
17
|
+
titleKey?: string
|
|
18
|
+
pageTitle?: string
|
|
19
|
+
pageTitleKey?: string
|
|
20
|
+
group?: string
|
|
21
|
+
groupKey?: string
|
|
22
|
+
pageGroup?: string
|
|
23
|
+
pageGroupKey?: string
|
|
24
|
+
// Ordering and visuals
|
|
25
|
+
order?: number
|
|
26
|
+
pageOrder?: number
|
|
27
|
+
icon?: ReactNode
|
|
28
|
+
navHidden?: boolean
|
|
29
|
+
// Dynamic flags
|
|
30
|
+
visible?: (ctx: RouteVisibilityContext) => boolean | Promise<boolean>
|
|
31
|
+
enabled?: (ctx: RouteVisibilityContext) => boolean | Promise<boolean>
|
|
32
|
+
// Optional static breadcrumb trail for header
|
|
33
|
+
breadcrumb?: Array<{ label: string; labelKey?: string; href?: string }>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
|
|
37
|
+
|
|
38
|
+
export type ApiHandler = (req: Request, ctx?: any) => Promise<Response> | Response
|
|
39
|
+
|
|
40
|
+
export type ModuleRoute = {
|
|
41
|
+
pattern?: string
|
|
42
|
+
path?: string
|
|
43
|
+
requireAuth?: boolean
|
|
44
|
+
requireRoles?: string[]
|
|
45
|
+
// Optional fine-grained feature requirements
|
|
46
|
+
requireFeatures?: string[]
|
|
47
|
+
title?: string
|
|
48
|
+
titleKey?: string
|
|
49
|
+
group?: string
|
|
50
|
+
groupKey?: string
|
|
51
|
+
icon?: ReactNode
|
|
52
|
+
order?: number
|
|
53
|
+
priority?: number
|
|
54
|
+
navHidden?: boolean
|
|
55
|
+
visible?: (ctx: RouteVisibilityContext) => boolean | Promise<boolean>
|
|
56
|
+
enabled?: (ctx: RouteVisibilityContext) => boolean | Promise<boolean>
|
|
57
|
+
breadcrumb?: Array<{ label: string; labelKey?: string; href?: string }>
|
|
58
|
+
Component: (props: any) => ReactNode | Promise<ReactNode>
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export type ModuleApiLegacy = {
|
|
62
|
+
method: HttpMethod
|
|
63
|
+
path: string
|
|
64
|
+
handler: ApiHandler
|
|
65
|
+
docs?: OpenApiMethodDoc
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export type ModuleApiRouteFile = {
|
|
69
|
+
path: string
|
|
70
|
+
handlers: Partial<Record<HttpMethod, ApiHandler>>
|
|
71
|
+
requireAuth?: boolean
|
|
72
|
+
requireRoles?: string[]
|
|
73
|
+
// Optional fine-grained feature requirements for the entire route file
|
|
74
|
+
// Note: per-method feature requirements should be expressed inside metadata
|
|
75
|
+
requireFeatures?: string[]
|
|
76
|
+
docs?: OpenApiRouteDoc
|
|
77
|
+
metadata?: Partial<Record<HttpMethod, unknown>>
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export type ModuleApi = ModuleApiLegacy | ModuleApiRouteFile
|
|
81
|
+
|
|
82
|
+
export type ModuleCli = {
|
|
83
|
+
command: string
|
|
84
|
+
run: (argv: string[]) => Promise<void> | void
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export type ModuleInfo = {
|
|
88
|
+
name?: string
|
|
89
|
+
title?: string
|
|
90
|
+
version?: string
|
|
91
|
+
description?: string
|
|
92
|
+
author?: string
|
|
93
|
+
license?: string
|
|
94
|
+
homepage?: string
|
|
95
|
+
copyright?: string
|
|
96
|
+
// Optional hard dependencies: module ids that must be enabled
|
|
97
|
+
requires?: string[]
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export type ModuleDashboardWidgetEntry = {
|
|
101
|
+
moduleId: string
|
|
102
|
+
key: string
|
|
103
|
+
source: 'app' | 'package'
|
|
104
|
+
loader: () => Promise<DashboardWidgetModule<any>>
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export type ModuleInjectionWidgetEntry = {
|
|
108
|
+
moduleId: string
|
|
109
|
+
key: string
|
|
110
|
+
source: 'app' | 'package'
|
|
111
|
+
loader: () => Promise<InjectionWidgetModule<any, any>>
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export type Module = {
|
|
115
|
+
id: string
|
|
116
|
+
info?: ModuleInfo
|
|
117
|
+
backendRoutes?: ModuleRoute[]
|
|
118
|
+
frontendRoutes?: ModuleRoute[]
|
|
119
|
+
apis?: ModuleApi[]
|
|
120
|
+
cli?: ModuleCli[]
|
|
121
|
+
translations?: Record<string, Record<string, string>>
|
|
122
|
+
// Optional: per-module feature declarations discovered from acl.ts (module root)
|
|
123
|
+
features?: Array<{ id: string; title: string; module: string }>
|
|
124
|
+
// Auto-discovered event subscribers
|
|
125
|
+
subscribers?: Array<{
|
|
126
|
+
id: string
|
|
127
|
+
event: string
|
|
128
|
+
persistent?: boolean
|
|
129
|
+
// Imported function reference; will be registered into event bus
|
|
130
|
+
handler: (payload: any, ctx: any) => Promise<void> | void
|
|
131
|
+
}>
|
|
132
|
+
// Auto-discovered queue workers
|
|
133
|
+
workers?: Array<{
|
|
134
|
+
id: string
|
|
135
|
+
queue: string
|
|
136
|
+
concurrency: number
|
|
137
|
+
// Imported function reference; will be called by the queue worker
|
|
138
|
+
handler: (job: unknown, ctx: unknown) => Promise<void> | void
|
|
139
|
+
}>
|
|
140
|
+
// Optional: per-module declared entity extensions and custom fields (static)
|
|
141
|
+
// Extensions discovered from data/extensions.ts; Custom fields discovered from ce.ts (entities[].fields)
|
|
142
|
+
entityExtensions?: import('./entities').EntityExtension[]
|
|
143
|
+
customFieldSets?: import('./entities').CustomFieldSet[]
|
|
144
|
+
// Optional: per-module declared custom entities (virtual/logical entities)
|
|
145
|
+
// Discovered from ce.ts (module root). Each entry represents an entityId with optional label/description.
|
|
146
|
+
customEntities?: Array<{ id: string; label?: string; description?: string }>
|
|
147
|
+
dashboardWidgets?: ModuleDashboardWidgetEntry[]
|
|
148
|
+
injectionWidgets?: ModuleInjectionWidgetEntry[]
|
|
149
|
+
injectionTable?: ModuleInjectionTable
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function normPath(s: string) {
|
|
153
|
+
return (s.startsWith('/') ? s : '/' + s).replace(/\/+$/, '') || '/'
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function matchPattern(pattern: string, pathname: string): Record<string, string | string[]> | undefined {
|
|
157
|
+
const p = normPath(pattern)
|
|
158
|
+
const u = normPath(pathname)
|
|
159
|
+
const pSegs = p.split('/').slice(1)
|
|
160
|
+
const uSegs = u.split('/').slice(1)
|
|
161
|
+
const params: Record<string, string | string[]> = {}
|
|
162
|
+
let i = 0
|
|
163
|
+
for (let j = 0; j < pSegs.length; j++, i++) {
|
|
164
|
+
const seg = pSegs[j]
|
|
165
|
+
const mCatchAll = seg.match(/^\[\.\.\.(.+)\]$/)
|
|
166
|
+
const mOptCatch = seg.match(/^\[\[\.\.\.(.+)\]\]$/)
|
|
167
|
+
const mDyn = seg.match(/^\[(.+)\]$/)
|
|
168
|
+
if (mCatchAll) {
|
|
169
|
+
const key = mCatchAll[1]
|
|
170
|
+
if (i >= uSegs.length) return undefined
|
|
171
|
+
params[key] = uSegs.slice(i)
|
|
172
|
+
i = uSegs.length
|
|
173
|
+
return i === uSegs.length ? params : undefined
|
|
174
|
+
} else if (mOptCatch) {
|
|
175
|
+
const key = mOptCatch[1]
|
|
176
|
+
params[key] = i < uSegs.length ? uSegs.slice(i) : []
|
|
177
|
+
i = uSegs.length
|
|
178
|
+
return params
|
|
179
|
+
} else if (mDyn) {
|
|
180
|
+
if (i >= uSegs.length) return undefined
|
|
181
|
+
params[mDyn[1]] = uSegs[i]
|
|
182
|
+
} else {
|
|
183
|
+
if (i >= uSegs.length || uSegs[i] !== seg) return undefined
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (i !== uSegs.length) return undefined
|
|
187
|
+
return params
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function getPattern(r: ModuleRoute) {
|
|
191
|
+
return r.pattern ?? r.path ?? '/'
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function findFrontendMatch(modules: Module[], pathname: string): { route: ModuleRoute; params: Record<string, string | string[]> } | undefined {
|
|
195
|
+
for (const m of modules) {
|
|
196
|
+
const routes = m.frontendRoutes ?? []
|
|
197
|
+
for (const r of routes) {
|
|
198
|
+
const params = matchPattern(getPattern(r), pathname)
|
|
199
|
+
if (params) return { route: r, params }
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function findBackendMatch(modules: Module[], pathname: string): { route: ModuleRoute; params: Record<string, string | string[]> } | undefined {
|
|
205
|
+
for (const m of modules) {
|
|
206
|
+
const routes = m.backendRoutes ?? []
|
|
207
|
+
for (const r of routes) {
|
|
208
|
+
const params = matchPattern(getPattern(r), pathname)
|
|
209
|
+
if (params) return { route: r, params }
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function findApi(modules: Module[], method: HttpMethod, pathname: string): { handler: ApiHandler; params: Record<string, string | string[]>; requireAuth?: boolean; requireRoles?: string[]; metadata?: any } | undefined {
|
|
215
|
+
for (const m of modules) {
|
|
216
|
+
const apis = m.apis ?? []
|
|
217
|
+
for (const a of apis) {
|
|
218
|
+
if ('handlers' in a) {
|
|
219
|
+
const params = matchPattern(a.path, pathname)
|
|
220
|
+
const handler = (a.handlers as any)[method]
|
|
221
|
+
if (params && handler) return { handler, params, requireAuth: a.requireAuth, requireRoles: (a as any).requireRoles, metadata: (a as any).metadata }
|
|
222
|
+
} else {
|
|
223
|
+
const al = a as ModuleApiLegacy
|
|
224
|
+
if (al.method === method && al.path === pathname) {
|
|
225
|
+
return { handler: al.handler, params: {} }
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// CLI modules registry - shared between CLI and module workers
|
|
233
|
+
let _cliModules: Module[] | null = null
|
|
234
|
+
|
|
235
|
+
export function registerCliModules(modules: Module[]) {
|
|
236
|
+
if (_cliModules !== null && process.env.NODE_ENV === 'development') {
|
|
237
|
+
console.debug('[Bootstrap] CLI modules re-registered (this may occur during HMR)')
|
|
238
|
+
}
|
|
239
|
+
_cliModules = modules
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function getCliModules(): Module[] {
|
|
243
|
+
// Return empty array if not registered - allows generate command to work without bootstrap
|
|
244
|
+
return _cliModules ?? []
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function hasCliModules(): boolean {
|
|
248
|
+
return _cliModules !== null && _cliModules.length > 0
|
|
249
|
+
}
|