@astrale-domains/shell 0.1.0-beta.46 → 0.1.0-beta.49

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/README.md +57 -5
  2. package/dist/domain.d.ts +49 -0
  3. package/dist/domain.js +12 -1
  4. package/dist/functions/namespace/index.js +3 -0
  5. package/dist/functions/namespace/link.js +30 -0
  6. package/dist/functions/namespace/namespace-errors.js +28 -0
  7. package/dist/functions/namespace/namespace-name.js +13 -0
  8. package/dist/functions/namespace/rename.js +31 -0
  9. package/dist/functions/namespace/unlink.js +21 -0
  10. package/dist/index.d.ts +1 -1
  11. package/dist/index.js +1 -1
  12. package/dist/mutations/namespace/alias-write.js +22 -0
  13. package/dist/mutations/namespace/index.js +3 -0
  14. package/dist/mutations/namespace/link-alias.js +15 -0
  15. package/dist/mutations/namespace/rename-alias.js +13 -0
  16. package/dist/mutations/namespace/unlink-alias.js +13 -0
  17. package/dist/queries/namespace/index.js +1 -0
  18. package/dist/queries/namespace/read-designated-node.js +12 -0
  19. package/dist/runtime.d.ts +731 -1
  20. package/dist/runtime.js +10 -1
  21. package/dist/schema/index.d.ts +1 -0
  22. package/dist/schema/index.js +1 -0
  23. package/dist/schema/modules/group/classes/group.d.ts +1 -0
  24. package/dist/schema/modules/group/classes/group.js +2 -0
  25. package/dist/schema/modules/namespace/classes/alias.d.ts +21 -0
  26. package/dist/schema/modules/namespace/classes/alias.js +13 -0
  27. package/dist/schema/modules/namespace/classes/index.d.ts +2 -0
  28. package/dist/schema/modules/namespace/classes/index.js +2 -0
  29. package/dist/schema/modules/namespace/classes/namespace.d.ts +31 -0
  30. package/dist/schema/modules/namespace/classes/namespace.js +10 -0
  31. package/dist/schema/modules/namespace/core/index.d.ts +1 -0
  32. package/dist/schema/modules/namespace/core/index.js +1 -0
  33. package/dist/schema/modules/namespace/core/views-namespace.d.ts +2 -0
  34. package/dist/schema/modules/namespace/core/views-namespace.js +7 -0
  35. package/dist/schema/modules/namespace/functions/index.js +3 -0
  36. package/dist/schema/modules/namespace/functions/link.js +11 -0
  37. package/dist/schema/modules/namespace/functions/rename.js +11 -0
  38. package/dist/schema/modules/namespace/functions/unlink.js +11 -0
  39. package/dist/schema/modules/namespace/index.d.ts +2 -0
  40. package/dist/schema/modules/namespace/index.js +2 -0
  41. package/dist/schema/modules/namespace/types/index.js +1 -0
  42. package/dist/schema/modules/namespace/types/namespace-name.js +7 -0
  43. package/dist/schema/modules/user/classes/user.d.ts +1 -0
  44. package/dist/schema/modules/user/classes/user.js +2 -0
  45. package/dist/schema/schema.d.ts +195 -0
  46. package/dist/schema/schema.js +8 -2
  47. package/package.json +3 -3
package/README.md CHANGED
@@ -1,7 +1,8 @@
1
1
  # Shell Domain
2
2
 
3
3
  Shell owns two concrete Identity classes, User and Group, the member_of_group relation,
4
- the admin/member/applications Core groups, three static methods, one Domain function, and one
4
+ the admin/member/applications Core groups, the Namespace class and its alias relation, the `views`
5
+ Core Namespace, three static methods, three Namespace methods, one Domain function, and one
5
6
  application View, which is its entrypoint. The Schema names the Domain `Shell`. There is no Space, setup function, personal home, or public repair method.
6
7
 
7
8
  ## Contracts
@@ -12,6 +13,12 @@ application View, which is its entrypoint. The Schema names the Domain `Shell`.
12
13
  | Group.assignMember | `{ member: UserPath \| GroupPath, group: GroupPath }` | `{}` |
