@pikku/core 0.6.9

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 (167) hide show
  1. package/CHANGELOG.md +339 -0
  2. package/README.md +5 -0
  3. package/dist/channel/channel-handler.d.ts +3 -0
  4. package/dist/channel/channel-handler.js +97 -0
  5. package/dist/channel/channel-runner.d.ts +26 -0
  6. package/dist/channel/channel-runner.js +86 -0
  7. package/dist/channel/channel-store.d.ts +14 -0
  8. package/dist/channel/channel-store.js +2 -0
  9. package/dist/channel/channel.types.d.ts +106 -0
  10. package/dist/channel/channel.types.js +1 -0
  11. package/dist/channel/eventhub-forwarder.d.ts +17 -0
  12. package/dist/channel/eventhub-forwarder.js +1 -0
  13. package/dist/channel/eventhub-service.d.ts +23 -0
  14. package/dist/channel/eventhub-service.js +1 -0
  15. package/dist/channel/eventhub-store.d.ts +5 -0
  16. package/dist/channel/eventhub-store.js +2 -0
  17. package/dist/channel/index.d.ts +9 -0
  18. package/dist/channel/index.js +9 -0
  19. package/dist/channel/local/index.d.ts +3 -0
  20. package/dist/channel/local/index.js +3 -0
  21. package/dist/channel/local/local-channel-handler.d.ts +17 -0
  22. package/dist/channel/local/local-channel-handler.js +41 -0
  23. package/dist/channel/local/local-channel-runner.d.ts +3 -0
  24. package/dist/channel/local/local-channel-runner.js +46 -0
  25. package/dist/channel/local/local-eventhub-service.d.ts +44 -0
  26. package/dist/channel/local/local-eventhub-service.js +81 -0
  27. package/dist/channel/log-channels.d.ts +6 -0
  28. package/dist/channel/log-channels.js +17 -0
  29. package/dist/channel/pikku-abstract-channel-handler.d.ts +15 -0
  30. package/dist/channel/pikku-abstract-channel-handler.js +33 -0
  31. package/dist/channel/serverless/index.d.ts +1 -0
  32. package/dist/channel/serverless/index.js +1 -0
  33. package/dist/channel/serverless/serverless-channel-runner.d.ts +10 -0
  34. package/dist/channel/serverless/serverless-channel-runner.js +98 -0
  35. package/dist/errors/error-handler.d.ts +39 -0
  36. package/dist/errors/error-handler.js +54 -0
  37. package/dist/errors/errors.d.ts +190 -0
  38. package/dist/errors/errors.js +315 -0
  39. package/dist/errors/index.d.ts +2 -0
  40. package/dist/errors/index.js +2 -0
  41. package/dist/http/http-route-runner.d.ts +30 -0
  42. package/dist/http/http-route-runner.js +208 -0
  43. package/dist/http/http-routes.types.d.ts +127 -0
  44. package/dist/http/http-routes.types.js +1 -0
  45. package/dist/http/http-session-service.d.ts +15 -0
  46. package/dist/http/http-session-service.js +1 -0
  47. package/dist/http/index.d.ts +6 -0
  48. package/dist/http/index.js +5 -0
  49. package/dist/http/log-http-routes.d.ts +6 -0
  50. package/dist/http/log-http-routes.js +17 -0
  51. package/dist/http/pikku-http-abstract-request.d.ts +57 -0
  52. package/dist/http/pikku-http-abstract-request.js +69 -0
  53. package/dist/http/pikku-http-abstract-response.d.ts +58 -0
  54. package/dist/http/pikku-http-abstract-response.js +54 -0
  55. package/dist/http/pikku-http-session-service.d.ts +45 -0
  56. package/dist/http/pikku-http-session-service.js +92 -0
  57. package/dist/index.d.ts +14 -0
  58. package/dist/index.js +14 -0
  59. package/dist/permissions.d.ts +10 -0
  60. package/dist/permissions.js +32 -0
  61. package/dist/pikku-request.d.ts +14 -0
  62. package/dist/pikku-request.js +21 -0
  63. package/dist/pikku-response.d.ts +6 -0
  64. package/dist/pikku-response.js +6 -0
  65. package/dist/scheduler/index.d.ts +3 -0
  66. package/dist/scheduler/index.js +3 -0
  67. package/dist/scheduler/log-schedulers.d.ts +6 -0
  68. package/dist/scheduler/log-schedulers.js +17 -0
  69. package/dist/scheduler/scheduler-runner.d.ts +21 -0
  70. package/dist/scheduler/scheduler-runner.js +73 -0
  71. package/dist/scheduler/scheduler.types.d.ts +21 -0
  72. package/dist/scheduler/scheduler.types.js +1 -0
  73. package/dist/schema.d.ts +26 -0
  74. package/dist/schema.js +100 -0
  75. package/dist/services/content-service.d.ts +55 -0
  76. package/dist/services/content-service.js +1 -0
  77. package/dist/services/index.d.ts +12 -0
  78. package/dist/services/index.js +14 -0
  79. package/dist/services/jwt-service.d.ts +29 -0
  80. package/dist/services/jwt-service.js +1 -0
  81. package/dist/services/local-content.d.ts +33 -0
  82. package/dist/services/local-content.js +6 -0
  83. package/dist/services/local-secrets.d.ts +26 -0
  84. package/dist/services/local-secrets.js +39 -0
  85. package/dist/services/local-variables.d.ts +7 -0
  86. package/dist/services/local-variables.js +12 -0
  87. package/dist/services/logger-console.d.ts +52 -0
  88. package/dist/services/logger-console.js +82 -0
  89. package/dist/services/logger.d.ts +48 -0
  90. package/dist/services/logger.js +9 -0
  91. package/dist/services/schema-service.d.ts +24 -0
  92. package/dist/services/schema-service.js +1 -0
  93. package/dist/services/secret-service.d.ts +17 -0
  94. package/dist/services/secret-service.js +1 -0
  95. package/dist/services/user-session-service.d.ts +6 -0
  96. package/dist/services/user-session-service.js +1 -0
  97. package/dist/services/variables-service.d.ts +4 -0
  98. package/dist/services/variables-service.js +1 -0
  99. package/dist/types/core.types.d.ts +109 -0
  100. package/dist/types/core.types.js +1 -0
  101. package/dist/types/functions.types.d.ts +27 -0
  102. package/dist/types/functions.types.js +1 -0
  103. package/dist/utils.d.ts +2 -0
  104. package/dist/utils.js +15 -0
  105. package/package.json +48 -0
  106. package/run-tests.sh +53 -0
  107. package/src/channel/channel-handler.ts +179 -0
  108. package/src/channel/channel-runner.ts +156 -0
  109. package/src/channel/channel-store.ts +32 -0
  110. package/src/channel/channel.types.ts +202 -0
  111. package/src/channel/eventhub-forwarder.ts +25 -0
  112. package/src/channel/eventhub-service.ts +30 -0
  113. package/src/channel/eventhub-store.ts +8 -0
  114. package/src/channel/index.ts +9 -0
  115. package/src/channel/local/index.ts +3 -0
  116. package/src/channel/local/local-channel-handler.ts +56 -0
  117. package/src/channel/local/local-channel-runner.test.ts +128 -0
  118. package/src/channel/local/local-channel-runner.ts +94 -0
  119. package/src/channel/local/local-eventhub-service.test.ts +95 -0
  120. package/src/channel/local/local-eventhub-service.ts +95 -0
  121. package/src/channel/log-channels.ts +20 -0
  122. package/src/channel/pikku-abstract-channel-handler.test.ts +77 -0
  123. package/src/channel/pikku-abstract-channel-handler.ts +44 -0
  124. package/src/channel/serverless/index.ts +1 -0
  125. package/src/channel/serverless/serverless-channel-runner.ts +190 -0
  126. package/src/errors/error-handler.ts +74 -0
  127. package/src/errors/error.test.ts +54 -0
  128. package/src/errors/errors.ts +340 -0
  129. package/src/errors/index.ts +2 -0
  130. package/src/http/http-route-runner.test.ts +193 -0
  131. package/src/http/http-route-runner.ts +365 -0
  132. package/src/http/http-routes.types.ts +189 -0
  133. package/src/http/http-session-service.ts +19 -0
  134. package/src/http/index.ts +16 -0
  135. package/src/http/log-http-routes.ts +20 -0
  136. package/src/http/pikku-http-abstract-request.ts +95 -0
  137. package/src/http/pikku-http-abstract-response.ts +79 -0
  138. package/src/http/pikku-http-session-service.test.ts +106 -0
  139. package/src/http/pikku-http-session-service.ts +135 -0
  140. package/src/index.ts +15 -0
  141. package/src/permissions.test.ts +58 -0
  142. package/src/permissions.ts +45 -0
  143. package/src/pikku-request.ts +19 -0
  144. package/src/pikku-response.ts +5 -0
  145. package/src/scheduler/index.ts +11 -0
  146. package/src/scheduler/log-schedulers.ts +20 -0
  147. package/src/scheduler/scheduler-runner.ts +132 -0
  148. package/src/scheduler/scheduler.types.ts +27 -0
  149. package/src/schema.test.ts +57 -0
  150. package/src/schema.ts +124 -0
  151. package/src/services/content-service.ts +69 -0
  152. package/src/services/index.ts +16 -0
  153. package/src/services/jwt-service.ts +36 -0
  154. package/src/services/local-content.ts +39 -0
  155. package/src/services/local-secrets.ts +43 -0
  156. package/src/services/local-variables.ts +17 -0
  157. package/src/services/logger-console.ts +97 -0
  158. package/src/services/logger.ts +57 -0
  159. package/src/services/schema-service.ts +26 -0
  160. package/src/services/secret-service.ts +17 -0
  161. package/src/services/user-session-service.ts +7 -0
  162. package/src/services/variables-service.ts +6 -0
  163. package/src/types/core.types.ts +162 -0
  164. package/src/types/functions.types.ts +44 -0
  165. package/src/utils.ts +21 -0
  166. package/tsconfig.json +13 -0
  167. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,58 @@
