@astrale-domains/shell 0.1.0-beta.12 → 0.1.0-beta.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -46
- package/dist/actions/index.d.ts +1 -0
- package/dist/actions/index.js +1 -0
- package/dist/actions/user/index.d.ts +1 -0
- package/dist/actions/user/index.js +1 -0
- package/dist/actions/user/resolve-home.d.ts +3 -0
- package/dist/actions/user/resolve-home.js +19 -0
- package/dist/application.d.ts +6 -0
- package/dist/application.js +30 -0
- package/dist/entrypoints/application.d.ts +1 -0
- package/dist/entrypoints/application.js +1 -0
- package/dist/entrypoints/runtime.d.ts +1 -0
- package/dist/entrypoints/runtime.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/mutations/group/index.d.ts +1 -0
- package/dist/mutations/group/index.js +1 -0
- package/dist/mutations/group/membership.d.ts +19 -0
- package/dist/mutations/group/membership.js +19 -0
- package/dist/mutations/space/access.d.ts +25 -0
- package/dist/mutations/space/access.js +23 -0
- package/dist/mutations/space/default.d.ts +19 -0
- package/dist/mutations/space/default.js +23 -0
- package/dist/mutations/space/index.d.ts +2 -0
- package/dist/mutations/space/index.js +2 -0
- package/dist/mutations/user/index.d.ts +1 -0
- package/dist/mutations/user/index.js +1 -0
- package/dist/mutations/user/repair.d.ts +37 -0
- package/dist/mutations/user/repair.js +83 -0
- package/dist/queries/space/access.d.ts +9 -0
- package/dist/queries/space/access.js +12 -0
- package/dist/queries/space/admin.d.ts +9 -0
- package/dist/queries/space/admin.js +20 -0
- package/dist/queries/space/index.d.ts +3 -0
- package/dist/queries/space/index.js +3 -0
- package/dist/queries/space/member.d.ts +9 -0
- package/dist/queries/space/member.js +17 -0
- package/dist/queries/user/groups.d.ts +9 -0
- package/dist/queries/user/groups.js +17 -0
- package/dist/queries/user/home.d.ts +9 -0
- package/dist/queries/user/home.js +21 -0
- package/dist/queries/user/index.d.ts +5 -0
- package/dist/queries/user/index.js +5 -0
- package/dist/queries/user/owned-spaces.d.ts +9 -0
- package/dist/queries/user/owned-spaces.js +17 -0
- package/dist/queries/user/profile.d.ts +4 -0
- package/dist/queries/user/profile.js +16 -0
- package/dist/queries/user/setup.d.ts +13 -0
- package/dist/queries/user/setup.js +16 -0
- package/dist/runtime.d.ts +10 -0
- package/dist/runtime.js +17 -0
- package/dist/schema/domain/index.d.ts +69 -1640
- package/dist/schema/domain/index.js +3 -2
- package/dist/schema/group/class.d.ts +13 -321
- package/dist/schema/group/class.js +3 -2
- package/dist/schema/group/methods.d.ts +2 -2
- package/dist/schema/group/methods.js +1 -5
- package/dist/schema/index.d.ts +6 -6
- package/dist/schema/space/class.d.ts +10 -78
- package/dist/schema/space/class.js +3 -2
- package/dist/schema/space/methods.d.ts +4 -4
- package/dist/schema/space/methods.js +1 -5
- package/dist/schema/user/class.d.ts +14 -274
- package/dist/schema/user/class.js +5 -5
- package/dist/workflows/group/assign.d.ts +3 -0
- package/dist/workflows/group/assign.js +3 -0
- package/dist/workflows/group/change.d.ts +6 -0
- package/dist/workflows/group/change.js +31 -0
- package/dist/workflows/group/index.d.ts +3 -0
- package/dist/workflows/group/index.js +3 -0
- package/dist/workflows/group/unassign.d.ts +3 -0
- package/dist/workflows/group/unassign.js +3 -0
- package/dist/workflows/index.d.ts +3 -0
- package/dist/workflows/index.js +3 -0
- package/dist/workflows/setup/admin.d.ts +3 -0
- package/dist/workflows/setup/admin.js +3 -0
- package/dist/workflows/setup/index.d.ts +5 -0
- package/dist/workflows/setup/index.js +5 -0
- package/dist/workflows/setup/member.d.ts +3 -0
- package/dist/workflows/setup/member.js +3 -0
- package/dist/workflows/setup/plan.d.ts +18 -0
- package/dist/workflows/setup/plan.js +88 -0
- package/dist/workflows/setup/provision.d.ts +5 -0
- package/dist/workflows/setup/provision.js +57 -0
- package/dist/workflows/setup/run.d.ts +7 -0
- package/dist/workflows/setup/run.js +74 -0
- package/dist/workflows/space/access.d.ts +7 -0
- package/dist/workflows/space/access.js +41 -0
- package/dist/workflows/space/add.d.ts +3 -0
- package/dist/workflows/space/add.js +3 -0
- package/dist/workflows/space/default.d.ts +3 -0
- package/dist/workflows/space/default.js +37 -0
- package/dist/workflows/space/index.d.ts +4 -0
- package/dist/workflows/space/index.js +4 -0
- package/dist/workflows/space/remove.d.ts +3 -0
- package/dist/workflows/space/remove.js +3 -0
- package/package.json +27 -11
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { issuer } from '@astrale-os/sdk/auth';
|
|
2
|
+
import { ResponseError, TransportError } from '@astrale-os/sdk/client';
|
|
3
|
+
import { InvocationError } from '@astrale-os/sdk/execution';
|
|
4
|
+
import { Path } from '@astrale-os/sdk/graph/path';
|
|
5
|
+
import { repairUser } from '#mutations/user';
|
|
6
|
+
import { observeUserSetup } from '#queries/user';
|
|
7
|
+
import { planUserSetup } from './plan.js';
|
|
8
|
+
import { provisioningRequest, provisioningResult } from './provision.js';
|
|
9
|
+
export async function runSetup(context, role) {
|
|
10
|
+
const { client, input, step } = context;
|
|
11
|
+
const identity = Object.freeze({
|
|
12
|
+
issuer: issuer.accept(input.issuer),
|
|
13
|
+
subject: input.subject,
|
|
14
|
+
});
|
|
15
|
+
const existing = await step.run('resolve-identity', () => client.auth.whois(identity));
|
|
16
|
+
if (existing === null) {
|
|
17
|
+
try {
|
|
18
|
+
const provisioned = await step.run('provision-identity', () => client.auth.provision(provisioningRequest(context.domain, input, role)));
|
|
19
|
+
return provisioningResult(provisioned);
|
|
20
|
+
}
|
|
21
|
+
catch (cause) {
|
|
22
|
+
if (!recoverableProvisionFailure(cause))
|
|
23
|
+
throw cause;
|
|
24
|
+
const recovered = await step.run('recover-provisioned-identity', () => client.auth.whois(identity));
|
|
25
|
+
if (recovered === null)
|
|
26
|
+
throw cause;
|
|
27
|
+
return convergeExisting(context, role, recovered.id, true);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return convergeExisting(context, role, existing.id, false);
|
|
31
|
+
}
|
|
32
|
+
async function convergeExisting(context, role, userId, changedBeforeObservation) {
|
|
33
|
+
const observed = await context.step.run('observe-user-setup', () => context.query(observeUserSetup, Path.id(userId).raw));
|
|
34
|
+
const plan = planUserSetup(context.domain, context.input, role, observed);
|
|
35
|
+
if (plan.kind === 'conflict')
|
|
36
|
+
return setupConflict(plan);
|
|
37
|
+
if (plan.kind === 'unchanged') {
|
|
38
|
+
return Object.freeze({
|
|
39
|
+
userId,
|
|
40
|
+
personalSpaceId: plan.personalSpaceId,
|
|
41
|
+
changed: changedBeforeObservation,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
const repaired = await context.step.run('repair-user-setup', () => context.mutate(repairUser, plan.repair));
|
|
45
|
+
if (repaired.ok) {
|
|
46
|
+
return Object.freeze({ userId, personalSpaceId: repaired.personalSpaceId, changed: true });
|
|
47
|
+
}
|
|
48
|
+
if (repaired.outcomeKnown) {
|
|
49
|
+
throw new InvocationError('CONFLICT', 'Shell User setup conflicted with concurrent graph state.', {
|
|
50
|
+
reason: { code: repaired.code, details: {} },
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
const recovered = await context.step.run('recover-user-repair', () => context.query(observeUserSetup, Path.id(userId).raw));
|
|
54
|
+
const recoveredPlan = planUserSetup(context.domain, context.input, role, recovered);
|
|
55
|
+
if (recoveredPlan.kind === 'conflict')
|
|
56
|
+
return setupConflict(recoveredPlan);
|
|
57
|
+
if (recoveredPlan.kind === 'unchanged') {
|
|
58
|
+
return Object.freeze({ userId, personalSpaceId: recoveredPlan.personalSpaceId, changed: true });
|
|
59
|
+
}
|
|
60
|
+
throw new InvocationError('BACKEND_UNAVAILABLE', 'Shell User setup outcome remains unknown after exact state observation.', { reason: { code: repaired.code, details: {} } });
|
|
61
|
+
}
|
|
62
|
+
function setupConflict(plan) {
|
|
63
|
+
throw new InvocationError('CONFLICT', plan.message, {
|
|
64
|
+
reason: { code: plan.reason, details: {} },
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
function recoverableProvisionFailure(cause) {
|
|
68
|
+
if (cause instanceof TransportError) {
|
|
69
|
+
return cause.context.kind === 'invocation' && cause.context.delivery === 'unknown';
|
|
70
|
+
}
|
|
71
|
+
return (cause instanceof ResponseError &&
|
|
72
|
+
(cause.reason?.code === 'PROVISION_CONFLICT' ||
|
|
73
|
+
cause.reason?.code === 'PROVISION_PRECONDITION_FAILED'));
|
|
74
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { WorkflowContext } from '@astrale-os/sdk/workflow';
|
|
2
|
+
import type { S, Shell } from '#schema';
|
|
3
|
+
export type DesiredSpaceAccess = 'member' | 'admin' | null;
|
|
4
|
+
type SpaceAccessCallable = typeof S.classes.Space.self.methods.addUser | typeof S.classes.Space.self.methods.removeUser;
|
|
5
|
+
type SpaceAccessContext = WorkflowContext<SpaceAccessCallable, Shell>;
|
|
6
|
+
export declare function setUserAccess(context: SpaceAccessContext, desired: DesiredSpaceAccess): Promise<Readonly<Record<never, never>>>;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { InvocationError } from '@astrale-os/sdk/execution';
|
|
2
|
+
import { changeSpaceAccess } from '#mutations/space';
|
|
3
|
+
import { observeDirectSpaceAccess } from '#queries/space';
|
|
4
|
+
export async function setUserAccess(context, desired) {
|
|
5
|
+
const observed = await context.step.run('observe-space-access', () => context.query(observeDirectSpaceAccess, context.input.user));
|
|
6
|
+
const current = state(observed, context.self);
|
|
7
|
+
if (current === desired)
|
|
8
|
+
return Object.freeze({});
|
|
9
|
+
const result = await context.step.run('change-space-access', () => context.mutate(changeSpaceAccess, {
|
|
10
|
+
user: context.input.user,
|
|
11
|
+
space: context.self,
|
|
12
|
+
addMember: desired === 'member' && current !== 'member',
|
|
13
|
+
removeMember: current === 'member' && desired !== 'member',
|
|
14
|
+
addAdmin: desired === 'admin' && current !== 'admin',
|
|
15
|
+
removeAdmin: current === 'admin' && desired !== 'admin',
|
|
16
|
+
guards: { member: observed.member.guard, admin: observed.admin.guard },
|
|
17
|
+
}));
|
|
18
|
+
if (result.ok)
|
|
19
|
+
return Object.freeze({});
|
|
20
|
+
if (result.outcomeKnown)
|
|
21
|
+
return accessConflict(result.code);
|
|
22
|
+
const recovered = await context.step.run('recover-space-access', () => context.query(observeDirectSpaceAccess, context.input.user));
|
|
23
|
+
if (state(recovered, context.self) === desired)
|
|
24
|
+
return Object.freeze({});
|
|
25
|
+
throw new InvocationError('BACKEND_UNAVAILABLE', 'Space access outcome remains unknown after exact observation.', { reason: { code: result.code, details: {} } });
|
|
26
|
+
}
|
|
27
|
+
function state(observation, space) {
|
|
28
|
+
const member = observation.member.spaces.some(({ id }) => id === space);
|
|
29
|
+
const admin = observation.admin.spaces.some(({ id }) => id === space);
|
|
30
|
+
if (member && admin) {
|
|
31
|
+
throw new InvocationError('CONFLICT', 'The User has conflicting direct Space access edges.', {
|
|
32
|
+
reason: { code: 'SPACE_ACCESS_AMBIGUOUS', details: {} },
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return admin ? 'admin' : member ? 'member' : null;
|
|
36
|
+
}
|
|
37
|
+
function accessConflict(code) {
|
|
38
|
+
throw new InvocationError('CONFLICT', 'Space access conflicted with concurrent graph state.', {
|
|
39
|
+
reason: { code, details: {} },
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { InvocationError } from '@astrale-os/sdk/execution';
|
|
2
|
+
import { Path } from '@astrale-os/sdk/graph/path';
|
|
3
|
+
import { defineWorkflow, } from '@astrale-os/sdk/workflow';
|
|
4
|
+
import { changeDefaultSpace } from '#mutations/space';
|
|
5
|
+
import { observeHome } from '#queries/user';
|
|
6
|
+
async function setDefault(context) {
|
|
7
|
+
const user = context.caller.principal;
|
|
8
|
+
const observed = await context.step.run('observe-default-space', () => context.query(observeHome, Path.id(user).raw));
|
|
9
|
+
if (observed.spaces.length > 1) {
|
|
10
|
+
throw new InvocationError('CONFLICT', 'The Shell User has more than one default Space.', {
|
|
11
|
+
reason: { code: 'MULTIPLE_DEFAULT_SPACES', details: {} },
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
if (observed.spaces[0]?.id === context.self)
|
|
15
|
+
return Object.freeze({});
|
|
16
|
+
const result = await context.step.run('change-default-space', () => context.mutate(changeDefaultSpace, {
|
|
17
|
+
user,
|
|
18
|
+
...(observed.spaces[0] === undefined ? {} : { previous: observed.spaces[0].id }),
|
|
19
|
+
next: context.self,
|
|
20
|
+
guard: observed.guard,
|
|
21
|
+
}));
|
|
22
|
+
if (result.ok)
|
|
23
|
+
return Object.freeze({});
|
|
24
|
+
if (result.outcomeKnown)
|
|
25
|
+
return defaultConflict(result.code);
|
|
26
|
+
const recovered = await context.step.run('recover-default-space', () => context.query(observeHome, Path.id(user).raw));
|
|
27
|
+
if (recovered.spaces.length === 1 && recovered.spaces[0]?.id === context.self) {
|
|
28
|
+
return Object.freeze({});
|
|
29
|
+
}
|
|
30
|
+
throw new InvocationError('BACKEND_UNAVAILABLE', 'Default Space outcome remains unknown after exact observation.', { reason: { code: result.code, details: {} } });
|
|
31
|
+
}
|
|
32
|
+
function defaultConflict(code) {
|
|
33
|
+
throw new InvocationError('CONFLICT', 'Default Space conflicted with concurrent graph state.', {
|
|
34
|
+
reason: { code, details: {} },
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
export const setDefaultSpaceWorkflow = defineWorkflow()('Space.setDefault', setDefault);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrale-domains/shell",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
4
|
-
"description": "Astrale Shell Domain schema
|
|
3
|
+
"version": "0.1.0-beta.13",
|
|
4
|
+
"description": "Astrale Shell Domain schema and remote product implementation",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
"dist"
|
|
13
13
|
],
|
|
14
14
|
"type": "module",
|
|
15
|
-
"main": "./dist/
|
|
16
|
-
"types": "./dist/
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
17
|
"imports": {
|
|
18
18
|
"#mutations": "./dist/mutations/index.js",
|
|
19
19
|
"#mutations/*": "./dist/mutations/*/index.js",
|
|
@@ -26,8 +26,16 @@
|
|
|
26
26
|
},
|
|
27
27
|
"exports": {
|
|
28
28
|
".": {
|
|
29
|
-
"types": "./dist/
|
|
30
|
-
"import": "./dist/
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"import": "./dist/index.js"
|
|
31
|
+
},
|
|
32
|
+
"./application": {
|
|
33
|
+
"types": "./dist/entrypoints/application.d.ts",
|
|
34
|
+
"import": "./dist/entrypoints/application.js"
|
|
35
|
+
},
|
|
36
|
+
"./runtime": {
|
|
37
|
+
"types": "./dist/runtime.d.ts",
|
|
38
|
+
"import": "./dist/entrypoints/runtime.js"
|
|
31
39
|
},
|
|
32
40
|
"./package.json": "./package.json"
|
|
33
41
|
},
|
|
@@ -39,22 +47,30 @@
|
|
|
39
47
|
"zod": "4.4.3"
|
|
40
48
|
},
|
|
41
49
|
"devDependencies": {
|
|
42
|
-
"@astrale-os/sdk": "0.5.0-beta.
|
|
50
|
+
"@astrale-os/sdk": "0.5.0-beta.51",
|
|
43
51
|
"@astrale/typescript-config": "npm:@jsr/astrale__typescript-config@~1.1.3",
|
|
52
|
+
"@types/node": "^22.0.0",
|
|
44
53
|
"typescript": "7.0.2",
|
|
45
54
|
"vitest": "4.1.10"
|
|
46
55
|
},
|
|
47
56
|
"peerDependencies": {
|
|
48
|
-
"@astrale-os/sdk": ">=0.5.0-beta.
|
|
57
|
+
"@astrale-os/sdk": ">=0.5.0-beta.51 <1.0.0"
|
|
58
|
+
},
|
|
59
|
+
"devEngines": {
|
|
60
|
+
"runtime": {
|
|
61
|
+
"name": "node",
|
|
62
|
+
"version": ">=24 <25 || >=26 <27",
|
|
63
|
+
"onFail": "error"
|
|
64
|
+
}
|
|
49
65
|
},
|
|
50
66
|
"engines": {
|
|
51
|
-
"node": ">=
|
|
67
|
+
"node": ">=22"
|
|
52
68
|
},
|
|
53
69
|
"scripts": {
|
|
54
70
|
"prebuild": "pnpm clean",
|
|
55
|
-
"build": "tsc --paths null &&
|
|
71
|
+
"build": "tsc --project tsconfig.package.json --paths null && node scripts/verify-platform-package.mjs",
|
|
56
72
|
"clean": "rm -rf dist",
|
|
57
|
-
"typecheck": "tsc --noEmit --paths null",
|
|
73
|
+
"typecheck": "tsc --noEmit --paths null && tsc --noEmit -p tsconfig.test.json",
|
|
58
74
|
"test": "NODE_PATH=$PWD/node_modules vitest run --config vitest.config.ts",
|
|
59
75
|
"test:file": "NODE_PATH=$PWD/node_modules vitest run --config vitest.config.ts"
|
|
60
76
|
}
|