@astrale-domains/shell 0.1.0-beta.11 → 0.1.0-beta.12

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 (50) hide show
  1. package/README.md +4 -4
  2. package/dist/schema/domain/index.d.ts +4 -4
  3. package/dist/schema/group/class.d.ts +17 -17
  4. package/dist/schema/index.d.ts +6 -6
  5. package/package.json +7 -15
  6. package/dist/actions/index.js +0 -1
  7. package/dist/actions/user/index.js +0 -1
  8. package/dist/actions/user/resolve-home.js +0 -19
  9. package/dist/application.d.ts +0 -9809
  10. package/dist/application.js +0 -29
  11. package/dist/entrypoints/application.d.ts +0 -1
  12. package/dist/entrypoints/application.js +0 -1
  13. package/dist/entrypoints/runtime.js +0 -1
  14. package/dist/index.d.ts +0 -3
  15. package/dist/index.js +0 -3
  16. package/dist/mutations/group/index.js +0 -1
  17. package/dist/mutations/group/membership.js +0 -19
  18. package/dist/mutations/space/access.js +0 -23
  19. package/dist/mutations/space/default.js +0 -23
  20. package/dist/mutations/space/index.js +0 -2
  21. package/dist/mutations/user/index.js +0 -1
  22. package/dist/mutations/user/repair.js +0 -83
  23. package/dist/queries/space/access.js +0 -12
  24. package/dist/queries/space/admin.js +0 -20
  25. package/dist/queries/space/index.js +0 -3
  26. package/dist/queries/space/member.js +0 -17
  27. package/dist/queries/user/groups.js +0 -17
  28. package/dist/queries/user/home.js +0 -21
  29. package/dist/queries/user/index.js +0 -5
  30. package/dist/queries/user/owned-spaces.js +0 -17
  31. package/dist/queries/user/profile.js +0 -16
  32. package/dist/queries/user/setup.js +0 -16
  33. package/dist/runtime.d.ts +0 -4
  34. package/dist/runtime.js +0 -18
  35. package/dist/workflows/group/assign.js +0 -3
  36. package/dist/workflows/group/change.js +0 -31
  37. package/dist/workflows/group/index.js +0 -3
  38. package/dist/workflows/group/unassign.js +0 -3
  39. package/dist/workflows/index.js +0 -3
  40. package/dist/workflows/setup/admin.js +0 -3
  41. package/dist/workflows/setup/index.js +0 -5
  42. package/dist/workflows/setup/member.js +0 -3
  43. package/dist/workflows/setup/plan.js +0 -88
  44. package/dist/workflows/setup/provision.js +0 -57
  45. package/dist/workflows/setup/run.js +0 -74
  46. package/dist/workflows/space/access.js +0 -41
  47. package/dist/workflows/space/add.js +0 -3
  48. package/dist/workflows/space/default.js +0 -37
  49. package/dist/workflows/space/index.js +0 -4
  50. package/dist/workflows/space/remove.js +0 -3