1
+ import { test, describe } from 'node:test'
2
+ import assert from 'assert'
3
+ import { getErrorResponse, addError } from './errors/error-handler.js'
4
+ import {
5
+ ForbiddenError,
6
+ BadRequestError,
7
+ NotFoundError,
8
+ } from './errors/errors.js'
9
+
10
+ describe('getErrorResponse', () => {
11
+ test('should return the correct error response for BadRequestError', () => {
12
+ const error = new BadRequestError()
13
+ const response = getErrorResponse(error)
14
+ assert.deepStrictEqual(response, {
15
+ status: 400,
16
+ message:
17
+ 'The server cannot or will not process the request due to client error (e.g., malformed request syntax).',
18
+ })
19
+ })
20
+
21
+ test('should return the correct error response for NotFoundError', () => {
22
+ const error = new NotFoundError()
23
+ const response = getErrorResponse(error)
24
+ assert.deepStrictEqual(response, {
25
+ status: 404,
26
+ message: 'The server cannot find the requested resource.',
27
+ })
28
+ })
29
+
30
+ test('should return the correct error response for ForbiddenError', () => {
31
+ const error = new ForbiddenError()
32
+ const response = getErrorResponse(error)
33
+ assert.deepStrictEqual(response, {
34
+ status: 403,
35
+ message:
36
+ 'The client does not have permission to access the requested resource.',
37
+ })
38
+ })
39
+
40
+ test('should return undefined for an unknown error', () => {
41
+ class UnknownError extends Error {}
42
+ const error = new UnknownError()
43
+ const response = getErrorResponse(error)
44
+ assert.strictEqual(response, undefined)
45
+ })
46
+
47
+ test('should return the correct error response for a custom error added to apiErrors', () => {
48
+ class CustomError extends Error {}
49
+ const customError = new CustomError()
50
+ const customErrorDetails = { status: 400, message: 'Custom Error' }
51
+
52
+ // Add the custom error to the apiErrors map
53
+ addError(CustomError, customErrorDetails)
54
+
55
+ const response = getErrorResponse(customError)
56
+ assert.deepStrictEqual(response, customErrorDetails)
57
+ })
58
+ })
@@ -0,0 +1,45 @@
1
+ import { CoreServices, CoreUserSession } from './types/core.types.js'
2
+ import { CoreAPIPermission } from './types/functions.types.js'
3
+
4
+ /**
5
+ * This function validates permissions by iterating over permission groups and executing the corresponding permission functions. If all functions in at least one group return true, the permission is considered valid.
6
+ * @param services - The core services required for permission validation.
7
+ * @param data - The data to be used in the permission validation functions.
8
+ * @param session - An optional user session for permission validation.
9
+ * @returns A promise that resolves to void.
10
+ */
11
+ export const verifyPermissions = async (
12
+ permissions:
13
+ | Record<string, CoreAPIPermission<any>[] | CoreAPIPermission<any>>
14
+ | undefined,
15
+ services: CoreServices,
16
+ data: any,
17
+ session?: CoreUserSession
18
+ ): Promise<boolean> => {
19
+ if (!permissions) {
20
+ return true
21
+ }
22
+
23
+ let valid = false
24
+ const permissionGroups = Object.values(permissions)
25
+ if (permissionGroups.length === 0) {
26
+ return true
27
+ }
28
+
29
+ for (const funcs of permissionGroups) {
30
+ if (funcs instanceof Array) {
31
+ const permissioned = await Promise.all(
32
+ funcs.map((func) => func(services, data, session))
33
+ )
34
+ if (permissioned.every((result) => result)) {
35
+ valid = true
36
+ }
37
+ } else {
38
+ valid = await funcs(services, data, session)
39
+ }
40
+ if (valid) {
41
+ return true
42
+ }
43
+ }
44
+ return false
45
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Simplest class to represent a pikku request.
3
+ * @template In - The type of the request body.
4
+ * @group RequestResponse
5
+ */
6
+ export abstract class PikkuRequest<In = any> {
7
+ constructor(private data?: In) {}
8
+
9
+ /**
10
+ * Retrieves the data
11
+ * @returns A promise that resolves to an object containing the combined data.
12
+ */
13
+ public async getData(): Promise<In> {
14
+ if (!this.data) {
15
+ throw new Error('Data not found')
16
+ }
17
+ return this.data
18
+ }
19
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Abstract class representing a pikku response.
3
+ * @group RequestResponse
4
+ */
5
+ export abstract class PikkuResponse {}
@@ -0,0 +1,11 @@
1
+ export * from './scheduler.types.js'
2
+
3
+ export {
4
+ runScheduledTask,
5
+ getScheduledTasks,
6
+ addScheduledTask,
7
+ clearScheduledTasks,
8
+ setScheduledTasksMeta,
9
+ } from './scheduler-runner.js'
10
+
11
+ export * from './log-schedulers.js'
@@ -0,0 +1,20 @@
1
+ import { Logger } from '../services/index.js'
2
+ import { getScheduledTasks } from './scheduler-runner.js'
3
+
4
+ /**
5
+ * Logs all the loaded scheduled tasks.
6
+ * @param logger - A logger for logging information.
7
+ */
8
+ export const logSchedulers = (logger: Logger) => {
9
+ const { scheduledTasks } = getScheduledTasks()
10
+ if (scheduledTasks.size === 0) {
11
+ logger.info('No scheduled tasks added')
12
+ return
13
+ }
14
+
15
+ let scheduledTasksMessage = 'Scheduled tasks:'
16
+ scheduledTasks.forEach(({ schedule }, name) => {
17
+ scheduledTasksMessage += `\n\t- ${name} -> ${schedule}`
18
+ })
19
+ logger.info(scheduledTasksMessage)
20
+ }
@@ -0,0 +1,132 @@
1
+ import type {
2
+ CoreServices,
3
+ CoreSingletonServices,
4
+ CoreUserSession,
5
+ CreateSessionServices,
6
+ } from '../types/core.types.js'
7
+ import type {
8
+ CoreScheduledTask,
9
+ ScheduledTasksMeta,
10
+ } from './scheduler.types.js'
11
+ import type { CoreAPIFunctionSessionless } from '../types/functions.types.js'
12
+
13
+ import { getErrorResponse } from '../errors/error-handler.js'
14
+ import { closeSessionServices } from '../utils.js'
15
+
16
+ export type RunScheduledTasksParams = {
17
+ name: string
18
+ session?: CoreUserSession
19
+ singletonServices: CoreSingletonServices
20
+ createSessionServices?: CreateSessionServices<
21
+ CoreSingletonServices,
22
+ CoreServices<CoreSingletonServices>,
23
+ CoreUserSession
24
+ >
25
+ }
26
+
27
+ if (!global.pikku?.scheduledTasks) {
28
+ global.pikku = global.pikku || {}
29
+ global.pikku.scheduledTasks = new Map<string, CoreScheduledTask>()
30
+ global.pikku.scheduledTasksMeta = []
31
+ }
32
+
33
+ const scheduledTasks = (): Map<string, CoreScheduledTask> => {
34
+ return global.pikku.scheduledTasks
35
+ }
36
+
37
+ const scheduledTasksMeta = (data?: ScheduledTasksMeta) => {
38
+ if (data) {
39
+ global.pikku.scheduledTasksMeta = data
40
+ }
41
+ return global.pikku.scheduledTasksMeta
42
+ }
43
+
44
+ export const addScheduledTask = <
45
+ APIFunction extends CoreAPIFunctionSessionless<void, void>,
46
+ >(
47
+ scheduledTask: CoreScheduledTask<APIFunction>
48
+ ) => {
49
+ if (scheduledTasks().has(scheduledTask.name)) {
50
+ throw new Error(`Scheduled task already exists: ${scheduledTask.name}`)
51
+ }
52
+ scheduledTasks().set(scheduledTask.name, scheduledTask)
53
+ }
54
+
55
+ export const clearScheduledTasks = () => {
56
+ scheduledTasks().clear()
57
+ }
58
+
59
+ export const setScheduledTasksMeta = (
60
+ _scheduledTasksMeta: ScheduledTasksMeta
61
+ ) => {
62
+ scheduledTasksMeta(_scheduledTasksMeta)
63
+ }
64
+
65
+ /**
66
+ * Returns all the cron jobs
67
+ * @internal
68
+ */
69
+ export const getScheduledTasks = () => {
70
+ return {
71
+ scheduledTasks: scheduledTasks(),
72
+ scheduledTasksMeta: scheduledTasksMeta(),
73
+ }
74
+ }
75
+
76
+ class ScheduledTaskNotFoundError extends Error {
77
+ constructor(title: string) {
78
+ super(`Scheduled task not found: ${title}`)
79
+ }
80
+ }
81
+
82
+ export async function runScheduledTask<
83
+ SingletonServices extends CoreSingletonServices = CoreSingletonServices,
84
+ UserSession extends CoreUserSession = CoreUserSession,
85
+ Services extends
86
+ CoreServices<SingletonServices> = CoreServices<SingletonServices>,
87
+ >({
88
+ name,
89
+ session,
90
+ singletonServices,
91
+ createSessionServices,
92
+ }: RunScheduledTasksParams): Promise<void> {
93
+ let sessionServices: CoreServices | undefined
94
+ const trackerId: string = crypto.randomUUID().toString()
95
+
96
+ try {
97
+ const task = scheduledTasks().get(name)
98
+
99
+ if (!task) {
100
+ throw new ScheduledTaskNotFoundError(`Scheduled task not found: ${name}`)
101
+ }
102
+
103
+ singletonServices.logger.info(
104
+ `Running schedule task: ${name} | schedule: ${task.schedule}}`
105
+ )
106
+
107
+ let allServices = singletonServices
108
+ if (createSessionServices) {
109
+ const sessionServices = await createSessionServices(
110
+ singletonServices,
111
+ {},
112
+ session
113
+ )
114
+ allServices = { ...singletonServices, ...sessionServices }
115
+ }
116
+
117
+ await task.func(allServices, undefined, session!)
118
+ } catch (e: any) {
119
+ const errorResponse = getErrorResponse(e)
120
+
121
+ if (errorResponse != null) {
122
+ singletonServices.logger.warn(`Error id: ${trackerId}`)
123
+ singletonServices.logger.error(e)
124
+ }
125
+
126
+ throw e
127
+ } finally {
128
+ if (sessionServices) {
129
+ await closeSessionServices(singletonServices.logger, sessionServices)
130
+ }
131
+ }
132
+ }
@@ -0,0 +1,27 @@
1
+ import { APIDocs, CoreUserSession } from '../types/core.types.js'
2
+ import { CoreAPIFunctionSessionless } from '../types/functions.types.js'
3
+
4
+ /**
5
+ * Represents metadata for scheduled tasks, including title, schedule, and documentation.
6
+ */
7
+ export type ScheduledTasksMeta<UserSession extends CoreUserSession = any> =
8
+ Array<{
9
+ name: string
10
+ schedule: string
11
+ session?: UserSession
12
+ docs?: APIDocs
13
+ }>
14
+
15
+ /**
16
+ * Represents a core scheduled task.
17
+ */
18
+ export type CoreScheduledTask<
19
+ APIFunction = CoreAPIFunctionSessionless<void, void>,
20
+ UserSession extends CoreUserSession = CoreUserSession,
21
+ > = {
22
+ name: string
23
+ schedule: string
24
+ func: APIFunction
25
+ docs?: APIDocs
26
+ session?: UserSession
27
+ }
@@ -0,0 +1,57 @@
1
+ import { test, describe, before } from 'node:test'
2
+ import * as assert from 'assert'
3
+ import { addSchema, coerceQueryStringToArray } from './schema.js'
4
+
5
+ describe('Schema', () => {
6
+ describe('coerceQueryStringToArray', () => {
7
+ before(() => {
8
+ // Add schemas dynamically using addSchema
9
+ addSchema('testSchema', {
10
+ properties: {
11
+ tags: { type: 'array' },
12
+ count: { type: 'number' },
13
+ name: { type: 'string' },
14
+ },
15
+ })
16
+
17
+ addSchema('booleanSchema', {
18
+ properties: {
19
+ isActive: true, // Invalid schema definition, but the function should handle it
20
+ tags: { type: 'array' },
21
+ },
22
+ })
23
+ })
24
+
25
+ test('should split a string into an array for properties of type array', () => {
26
+ const data = { tags: 'a,b,c' }
27
+ coerceQueryStringToArray('testSchema', data)
28
+ assert.deepStrictEqual(data.tags, ['a', 'b', 'c'])
29
+ })
30
+
31
+ test('should not modify properties of type array if they are already arrays', () => {
32
+ const data = { tags: ['a', 'b', 'c'] }
33
+ coerceQueryStringToArray('testSchema', data)
34
+ assert.deepStrictEqual(data.tags, ['a', 'b', 'c'])
35
+ })
36
+
37
+ test('should not modify properties that are not type array', () => {
38
+ const data = { count: 5, name: 'example' }
39
+ coerceQueryStringToArray('testSchema', data)
40
+ assert.strictEqual(data.count, 5)
41
+ assert.strictEqual(data.name, 'example')
42
+ })
43
+
44
+ test('should handle cases where the data object does not have a key present in the schema', () => {
45
+ const data = { unknownKey: 'shouldRemain' }
46
+ coerceQueryStringToArray('testSchema', data)
47
+ assert.strictEqual(data.unknownKey, 'shouldRemain')
48
+ })
49
+
50
+ test('should handle cases where schema properties contain boolean values', () => {
51
+ const data = { tags: 'a,b,c', isActive: 'true' }
52
+ coerceQueryStringToArray('booleanSchema', data)
53
+ assert.deepStrictEqual(data.tags, ['a', 'b', 'c'])
54
+ assert.strictEqual(data.isActive, 'true') // No coercion should happen
55
+ })
56
+ })
57
+ })
package/src/schema.ts ADDED
@@ -0,0 +1,124 @@
1
+ import { Logger } from './services/logger.js'
2
+ import { getRoutes } from './http/http-route-runner.js'
3
+ import { SchemaService } from './services/schema-service.js'
4
+ import { BadRequestError } from './errors/errors.js'
5
+
6
+ /**
7
+ * Retrieves the global schemas map.
8
+ * @returns A map of schemas.
9
+ */
10
+ export const getSchemas = () => {
11
+ if (!global.pikkuSchemas) {
12
+ global.pikkuSchemas = new Map<string, any>()
13
+ }
14
+ return global.pikkuSchemas
15
+ }
16
+
17
+ /**
18
+ * Retrieves a schema from the schemas map.
19
+ * @returns A schema.
20
+ */
21
+ export const getSchema = (schemaName: string): any => {
22
+ const schemas = getSchemas()
23
+ const schema = schemas.get(schemaName)
24
+ if (!schema) {
25
+ throw new Error(`Schema not found: ${schemaName}`)
26
+ }
27
+ return schema
28
+ }
29
+
30
+ /**
31
+ * Adds a schema to the global schemas map.
32
+ * @param name - The name of the schema.
33
+ * @param value - The schema value.
34
+ * @ignore
35
+ */
36
+ export const addSchema = (name: string, value: any) => {
37
+ getSchemas().set(name, value)
38
+ }
39
+
40
+ /**
41
+ * Loads a schema and compiles it into a validator.
42
+ * @param logger - A logger for logging information.
43
+ */
44
+ export const compileAllSchemas = (
45
+ logger: Logger,
46
+ schemaService?: SchemaService
47
+ ) => {
48
+ if (!schemaService) {
49
+ throw new Error('SchemaService needs to be defined to load schemas')
50
+ }
51
+ for (const [name, schema] of getSchemas()) {
52
+ schemaService.compileSchema(name, schema)
53
+ }
54
+ validateAllSchemasLoaded(logger, schemaService)
55
+ }
56
+
57
+ const validateAllSchemasLoaded = (
58
+ logger: Logger,
59
+ schemaService: SchemaService
60
+ ) => {
61
+ const { routesMeta } = getRoutes()
62
+ const validators = schemaService.getSchemaNames()
63
+
64
+ const missingSchemas: string[] = []
65
+
66
+ for (const route of routesMeta) {
67
+ if (!route.input || validators.has(route.input)) {
68
+ continue
69
+ }
70
+ missingSchemas.push(route.input)
71
+ }
72
+
73
+ if (missingSchemas.length > 0) {
74
+ logger.error(
75
+ `Error: Failed to load schemas:\n.${missingSchemas.join('\n')}`
76
+ )
77
+ logger.error('\tHave you run the schema generation?')
78
+ logger.error('\tnpx @pikku/cli schemas')
79
+ } else {
80
+ logger.info('All schemas loaded')
81
+ }
82
+ }
83
+
84
+ export const coerceQueryStringToArray = (schemaName: string, data: any) => {
85
+ const schema = getSchema(schemaName)
86
+ for (const key in schema.properties) {
87
+ const property = schema.properties[key]
88
+ if (typeof property === 'boolean') {
89
+ continue
90
+ }
91
+ const type = property.type
92
+ if (typeof type === 'boolean') {
93
+ continue
94
+ }
95
+ if (type === 'array' && typeof data[key] === 'string') {
96
+ data[key] = data[key].split(',')
97
+ }
98
+ }
99
+ }
100
+
101
+ export const validateAndCoerce = (
102
+ logger: Logger,
103
+ schemaService: SchemaService | undefined,
104
+ schemaName: string | undefined | null,
105
+ data: any,
106
+ coerceToArray: boolean
107
+ ) => {
108
+ if (schemaService) {
109
+ if (!schemaName) {
110
+ if (data && (data.length > 0 || Object.keys(data).length > 0)) {
111
+ logger.warn('No schema provided, but data was passed')
112
+ throw new BadRequestError('No data expected')
113
+ } else {
114
+ return
115
+ }
116
+ }
117
+ const schema = getSchema(schemaName)
118
+ schemaService.compileSchema(schemaName, schema)
119
+ if (coerceToArray) {
120
+ coerceQueryStringToArray(schemaName, data)
121
+ }
122
+ schemaService.validateSchema(schemaName, data)
123
+ }
124
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Interface for handling content operations.
3
+ */
4
+ export interface ContentService {
5
+ /**
6
+ * Signs a content key to provide secure access.
7
+ * @param contentKey - The key of the content to sign.
8
+ * @returns A promise that resolves to the signed content key URL.
9
+ */
10
+ signContentKey: (
11
+ contentKey: string,
12
+ dateLessThan: Date,
13
+ dateGreaterThan?: Date
14
+ ) => Promise<string>
15
+
16
+ /**
17
+ * Signs a URL to provide secure access.
18
+ * @param url - The URL to sign.
19
+ * @returns A promise that resolves to the signed URL.
20
+ */
21
+ signURL: (
22
+ url: string,
23
+ dateLessThan: Date,
24
+ dateGreaterThan?: Date
25
+ ) => Promise<string>
26
+
27
+ /**
28
+ * Gets an upload URL for a file.
29
+ * @param fileKey - The key of the file to upload.
30
+ * @param contentType - The content type of the file.
31
+ * @returns A promise that resolves to an object containing the upload URL and asset key.
32
+ * This method generates an upload URL for the specified file key and content type, allowing the file to be uploaded.
33
+ */
34
+ getUploadURL: (
35
+ fileKey: string,
36
+ contentType: string
37
+ ) => Promise<{ uploadUrl: string; assetKey: string }>
38
+
39
+ /**
40
+ * Deletes a file.
41
+ * @param fileName - The name of the file to delete.
42
+ * @returns A promise that resolves to a boolean indicating success.
43
+ * This method deletes the specified file and returns a boolean indicating whether the deletion was successful.
44
+ */
45
+ deleteFile?: (fileName: string) => Promise<boolean>
46
+
47
+ /**
48
+ * Writes a file.
49
+ * @param assetKey - The key of the asset to write.
50
+ * @param buffer - The buffer containing the file data.
51
+ * @returns A promise that resolves to a boolean indicating success.
52
+ */
53
+ writeFile?: (assetKey: string, buffer: Buffer) => Promise<boolean>
54
+
55
+ /**
56
+ * This method copies the file from the specified absolute path to the location identified by the asset key and returns a boolean indicating success.
57
+ * @param assetKey - The key of the asset to copy.
58
+ * @param fromAbsolutePath - The absolute path of the source file.
59
+ * @returns A promise that resolves to a boolean indicating success.
60
+ */
61
+ copyFile?: (assetKey: string, fromAbsolutePath: string) => Promise<boolean>
62
+
63
+ /**
64
+ * Reads a file.
65
+ * @param assetKey - The key of the asset to read.
66
+ * @returns A promise that resolves to a buffer containing the file data.
67
+ */
68
+ readFile?: (assetKey: string) => Promise<Buffer>
69
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @module @pikku/core
3
+ */
4
+
5
+ // Interfaces
6
+ export * from './content-service.js'
7
+ export * from './jwt-service.js'
8
+ export * from './logger.js'
9
+ export * from './logger-console.js'
10
+ export * from './secret-service.js'
11
+ export * from './variables-service.js'
12
+ export * from './schema-service.js'
13
+
14
+ // Local implementations
15
+ export * from './local-secrets.js'
16
+ export * from './local-variables.js'
@@ -0,0 +1,36 @@
1
+ import { CoreUserSession } from '../types/core.types.js'
2
+
3
+ /**
4
+ * Interface for handling JSON Web Tokens (JWT).
5
+ * @template UserSession - The type of the user session.
6
+ */
7
+ export interface JWTService<UserSession = CoreUserSession> {
8
+ /**
9
+ * Encodes a payload into a JWT.
10
+ * @param expiresIn - The expiration time of the token.
11
+ * @param payload - The payload to encode.
12
+ * @returns A promise that resolves to the encoded JWT.
13
+ */
14
+ encode: <T extends any>(expiresIn: string, payload: T) => Promise<string>
15
+
16
+ /**
17
+ * Decodes a JWT into its payload.
18
+ * @param hash - The JWT to decode.
19
+ * @param invalidHashError - An optional error to throw if the hash is invalid.
20
+ * @param debug - An optional flag for debugging.
21
+ * @returns A promise that resolves to the decoded payload.
22
+ */
23
+ decode: <T>(
24
+ hash: string,
25
+ invalidHashError?: Error,
26
+ debug?: boolean
27
+ ) => Promise<T>
28
+
29
+ /**
30
+ * Decodes a user session from a JWT.
31
+ * @param jwtToken - The JWT representing the user session.
32
+ * @param debug - An optional flag for debugging.
33
+ * @returns A promise that resolves to the decoded user session.
34
+ */
35
+ decodeSession: (jwtToken: string, debug?: any) => Promise<UserSession>
36
+ }
@@ -0,0 +1,39 @@
1
+ import { ContentService } from './content-service.js'
2
+ import { Logger } from './logger.js'
3
+
4
+ export interface LocalContentConfig {
5
+ contentDirectory: string
6
+ assetsUrl: string
7
+ uploadUrl: string
8
+ }
9
+
10
+ /**
11
+ * Service for handling local content operations.
12
+ */
13
+ export abstract class LocalContent implements ContentService {
14
+ constructor(_config: LocalContentConfig, _logger: Logger) {}
15
+
16
+ public abstract getUploadURL: (
17
+ fileKey: string,
18
+ contentType: string
19
+ ) => Promise<{ uploadUrl: string; assetKey: string }>
20
+
21
+ /**
22
+ * Initializes the local content service.
23
+ */
24
+ public abstract init(): Promise<void>
25
+
26
+ /**
27
+ * Signs a URL to provide secure access.
28
+ * @param url - The URL to sign.
29
+ * @returns A promise that resolves to the signed URL.
30
+ */
31
+ public abstract signURL(url: string): Promise<string>
32
+
33
+ /**
34
+ * Signs a content key to provide secure access to an asset.
35
+ * @param assetKey - The key of the asset to sign.
36
+ * @returns A promise that resolves to the signed content key URL.
37
+ */
38
+ public abstract signContentKey(assetKey: string): Promise<string>
39
+ }