13
14
  | Group.unassignMember | Same | `{}` |
14
15
 
16
+ | Namespace method | Input | Output |
17
+ | --- | --- | --- |
18
+ | Namespace.link | `{ name: string, target: NodePath }` | `{}` |
19
+ | Namespace.unlink | `{ name: string }` | `{}` |
20
+ | Namespace.rename | `{ name: string, to: string }` | `{}` |
21
+
15
22
  | Domain function | Input | Output |
16
23
  | --- | --- | --- |
17
24
  | listEntrypoints | `{}` | `{ entrypoints: { origin, name, view }[] }` |
@@ -27,7 +34,9 @@ The workspace opens only once listEntrypoints answers, so every profile that adm
27
34
  the workspace holds it. Administrators and members hold both listEntrypoints and View.resolve
28
35
  directly (a Core Edge cannot author Kernel `extends_with`): an invited person, whom Admin assigns
29
36
  to Core member, can open the workspace. The Core `applications` Group carries the same two
30
- capabilities for Identities outside Members. Share them dynamically with
37
+ capabilities for Identities outside Members, and `query`: the `/v/<name>` page reads its Path with
38
+ a Query under the visitor's own rights, so read rules still decide what it sees. Share them
39
+ dynamically with
31
40
  `Group.assignMember({ member: identity, group: applications })`, which composes the profile through
32
41
  `extends_with`; `unassignMember` withdraws it.
33
42
 
@@ -52,6 +61,48 @@ With Kernel Host beta94 or newer, an external caller needs callable authority th
52
61
  identity composition AND a passing callable Policy. Declare exact foreign methods in the calling
53
62
  Domain's requirements; can_use no longer bypasses the Policy. Root and exact owner exceptions remain.
54
63
 
64
+ ## Namespaces and aliases
65
+
66
+ **An alias names a Node. It grants nothing.** A name designates at most one Node in its
67
+ Namespace, for every author: `alias` declares the Kernel key `unique: [['source', 'slug']]` in its
68
+ first release, so the Kernel refuses a taken name on the final state of any Mutation, Shell's or an
69
+ administrator's own `mutate`. Edge identity gives a Node at most one alias per Namespace. An alias
70
+ is a Path, `/:shell.astrale.ai:core.views~"shell.astrale.ai:class.alias"(roadmap)`, and reaches its
71
+ Node only for a reader who may read that Node. Its target endpoint accepts any Node, a Class Shell
72
+ does not own, so the alias detaches when its Node is deleted; the Namespace stays.
73
+
74
+ The Core Namespace `views` holds the names opened at `/v/<name>`. Namespace and alias declare
75
+ `read: true` and `traverse: true`: a reader lists and follows the aliases whose Node it may read,
76
+ and an alias toward any other Node does not exist for it. Shell, through its own alias Edges,
77
+ finds the Node of every name, even one it does not read.
78
+
79
+ `link` names any Node its target Path reaches, whatever Domain owns it: an alias grants nothing,
80
+ and each reader sees only the aliases toward Nodes it may read. Offering only Nodes the
81
+ administrator sees is the interface's courtesy, not a server guarantee; any writer already learns
82
+ from a Mutation's locator whether a Path reaches a Node.
83
+
84
+ | Method | Calls | Refusals |
85
+ | --- | --- | --- |
86
+ | link | One Mutation with Shell's authority; its locator resolves `target` | `INVALID_NAME`, `TARGET_NOT_FOUND`, `NAME_TAKEN`, `TARGET_ALREADY_ALIASED` |
87
+ | unlink | One read of the alias Path with Shell's authority, then one Mutation | `INVALID_NAME`, `NAME_NOT_FOUND` |
88
+ | rename | Same; renaming a name to itself writes nothing | `INVALID_NAME`, `NAME_NOT_FOUND`, `NAME_TAKEN` |
89
+
90
+ A name is an Edge slug in lower case (`INVALID_NAME`). That rule is Shell's: an administrator
91
+ writing through `mutate` bypasses it, never the key. Nothing guards a name's uniqueness but the key.
92
+ `unlink` and `rename` change the alias only while it still carries the name they read, so a
93
+ concurrent rename answers `NAME_NOT_FOUND` instead of changing another name. Shell maps the Kernel's
94
+ sealed refusals:
95
+
96
+ | Kernel refusal of the Mutation | link | unlink | rename |
97
+ | --- | --- | --- | --- |
98
+ | `MUTATION_POST_STATE_INVALID`, rule `duplicate-key` | `NAME_TAKEN` | propagates | `NAME_TAKEN` |
99
+ | `MUTATION_PRECONDITION_FAILED` (the alias Edge exists, is gone, or carries another name) | `TARGET_ALREADY_ALIASED` | `NAME_NOT_FOUND` | `NAME_NOT_FOUND` |
100
+ | `MUTATION_LOCATOR_INVALID` (the target Path reaches no Node, or the target was deleted) | `TARGET_NOT_FOUND` | `NAME_NOT_FOUND` | `NAME_NOT_FOUND` |
101
+
102
+ Every other failure propagates. Core admin holds the three methods, `read` and `traverse` on
103
+ Namespace, and every operation on alias. Administration follows `administersShellClass` on the
104
+ Namespace, as for Groups.
105
+
55
106
  ## Flows