@@ -1,29 +0,0 @@
1
- import { defineApplication, defineFrontend, external, requirements, } from '@astrale-os/sdk/application';
2
- import { K } from '@astrale-os/sdk/schema';
3
- import runtime from './runtime.js';
4
- import { ShellSchema } from './schema/index.js';
5
- export const frontend = defineFrontend({
6
- schema: ShellSchema,
7
- source: external('https://app.astrale.ai'),
8
- routes: {
9
- group: '/groups',
10
- home: '/',
11
- space: '/spaces',
12
- user: '/users',
13
- },
14
- entrypoint: 'home',
15
- });
16
- export const application = defineApplication({
17
- schema: ShellSchema,
18
- runtime,
19
- frontend,
20
- requirements: requirements({
21
- callables: [
22
- K.classes.Identity.static.methods.whois,
23
- K.functions.provision,
24
- K.functions.query,
25
- K.functions.mutate,
26
- ],
27
- }),
28
- });
29
- export default application;
@@ -1 +0,0 @@
1
- export { application, default } from '../application.js';
@@ -1 +0,0 @@
1
- export { application, default } from '../application.js';
@@ -1 +0,0 @@
1
- export { default } from '../runtime.js';
package/dist/index.d.ts DELETED
@@ -1,3 +0,0 @@
1
- export { Group, ORIGIN, S, ShellCore, ShellSchema, Space, User, adminGroup, administers_space, belongsToSpace, default_space, defaultSpace, groupAssignUser, groupCore, groupUnassignUser, groupView, homeView, mayAdministerSpace, maySetupUsers, memberGroup, member_of_group, member_of_space, owns_space, ownsSpace, resolveHome, setupAdmin, setupMember, spaceAddUser, spaceCore, spaceRemoveUser, spaceSetDefault, spaceView, userView, type GroupMembershipInput, type GroupMembershipResult, type ResolveHomeResult, type SetDefaultSpaceInput, type SetDefaultSpaceResult, type SetupUserInput, type SetupUserResult, type Shell, type SpaceUserAccess, type SpaceUserAccessInput, type SpaceUserAccessResult, type SpaceUserInput, type SpaceUserResult, } from './schema/index.js';
2
- export { application } from './application.js';
3
- export { default as runtime } from './runtime.js';
package/dist/index.js DELETED
@@ -1,3 +0,0 @@
1
- export { Group, ORIGIN, S, ShellCore, ShellSchema, Space, User, adminGroup, administers_space, belongsToSpace, default_space, defaultSpace, groupAssignUser, groupCore, groupUnassignUser, groupView, homeView, mayAdministerSpace, maySetupUsers, memberGroup, member_of_group, member_of_space, owns_space, ownsSpace, resolveHome, setupAdmin, setupMember, spaceAddUser, spaceCore, spaceRemoveUser, spaceSetDefault, spaceView, userView, } from './schema/index.js';
2
- export { application } from './application.js';
3
- export { default as runtime } from './runtime.js';
@@ -1 +0,0 @@
1
- export { changeGroupMembership, } from './membership.js';
@@ -1,19 +0,0 @@
1
- import { Path } from '@astrale-os/sdk/graph/path';
2
- import { defineMutation } from '@astrale-os/sdk/mutation';
3
- export const changeGroupMembership = defineMutation()((domain) => ({
4
- id: 'shell.group.membership',
5
- build(change, mutation) {
6
- mutation.expect.query(change.guard);
7
- const edge = {
8
- class: domain.classes.member_of_group,
9
- source: Path.parse(change.user),
10
- target: Path.id(change.group),
11
- };
12
- if (change.present)
13
- mutation.createEdge(edge);
14
- else
15
- mutation.deleteEdge(edge);
16
- },
17
- project: () => Object.freeze({ ok: true }),
18
- reject: (failure) => Object.freeze({ ok: false, code: failure.code, outcomeKnown: failure.outcomeKnown }),
19
- }));
@@ -1,23 +0,0 @@
1
- import { Path } from '@astrale-os/sdk/graph/path';
2
- import { defineMutation } from '@astrale-os/sdk/mutation';
3
- export const changeSpaceAccess = defineMutation()((domain) => ({
4
- id: 'shell.space.access',
5
- build(change, mutation) {
6
- mutation.expect.query(change.guards.member);
7
- mutation.expect.query(change.guards.admin);
8
- const source = Path.parse(change.user);
9
- const target = Path.id(change.space);
10
- const member = { class: domain.classes.member_of_space, source, target };
11
- const admin = { class: domain.classes.administers_space, source, target };
12
- if (change.removeMember)
13
- mutation.deleteEdge(member);
14
- if (change.removeAdmin)
15
- mutation.deleteEdge(admin);
16
- if (change.addMember)
17
- mutation.createEdge(member);
18
- if (change.addAdmin)
19
- mutation.createEdge(admin);
20
- },
21
- project: () => Object.freeze({ ok: true }),
22
- reject: (failure) => Object.freeze({ ok: false, code: failure.code, outcomeKnown: failure.outcomeKnown }),
23
- }));
@@ -1,23 +0,0 @@
1
- import { Path } from '@astrale-os/sdk/graph/path';
2
- import { defineMutation } from '@astrale-os/sdk/mutation';
3
- export const changeDefaultSpace = defineMutation()((domain) => ({
4
- id: 'shell.space.default',
5
- build(change, mutation) {
6
- mutation.expect.query(change.guard);
7
- const source = Path.id(change.user);
8
- if (change.previous !== undefined) {
9
- mutation.deleteEdge({
10
- class: domain.classes.default_space,
11
- source,
12
- target: Path.id(change.previous),
13
- });
14
- }
15
- mutation.createEdge({
16
- class: domain.classes.default_space,
17
- source,
18
- target: Path.id(change.next),
19
- });
20
- },
21
- project: () => Object.freeze({ ok: true }),
22
- reject: (failure) => Object.freeze({ ok: false, code: failure.code, outcomeKnown: failure.outcomeKnown }),
23
- }));
@@ -1,2 +0,0 @@
1
- export { changeSpaceAccess, } from './access.js';
2
- export { changeDefaultSpace, } from './default.js';
@@ -1 +0,0 @@
1
- export { repairUser } from './repair.js';
@@ -1,83 +0,0 @@
1
- import { LocalBinding } from '@astrale-os/sdk/graph';
2
- import { Path } from '@astrale-os/sdk/graph/path';
3
- import { defineMutation } from '@astrale-os/sdk/mutation';
4
- const personalSpace = LocalBinding('personalSpace');
5
- export const repairUser = defineMutation()((domain) => ({
6
- id: 'shell.user.repair',
7
- build(repair, mutation) {
8
- mutation.expect.query(repair.guards.home);
9
- mutation.expect.query(repair.guards.ownedSpaces);
10
- mutation.expect.query(repair.guards.groups);
11
- mutation.expect.node({
12
- node: Path.id(repair.userId),
13
- class: domain.classes.User,
14
- props: repair.currentDisplayName === undefined
15
- ? { absent: ['displayName'] }
16
- : { equals: { displayName: repair.currentDisplayName } },
17
- });
18
- if (repair.displayName !== undefined) {
19
- mutation.updateNode({
20
- node: Path.id(repair.userId),
21
- class: domain.classes.User,
22
- props: { set: { displayName: repair.displayName } },
23
- });
24
- }
25
- const home = repair.home.kind === 'create'
26
- ? mutation.createNode({
27
- as: personalSpace,
28
- class: domain.classes.Space,
29
- props: { name: repair.home.name },
30
- })
31
- : Path.id(repair.home.id);
32
- if (repair.home.kind === 'existing' && repair.home.name !== undefined) {
33
- mutation.expect.node({
34
- node: Path.id(repair.home.id),
35
- class: domain.classes.Space,
36
- props: { equals: { name: repair.home.currentName } },
37
- });
38
- mutation.updateNode({
39
- node: Path.id(repair.home.id),
40
- class: domain.classes.Space,
41
- props: { set: { name: repair.home.name } },
42
- });
43
- }
44
- if (repair.addOwnership) {
45
- mutation.createEdge({
46
- class: domain.classes.owns_space,
47
- source: Path.id(repair.userId),
48
- target: home,
49
- });
50
- }
51
- if (repair.addDefault) {
52
- mutation.createEdge({
53
- class: domain.classes.default_space,
54
- source: Path.id(repair.userId),
55
- target: home,
56
- });
57
- }
58
- if (repair.addMember) {
59
- mutation.createEdge({
60
- class: domain.classes.member_of_group,
61
- source: Path.id(repair.userId),
62
- target: Path.project(domain.core.nodes.member.ref),
63
- });
64
- }
65
- if (repair.addAdmin) {
66
- mutation.createEdge({
67
- class: domain.classes.member_of_group,
68
- source: Path.id(repair.userId),
69
- target: Path.project(domain.core.nodes.admin.ref),
70
- });
71
- }
72
- },
73
- project(result, repair) {
74
- const personalSpaceId = repair.home.kind === 'existing' ? repair.home.id : result.createdNodes[personalSpace];
75
- if (personalSpaceId === undefined) {
76
- throw new TypeError('Shell User repair omitted its created personal Space identity.');
77
- }
78
- return Object.freeze({ ok: true, personalSpaceId });
79
- },
80
- reject(failure) {
81
- return Object.freeze({ ok: false, code: failure.code, outcomeKnown: failure.outcomeKnown });
82
- },
83
- }));
@@ -1,12 +0,0 @@
1
- import { defineCompositeQuery } from '@astrale-os/sdk/query';
2
- import { observeAdminSpaces } from './admin.js';
3
- import { observeMemberSpaces } from './member.js';
4
- export const observeDirectSpaceAccess = defineCompositeQuery()(() => ({
5
- id: 'shell.space.direct-access',
6
- compose({ combine, query }, user) {
7
- return combine({
8
- admin: query('admin', observeAdminSpaces, user),
9
- member: query('member', observeMemberSpaces, user),
10
- });
11
- },
12
- }));
@@ -1,20 +0,0 @@
1
- import { Path } from '@astrale-os/sdk/graph/path';
2
- import { Query, defineQuery, queryResult } from '@astrale-os/sdk/query';
3
- export const observeAdminSpaces = defineQuery()((domain) => ({
4
- id: 'shell.space.admin-access',
5
- build(user) {
6
- const source = Query.from({ nodes: [Path.parse(user)] });
7
- const spaces = source.expand({
8
- via: [domain.classes.administers_space],
9
- direction: 'outgoing',
10
- });
11
- return spaces.select({ kind: 'nodes', binding: spaces.node, projection: { kind: 'value' } });
12
- },
13
- pagination: { maximumPages: 64 },
14
- project(pages, _user, { ast, complete }) {
15
- return Object.freeze({
16
- spaces: queryResult.completeNodeValues(pages, 'direct administered Spaces'),
17
- guard: Object.freeze({ query: ast, equals: complete }),
18
- });
19
- },
20
- }));
@@ -1,3 +0,0 @@
1
- export { observeAdminSpaces } from './admin.js';
2
- export { observeDirectSpaceAccess } from './access.js';
3
- export { observeMemberSpaces } from './member.js';
@@ -1,17 +0,0 @@
1
- import { Path } from '@astrale-os/sdk/graph/path';
2
- import { Query, defineQuery, queryResult } from '@astrale-os/sdk/query';
3
- export const observeMemberSpaces = defineQuery()((domain) => ({
4
- id: 'shell.space.member-access',
5
- build(user) {
6
- const source = Query.from({ nodes: [Path.parse(user)] });
7
- const spaces = source.expand({ via: [domain.classes.member_of_space], direction: 'outgoing' });
8
- return spaces.select({ kind: 'nodes', binding: spaces.node, projection: { kind: 'value' } });
9
- },
10
- pagination: { maximumPages: 64 },
11
- project(pages, _user, { ast, complete }) {
12
- return Object.freeze({
13
- spaces: queryResult.completeNodeValues(pages, 'direct member Spaces'),
14
- guard: Object.freeze({ query: ast, equals: complete }),
15
- });
16
- },
17
- }));
@@ -1,17 +0,0 @@
1
- import { Path } from '@astrale-os/sdk/graph/path';
2
- import { Query, defineQuery, queryResult } from '@astrale-os/sdk/query';
3
- export const observeUserGroups = defineQuery()((domain) => ({
4
- id: 'shell.user.groups',
5
- build(user) {
6
- const source = Query.from({ nodes: [Path.parse(user)] });
7
- const groups = source.expand({ via: [domain.classes.member_of_group], direction: 'outgoing' });
8
- return groups.select({ kind: 'nodes', binding: groups.node, projection: { kind: 'value' } });
9
- },
10
- pagination: { maximumPages: 64 },
11
- project(pages, _user, { ast, complete }) {
12
- return Object.freeze({
13
- groups: queryResult.completeNodeValues(pages, 'Shell User Groups'),
14
- guard: Object.freeze({ query: ast, equals: complete }),
15
- });
16
- },
17
- }));
@@ -1,21 +0,0 @@
1
- import { Path } from '@astrale-os/sdk/graph/path';
2
- import { Query, defineQuery, queryResult } from '@astrale-os/sdk/query';
3
- export const observeHome = defineQuery()((domain) => ({
4
- id: 'shell.user.home',
5
- build(user) {
6
- const source = Query.from({ nodes: [Path.parse(user)] });
7
- const spaces = source.expand({
8
- via: [domain.classes.default_space],
9
- direction: 'outgoing',
10
- });
11
- return spaces.select({ kind: 'nodes', binding: spaces.node, projection: { kind: 'value' } });
12
- },
13
- page: { size: 2 },
14
- pagination: { maximumPages: 2 },
15
- project(pages, _user, { ast, complete }) {
16
- return Object.freeze({
17
- spaces: queryResult.completeNodeValues(pages, 'Shell User home'),
18
- guard: Object.freeze({ query: ast, equals: complete }),
19
- });
20
- },
21
- }));
@@ -1,5 +0,0 @@
1
- export { observeUserGroups } from './groups.js';
2
- export { observeHome } from './home.js';
3
- export { observeOwnedSpaces } from './owned-spaces.js';
4
- export { readUserProfile } from './profile.js';
5
- export { observeUserSetup } from './setup.js';
@@ -1,17 +0,0 @@
1
- import { Path } from '@astrale-os/sdk/graph/path';
2
- import { Query, defineQuery, queryResult } from '@astrale-os/sdk/query';
3
- export const observeOwnedSpaces = defineQuery()((domain) => ({
4
- id: 'shell.user.owned-spaces',
5
- build(user) {
6
- const source = Query.from({ nodes: [Path.parse(user)] });
7
- const spaces = source.expand({ via: [domain.classes.owns_space], direction: 'outgoing' });
8
- return spaces.select({ kind: 'nodes', binding: spaces.node, projection: { kind: 'value' } });
9
- },
10
- pagination: { maximumPages: 64 },
11
- project(pages, _user, { ast, complete }) {
12
- return Object.freeze({
13
- spaces: queryResult.completeNodeValues(pages, 'Shell User owned Spaces'),
14
- guard: Object.freeze({ query: ast, equals: complete }),
15
- });
16
- },
17
- }));
@@ -1,16 +0,0 @@
1
- import { Path } from '@astrale-os/sdk/graph/path';
2
- import { Query, defineQuery, queryResult } from '@astrale-os/sdk/query';
3
- export const readUserProfile = defineQuery()((domain) => ({
4
- id: 'shell.user.profile',
5
- build(user) {
6
- return Query.from({ nodes: [Path.parse(user)] })
7
- .filter({ class: domain.classes.User })
8
- .select({
9
- kind: 'nodes',
10
- projection: { kind: 'value' },
11
- });
12
- },
13
- project(result) {
14
- return queryResult.exactlyOneNode(result, 'Shell User profile');
15
- },
16
- }));
@@ -1,16 +0,0 @@
1
- import { defineCompositeQuery } from '@astrale-os/sdk/query';
2
- import { observeUserGroups } from './groups.js';
3
- import { observeHome } from './home.js';
4
- import { observeOwnedSpaces } from './owned-spaces.js';
5
- import { readUserProfile } from './profile.js';
6
- export const observeUserSetup = defineCompositeQuery()(() => ({
7
- id: 'shell.user.setup',
8
- compose({ combine, query }, userId) {
9
- return combine({
10
- profile: query('profile', readUserProfile, userId),
11
- home: query('home', observeHome, userId),
12
- ownedSpaces: query('owned-spaces', observeOwnedSpaces, userId),
13
- groups: query('groups', observeUserGroups, userId),
14
- });
15
- },
16
- }));
package/dist/runtime.d.ts DELETED
@@ -1,4 +0,0 @@
1
- import type { Runtime } from '@astrale-os/sdk/runtime'
2
-
3
- declare const runtime: Runtime
4
- export default runtime
package/dist/runtime.js DELETED
@@ -1,18 +0,0 @@
1
- import { defineRuntime } from '@astrale-os/sdk/runtime';
2
- import { resolveHomeAction } from './actions/index.js';
3
- import { addSpaceUserWorkflow, assignGroupUserWorkflow, removeSpaceUserWorkflow, setDefaultSpaceWorkflow, setupAdminWorkflow, setupMemberWorkflow, unassignGroupUserWorkflow, } from './workflows/index.js';
4
- const runtime = defineRuntime()({
5
- integrations: {},
6
- initialize: () => ({ providers: {} }),
7
- actions: [resolveHomeAction],
8
- workflows: [
9
- setupAdminWorkflow,
10
- setupMemberWorkflow,
11
- assignGroupUserWorkflow,
12
- unassignGroupUserWorkflow,
13
- addSpaceUserWorkflow,
14
- removeSpaceUserWorkflow,
15
- setDefaultSpaceWorkflow,
16
- ],
17
- });
18
- export default runtime;
@@ -1,3 +0,0 @@
1
- import { defineWorkflow } from '@astrale-os/sdk/workflow';
2
- import { changeMembership } from './change.js';
3
- export const assignGroupUserWorkflow = defineWorkflow()('Group.assignUser', (context) => changeMembership(context, true));
@@ -1,31 +0,0 @@
1
- import { InvocationError } from '@astrale-os/sdk/execution';
2
- import { changeGroupMembership } from '#mutations/group';
3
- import { observeUserGroups } from '#queries/user';
4
- export async function changeMembership(context, present) {
5
- const observed = await context.step.run('observe-membership', () => context.query(observeUserGroups, context.input.user));
6
- const wasPresent = includes(observed.groups, context.self);
7
- if (wasPresent === present)
8
- return Object.freeze({});
9
- const result = await context.step.run('change-membership', () => context.mutate(changeGroupMembership, {
10
- user: context.input.user,
11
- group: context.self,
12
- present,
13
- guard: observed.guard,
14
- }));
15
- if (result.ok)
16
- return Object.freeze({});
17
- if (result.outcomeKnown)
18
- return membershipConflict(result.code);
19
- const recovered = await context.step.run('recover-membership', () => context.query(observeUserGroups, context.input.user));
20
- if (includes(recovered.groups, context.self) === present)
21
- return Object.freeze({});
22
- throw new InvocationError('BACKEND_UNAVAILABLE', 'Group membership outcome remains unknown after exact observation.', { reason: { code: result.code, details: {} } });
23
- }
24
- function includes(groups, group) {
25
- return groups.some(({ id }) => id === group);
26
- }
27
- function membershipConflict(code) {
28
- throw new InvocationError('CONFLICT', 'Group membership conflicted with concurrent graph state.', {
29
- reason: { code, details: {} },
30
- });
31
- }
@@ -1,3 +0,0 @@
1
- export { assignGroupUserWorkflow } from './assign.js';
2
- export { changeMembership } from './change.js';
3
- export { unassignGroupUserWorkflow } from './unassign.js';
@@ -1,3 +0,0 @@
1
- import { defineWorkflow } from '@astrale-os/sdk/workflow';
2
- import { changeMembership } from './change.js';
3
- export const unassignGroupUserWorkflow = defineWorkflow()('Group.unassignUser', (context) => changeMembership(context, false));
@@ -1,3 +0,0 @@
1
- export * from '#workflows/group';
2
- export * from '#workflows/setup';
3
- export * from '#workflows/space';
@@ -1,3 +0,0 @@
1
- import { defineWorkflow } from '@astrale-os/sdk/workflow';
2
- import { runSetup } from './run.js';
3
- export const setupAdminWorkflow = defineWorkflow()('setupAdmin', (context) => runSetup(context, 'admin'));
@@ -1,5 +0,0 @@
1
- export { setupAdminWorkflow } from './admin.js';
2
- export { setupMemberWorkflow } from './member.js';
3
- export { planUserSetup } from './plan.js';
4
- export { provisioningRequest, provisioningResult } from './provision.js';
5
- export { runSetup } from './run.js';
@@ -1,3 +0,0 @@
1
- import { defineWorkflow } from '@astrale-os/sdk/workflow';
2
- import { runSetup } from './run.js';
3
- export const setupMemberWorkflow = defineWorkflow()('setupMember', (context) => runSetup(context, 'member'));
@@ -1,88 +0,0 @@
1
- export function planUserSetup(domain, input, role, observation) {
2
- const profile = observation.profile;
3
- if (profile.class !== domain.classes.User.key) {
4
- return conflict('IDENTITY_CLASS_CONFLICT', 'The asserted identity already belongs to a non-Shell User Class.');
5
- }
6
- const identityIssuer = profile.props[domain.classes.User.properties.iss.key];
7
- const identitySubject = profile.props[domain.classes.User.properties.sub.key];
8
- if (identityIssuer !== input.issuer || identitySubject !== input.subject) {
9
- return conflict('IDENTITY_FACT_CONFLICT', 'The resolved Shell User does not retain the asserted issuer and subject.');
10
- }
11
- if (observation.home.spaces.length > 1) {
12
- return conflict('MULTIPLE_DEFAULT_SPACES', 'The Shell User has more than one default personal Space.');
13
- }
14
- const currentHome = observation.home.spaces[0];
15
- if (currentHome !== undefined && currentHome.class !== domain.classes.Space.key) {
16
- return conflict('DEFAULT_SPACE_CLASS_CONFLICT', 'The Shell User default target is not a Shell Space.');
17
- }
18
- const candidates = currentHome === undefined
19
- ? observation.ownedSpaces.spaces.filter((space) => space.class === domain.classes.Space.key &&
20
- space.props[domain.classes.Space.properties.name.key] === input.personalSpaceName)
21
- : [];
22
- if (candidates.length > 1) {
23
- return conflict('AMBIGUOUS_PERSONAL_SPACE', 'More than one owned Space matches the requested personal Space name.');
24
- }
25
- const home = currentHome ?? candidates[0];
26
- const currentDisplayName = profile.props[domain.classes.User.properties.displayName.key];
27
- if (currentDisplayName !== undefined && typeof currentDisplayName !== 'string') {
28
- throw new TypeError('Shell User displayName is not a string.');
29
- }
30
- const member = hasGroup(domain, observation.groups.groups, 'core.member');
31
- const admin = hasGroup(domain, observation.groups.groups, 'core.admin');
32
- const ownsHome = home !== undefined && observation.ownedSpaces.spaces.some((space) => space.id === home.id);
33
- let homeName;
34
- if (home !== undefined) {
35
- const value = home.props[domain.classes.Space.properties.name.key];
36
- if (typeof value !== 'string') {
37
- throw new TypeError('Shell personal Space name is not a string.');
38
- }
39
- homeName = value;
40
- }
41
- const displayName = currentDisplayName === input.displayName ? undefined : input.displayName;
42
- const desiredHomeName = homeName === input.personalSpaceName ? undefined : input.personalSpaceName;
43
- const addDefault = currentHome === undefined;
44
- const addOwnership = home === undefined || !ownsHome;
45
- const addMember = !member;
46
- const addAdmin = role === 'admin' && !admin;
47
- if (displayName === undefined &&
48
- desiredHomeName === undefined &&
49
- !addDefault &&
50
- !addOwnership &&
51
- !addMember &&
52
- !addAdmin) {
53
- return Object.freeze({ kind: 'unchanged', personalSpaceId: home.id });
54
- }
55
- return Object.freeze({
56
- kind: 'repair',
57
- repair: Object.freeze({
58
- userId: profile.id,
59
- ...(currentDisplayName === undefined ? {} : { currentDisplayName }),
60
- ...(displayName === undefined ? {} : { displayName }),
61
- home: home === undefined
62
- ? Object.freeze({ kind: 'create', name: input.personalSpaceName })
63
- : Object.freeze({
64
- kind: 'existing',
65
- id: home.id,
66
- currentName: homeName,
67
- ...(desiredHomeName === undefined ? {} : { name: desiredHomeName }),
68
- }),
69
- addDefault,
70
- addOwnership,
71
- addMember,
72
- addAdmin,
73
- guards: Object.freeze({
74
- home: observation.home.guard,
75
- ownedSpaces: observation.ownedSpaces.guard,
76
- groups: observation.groups.guard,
77
- }),
78
- }),
79
- });
80
- }
81
- function hasGroup(domain, groups, subject) {
82
- return groups.some((group) => group.class === domain.classes.Group.key &&
83
- group.props[domain.classes.Group.properties.iss.key] === domain.origin &&
84
- group.props[domain.classes.Group.properties.sub.key] === subject);
85
- }
86
- function conflict(reason, message) {
87
- return Object.freeze({ kind: 'conflict', reason, message });
88
- }
@@ -1,57 +0,0 @@
1
- import { issuer } from '@astrale-os/sdk/auth';
2
- import { LocalBinding } from '@astrale-os/sdk/graph';
3
- import { Path } from '@astrale-os/sdk/graph/path';
4
- import { MutationAST } from '@astrale-os/sdk/mutation';
5
- const user = LocalBinding('user');
6
- const personalSpace = LocalBinding('personalSpace');
7
- export function provisioningRequest(domain, input, role) {
8
- const mutation = MutationAST.build((change) => {
9
- const createdUser = change.createNode({
10
- as: user,
11
- class: domain.classes.User.key,
12
- props: domain.classes.User.properties.from({ displayName: input.displayName }),
13
- });
14
- const createdSpace = change.createNode({
15
- as: personalSpace,
16
- class: domain.classes.Space.key,
17
- props: domain.classes.Space.properties.from({ name: input.personalSpaceName }),
18
- });
19
- change.createEdge({
20
- class: domain.classes.owns_space.key,
21
- source: createdUser,
22
- target: createdSpace,
23
- });
24
- change.createEdge({
25
- class: domain.classes.default_space.key,
26
- source: createdUser,
27
- target: createdSpace,
28
- });
29
- change.createEdge({
30
- class: domain.classes.member_of_group.key,
31
- source: createdUser,
32
- target: Path.project(domain.core.nodes.member.ref),
33
- });
34
- if (role === 'admin') {
35
- change.createEdge({
36
- class: domain.classes.member_of_group.key,
37
- source: createdUser,
38
- target: Path.project(domain.core.nodes.admin.ref),
39
- });
40
- }
41
- });
42
- return Object.freeze({
43
- idempotencyKey: input.operationId,
44
- mutation,
45
- identities: Object.freeze({
46
- [user]: Object.freeze({ issuer: issuer.accept(input.issuer), subject: input.subject }),
47
- }),
48
- });
49
- }
50
- export function provisioningResult(result) {
51
- const userId = result.createdNodes[user];
52
- const personalSpaceId = result.createdNodes[personalSpace];
53
- if (userId === undefined || personalSpaceId === undefined) {
54
- throw new TypeError('Shell provisioning omitted its User or personal Space identity.');
55
- }
56
- return Object.freeze({ userId, personalSpaceId, changed: true });
57
- }