@jskit-ai/users-core 0.1.172 → 0.1.174

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 (58) hide show
  1. package/package.json +41 -253
  2. package/patterns/user-administration-server/PATTERN.md +64 -0
  3. package/patterns/user-administration-server/example/packages/users/package.json +42 -0
  4. package/patterns/user-administration-server/example/packages/users/src/server/UsersFeature.js +13 -0
  5. package/patterns/user-administration-server/example/packages/users/src/shared/index.js +1 -0
  6. package/patterns/user-administration-server/example/packages/users-workspace/package.json +43 -0
  7. package/patterns/user-administration-server/example/packages/users-workspace/src/server/UsersWorkspaceFeature.js +22 -0
  8. package/patterns/user-administration-server/example/packages/users-workspace/src/shared/userResource.js +56 -0
  9. package/src/server/UsersExtensionsProvider.js +14 -0
  10. package/src/server/UsersFeature.js +119 -0
  11. package/src/server/UsersIdentityProvider.js +57 -0
  12. package/src/server/accountNotifications/accountNotificationsActions.js +17 -5
  13. package/src/server/accountNotifications/bootAccountNotificationsRoutes.js +4 -6
  14. package/src/server/accountPreferences/accountPreferencesActions.js +17 -5
  15. package/src/server/accountPreferences/bootAccountPreferencesRoutes.js +4 -6
  16. package/src/server/accountProfile/accountProfileActions.js +28 -14
  17. package/src/server/accountProfile/bootAccountProfileRoutes.js +11 -10
  18. package/src/server/accountSecurity/accountSecurityActions.js +27 -17
  19. package/src/server/accountSecurity/bootAccountSecurityRoutes.js +4 -7
  20. package/src/server/common/services/authProfileSyncService.js +11 -4
  21. package/src/server/common/support/realtimeServiceEvents.js +8 -11
  22. package/src/server/usersBootstrapContributor.js +4 -28
  23. package/src/server/usersExtensions.js +65 -0
  24. package/test/authProfileSyncService.test.js +2 -2
  25. package/test/exportsContract.test.js +6 -1
  26. package/test/featureRuntime.test.js +78 -0
  27. package/test/usersBootstrapContributor.test.js +1 -43
  28. package/test/usersPackageScaffoldContract.test.js +58 -204
  29. package/test/usersRouteRequestInputValidator.test.js +19 -43
  30. package/src/server/UsersCoreServiceProvider.js +0 -56
  31. package/src/server/accountNotifications/registerAccountNotifications.js +0 -37
  32. package/src/server/accountPreferences/registerAccountPreferences.js +0 -37
  33. package/src/server/accountProfile/registerAccountProfile.js +0 -56
  34. package/src/server/accountSecurity/registerAccountSecurity.js +0 -29
  35. package/src/server/common/registerCommonRepositories.js +0 -23
  36. package/src/server/common/registerSharedApi.js +0 -9
  37. package/src/server/profileSyncLifecycleContributorRegistry.js +0 -56
  38. package/src/server/registerUsersBootstrap.js +0 -20
  39. package/src/server/registerUsersCore.js +0 -22
  40. package/templates/packages/users/package.json +0 -82
  41. package/templates/packages/users/src/server/UsersProvider.js +0 -90
  42. package/templates/packages/users/src/server/actions.js +0 -68
  43. package/templates/packages/users/src/server/registerRoutes.js +0 -101
  44. package/templates/packages/users/src/server/repository.js +0 -51
  45. package/templates/packages/users/src/server/service.js +0 -32
  46. package/templates/packages/users-workspace/package.json +0 -83
  47. package/templates/packages/users-workspace/src/server/UsersProvider.js +0 -91
  48. package/templates/packages/users-workspace/src/server/actions.js +0 -77
  49. package/templates/packages/users-workspace/src/server/registerRoutes.js +0 -111
  50. package/test/providerLifecycle.test.js +0 -63
  51. package/test/registerCommonRepositories.test.js +0 -57
  52. package/test/registerServiceRealtimeEvents.test.js +0 -57
  53. package/test/registerUsersCore.test.js +0 -53
  54. /package/{templates/migrations → migrations}/users_core_generic_initial.cjs +0 -0
  55. /package/{templates/migrations → migrations}/users_core_profile_updated_at.cjs +0 -0
  56. /package/{templates/migrations → migrations}/users_core_profile_username.cjs +0 -0
  57. /package/{templates → patterns/user-administration-server/example}/packages/users/src/shared/userResource.js +0 -0
  58. /package/{templates/packages/users → patterns/user-administration-server/example/packages/users-workspace}/src/shared/index.js +0 -0