56
107
 
57
108
  ```text
@@ -91,9 +142,10 @@ Kernel E2E evidence, not a handler test, qualifies this behavior.
91
142
 
92
143
  The Shell principal requests all Kernel root functions, the required extends_with operations,
93
144
  and the declared grant/revoke authority. Core admin receives the full declared Shell profile,
94
- including invite, assignMember and unassignMember so humans inheriting admin can call those methods.
95
- Core member receives Download, Introspect, Query, Mutate, View.resolve and listEntrypoints, not
96
- blanket class-read grants.
145
+ including invite, assignMember, unassignMember, link, unlink and rename so humans inheriting admin
146
+ can call those methods. Core member receives Download, Introspect, Query, Mutate, View.resolve and
147
+ listEntrypoints, not blanket class-read grants. Core applications receives listEntrypoints,
148
+ View.resolve and Query.
97
149
  The Domain definition's requirements, including Core Identity capabilities in requirements.core, are owned by domain.ts and materialized by installation,
98
150
  never by invitation handlers.
99
151
 
package/dist/domain.d.ts CHANGED
@@ -7,6 +7,7 @@ declare const frontend: import("@astrale-os/sdk/view").Frontend<import("@astrale
7
7
  readonly classes: {
8
8
  readonly User: import("@astrale-os/sdk/schema").AuthoredNodeClass<{
9
9
  readonly icon: import("@astrale-os/sdk/schema").ClassIcon;
10
+ readonly inheritance: "open";
10
11
  readonly policies: {
11
12
  readonly read: true;
12
13
  };
@@ -30,6 +31,7 @@ declare const frontend: import("@astrale-os/sdk/view").Frontend<import("@astrale
30
31
  }>;
31
32
  readonly Group: import("@astrale-os/sdk/schema").AuthoredNodeClass<{
32
33
  readonly icon: import("@astrale-os/sdk/schema").ClassIcon;
34
+ readonly inheritance: "open";
33
35
  readonly policies: {
34
36
  readonly read: true;
35
37
  };
@@ -67,6 +69,52 @@ declare const frontend: import("@astrale-os/sdk/view").Frontend<import("@astrale
67
69
  readonly constraints: Record<never, never>;
68
70
  readonly effectiveProperties: never;
69
71
  }>;
72
+ readonly Namespace: import("@astrale-os/sdk/schema").AuthoredNodeClass<{
73
+ readonly icon: import("@astrale-os/sdk/schema").ClassIcon;
74
+ readonly policies: {
75
+ readonly read: true;
76
+ readonly traverse: true;
77
+ };
78
+ readonly extends: readonly [import("@astrale-os/sdk/schema").KernelClass<"Named">, import("@astrale-os/sdk/schema").KernelClass<"Descriptable">];
79
+ readonly methods: {
80
+ readonly link: import("@astrale-os/sdk/schema").Method<import("@astrale-os/sdk/schema").CallableProjection<{
81
+ name: string;
82
+ target: string;
83
+ }, {
84
+ readonly mode: 'value';
85
+ readonly value: Record<string, never>;
86
+ }, "authorized", false, false, undefined, undefined>>;
87
+ readonly unlink: import("@astrale-os/sdk/schema").Method<import("@astrale-os/sdk/schema").CallableProjection<{
88
+ name: string;
89
+ }, {
90
+ readonly mode: 'value';
91
+ readonly value: Record<string, never>;
92
+ }, "authorized", false, false, undefined, undefined>>;
93
+ readonly rename: import("@astrale-os/sdk/schema").Method<import("@astrale-os/sdk/schema").CallableProjection<{
94
+ name: string;
95
+ to: string;
96
+ }, {
97
+ readonly mode: 'value';
98
+ readonly value: Record<string, never>;
99
+ }, "authorized", false, false, undefined, undefined>>;
100
+ };
101
+ }>;
102
+ readonly alias: import("@astrale-os/sdk/schema").DirectedEdgeClass<{
103
+ readonly depth: 0;
104
+ readonly abstract: false;
105
+ readonly extends: readonly [];
106
+ readonly properties: never;
107
+ readonly source: {
108
+ readonly as: "namespace";
109
+ readonly outgoing: "0..*";
110
+ };
111
+ readonly target: {
112
+ readonly as: "node";
113
+ readonly incoming: "0..*";
114
+ };
115
+ readonly constraints: Record<never, never>;
116
+ readonly effectiveProperties: never;
117
+ }>;
70
118
  };
71
119
  readonly functions: {
72
120
  readonly listEntrypoints: import("@astrale-os/sdk/schema").Function<import("@astrale-os/sdk/schema").CallableProjection<Record<string, never>, {
@@ -94,6 +142,7 @@ declare const frontend: import("@astrale-os/sdk/view").Frontend<import("@astrale
94
142
  readonly admin: import("@astrale-os/sdk/schema").CoreNode;
95
143
  readonly applications: import("@astrale-os/sdk/schema").CoreNode;
96
144
  readonly member: import("@astrale-os/sdk/schema").CoreNode;
145
+ readonly views: import("@astrale-os/sdk/schema").CoreNode;
97
146
  };
98
147
  };
99
148
  }, {
package/dist/domain.js CHANGED
@@ -31,6 +31,9 @@ const domainRequirements = requirements({
31
31
  S.classes.User.static.methods.invite,
32
32
  S.classes.Group.static.methods.assignMember,
33
33
  S.classes.Group.static.methods.unassignMember,
34
+ S.classes.Namespace.methods.link,
35
+ S.classes.Namespace.methods.unlink,
36
+ S.classes.Namespace.methods.rename,
34
37
  // Core Edges cannot author Kernel extends_with, so administrators hold it directly.
35
38
  S.functions.listEntrypoints,
36
39
  ],
@@ -44,6 +47,8 @@ const domainRequirements = requirements({
44
47
  class: S.classes.member_of_group,
45
48
  operations: ['create', 'read', 'traverse', 'update', 'delete'],
46
49
  },
50
+ { class: S.classes.Namespace, operations: ['read', 'traverse'] },
51
+ { class: S.classes.alias, operations: ['create', 'read', 'traverse', 'update', 'delete'] },
47
52
  { class: K.classes.extends_with, operations: ['create', 'read', 'traverse', 'delete'] },
48
53
  ],
49
54
  authority: {
@@ -66,8 +71,14 @@ const domainRequirements = requirements({
66
71
  },
67
72
  {
68
73
  // For Identities outside Members: Group.assignMember(identity, applications) composes it.
74
+ // `query` opens `/v/<name>`: the full page reads that Path with a Query under the visitor's
75
+ // own rights, so read rules still decide what it sees.
69
76
  subject: S.core.nodes.applications,
70
- functions: [S.functions.listEntrypoints, K.classes.View.static.methods.resolve],
77
+ functions: [
78
+ S.functions.listEntrypoints,
79
+ K.classes.View.static.methods.resolve,
80
+ K.functions.query,
81
+ ],
71
82
  },
72
83
  ],
73
84
  });
@@ -0,0 +1,3 @@
1
+ export { link } from './link.js';
2
+ export { unlink } from './unlink.js';
3
+ export { rename } from './rename.js';
@@ -0,0 +1,30 @@
1
+ import { defineAction } from '@astrale-os/sdk/action';
2
+ import { Path } from '@astrale-os/sdk/graph/path';
3
+ import { linkAlias } from '../../mutations/namespace/index.js';
4
+ import { namespaceErrors } from './namespace-errors.js';
5
+ import { namespaceSlug } from './namespace-name.js';
6
+ /**
7
+ * One Mutation with Shell's own authority. An alias grants nothing and each reader sees only the
8
+ * aliases toward Nodes it may read, so `link` names any Node its target Path reaches; the `alias`
9
+ * key keeps a name to one Node for every author.
10
+ */
11
+ export const link = defineAction()('Namespace.link', { errors: namespaceErrors }, async ({ error, mutate, input, self }) => {
12
+ const name = namespaceSlug(input.name);
13
+ if (name === undefined)
14
+ throw error('INVALID_NAME', { name: input.name });
15
+ const result = await mutate(linkAlias, {
16
+ namespace: self,
17
+ name,
18
+ target: Path.parse(input.target),
19
+ });
20
+ if (result.ok)
21
+ return {};
22
+ switch (result.refusal) {
23
+ case 'NAME_TAKEN':
24
+ throw error('NAME_TAKEN', { name });
25
+ case 'TARGET_ALREADY_ALIASED':
26
+ throw error('TARGET_ALREADY_ALIASED', {});
27
+ case 'TARGET_NOT_FOUND':
28
+ throw error('TARGET_NOT_FOUND', {});
29
+ }
30
+ });
@@ -0,0 +1,28 @@
1
+ import { z } from 'zod';
2
+ export const namespaceErrors = {
3
+ INVALID_NAME: {
4
+ family: 'VALIDATION_ERROR',
5
+ message: 'A name uses lower-case letters, digits, ".", "_" and "-", and starts and ends with a letter or digit.',
6
+ details: z.strictObject({ name: z.string() }),
7
+ },
8
+ TARGET_NOT_FOUND: {
9
+ family: 'NOT_FOUND',
10
+ message: 'The target Path reaches no Node.',
11
+ details: z.strictObject({}),
12
+ },
13
+ NAME_TAKEN: {
14
+ family: 'CONFLICT',
15
+ message: 'This name already designates a Node in this Namespace.',
16
+ details: z.strictObject({ name: z.string() }),
17
+ },
18
+ TARGET_ALREADY_ALIASED: {
19
+ family: 'CONFLICT',
20
+ message: 'This Node already has an alias in this Namespace; rename it instead.',
21
+ details: z.strictObject({}),
22
+ },
23
+ NAME_NOT_FOUND: {
24
+ family: 'NOT_FOUND',
25
+ message: 'No alias of this Namespace has this name.',
26
+ details: z.strictObject({ name: z.string() }),
27
+ },
28
+ };
@@ -0,0 +1,13 @@
1
+ import { EdgeSlug, isEdgeSlug } from '@astrale-os/sdk/graph/edge';
2
+ import { Path } from '@astrale-os/sdk/graph/path';
3
+ /** The Edge slug a name is: an exact Edge slug, in lower case. Anything else is no name. */
4
+ export function namespaceSlug(name) {
5
+ return isEdgeSlug(name) && name === name.toLowerCase() ? EdgeSlug(name) : undefined;
6
+ }
7
+ /**
8
+ * The Path of one name of a Namespace: `@<namespace>~"<alias>"(<name>)`. The `alias` key comes from
9
+ * the admitted Domain, so the Worker never loads the Schema's builder.
10
+ */
11
+ export function aliasPath(domain, namespace, name) {
12
+ return Path.id(namespace).out(domain.classes.alias.key, { slug: name });
13
+ }
@@ -0,0 +1,31 @@
1
+ import { defineAction } from '@astrale-os/sdk/action';
2
+ import { renameAlias } from '../../mutations/namespace/index.js';
3
+ import { readDesignatedNode } from '../../queries/namespace/index.js';
4
+ import { namespaceErrors } from './namespace-errors.js';
5
+ import { aliasPath, namespaceSlug } from './namespace-name.js';
6
+ /**
7
+ * One read of the Node the name designates, then one Mutation renaming its alias while it still
8
+ * carries that name. Renaming a name to itself finds the Node and writes nothing.
9
+ */
10
+ export const rename = defineAction()('Namespace.rename', { errors: namespaceErrors }, async ({ domain, error, query, mutate, input, self }) => {
11
+ const name = namespaceSlug(input.name);
12
+ if (name === undefined)
13
+ throw error('INVALID_NAME', { name: input.name });
14
+ const to = namespaceSlug(input.to);
15
+ if (to === undefined)
16
+ throw error('INVALID_NAME', { name: input.to });
17
+ const target = await query(readDesignatedNode, { path: aliasPath(domain, self, name) });
18
+ if (target === undefined)
19
+ throw error('NAME_NOT_FOUND', { name });
20
+ if (to === name)
21
+ return {};
22
+ const result = await mutate(renameAlias, { namespace: self, target, name, to });
23
+ if (result.ok)
24
+ return {};
25
+ switch (result.refusal) {
26
+ case 'NAME_TAKEN':
27
+ throw error('NAME_TAKEN', { name: to });
28
+ case 'NAME_NOT_FOUND':
29
+ throw error('NAME_NOT_FOUND', { name });
30
+ }
31
+ });
@@ -0,0 +1,21 @@
1
+ import { defineAction } from '@astrale-os/sdk/action';
2
+ import { unlinkAlias } from '../../mutations/namespace/index.js';
3
+ import { readDesignatedNode } from '../../queries/namespace/index.js';
4
+ import { namespaceErrors } from './namespace-errors.js';
5
+ import { aliasPath, namespaceSlug } from './namespace-name.js';
6
+ /**
7
+ * One read of the Node the name designates, then one Mutation deleting its alias while it still
8
+ * carries that name: a concurrent rename answers NAME_NOT_FOUND instead of removing another name.
9
+ */
10
+ export const unlink = defineAction()('Namespace.unlink', { errors: namespaceErrors }, async ({ domain, error, query, mutate, input, self }) => {
11
+ const name = namespaceSlug(input.name);
12
+ if (name === undefined)
13
+ throw error('INVALID_NAME', { name: input.name });
14
+ const target = await query(readDesignatedNode, { path: aliasPath(domain, self, name) });
15
+ if (target === undefined)
16
+ throw error('NAME_NOT_FOUND', { name });
17
+ const result = await mutate(unlinkAlias, { namespace: self, target, name });
18
+ if (result.ok)
19
+ return {};
20
+ throw error('NAME_NOT_FOUND', { name });
21
+ });
package/dist/index.d.ts CHANGED
@@ -1 +1 @@
1
- export { ShellSchema, S, type Shell, type ShellClasses, type ShellSchemaInput, type InvitationSummary, User, Group, member_of_group, adminGroup, memberGroup, } from './schema/index.js';
1
+ export { ShellSchema, S, type Shell, type ShellClasses, type ShellSchemaInput, type InvitationSummary, User, Group, member_of_group, adminGroup, memberGroup, Namespace, alias, viewsNamespace, } from './schema/index.js';
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- export { ShellSchema, S, User, Group, member_of_group, adminGroup, memberGroup, } from './schema/index.js';
1
+ export { ShellSchema, S, User, Group, member_of_group, adminGroup, memberGroup, Namespace, alias, viewsNamespace, } from './schema/index.js';
@@ -0,0 +1,22 @@
1
+ export const written = Object.freeze({ ok: true });
2
+ /** Turn the Kernel refusals an alias write expects into its Namespace refusal; others propagate. */
3
+ export function rejectAliasWrite(refusals) {
4
+ return (failure) => {
5
+ if (failure.outcomeKnown) {
6
+ if (refusals.name !== undefined &&
7
+ failure.code === 'MUTATION_POST_STATE_INVALID' &&
8
+ 'rule' in failure &&
9
+ failure.rule === 'duplicate-key') {
10
+ return refused(refusals.name);
11
+ }
12
+ if (failure.code === 'MUTATION_PRECONDITION_FAILED')
13
+ return refused(refusals.edge);
14
+ if (failure.code === 'MUTATION_LOCATOR_INVALID')
15
+ return refused(refusals.node);
16
+ }
17
+ throw failure;
18
+ };
19
+ }
20
+ function refused(refusal) {
21
+ return Object.freeze({ ok: false, refusal });
22
+ }
@@ -0,0 +1,3 @@
1
+ export { linkAlias } from './link-alias.js';
2
+ export { unlinkAlias } from './unlink-alias.js';
3
+ export { renameAlias } from './rename-alias.js';
@@ -0,0 +1,15 @@
1
+ import { defineMutation } from '@astrale-os/sdk/mutation';
2
+ import { rejectAliasWrite, written } from './alias-write.js';
3
+ /** Create the alias `name` from a Namespace to the one Node a Path reaches. */
4
+ export const linkAlias = defineMutation()((domain) => ({
5
+ id: 'shell.namespace.alias.link',
6
+ build({ namespace, name, target }, mutation) {
7
+ mutation.createEdge({ class: domain.classes.alias, source: namespace, target, slug: name });
8
+ },
9
+ project: () => written,
10
+ reject: rejectAliasWrite({
11
+ name: 'NAME_TAKEN',
12
+ edge: 'TARGET_ALREADY_ALIASED',
13
+ node: 'TARGET_NOT_FOUND',
14
+ }),
15
+ }));
@@ -0,0 +1,13 @@
1
+ import { defineMutation } from '@astrale-os/sdk/mutation';
2
+ import { rejectAliasWrite, written } from './alias-write.js';
3
+ /** Rename the alias `name` of one Node in a Namespace to `to`; the Node it names stays the same. */
4
+ export const renameAlias = defineMutation()((domain) => ({
5
+ id: 'shell.namespace.alias.rename',
6
+ build({ namespace, target, name, to }, mutation) {
7
+ const alias = { class: domain.classes.alias, source: namespace, target };
8
+ mutation.expect.edge({ ...alias, slug: name });
9
+ mutation.updateEdge({ ...alias, slug: to });
10
+ },
11
+ project: () => written,
12
+ reject: rejectAliasWrite({ name: 'NAME_TAKEN', edge: 'NAME_NOT_FOUND', node: 'NAME_NOT_FOUND' }),
13
+ }));
@@ -0,0 +1,13 @@
1
+ import { defineMutation } from '@astrale-os/sdk/mutation';
2
+ import { rejectAliasWrite, written } from './alias-write.js';
3
+ /** Delete the alias `name` of one Node in a Namespace: Edge identity gives the Node at most one. */
4
+ export const unlinkAlias = defineMutation()((domain) => ({
5
+ id: 'shell.namespace.alias.unlink',
6
+ build({ namespace, target, name }, mutation) {
7
+ const alias = { class: domain.classes.alias, source: namespace, target };
8
+ mutation.expect.edge({ ...alias, slug: name });
9
+ mutation.deleteEdge(alias);
10
+ },
11
+ project: () => written,
12
+ reject: rejectAliasWrite({ edge: 'NAME_NOT_FOUND', node: 'NAME_NOT_FOUND' }),
13
+ }));
@@ -0,0 +1 @@
1
+ export { readDesignatedNode } from './read-designated-node.js';
@@ -0,0 +1,12 @@
1
+ import { defineQuery, Query, queryResult } from '@astrale-os/sdk/query';
2
+ /**
3
+ * The one Node a Path designates for the authority that reads it, or nothing: a Node it cannot
4
+ * see, and a name no alias holds, read the same.
5
+ */
6
+ export const readDesignatedNode = defineQuery()(() => ({
7
+ id: 'shell.namespace.designated-node',
8
+ page: { size: 1 },
9
+ pagination: { maximumPages: 1 },
10
+ build: ({ path }) => Query.from({ nodes: [path] }).select({ kind: 'nodes', projection: { kind: 'reference' } }),
11
+ project: (pages) => queryResult.completeNodeIds(pages, 'Shell designated Node')[0],
12
+ }));