@jskit-ai/users-core 0.1.56 → 0.1.58

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 (47) hide show
  1. package/package.descriptor.mjs +175 -6
  2. package/package.json +6 -6
  3. package/src/server/accountNotifications/accountNotificationsService.js +3 -3
  4. package/src/server/accountNotifications/registerAccountNotifications.js +2 -2
  5. package/src/server/accountPreferences/accountPreferencesService.js +3 -3
  6. package/src/server/accountPreferences/registerAccountPreferences.js +2 -2
  7. package/src/server/accountProfile/accountProfileService.js +5 -5
  8. package/src/server/accountProfile/avatarService.js +6 -6
  9. package/src/server/accountProfile/registerAccountProfile.js +3 -3
  10. package/src/server/accountSecurity/accountSecurityService.js +3 -3
  11. package/src/server/accountSecurity/registerAccountSecurity.js +2 -2
  12. package/src/server/common/registerCommonRepositories.js +5 -5
  13. package/src/server/common/repositories/{usersRepository.js → userProfilesRepository.js} +117 -95
  14. package/src/server/common/repositories/userSettingsRepository.js +12 -11
  15. package/src/server/common/resources/userProfilesResource.js +203 -0
  16. package/src/server/common/services/accountContextService.js +4 -4
  17. package/src/server/common/services/authProfileSyncService.js +11 -11
  18. package/src/server/common/support/identity.js +17 -0
  19. package/src/server/registerUsersBootstrap.js +2 -2
  20. package/src/server/registerUsersCore.js +2 -2
  21. package/src/server/usersBootstrapContributor.js +5 -5
  22. package/src/shared/resources/userProfileResource.js +1 -1
  23. package/src/shared/resources/userSettingsFields.js +10 -4
  24. package/src/shared/resources/userSettingsResource.js +1 -1
  25. package/templates/packages/main/src/shared/resources/userSettingsFields.js +10 -10
  26. package/templates/packages/users/package.descriptor.mjs +65 -0
  27. package/templates/packages/users/package.json +10 -0
  28. package/templates/packages/users/src/server/UsersProvider.js +91 -0
  29. package/templates/packages/users/src/server/actionIds.js +6 -0
  30. package/templates/packages/users/src/server/actions.js +73 -0
  31. package/templates/packages/users/src/server/listConfig.js +16 -0
  32. package/templates/packages/users/src/server/registerRoutes.js +87 -0
  33. package/templates/packages/users/src/server/repository.js +41 -0
  34. package/templates/packages/users/src/server/service.js +28 -0
  35. package/templates/packages/users/src/shared/index.js +1 -0
  36. package/templates/packages/users/src/shared/userResource.js +74 -0
  37. package/templates/packages/users-workspace/package.descriptor.mjs +66 -0
  38. package/templates/packages/users-workspace/src/server/UsersProvider.js +92 -0
  39. package/templates/packages/users-workspace/src/server/actions.js +74 -0
  40. package/templates/packages/users-workspace/src/server/registerRoutes.js +93 -0
  41. package/test/authProfileSyncService.test.js +3 -3
  42. package/test/avatarService.test.js +2 -2
  43. package/test/registerCommonRepositories.test.js +37 -0
  44. package/test/repositoryContracts.test.js +48 -5
  45. package/test/usersBootstrapContributor.test.js +2 -2
  46. package/test/usersPackageScaffoldContract.test.js +98 -0
  47. package/test/usersRouteResources.test.js +1 -1