@@ -1,91 +0,0 @@
1
- import { resolveAppConfig } from "@jskit-ai/kernel/server/support";
2
- import { resolveCrudSurfacePolicyFromAppConfig } from "@jskit-ai/crud-core/server/crudModuleConfig";
3
- import {
4
- INTERNAL_JSON_REST_API,
5
- addResourceIfMissing,
6
- createJsonRestResourceScopeOptions
7
- } from "@jskit-ai/json-rest-api-core/server/jsonRestApiHost";
8
- import { withActionDefaults } from "@jskit-ai/kernel/shared/actions";
9
- import { toDatabaseDateTimeUtc } from "@jskit-ai/database-runtime/shared";
10
- import { createRepository } from "./repository.js";
11
- import { createService } from "./service.js";
12
- import { createActions } from "./actions.js";
13
- import { registerRoutes } from "./registerRoutes.js";
14
- import { resource } from "../shared/userResource.js";
15
-
16
- const CRUD_MODULE_CONFIG = Object.freeze({
17
- namespace: "users",
18
- surface: "admin",
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 startsAfter = ["json-rest-api.core", "local.main", "runtime.actions"];
33
-
34
- register(app) {
35
- const crudPolicy = resolveCrudPolicyFromApp(app);
36
-
37
- app.singleton("repository.users", (scope) => {
38
- const api = scope.make(INTERNAL_JSON_REST_API);
39
- const knex = scope.make("jskit.database.knex");
40
- return createRepository({
41
- api,
42
- knex
43
- });
44
- });
45
-
46
- app.service(
47
- "crud.users",
48
- (scope) => {
49
- return createService({
50
- usersRepository: scope.make("repository.users")
51
- });
52
- }
53
- );
54
-
55
- app.actions(
56
- withActionDefaults(
57
- createActions({
58
- surface: crudPolicy.surfaceId
59
- }),
60
- {
61
- domain: "crud",
62
- dependencies: {
63
- usersService: "crud.users"
64
- }
65
- }
66
- )
67
- );
68
- }
69
-
70
- async boot(app) {
71
- const crudPolicy = resolveCrudPolicyFromApp(app);
72
- const api = app.make(INTERNAL_JSON_REST_API);
73
- await addResourceIfMissing(
74
- api,
75
- "users",
76
- createJsonRestResourceScopeOptions(resource, {
77
- writeSerializers: {
78
- "datetime-utc": toDatabaseDateTimeUtc
79
- }
80
- })
81
- );
82
- registerRoutes(app, {
83
- routeOwnershipFilter: crudPolicy.ownershipFilter,
84
- routeSurface: crudPolicy.surfaceId,
85
- routeSurfaceRequiresWorkspace: crudPolicy.surfaceDefinition.requiresWorkspace === true,
86
- routeRelativePath: crudPolicy.relativePath
87
- });
88
- }
89
- }
90
-
91
- export { UsersProvider };
@@ -1,77 +0,0 @@
1
- import {
2
- composeSchemaDefinitions,
3
- recordIdParamsValidator
4
- } from "@jskit-ai/kernel/shared/validators";
5
- import {
6
- createStandardCrudListQueryValidators,
7
- createStandardCrudViewQueryValidators
8
- } from "@jskit-ai/crud-core/server/listQueryValidators";
9
- import { workspaceSlugParamsValidator } from "@jskit-ai/workspaces-core/server/validators/routeParamsValidator";
10
- import { resource } from "../shared/userResource.js";
11
-
12
- const authenticatedPermission = Object.freeze({
13
- require: "authenticated"
14
- });
15
-
16
- function buildListQuery(input = {}) {
17
- const query = { ...(input || {}) };
18
- delete query.workspaceSlug;
19
- return query;
20
- }
21
-
22
- function createActions({ surface } = {}) {
23
- return Object.freeze([
24
- {
25
- id: "crud.users.list",
26
- version: 1,
27
- kind: "query",
28
- channels: ["api", "automation", "internal"],
29
- surfaces: [surface],
30
- permission: authenticatedPermission,
31
- input: composeSchemaDefinitions([
32
- workspaceSlugParamsValidator,
33
- ...createStandardCrudListQueryValidators({ resource })
34
- ]),
35
- output: null,
36
- idempotency: "none",
37
- audit: {
38
- actionName: "crud.users.list"
39
- },
40
- observability: {},
41
- async execute(input, context, deps) {
42
- return deps.usersService.queryDocuments(buildListQuery(input), {
43
- context,
44
- visibilityContext: context?.visibilityContext
45
- });
46
- }
47
- },
48
- {
49
- id: "crud.users.view",
50
- version: 1,
51
- kind: "query",
52
- channels: ["api", "automation", "internal"],
53
- surfaces: [surface],
54
- permission: authenticatedPermission,
55
- input: composeSchemaDefinitions([
56
- workspaceSlugParamsValidator,
57
- recordIdParamsValidator,
58
- ...createStandardCrudViewQueryValidators()
59
- ]),
60
- output: null,
61
- idempotency: "none",
62
- audit: {
63
- actionName: "crud.users.view"
64
- },
65
- observability: {},
66
- async execute(input, context, deps) {
67
- const { workspaceSlug, recordId, ...query } = input || {};
68
- return deps.usersService.getDocumentById(recordId, query, {
69
- context,
70
- visibilityContext: context?.visibilityContext
71
- });
72
- }
73
- }
74
- ]);
75
- }
76
-
77
- export { createActions };
@@ -1,111 +0,0 @@
1
- import { createJsonApiResourceRouteContract } from "@jskit-ai/http-runtime/shared/validators/jsonApiRouteTransport";
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 {
10
- composeSchemaDefinitions,
11
- recordIdParamsValidator
12
- } from "@jskit-ai/kernel/shared/validators";
13
- import { routeParamsValidator } from "@jskit-ai/workspaces-core/server/validators/routeParamsValidator";
14
- import { buildWorkspaceInputFromRouteParams } from "@jskit-ai/workspaces-core/server/support/workspaceRouteInput";
15
- import { resource } from "../shared/userResource.js";
16
-
17
- const listCursorPaginationQueryValidator = createCrudCursorPaginationQueryValidator({
18
- orderBy: resource.defaultSort
19
- });
20
- const listRouteQueryValidator = composeSchemaDefinitions([
21
- listCursorPaginationQueryValidator,
22
- listSearchQueryValidator
23
- ]);
24
- const RESOURCE_ROUTE_CONTRACT_TYPE = resource.namespace;
25
- const listRouteContract = createJsonApiResourceRouteContract({
26
- type: RESOURCE_ROUTE_CONTRACT_TYPE,
27
- query: listRouteQueryValidator,
28
- output: resource.operations.view.output,
29
- outputKind: "collection"
30
- });
31
- const viewRouteContract = createJsonApiResourceRouteContract({
32
- type: RESOURCE_ROUTE_CONTRACT_TYPE,
33
- output: resource.operations.view.output,
34
- outputKind: "record"
35
- });
36
- const viewRouteParamsValidator = composeSchemaDefinitions([
37
- routeParamsValidator,
38
- recordIdParamsValidator
39
- ]);
40
-
41
- function registerRoutes(
42
- app,
43
- {
44
- routeOwnershipFilter = "public",
45
- routeSurface = "",
46
- routeSurfaceRequiresWorkspace = false,
47
- routeRelativePath = ""
48
- } = {}
49
- ) {
50
- const router = app.make("jskit.http.router");
51
- const normalizedRouteSurface = normalizeSurfaceId(routeSurface);
52
- const routeBase = resolveScopedApiBasePath({
53
- routeBase: routeSurfaceRequiresWorkspace === true ? "/w/:workspaceSlug" : "/",
54
- relativePath: routeRelativePath,
55
- strictParams: false
56
- });
57
-
58
- router.register(
59
- "GET",
60
- routeBase,
61
- {
62
- auth: "required",
63
- surface: normalizedRouteSurface,
64
- visibility: checkRouteVisibility(routeOwnershipFilter),
65
- meta: {
66
- tags: ["crud"],
67
- summary: "List users."
68
- },
69
- ...listRouteContract,
70
- params: routeParamsValidator
71
- },
72
- async function (request, reply) {
73
- const response = await request.executeAction({
74
- actionId: "crud.users.list",
75
- input: {
76
- ...buildWorkspaceInputFromRouteParams(request.input.params),
77
- ...(request.input.query || {})
78
- }
79
- });
80
- reply.code(200).send(response);
81
- }
82
- );
83
-
84
- router.register(
85
- "GET",
86
- `${routeBase}/:recordId`,
87
- {
88
- auth: "required",
89
- surface: normalizedRouteSurface,
90
- visibility: checkRouteVisibility(routeOwnershipFilter),
91
- meta: {
92
- tags: ["crud"],
93
- summary: "View a user."
94
- },
95
- ...viewRouteContract,
96
- params: viewRouteParamsValidator
97
- },
98
- async function (request, reply) {
99
- const response = await request.executeAction({
100
- actionId: "crud.users.view",
101
- input: {
102
- ...buildWorkspaceInputFromRouteParams(request.input.params),
103
- recordId: request.input.params.recordId
104
- }
105
- });
106
- reply.code(200).send(response);
107
- }
108
- );
109
- }
110
-
111
- export { registerRoutes };
@@ -1,63 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import { UsersCoreServiceProvider } from "../src/server/UsersCoreServiceProvider.js";
4
- import { WorkspacesCoreServiceProvider } from "../../workspaces-core/src/server/WorkspacesCoreServiceProvider.js";
5
-
6
- function createRegisterPhaseProbe() {
7
- let makeCalls = 0;
8
-
9
- const target = {
10
- singleton() {
11
- return proxy;
12
- },
13
- service() {
14
- return proxy;
15
- },
16
- actions() {
17
- return proxy;
18
- },
19
- instance() {
20
- return proxy;
21
- },
22
- tag() {
23
- return proxy;
24
- },
25
- has() {
26
- return false;
27
- },
28
- make() {
29
- makeCalls += 1;
30
- return null;
31
- }
32
- };
33
-
34
- const proxy = new Proxy(target, {
35
- get(source, property) {
36
- if (property === "makeCalls") {
37
- return makeCalls;
38
- }
39
- if (Object.prototype.hasOwnProperty.call(source, property)) {
40
- return source[property];
41
- }
42
- return () => proxy;
43
- }
44
- });
45
-
46
- return proxy;
47
- }
48
-
49
- test("UsersCoreServiceProvider register phase does not resolve container services eagerly", async () => {
50
- const app = createRegisterPhaseProbe();
51
-
52
- await new UsersCoreServiceProvider().register(app);
53
-
54
- assert.equal(app.makeCalls, 0);
55
- });
56
-
57
- test("WorkspacesCoreServiceProvider register phase does not resolve container services eagerly", async () => {
58
- const app = createRegisterPhaseProbe();
59
-
60
- await new WorkspacesCoreServiceProvider().register(app);
61
-
62
- assert.equal(app.makeCalls, 0);
63
- });
@@ -1,57 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import { registerCommonRepositories } from "../src/server/common/registerCommonRepositories.js";
4
-
5
- test("registerCommonRepositories exposes the shared users-core repositories", async () => {
6
- const bindings = new Map();
7
- const app = {
8
- singleton(token, factory) {
9
- bindings.set(token, factory);
10
- return this;
11
- }
12
- };
13
-
14
- registerCommonRepositories(app);
15
-
16
- assert.equal(typeof bindings.get("internal.repository.user-settings"), "function");
17
- assert.equal(typeof bindings.get("internal.repository.user-profiles"), "function");
18
-
19
- const scope = {
20
- make(token) {
21
- if (token === "internal.json-rest-api") {
22
- return {
23
- resources: {
24
- userSettings: {
25
- query() {},
26
- post() {},
27
- patch() {}
28
- },
29
- userProfiles: {
30
- query() {},
31
- post() {},
32
- patch() {}
33
- }
34
- }
35
- };
36
- }
37
-
38
- if (token === "jskit.database.knex") {
39
- return Object.assign(() => {
40
- throw new Error("query execution not expected");
41
- }, {
42
- async transaction(work) {
43
- return work({ trxId: "trx-1" });
44
- }
45
- });
46
- }
47
-
48
- throw new Error(`Unexpected token: ${token}`);
49
- }
50
- };
51
-
52
- const settingsRepository = bindings.get("internal.repository.user-settings")(scope);
53
- const profileRepository = bindings.get("internal.repository.user-profiles")(scope);
54
-
55
- assert.equal(typeof settingsRepository.findByUserId, "function");
56
- assert.equal(typeof profileRepository.findById, "function");
57
- });
@@ -1,57 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import { registerAccountProfile } from "../src/server/accountProfile/registerAccountProfile.js";
4
- import { registerAccountPreferences } from "../src/server/accountPreferences/registerAccountPreferences.js";
5
- import { registerAccountNotifications } from "../src/server/accountNotifications/registerAccountNotifications.js";
6
-
7
- function createAppDouble() {
8
- const serviceCalls = [];
9
-
10
- return {
11
- serviceCalls,
12
- app: {
13
- singleton() {
14
- return this;
15
- },
16
- service(token, factory, metadata) {
17
- serviceCalls.push({
18
- token,
19
- factory,
20
- metadata
21
- });
22
- return this;
23
- },
24
- actions() {
25
- return this;
26
- }
27
- }
28
- };
29
- }
30
-
31
- function findServiceCall(serviceCalls, token) {
32
- return serviceCalls.find((entry) => entry.token === token) || null;
33
- }
34
-
35
- test("account register functions publish account.settings.changed for update operations", () => {
36
- const profileApp = createAppDouble();
37
- registerAccountProfile(profileApp.app);
38
- const profile = findServiceCall(profileApp.serviceCalls, "users.accountProfile.service");
39
- assert.equal(profile?.metadata?.events?.updateProfile?.[0]?.realtime?.event, "account.settings.changed");
40
- assert.equal(profile?.metadata?.events?.updateProfile?.[1]?.realtime?.event, "users.bootstrap.changed");
41
- assert.equal(profile?.metadata?.events?.uploadAvatar?.[0]?.realtime?.event, "account.settings.changed");
42
- assert.equal(profile?.metadata?.events?.uploadAvatar?.[1]?.realtime?.event, "users.bootstrap.changed");
43
- assert.equal(profile?.metadata?.events?.deleteAvatar?.[0]?.realtime?.event, "account.settings.changed");
44
- assert.equal(profile?.metadata?.events?.deleteAvatar?.[1]?.realtime?.event, "users.bootstrap.changed");
45
-
46
- const preferencesApp = createAppDouble();
47
- registerAccountPreferences(preferencesApp.app);
48
- const preferences = findServiceCall(preferencesApp.serviceCalls, "users.accountPreferences.service");
49
- assert.equal(preferences?.metadata?.events?.updatePreferences?.[0]?.realtime?.event, "account.settings.changed");
50
- assert.equal(preferences?.metadata?.events?.updatePreferences?.[1]?.realtime?.event, "users.bootstrap.changed");
51
-
52
- const notificationsApp = createAppDouble();
53
- registerAccountNotifications(notificationsApp.app);
54
- const notifications = findServiceCall(notificationsApp.serviceCalls, "users.accountNotifications.service");
55
- assert.equal(notifications?.metadata?.events?.updateNotifications?.[0]?.realtime?.event, "account.settings.changed");
56
- assert.equal(notifications?.metadata?.events?.updateNotifications?.[1]?.realtime?.event, "users.bootstrap.changed");
57
- });
@@ -1,53 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
- import { registerUsersCore } from "../src/server/registerUsersCore.js";
4
-
5
- test("registerUsersCore only binds users-core singletons", () => {
6
- const singletonTokens = [];
7
- const app = {
8
- has() {
9
- return false;
10
- },
11
- singleton(token) {
12
- singletonTokens.push(String(token || ""));
13
- return this;
14
- }
15
- };
16
-
17
- registerUsersCore(app);
18
-
19
- assert.deepEqual(singletonTokens.sort(), [
20
- "auth.profile.projector",
21
- "users.profile.sync.service"
22
- ]);
23
- });
24
-
25
- test("registerUsersCore still works when action runtime has not installed actionSurfaceSource yet", () => {
26
- const app = {
27
- has() {
28
- return false;
29
- },
30
- singleton() {
31
- return this;
32
- }
33
- };
34
-
35
- assert.doesNotThrow(() => registerUsersCore(app));
36
- });
37
-
38
- test("registerUsersCore preserves an existing auth profile projector", () => {
39
- const singletonTokens = [];
40
- const app = {
41
- has(token) {
42
- return token === "auth.profile.projector";
43
- },
44
- singleton(token) {
45
- singletonTokens.push(String(token || ""));
46
- return this;
47
- }
48
- };
49
-
50
- registerUsersCore(app);
51
-
52
- assert.deepEqual(singletonTokens, ["users.profile.sync.service"]);
53
- });