@@ -8,8 +8,8 @@ function registerUsersCore(app) {
8
8
 
9
9
  app.singleton("users.profile.sync.service", (scope) => {
10
10
  return createAuthProfileSyncService({
11
- usersRepository: scope.make("usersRepository"),
12
- userSettingsRepository: scope.make("userSettingsRepository"),
11
+ userProfilesRepository: scope.make("internal.repository.user-profiles"),
12
+ userSettingsRepository: scope.make("internal.repository.user-settings"),
13
13
  lifecycleContributors: resolveProfileSyncLifecycleContributors(scope)
14
14
  });
15
15
  });
@@ -97,7 +97,7 @@ function mapUserSettingsBootstrap(settings = {}) {
97
97
  }
98
98
 
99
99
  function createUsersBootstrapContributor({
100
- usersRepository,
100
+ userProfilesRepository,
101
101
  userSettingsRepository,
102
102
  appConfig = {},
103
103
  authService
@@ -105,11 +105,11 @@ function createUsersBootstrapContributor({
105
105
  const contributorId = "users.bootstrap";
106
106
  const appState = resolveAppState(appConfig);
107
107
 
108
- requireServiceMethod(usersRepository, "findById", contributorId, {
109
- serviceLabel: "usersRepository"
108
+ requireServiceMethod(userProfilesRepository, "findById", contributorId, {
109
+ serviceLabel: "internal.repository.user-profiles"
110
110
  });
111
111
  requireServiceMethod(userSettingsRepository, "ensureForUserId", contributorId, {
112
- serviceLabel: "userSettingsRepository"
112
+ serviceLabel: "internal.repository.user-settings"
113
113
  });
114
114
 
115
115
  return Object.freeze({
@@ -138,7 +138,7 @@ function createUsersBootstrapContributor({
138
138
  });
139
139
 
140
140
  if (normalizedUser) {
141
- const latestProfile = (await usersRepository.findById(normalizedUser.id)) || normalizedUser;
141
+ const latestProfile = (await userProfilesRepository.findById(normalizedUser.id)) || normalizedUser;
142
142
  const userSettings = await userSettingsRepository.ensureForUserId(latestProfile.id);
143
143
 
144
144
  payload = {
@@ -91,7 +91,7 @@ const avatarOperationOutputValidator = Object.freeze({
91
91
  const USER_PROFILE_OPERATION_MESSAGES = createOperationMessages();
92
92
 
93
93
  const userProfileResource = Object.freeze({
94
- resource: "userProfile",
94
+ namespace: "userProfile",
95
95
  operations: Object.freeze({
96
96
  view: Object.freeze({
97
97
  method: "GET",
@@ -24,9 +24,13 @@ function defineField(field = {}) {
24
24
  if (!field.outputSchema || typeof field.outputSchema !== "object") {
25
25
  throw new TypeError(`userSettingsFields.defineField("${key}") requires outputSchema.`);
26
26
  }
27
- const dbColumn = normalizeText(field.dbColumn);
28
- if (!dbColumn) {
29
- throw new TypeError(`userSettingsFields.defineField("${key}") requires dbColumn.`);
27
+ const repository = field?.repository;
28
+ if (!repository || typeof repository !== "object" || Array.isArray(repository)) {
29
+ throw new TypeError(`userSettingsFields.defineField("${key}") requires repository.column.`);
30
+ }
31
+ const repositoryColumn = normalizeText(repository.column);
32
+ if (!repositoryColumn) {
33
+ throw new TypeError(`userSettingsFields.defineField("${key}") requires repository.column.`);
30
34
  }
31
35
  const section = normalizeLowerText(field.section);
32
36
  if (!USER_SETTINGS_SECTION_VALUES.includes(section)) {
@@ -47,7 +51,9 @@ function defineField(field = {}) {
47
51
  userSettingsFields.push({
48
52
  key,
49
53
  section,
50
- dbColumn,
54
+ repository: Object.freeze({
55
+ column: repositoryColumn
56
+ }),
51
57
  required: field.required !== false,
52
58
  includeInBootstrap: field.includeInBootstrap !== false,
53
59
  inputSchema: field.inputSchema,
@@ -311,7 +311,7 @@ const emptyBodyValidator = Object.freeze({
311
311
  const USER_SETTINGS_OPERATION_MESSAGES = createOperationMessages();
312
312
 
313
313
  const userSettingsResource = Object.freeze({
314
- resource: "userSettings",
314
+ namespace: "userSettings",
315
315
  operations: Object.freeze({
316
316
  view: Object.freeze({
317
317
  method: "GET",
@@ -20,7 +20,7 @@ resetUserSettingsFields();
20
20
  defineField({
21
21
  key: "theme",
22
22
  section: USER_SETTINGS_SECTIONS.PREFERENCES,
23
- dbColumn: "theme",
23
+ repository: { column: "theme" },
24
24
  required: true,
25
25
  inputSchema: Type.String({ minLength: 1, maxLength: 32 }),
26
26
  outputSchema: Type.String({ minLength: 1, maxLength: 32 }),
@@ -32,7 +32,7 @@ defineField({
32
32
  defineField({
33
33
  key: "locale",
34
34
  section: USER_SETTINGS_SECTIONS.PREFERENCES,
35
- dbColumn: "locale",
35
+ repository: { column: "locale" },
36
36
  required: true,
37
37
  inputSchema: Type.String({ minLength: 1, maxLength: 24 }),
38
38
  outputSchema: Type.String({ minLength: 1, maxLength: 24 }),
@@ -44,7 +44,7 @@ defineField({
44
44
  defineField({
45
45
  key: "timeZone",
46
46
  section: USER_SETTINGS_SECTIONS.PREFERENCES,
47
- dbColumn: "time_zone",
47
+ repository: { column: "time_zone" },
48
48
  required: true,
49
49
  inputSchema: Type.String({ minLength: 1, maxLength: 64 }),
50
50
  outputSchema: Type.String({ minLength: 1, maxLength: 64 }),
@@ -56,7 +56,7 @@ defineField({
56
56
  defineField({
57
57
  key: "dateFormat",
58
58
  section: USER_SETTINGS_SECTIONS.PREFERENCES,
59
- dbColumn: "date_format",
59
+ repository: { column: "date_format" },
60
60
  required: true,
61
61
  inputSchema: Type.String({ minLength: 1, maxLength: 32 }),
62
62
  outputSchema: Type.String({ minLength: 1, maxLength: 32 }),
@@ -68,7 +68,7 @@ defineField({
68
68
  defineField({
69
69
  key: "numberFormat",
70
70
  section: USER_SETTINGS_SECTIONS.PREFERENCES,
71
- dbColumn: "number_format",
71
+ repository: { column: "number_format" },
72
72
  required: true,
73
73
  inputSchema: Type.String({ minLength: 1, maxLength: 32 }),
74
74
  outputSchema: Type.String({ minLength: 1, maxLength: 32 }),
@@ -80,7 +80,7 @@ defineField({
80
80
  defineField({
81
81
  key: "currencyCode",
82
82
  section: USER_SETTINGS_SECTIONS.PREFERENCES,
83
- dbColumn: "currency_code",
83
+ repository: { column: "currency_code" },
84
84
  required: true,
85
85
  inputSchema: Type.String({ minLength: 3, maxLength: 3, pattern: "^[A-Za-z]{3}$" }),
86
86
  outputSchema: Type.String({ minLength: 3, maxLength: 3, pattern: "^[A-Z]{3}$" }),
@@ -92,7 +92,7 @@ defineField({
92
92
  defineField({
93
93
  key: "avatarSize",
94
94
  section: USER_SETTINGS_SECTIONS.PREFERENCES,
95
- dbColumn: "avatar_size",
95
+ repository: { column: "avatar_size" },
96
96
  required: true,
97
97
  inputSchema: Type.Integer({ minimum: 1 }),
98
98
  outputSchema: Type.Integer({ minimum: 1 }),
@@ -104,7 +104,7 @@ defineField({
104
104
  defineField({
105
105
  key: "productUpdates",
106
106
  section: USER_SETTINGS_SECTIONS.NOTIFICATIONS,
107
- dbColumn: "notify_product_updates",
107
+ repository: { column: "notify_product_updates" },
108
108
  required: true,
109
109
  inputSchema: Type.Boolean(),
110
110
  outputSchema: Type.Boolean(),
@@ -116,7 +116,7 @@ defineField({
116
116
  defineField({
117
117
  key: "accountActivity",
118
118
  section: USER_SETTINGS_SECTIONS.NOTIFICATIONS,
119
- dbColumn: "notify_account_activity",
119
+ repository: { column: "notify_account_activity" },
120
120
  required: true,
121
121
  inputSchema: Type.Boolean(),
122
122
  outputSchema: Type.Boolean(),
@@ -128,7 +128,7 @@ defineField({
128
128
  defineField({
129
129
  key: "securityAlerts",
130
130
  section: USER_SETTINGS_SECTIONS.NOTIFICATIONS,
131
- dbColumn: "notify_security_alerts",
131
+ repository: { column: "notify_security_alerts" },
132
132
  required: true,
133
133
  inputSchema: Type.Boolean(),
134
134
  outputSchema: Type.Boolean(),
@@ -0,0 +1,65 @@
1
+ export default Object.freeze({
2
+ packageVersion: 1,
3
+ packageId: "@local/users",
4
+ version: "0.1.0",
5
+ kind: "runtime",
6
+ description: "App-local CRUD package (users).",
7
+ dependsOn: [
8
+ "@jskit-ai/auth-core",
9
+ "@jskit-ai/crud-core",
10
+ "@jskit-ai/database-runtime",
11
+ "@jskit-ai/http-runtime",
12
+ "@jskit-ai/users-core"
13
+ ],
14
+ capabilities: {
15
+ provides: [
16
+ "crud.users"
17
+ ],
18
+ requires: [
19
+ "runtime.actions",
20
+ "runtime.database",
21
+ "auth.policy"
22
+ ]
23
+ },
24
+ runtime: {
25
+ server: {
26
+ providers: [
27
+ {
28
+ entrypoint: "src/server/UsersProvider.js",
29
+ export: "UsersProvider"
30
+ }
31
+ ]
32
+ }
33
+ },
34
+ metadata: {
35
+ apiSummary: {
36
+ surfaces: [
37
+ {
38
+ subpath: "./server/actionIds",
39
+ summary: "App-local CRUD public action identifiers."
40
+ },
41
+ {
42
+ subpath: "./shared",
43
+ summary: "App-local CRUD shared resource."
44
+ }
45
+ ],
46
+ containerTokens: {
47
+ server: [
48
+ "repository.users",
49
+ "crud.users"
50
+ ]
51
+ }
52
+ }
53
+ },
54
+ mutations: {
55
+ dependencies: {
56
+ runtime: {},
57
+ dev: {}
58
+ },
59
+ packageJson: {
60
+ scripts: {}
61
+ },
62
+ procfile: {},
63
+ files: []
64
+ }
65
+ });
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "@local/users",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "exports": {
7
+ "./server/actionIds": "./src/server/actionIds.js",
8
+ "./shared": "./src/shared/index.js"
9
+ }
10
+ }
@@ -0,0 +1,91 @@
1
+ import { resolveAppConfig } from "@jskit-ai/kernel/server/support";
2
+ import { resolveCrudSurfacePolicyFromAppConfig } from "@jskit-ai/crud-core/server/crudModuleConfig";
3
+ import {
4
+ createCrudLookupResolver,
5
+ createCrudLookup
6
+ } from "@jskit-ai/crud-core/server/lookups";
7
+ import { withActionDefaults } from "@jskit-ai/kernel/shared/actions";
8
+ import { createRepository } from "./repository.js";
9
+ import {
10
+ createService,
11
+ serviceEvents
12
+ } from "./service.js";
13
+ import { createActions } from "./actions.js";
14
+ import { registerRoutes } from "./registerRoutes.js";
15
+
16
+ const CRUD_MODULE_CONFIG = Object.freeze({
17
+ namespace: "users",
18
+ surface: "home",
19
+ ownershipFilter: "public",
20
+ relativePath: "/users"
21
+ });
22
+
23
+ function resolveCrudPolicyFromApp(app) {
24
+ return resolveCrudSurfacePolicyFromAppConfig(CRUD_MODULE_CONFIG, resolveAppConfig(app), {
25
+ context: "UsersProvider"
26
+ });
27
+ }
28
+
29
+ class UsersProvider {
30
+ static id = "crud.users";
31
+
32
+ static dependsOn = ["runtime.actions", "runtime.database", "auth.policy.fastify", "local.main"];
33
+
34
+ register(app) {
35
+ if (!app || typeof app.singleton !== "function" || typeof app.service !== "function" || typeof app.actions !== "function") {
36
+ throw new Error("UsersProvider requires application singleton()/service()/actions().");
37
+ }
38
+
39
+ const crudPolicy = resolveCrudPolicyFromApp(app);
40
+
41
+ app.singleton("repository.users", (scope) => {
42
+ const knex = scope.make("jskit.database.knex");
43
+ return createRepository(knex, {
44
+ resolveLookup: createCrudLookupResolver(scope)
45
+ });
46
+ });
47
+
48
+ app.singleton("lookup.users", (scope) => {
49
+ return createCrudLookup(scope.make("repository.users"), {
50
+ ownershipFilter: crudPolicy.ownershipFilter
51
+ });
52
+ });
53
+
54
+ app.service(
55
+ "crud.users",
56
+ (scope) => {
57
+ return createService({
58
+ usersRepository: scope.make("repository.users")
59
+ });
60
+ },
61
+ {
62
+ events: serviceEvents
63
+ }
64
+ );
65
+
66
+ app.actions(
67
+ withActionDefaults(
68
+ createActions({
69
+ surface: crudPolicy.surfaceId
70
+ }),
71
+ {
72
+ domain: "crud",
73
+ dependencies: {
74
+ usersService: "crud.users"
75
+ }
76
+ }
77
+ )
78
+ );
79
+ }
80
+
81
+ boot(app) {
82
+ const crudPolicy = resolveCrudPolicyFromApp(app);
83
+ registerRoutes(app, {
84
+ routeOwnershipFilter: crudPolicy.ownershipFilter,
85
+ routeSurface: crudPolicy.surfaceId,
86
+ routeRelativePath: crudPolicy.relativePath
87
+ });
88
+ }
89
+ }
90
+
91
+ export { UsersProvider };
@@ -0,0 +1,6 @@
1
+ const actionIds = Object.freeze({
2
+ list: "crud.users.list",
3
+ view: "crud.users.view"
4
+ });
5
+
6
+ export { actionIds };
@@ -0,0 +1,73 @@
1
+ import { recordIdParamsValidator } from "@jskit-ai/kernel/shared/validators";
2
+ import {
3
+ createCrudCursorPaginationQueryValidator,
4
+ listSearchQueryValidator
5
+ } from "@jskit-ai/crud-core/server/listQueryValidators";
6
+ import { resource } from "../shared/userResource.js";
7
+ import { actionIds } from "./actionIds.js";
8
+ import { LIST_CONFIG } from "./listConfig.js";
9
+
10
+ const listCursorPaginationQueryValidator = createCrudCursorPaginationQueryValidator(LIST_CONFIG);
11
+ const authenticatedPermission = Object.freeze({
12
+ require: "authenticated"
13
+ });
14
+
15
+ function requireActionSurface(surface = "") {
16
+ const normalizedSurface = String(surface || "").trim().toLowerCase();
17
+ if (!normalizedSurface) {
18
+ throw new TypeError("createActions requires a non-empty surface.");
19
+ }
20
+
21
+ return normalizedSurface;
22
+ }
23
+
24
+ function createActions({ surface = "" } = {}) {
25
+ const actionSurface = requireActionSurface(surface);
26
+
27
+ return Object.freeze([
28
+ {
29
+ id: actionIds.list,
30
+ version: 1,
31
+ kind: "query",
32
+ channels: ["api", "automation", "internal"],
33
+ surfaces: [actionSurface],
34
+ permission: authenticatedPermission,
35
+ inputValidator: [listCursorPaginationQueryValidator, listSearchQueryValidator],
36
+ outputValidator: resource.operations.list.outputValidator,
37
+ idempotency: "none",
38
+ audit: {
39
+ actionName: actionIds.list
40
+ },
41
+ observability: {},
42
+ async execute(input, context, deps) {
43
+ return deps.usersService.listRecords(input, {
44
+ context,
45
+ visibilityContext: context?.visibilityContext
46
+ });
47
+ }
48
+ },
49
+ {
50
+ id: actionIds.view,
51
+ version: 1,
52
+ kind: "query",
53
+ channels: ["api", "automation", "internal"],
54
+ surfaces: [actionSurface],
55
+ permission: authenticatedPermission,
56
+ inputValidator: recordIdParamsValidator,
57
+ outputValidator: resource.operations.view.outputValidator,
58
+ idempotency: "none",
59
+ audit: {
60
+ actionName: actionIds.view
61
+ },
62
+ observability: {},
63
+ async execute(input, context, deps) {
64
+ return deps.usersService.getRecord(input.recordId, {
65
+ context,
66
+ visibilityContext: context?.visibilityContext
67
+ });
68
+ }
69
+ }
70
+ ]);
71
+ }
72
+
73
+ export { createActions };
@@ -0,0 +1,16 @@
1
+ const LIST_CONFIG = Object.freeze({
2
+ searchColumns: ["display_name", "email", "username"],
3
+ orderBy: [
4
+ {
5
+ column: "display_name",
6
+ direction: "asc",
7
+ nulls: "last"
8
+ },
9
+ {
10
+ column: "email",
11
+ direction: "asc"
12
+ }
13
+ ]
14
+ });
15
+
16
+ export { LIST_CONFIG };
@@ -0,0 +1,87 @@
1
+ import { withStandardErrorResponses } from "@jskit-ai/http-runtime/shared/validators/errorResponses";
2
+ import { normalizeSurfaceId } from "@jskit-ai/kernel/shared/surface/registry";
3
+ import {
4
+ createCrudCursorPaginationQueryValidator,
5
+ listSearchQueryValidator
6
+ } from "@jskit-ai/crud-core/server/listQueryValidators";
7
+ import { resolveScopedApiBasePath } from "@jskit-ai/kernel/shared/surface";
8
+ import { checkRouteVisibility } from "@jskit-ai/kernel/shared/support/visibility";
9
+ import { recordIdParamsValidator } from "@jskit-ai/kernel/shared/validators";
10
+ import { actionIds } from "./actionIds.js";
11
+ import { LIST_CONFIG } from "./listConfig.js";
12
+ import { resource } from "../shared/userResource.js";
13
+
14
+ const listCursorPaginationQueryValidator = createCrudCursorPaginationQueryValidator(LIST_CONFIG);
15
+
16
+ function registerRoutes(
17
+ app,
18
+ {
19
+ routeOwnershipFilter = "public",
20
+ routeSurface = "",
21
+ routeRelativePath = ""
22
+ } = {}
23
+ ) {
24
+ const router = app.make("jskit.http.router");
25
+ const normalizedRouteSurface = normalizeSurfaceId(routeSurface);
26
+ const routeBase = resolveScopedApiBasePath({
27
+ routeBase: "/",
28
+ relativePath: routeRelativePath,
29
+ strictParams: false
30
+ });
31
+
32
+ router.register(
33
+ "GET",
34
+ routeBase,
35
+ {
36
+ auth: "required",
37
+ surface: normalizedRouteSurface,
38
+ visibility: checkRouteVisibility(routeOwnershipFilter),
39
+ meta: {
40
+ tags: ["crud"],
41
+ summary: "List users."
42
+ },
43
+ queryValidator: [listCursorPaginationQueryValidator, listSearchQueryValidator],
44
+ responseValidators: withStandardErrorResponses({
45
+ 200: resource.operations.list.outputValidator
46
+ })
47
+ },
48
+ async function (request, reply) {
49
+ const response = await request.executeAction({
50
+ actionId: actionIds.list,
51
+ input: {
52
+ ...(request.input.query || {})
53
+ }
54
+ });
55
+ reply.code(200).send(response);
56
+ }
57
+ );
58
+
59
+ router.register(
60
+ "GET",
61
+ `${routeBase}/:recordId`,
62
+ {
63
+ auth: "required",
64
+ surface: normalizedRouteSurface,
65
+ visibility: checkRouteVisibility(routeOwnershipFilter),
66
+ meta: {
67
+ tags: ["crud"],
68
+ summary: "View a user."
69
+ },
70
+ paramsValidator: recordIdParamsValidator,
71
+ responseValidators: withStandardErrorResponses({
72
+ 200: resource.operations.view.outputValidator
73
+ })
74
+ },
75
+ async function (request, reply) {
76
+ const response = await request.executeAction({
77
+ actionId: actionIds.view,
78
+ input: {
79
+ recordId: request.input.params.recordId
80
+ }
81
+ });
82
+ reply.code(200).send(response);
83
+ }
84
+ );
85
+ }
86
+
87
+ export { registerRoutes };
@@ -0,0 +1,41 @@
1
+ import { createCrudResourceRuntime } from "@jskit-ai/crud-core/server/resourceRuntime";
2
+ import { resource } from "../shared/userResource.js";
3
+ import { LIST_CONFIG } from "./listConfig.js";
4
+
5
+ const REPOSITORY_CONFIG = Object.freeze({
6
+ context: "users repository",
7
+ list: LIST_CONFIG
8
+ });
9
+
10
+ function createRepository(knex, options = {}) {
11
+ const resourceRuntime = createCrudResourceRuntime(resource, knex, {
12
+ ...options,
13
+ ...REPOSITORY_CONFIG
14
+ });
15
+
16
+ async function list(query = {}, callOptions = {}) {
17
+ return resourceRuntime.list(query, callOptions);
18
+ }
19
+
20
+ async function findById(recordId, callOptions = {}) {
21
+ return resourceRuntime.findById(recordId, callOptions);
22
+ }
23
+
24
+ async function listByIds(ids = [], callOptions = {}) {
25
+ return resourceRuntime.listByIds(ids, callOptions);
26
+ }
27
+
28
+ async function listByForeignIds(ids = [], foreignKey = "", callOptions = {}) {
29
+ return resourceRuntime.listByForeignIds(ids, foreignKey, callOptions);
30
+ }
31
+
32
+ return Object.freeze({
33
+ withTransaction: resourceRuntime.withTransaction,
34
+ list,
35
+ findById,
36
+ listByIds,
37
+ listByForeignIds
38
+ });
39
+ }
40
+
41
+ export { createRepository };
@@ -0,0 +1,28 @@
1
+ import {
2
+ createCrudServiceRuntime,
3
+ crudServiceListRecords,
4
+ crudServiceGetRecord
5
+ } from "@jskit-ai/crud-core/server/serviceMethods";
6
+ import { resource } from "../shared/userResource.js";
7
+
8
+ const serviceRuntime = createCrudServiceRuntime(resource, {
9
+ context: "usersService"
10
+ });
11
+ const serviceEvents = Object.freeze({});
12
+
13
+ function createService({ usersRepository } = {}) {
14
+ if (!usersRepository) {
15
+ throw new TypeError("createService requires usersRepository.");
16
+ }
17
+
18
+ return Object.freeze({
19
+ listRecords(query = {}, options = {}) {
20
+ return crudServiceListRecords(serviceRuntime, usersRepository, {}, query, options);
21
+ },
22
+ getRecord(recordId, options = {}) {
23
+ return crudServiceGetRecord(serviceRuntime, usersRepository, {}, recordId, options);
24
+ }
25
+ });
26
+ }
27
+
28
+ export { createService, serviceEvents };
@@ -0,0 +1 @@
1
+ export { resource } from "./userResource.js";
@@ -0,0 +1,74 @@
1
+ import { Type } from "typebox";
2
+ import { toIsoString } from "@jskit-ai/database-runtime/shared";
3
+ import {
4
+ createCursorListValidator,
5
+ normalizeObjectInput,
6
+ recordIdSchema
7
+ } from "@jskit-ai/kernel/shared/validators";
8
+ import {
9
+ normalizeIfPresent,
10
+ normalizeRecordId,
11
+ normalizeText
12
+ } from "@jskit-ai/kernel/shared/support/normalize";
13
+
14
+ const recordOutputSchema = Type.Object(
15
+ {
16
+ id: recordIdSchema,
17
+ name: Type.String({ minLength: 1 }),
18
+ email: Type.String(),
19
+ username: Type.String(),
20
+ createdAt: Type.String({ format: "date-time", minLength: 1 })
21
+ },
22
+ { additionalProperties: false }
23
+ );
24
+
25
+ const createBodySchema = Type.Object({}, { additionalProperties: false });
26
+
27
+ const recordOutputValidator = Object.freeze({
28
+ schema: recordOutputSchema,
29
+ normalize(payload = {}) {
30
+ const source = normalizeObjectInput(payload);
31
+
32
+ return {
33
+ id: normalizeIfPresent(source.id, normalizeRecordId),
34
+ name: normalizeText(source.name || source.email || source.username || source.id),
35
+ email: normalizeText(source.email),
36
+ username: normalizeText(source.username),
37
+ createdAt: normalizeIfPresent(source.createdAt, toIsoString)
38
+ };
39
+ }
40
+ });
41
+
42
+ const createBodyValidator = Object.freeze({
43
+ schema: createBodySchema,
44
+ normalize: normalizeObjectInput
45
+ });
46
+
47
+ const resource = Object.freeze({
48
+ namespace: "users",
49
+ tableName: "users",
50
+ idColumn: "id",
51
+ operations: Object.freeze({
52
+ list: Object.freeze({
53
+ method: "GET",
54
+ outputValidator: createCursorListValidator(recordOutputValidator)
55
+ }),
56
+ view: Object.freeze({
57
+ method: "GET",
58
+ outputValidator: recordOutputValidator
59
+ }),
60
+ create: Object.freeze({
61
+ method: "POST",
62
+ bodyValidator: createBodyValidator,
63
+ outputValidator: recordOutputValidator
64
+ })
65
+ }),
66
+ fieldMeta: Object.freeze([
67
+ Object.freeze({
68
+ key: "name",
69
+ repository: { column: "display_name" }
70
+ })
71
+ ])
72
+ });
73
+
74
+ export { resource };