@aigne/afs 1.11.0-beta → 1.11.0-beta.10

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 (164) hide show
  1. package/LICENSE.md +17 -84
  2. package/README.md +4 -13
  3. package/dist/_virtual/rolldown_runtime.mjs +7 -0
  4. package/dist/afs.cjs +1330 -0
  5. package/dist/afs.d.cts +275 -0
  6. package/dist/afs.d.cts.map +1 -0
  7. package/dist/afs.d.mts +275 -0
  8. package/dist/afs.d.mts.map +1 -0
  9. package/dist/afs.mjs +1331 -0
  10. package/dist/afs.mjs.map +1 -0
  11. package/dist/capabilities/index.d.mts +2 -0
  12. package/dist/capabilities/types.d.cts +100 -0
  13. package/dist/capabilities/types.d.cts.map +1 -0
  14. package/dist/capabilities/types.d.mts +100 -0
  15. package/dist/capabilities/types.d.mts.map +1 -0
  16. package/dist/capabilities/world-mapping.cjs +20 -0
  17. package/dist/capabilities/world-mapping.d.cts +139 -0
  18. package/dist/capabilities/world-mapping.d.cts.map +1 -0
  19. package/dist/capabilities/world-mapping.d.mts +139 -0
  20. package/dist/capabilities/world-mapping.d.mts.map +1 -0
  21. package/dist/capabilities/world-mapping.mjs +20 -0
  22. package/dist/capabilities/world-mapping.mjs.map +1 -0
  23. package/dist/error.cjs +63 -0
  24. package/dist/error.d.cts +39 -0
  25. package/dist/error.d.cts.map +1 -0
  26. package/dist/error.d.mts +39 -0
  27. package/dist/error.d.mts.map +1 -0
  28. package/dist/error.mjs +59 -0
  29. package/dist/error.mjs.map +1 -0
  30. package/dist/index.cjs +72 -345
  31. package/dist/index.d.cts +18 -300
  32. package/dist/index.d.mts +20 -300
  33. package/dist/index.mjs +16 -342
  34. package/dist/loader/index.cjs +110 -0
  35. package/dist/loader/index.d.cts +48 -0
  36. package/dist/loader/index.d.cts.map +1 -0
  37. package/dist/loader/index.d.mts +48 -0
  38. package/dist/loader/index.d.mts.map +1 -0
  39. package/dist/loader/index.mjs +110 -0
  40. package/dist/loader/index.mjs.map +1 -0
  41. package/dist/meta/index.cjs +4 -0
  42. package/dist/meta/index.mjs +6 -0
  43. package/dist/meta/kind.cjs +161 -0
  44. package/dist/meta/kind.d.cts +134 -0
  45. package/dist/meta/kind.d.cts.map +1 -0
  46. package/dist/meta/kind.d.mts +134 -0
  47. package/dist/meta/kind.d.mts.map +1 -0
  48. package/dist/meta/kind.mjs +157 -0
  49. package/dist/meta/kind.mjs.map +1 -0
  50. package/dist/meta/path.cjs +116 -0
  51. package/dist/meta/path.d.cts +43 -0
  52. package/dist/meta/path.d.cts.map +1 -0
  53. package/dist/meta/path.d.mts +43 -0
  54. package/dist/meta/path.d.mts.map +1 -0
  55. package/dist/meta/path.mjs +112 -0
  56. package/dist/meta/path.mjs.map +1 -0
  57. package/dist/meta/type.d.cts +96 -0
  58. package/dist/meta/type.d.cts.map +1 -0
  59. package/dist/meta/type.d.mts +96 -0
  60. package/dist/meta/type.d.mts.map +1 -0
  61. package/dist/meta/validation.cjs +77 -0
  62. package/dist/meta/validation.d.cts +19 -0
  63. package/dist/meta/validation.d.cts.map +1 -0
  64. package/dist/meta/validation.d.mts +19 -0
  65. package/dist/meta/validation.d.mts.map +1 -0
  66. package/dist/meta/validation.mjs +77 -0
  67. package/dist/meta/validation.mjs.map +1 -0
  68. package/dist/meta/well-known-kinds.cjs +228 -0
  69. package/dist/meta/well-known-kinds.d.cts +52 -0
  70. package/dist/meta/well-known-kinds.d.cts.map +1 -0
  71. package/dist/meta/well-known-kinds.d.mts +52 -0
  72. package/dist/meta/well-known-kinds.d.mts.map +1 -0
  73. package/dist/meta/well-known-kinds.mjs +219 -0
  74. package/dist/meta/well-known-kinds.mjs.map +1 -0
  75. package/dist/node_modules/.pnpm/@types_json-schema@7.0.15/node_modules/@types/json-schema/index.d.cts +141 -0
  76. package/dist/node_modules/.pnpm/@types_json-schema@7.0.15/node_modules/@types/json-schema/index.d.cts.map +1 -0
  77. package/dist/node_modules/.pnpm/@types_json-schema@7.0.15/node_modules/@types/json-schema/index.d.mts +141 -0
  78. package/dist/node_modules/.pnpm/@types_json-schema@7.0.15/node_modules/@types/json-schema/index.d.mts.map +1 -0
  79. package/dist/path.cjs +255 -0
  80. package/dist/path.d.cts +93 -0
  81. package/dist/path.d.cts.map +1 -0
  82. package/dist/path.d.mts +93 -0
  83. package/dist/path.d.mts.map +1 -0
  84. package/dist/path.mjs +249 -0
  85. package/dist/path.mjs.map +1 -0
  86. package/dist/provider/base.cjs +425 -0
  87. package/dist/provider/base.d.cts +175 -0
  88. package/dist/provider/base.d.cts.map +1 -0
  89. package/dist/provider/base.d.mts +175 -0
  90. package/dist/provider/base.d.mts.map +1 -0
  91. package/dist/provider/base.mjs +426 -0
  92. package/dist/provider/base.mjs.map +1 -0
  93. package/dist/provider/decorators.cjs +268 -0
  94. package/dist/provider/decorators.d.cts +244 -0
  95. package/dist/provider/decorators.d.cts.map +1 -0
  96. package/dist/provider/decorators.d.mts +244 -0
  97. package/dist/provider/decorators.d.mts.map +1 -0
  98. package/dist/provider/decorators.mjs +256 -0
  99. package/dist/provider/decorators.mjs.map +1 -0
  100. package/dist/provider/index.cjs +19 -0
  101. package/dist/provider/index.d.cts +5 -0
  102. package/dist/provider/index.d.mts +5 -0
  103. package/dist/provider/index.mjs +5 -0
  104. package/dist/provider/router.cjs +185 -0
  105. package/dist/provider/router.d.cts +50 -0
  106. package/dist/provider/router.d.cts.map +1 -0
  107. package/dist/provider/router.d.mts +50 -0
  108. package/dist/provider/router.d.mts.map +1 -0
  109. package/dist/provider/router.mjs +185 -0
  110. package/dist/provider/router.mjs.map +1 -0
  111. package/dist/provider/types.d.cts +113 -0
  112. package/dist/provider/types.d.cts.map +1 -0
  113. package/dist/provider/types.d.mts +113 -0
  114. package/dist/provider/types.d.mts.map +1 -0
  115. package/dist/registry.cjs +358 -0
  116. package/dist/registry.d.cts +96 -0
  117. package/dist/registry.d.cts.map +1 -0
  118. package/dist/registry.d.mts +96 -0
  119. package/dist/registry.d.mts.map +1 -0
  120. package/dist/registry.mjs +360 -0
  121. package/dist/registry.mjs.map +1 -0
  122. package/dist/type.cjs +34 -0
  123. package/dist/type.d.cts +420 -0
  124. package/dist/type.d.cts.map +1 -0
  125. package/dist/type.d.mts +420 -0
  126. package/dist/type.d.mts.map +1 -0
  127. package/dist/type.mjs +33 -0
  128. package/dist/type.mjs.map +1 -0
  129. package/dist/utils/camelize.d.cts.map +1 -1
  130. package/dist/utils/camelize.d.mts.map +1 -1
  131. package/dist/utils/schema.cjs +129 -0
  132. package/dist/utils/schema.d.cts +65 -0
  133. package/dist/utils/schema.d.cts.map +1 -0
  134. package/dist/utils/schema.d.mts +65 -0
  135. package/dist/utils/schema.d.mts.map +1 -0
  136. package/dist/utils/schema.mjs +124 -0
  137. package/dist/utils/schema.mjs.map +1 -0
  138. package/dist/utils/type-utils.d.cts.map +1 -1
  139. package/dist/utils/type-utils.d.mts.map +1 -1
  140. package/dist/utils/uri-template.cjs +123 -0
  141. package/dist/utils/uri-template.d.cts +48 -0
  142. package/dist/utils/uri-template.d.cts.map +1 -0
  143. package/dist/utils/uri-template.d.mts +48 -0
  144. package/dist/utils/uri-template.d.mts.map +1 -0
  145. package/dist/utils/uri-template.mjs +120 -0
  146. package/dist/utils/uri-template.mjs.map +1 -0
  147. package/dist/utils/uri.cjs +49 -0
  148. package/dist/utils/uri.d.cts +34 -0
  149. package/dist/utils/uri.d.cts.map +1 -0
  150. package/dist/utils/uri.d.mts +34 -0
  151. package/dist/utils/uri.d.mts.map +1 -0
  152. package/dist/utils/uri.mjs +49 -0
  153. package/dist/utils/uri.mjs.map +1 -0
  154. package/dist/utils/zod.cjs +6 -8
  155. package/dist/utils/zod.d.cts +2 -2
  156. package/dist/utils/zod.d.cts.map +1 -1
  157. package/dist/utils/zod.d.mts +2 -2
  158. package/dist/utils/zod.d.mts.map +1 -1
  159. package/dist/utils/zod.mjs +6 -8
  160. package/dist/utils/zod.mjs.map +1 -1
  161. package/package.json +27 -4
  162. package/dist/index.d.cts.map +0 -1
  163. package/dist/index.d.mts.map +0 -1
  164. package/dist/index.mjs.map +0 -1
@@ -0,0 +1,93 @@
1
+ //#region src/path.d.ts
2
+ /**
3
+ * AFS Path Validation and Normalization
4
+ *
5
+ * All AFS paths must follow Unix filesystem semantics:
6
+ * - Must be absolute paths (start with /)
7
+ * - Use / as path separator (not Windows \)
8
+ * - No NUL characters in path components
9
+ * - Consistent semantics regardless of underlying platform
10
+ */
11
+ /**
12
+ * Error thrown when path validation fails
13
+ */
14
+ declare class AFSPathError extends Error {
15
+ readonly path: string;
16
+ constructor(message: string, path: string);
17
+ }
18
+ /**
19
+ * Normalize a path by:
20
+ * - Collapsing multiple consecutive slashes
21
+ * - Resolving . (current directory)
22
+ * - Resolving .. (parent directory) while preventing escape above root
23
+ * - Removing trailing slashes (except for root /)
24
+ * - Rejecting whitespace-only path segments
25
+ *
26
+ * @throws AFSPathError if a path segment is whitespace-only
27
+ */
28
+ declare function normalizePath(path: string): string;
29
+ /**
30
+ * Validate an AFS path
31
+ *
32
+ * @param path - The path to validate
33
+ * @throws AFSPathError if the path is invalid
34
+ * @returns The normalized path
35
+ */
36
+ declare function validatePath(path: string): string;
37
+ /**
38
+ * Validate a module name
39
+ *
40
+ * Module names must:
41
+ * - Not be empty or whitespace-only
42
+ * - Not contain / or \
43
+ * - Not contain control characters
44
+ * - Not be "." or ".."
45
+ *
46
+ * @param name - The module name to validate
47
+ * @throws Error if the name is invalid
48
+ */
49
+ declare function validateModuleName(name: string): void;
50
+ /**
51
+ * Parsed canonical AFS path
52
+ */
53
+ interface ParsedCanonicalPath {
54
+ /** Namespace name, or null for default namespace */
55
+ namespace: string | null;
56
+ /** Path within the namespace (always starts with /) */
57
+ path: string;
58
+ }
59
+ /**
60
+ * Check if a string is a canonical AFS path
61
+ *
62
+ * Canonical paths have the format:
63
+ * - $afs/path (default namespace)
64
+ * - $afs:namespace/path (named namespace)
65
+ *
66
+ * @param input - String to check
67
+ * @returns true if input is a canonical AFS path
68
+ */
69
+ declare function isCanonicalPath(input: string): boolean;
70
+ /**
71
+ * Parse a canonical AFS path
72
+ *
73
+ * Canonical paths have the format:
74
+ * - $afs/path (default namespace)
75
+ * - $afs:namespace/path (named namespace)
76
+ *
77
+ * @param canonical - Canonical path string
78
+ * @returns Parsed path with namespace and path
79
+ * @throws AFSPathError if path format is invalid
80
+ */
81
+ declare function parseCanonicalPath(canonical: string): ParsedCanonicalPath;
82
+ /**
83
+ * Create a canonical AFS path from components
84
+ *
85
+ * @param namespace - Namespace name, or null for default namespace
86
+ * @param path - Path within namespace (must start with /)
87
+ * @returns Canonical path string
88
+ * @throws AFSPathError if namespace or path is invalid
89
+ */
90
+ declare function toCanonicalPath(namespace: string | null, path: string): string;
91
+ //#endregion
92
+ export { AFSPathError, ParsedCanonicalPath, isCanonicalPath, normalizePath, parseCanonicalPath, toCanonicalPath, validateModuleName, validatePath };
93
+ //# sourceMappingURL=path.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.d.mts","names":[],"sources":["../src/path.ts"],"mappings":";;AAaA;;;;;;;;;;;cAAa,YAAA,SAAqB,KAAA;EAAA,SAGd,IAAA;cADhB,OAAA,UACgB,IAAA;AAAA;;AAwGpB;;;;;AAyCA;;;;iBA9EgB,aAAA,CAAc,IAAA;AAiJ9B;;;;;AAuDA;;AAvDA,iBA5GgB,YAAA,CAAa,IAAA;;;AAmN7B;;;;;AAoFA;;;;;iBA9PgB,kBAAA,CAAmB,IAAA;;;;UAmElB,mBAAA;;EAEf,SAAA;;EAEA,IAAA;AAAA;;;;;;;;;;;iBAmDc,eAAA,CAAgB,KAAA;;;;;;;;;;;;iBAgDhB,kBAAA,CAAmB,SAAA,WAAoB,mBAAA;;;;;;;;;iBAoFvC,eAAA,CAAgB,SAAA,iBAA0B,IAAA"}
package/dist/path.mjs ADDED
@@ -0,0 +1,249 @@
1
+ //#region src/path.ts
2
+ /**
3
+ * AFS Path Validation and Normalization
4
+ *
5
+ * All AFS paths must follow Unix filesystem semantics:
6
+ * - Must be absolute paths (start with /)
7
+ * - Use / as path separator (not Windows \)
8
+ * - No NUL characters in path components
9
+ * - Consistent semantics regardless of underlying platform
10
+ */
11
+ /**
12
+ * Error thrown when path validation fails
13
+ */
14
+ var AFSPathError = class extends Error {
15
+ constructor(message, path) {
16
+ super(message);
17
+ this.path = path;
18
+ this.name = "AFSPathError";
19
+ }
20
+ };
21
+ /**
22
+ * Characters that are not allowed in AFS paths
23
+ */
24
+ const FORBIDDEN_CHARS = [
25
+ "\0",
26
+ "",
27
+ "",
28
+ "",
29
+ "",
30
+ "",
31
+ "",
32
+ "\x07",
33
+ "\b",
34
+ " ",
35
+ "\n",
36
+ "\v",
37
+ "\f",
38
+ "\r",
39
+ "",
40
+ "",
41
+ "",
42
+ "",
43
+ "",
44
+ "",
45
+ "",
46
+ "",
47
+ "",
48
+ "",
49
+ "",
50
+ "",
51
+ "",
52
+ "\x1B",
53
+ "",
54
+ "",
55
+ "",
56
+ ""
57
+ ];
58
+ /**
59
+ * Check if a string contains any forbidden control characters
60
+ */
61
+ function containsForbiddenChars(str) {
62
+ for (const char of FORBIDDEN_CHARS) if (str.includes(char)) return true;
63
+ return false;
64
+ }
65
+ /**
66
+ * Normalize a path by:
67
+ * - Collapsing multiple consecutive slashes
68
+ * - Resolving . (current directory)
69
+ * - Resolving .. (parent directory) while preventing escape above root
70
+ * - Removing trailing slashes (except for root /)
71
+ * - Rejecting whitespace-only path segments
72
+ *
73
+ * @throws AFSPathError if a path segment is whitespace-only
74
+ */
75
+ function normalizePath(path) {
76
+ const segments = path.split("/").filter((s) => s !== "");
77
+ const result = [];
78
+ for (const segment of segments) {
79
+ if (segment.trim() === "") throw new AFSPathError("Path segment cannot be whitespace-only", path);
80
+ if (segment === ".") continue;
81
+ if (segment === "..") {
82
+ if (result.length > 0) result.pop();
83
+ continue;
84
+ }
85
+ result.push(segment);
86
+ }
87
+ return `/${result.join("/")}`;
88
+ }
89
+ /**
90
+ * Validate an AFS path
91
+ *
92
+ * @param path - The path to validate
93
+ * @throws AFSPathError if the path is invalid
94
+ * @returns The normalized path
95
+ */
96
+ function validatePath(path) {
97
+ if (!path || path.trim() === "") throw new AFSPathError("Path cannot be empty or whitespace-only", path);
98
+ if (containsForbiddenChars(path)) throw new AFSPathError("Path contains forbidden control characters", path);
99
+ if (!path.startsWith("/")) throw new AFSPathError("Path must be absolute (start with /)", path);
100
+ if (path.replace(/^\/+/, "").startsWith("~")) throw new AFSPathError("Path cannot use tilde expansion", path);
101
+ return normalizePath(path);
102
+ }
103
+ /**
104
+ * Validate a module name
105
+ *
106
+ * Module names must:
107
+ * - Not be empty or whitespace-only
108
+ * - Not contain / or \
109
+ * - Not contain control characters
110
+ * - Not be "." or ".."
111
+ *
112
+ * @param name - The module name to validate
113
+ * @throws Error if the name is invalid
114
+ */
115
+ function validateModuleName(name) {
116
+ if (!name || name.trim() === "") throw new Error("Module name cannot be empty or whitespace-only");
117
+ if (containsForbiddenChars(name)) throw new Error(`Invalid module name: ${name}. Module name must not contain control characters`);
118
+ if (name.includes("/")) throw new Error(`Invalid module name: ${name}. Module name must not contain '/'`);
119
+ if (name.includes("\\")) throw new Error(`Invalid module name: ${name}. Module name must not contain '\\'`);
120
+ if (name === ".") throw new Error("Invalid module name: '.'. Module name cannot be '.'");
121
+ if (name === "..") throw new Error("Invalid module name: '..'. Module name cannot be '..'");
122
+ }
123
+ /**
124
+ * Canonical path prefix for AFS
125
+ */
126
+ const CANONICAL_PREFIX = "$afs";
127
+ /**
128
+ * Characters forbidden in namespace names (security-sensitive)
129
+ */
130
+ const NAMESPACE_FORBIDDEN_CHARS = [
131
+ "/",
132
+ "\\",
133
+ ":",
134
+ ";",
135
+ "|",
136
+ "&",
137
+ "`",
138
+ "$",
139
+ "(",
140
+ ")",
141
+ ">",
142
+ "<",
143
+ "\n",
144
+ "\r",
145
+ " ",
146
+ "\0",
147
+ ...FORBIDDEN_CHARS
148
+ ];
149
+ /**
150
+ * Check if a string contains any namespace-forbidden characters
151
+ */
152
+ function containsNamespaceForbiddenChars(str) {
153
+ for (const char of NAMESPACE_FORBIDDEN_CHARS) if (str.includes(char)) return true;
154
+ return false;
155
+ }
156
+ /**
157
+ * Validate a namespace name
158
+ *
159
+ * @throws AFSPathError if namespace is invalid
160
+ */
161
+ function validateNamespace(namespace, fullPath) {
162
+ if (!namespace || namespace.trim() === "") throw new AFSPathError("Namespace cannot be empty or whitespace-only", fullPath);
163
+ if (containsNamespaceForbiddenChars(namespace)) throw new AFSPathError(`Namespace contains forbidden characters: ${namespace}`, fullPath);
164
+ if (namespace === "." || namespace === "..") throw new AFSPathError("Namespace cannot be '.' or '..'", fullPath);
165
+ if (namespace.startsWith("..") || namespace.includes("/..") || namespace.includes("\\..")) throw new AFSPathError("Namespace cannot contain path traversal", fullPath);
166
+ }
167
+ /**
168
+ * Check if a string is a canonical AFS path
169
+ *
170
+ * Canonical paths have the format:
171
+ * - $afs/path (default namespace)
172
+ * - $afs:namespace/path (named namespace)
173
+ *
174
+ * @param input - String to check
175
+ * @returns true if input is a canonical AFS path
176
+ */
177
+ function isCanonicalPath(input) {
178
+ if (!input || typeof input !== "string") return false;
179
+ if (!input.startsWith(CANONICAL_PREFIX)) return false;
180
+ const afterPrefix = input.slice(4);
181
+ if (afterPrefix.length === 0) return false;
182
+ const firstChar = afterPrefix[0];
183
+ if (firstChar !== "/" && firstChar !== ":") return false;
184
+ if (firstChar === ":") {
185
+ const slashIndex = afterPrefix.indexOf("/", 1);
186
+ if (slashIndex === -1) return false;
187
+ const namespace = afterPrefix.slice(1, slashIndex);
188
+ if (!namespace || namespace.trim() === "") return false;
189
+ }
190
+ return true;
191
+ }
192
+ /**
193
+ * Parse a canonical AFS path
194
+ *
195
+ * Canonical paths have the format:
196
+ * - $afs/path (default namespace)
197
+ * - $afs:namespace/path (named namespace)
198
+ *
199
+ * @param canonical - Canonical path string
200
+ * @returns Parsed path with namespace and path
201
+ * @throws AFSPathError if path format is invalid
202
+ */
203
+ function parseCanonicalPath(canonical) {
204
+ if (!canonical || canonical.trim() === "") throw new AFSPathError("Canonical path cannot be empty", canonical);
205
+ if (containsForbiddenChars(canonical)) throw new AFSPathError("Canonical path contains forbidden control characters", canonical);
206
+ if (!canonical.startsWith(CANONICAL_PREFIX)) throw new AFSPathError(`Canonical path must start with '${CANONICAL_PREFIX}', got: ${canonical}`, canonical);
207
+ const afterPrefix = canonical.slice(4);
208
+ if (afterPrefix.length === 0) throw new AFSPathError("Canonical path must have '/' or ':' after $afs", canonical);
209
+ const firstChar = afterPrefix[0];
210
+ if (firstChar === "/") return {
211
+ namespace: null,
212
+ path: normalizePath(afterPrefix)
213
+ };
214
+ if (firstChar === ":") {
215
+ const rest = afterPrefix.slice(1);
216
+ const slashIndex = rest.indexOf("/");
217
+ if (slashIndex === -1) throw new AFSPathError("Named namespace path must have '/' after namespace", canonical);
218
+ const namespace = rest.slice(0, slashIndex);
219
+ const pathPart = rest.slice(slashIndex);
220
+ validateNamespace(namespace, canonical);
221
+ if (namespace.includes(":")) throw new AFSPathError("Namespace cannot contain ':'", canonical);
222
+ return {
223
+ namespace,
224
+ path: normalizePath(pathPart)
225
+ };
226
+ }
227
+ throw new AFSPathError(`Canonical path must have '/' or ':' after $afs, got: '${firstChar}'`, canonical);
228
+ }
229
+ /**
230
+ * Create a canonical AFS path from components
231
+ *
232
+ * @param namespace - Namespace name, or null for default namespace
233
+ * @param path - Path within namespace (must start with /)
234
+ * @returns Canonical path string
235
+ * @throws AFSPathError if namespace or path is invalid
236
+ */
237
+ function toCanonicalPath(namespace, path) {
238
+ if (!path || path.trim() === "") throw new AFSPathError("Path cannot be empty", path);
239
+ if (!path.startsWith("/")) throw new AFSPathError("Path must start with '/'", path);
240
+ if (containsForbiddenChars(path)) throw new AFSPathError("Path contains forbidden control characters", path);
241
+ const normalizedPath = normalizePath(path);
242
+ if (namespace === null || namespace === void 0) return `${CANONICAL_PREFIX}${normalizedPath}`;
243
+ validateNamespace(namespace, `${CANONICAL_PREFIX}:${namespace}${path}`);
244
+ return `${CANONICAL_PREFIX}:${namespace}${normalizedPath}`;
245
+ }
246
+
247
+ //#endregion
248
+ export { AFSPathError, isCanonicalPath, normalizePath, parseCanonicalPath, toCanonicalPath, validateModuleName, validatePath };
249
+ //# sourceMappingURL=path.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.mjs","names":[],"sources":["../src/path.ts"],"sourcesContent":["/**\n * AFS Path Validation and Normalization\n *\n * All AFS paths must follow Unix filesystem semantics:\n * - Must be absolute paths (start with /)\n * - Use / as path separator (not Windows \\)\n * - No NUL characters in path components\n * - Consistent semantics regardless of underlying platform\n */\n\n/**\n * Error thrown when path validation fails\n */\nexport class AFSPathError extends Error {\n constructor(\n message: string,\n public readonly path: string,\n ) {\n super(message);\n this.name = \"AFSPathError\";\n }\n}\n\n/**\n * Characters that are not allowed in AFS paths\n */\nconst FORBIDDEN_CHARS = [\n \"\\x00\", // NUL\n \"\\x01\", // SOH\n \"\\x02\", // STX\n \"\\x03\", // ETX\n \"\\x04\", // EOT\n \"\\x05\", // ENQ\n \"\\x06\", // ACK\n \"\\x07\", // BEL\n \"\\x08\", // BS\n \"\\x09\", // TAB\n \"\\x0a\", // LF\n \"\\x0b\", // VT\n \"\\x0c\", // FF\n \"\\x0d\", // CR\n \"\\x0e\", // SO\n \"\\x0f\", // SI\n \"\\x10\", // DLE\n \"\\x11\", // DC1\n \"\\x12\", // DC2\n \"\\x13\", // DC3\n \"\\x14\", // DC4\n \"\\x15\", // NAK\n \"\\x16\", // SYN\n \"\\x17\", // ETB\n \"\\x18\", // CAN\n \"\\x19\", // EM\n \"\\x1a\", // SUB\n \"\\x1b\", // ESC\n \"\\x1c\", // FS\n \"\\x1d\", // GS\n \"\\x1e\", // RS\n \"\\x1f\", // US\n];\n\n/**\n * Check if a string contains any forbidden control characters\n */\nfunction containsForbiddenChars(str: string): boolean {\n for (const char of FORBIDDEN_CHARS) {\n if (str.includes(char)) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Normalize a path by:\n * - Collapsing multiple consecutive slashes\n * - Resolving . (current directory)\n * - Resolving .. (parent directory) while preventing escape above root\n * - Removing trailing slashes (except for root /)\n * - Rejecting whitespace-only path segments\n *\n * @throws AFSPathError if a path segment is whitespace-only\n */\nexport function normalizePath(path: string): string {\n // Split into segments, filtering out empty segments (from multiple slashes)\n const segments = path.split(\"/\").filter((s) => s !== \"\");\n\n const result: string[] = [];\n\n for (const segment of segments) {\n // Check for whitespace-only segments\n if (segment.trim() === \"\") {\n throw new AFSPathError(\"Path segment cannot be whitespace-only\", path);\n }\n\n if (segment === \".\") {\n // Current directory - skip\n continue;\n }\n if (segment === \"..\") {\n // Parent directory - pop if possible, but never go above root\n if (result.length > 0) {\n result.pop();\n }\n // If result is empty, we're at root - don't go above\n continue;\n }\n result.push(segment);\n }\n\n return `/${result.join(\"/\")}`;\n}\n\n/**\n * Validate an AFS path\n *\n * @param path - The path to validate\n * @throws AFSPathError if the path is invalid\n * @returns The normalized path\n */\nexport function validatePath(path: string): string {\n // Check for empty or whitespace-only path\n if (!path || path.trim() === \"\") {\n throw new AFSPathError(\"Path cannot be empty or whitespace-only\", path);\n }\n\n // Check for forbidden control characters\n if (containsForbiddenChars(path)) {\n throw new AFSPathError(\"Path contains forbidden control characters\", path);\n }\n\n // Must start with / (absolute path)\n if (!path.startsWith(\"/\")) {\n throw new AFSPathError(\"Path must be absolute (start with /)\", path);\n }\n\n // Check for tilde expansion attempts (must check before normalization)\n // Tilde at start of path (after any leading /) is an attack\n const trimmedPath = path.replace(/^\\/+/, \"\");\n if (trimmedPath.startsWith(\"~\")) {\n throw new AFSPathError(\"Path cannot use tilde expansion\", path);\n }\n\n // Normalize the path\n const normalized = normalizePath(path);\n\n return normalized;\n}\n\n/**\n * Validate a module name\n *\n * Module names must:\n * - Not be empty or whitespace-only\n * - Not contain / or \\\n * - Not contain control characters\n * - Not be \".\" or \"..\"\n *\n * @param name - The module name to validate\n * @throws Error if the name is invalid\n */\nexport function validateModuleName(name: string): void {\n // Check for empty or whitespace-only name\n if (!name || name.trim() === \"\") {\n throw new Error(\"Module name cannot be empty or whitespace-only\");\n }\n\n // Check for forbidden control characters\n if (containsForbiddenChars(name)) {\n throw new Error(\n `Invalid module name: ${name}. Module name must not contain control characters`,\n );\n }\n\n // Check for path separators\n if (name.includes(\"/\")) {\n throw new Error(`Invalid module name: ${name}. Module name must not contain '/'`);\n }\n\n if (name.includes(\"\\\\\")) {\n throw new Error(`Invalid module name: ${name}. Module name must not contain '\\\\'`);\n }\n\n // Check for . and ..\n if (name === \".\") {\n throw new Error(\"Invalid module name: '.'. Module name cannot be '.'\");\n }\n\n if (name === \"..\") {\n throw new Error(\"Invalid module name: '..'. Module name cannot be '..'\");\n }\n}\n\n// =============================================================================\n// CANONICAL PATH PARSING\n// =============================================================================\n\n/**\n * Canonical path prefix for AFS\n */\nconst CANONICAL_PREFIX = \"$afs\";\n\n/**\n * Characters forbidden in namespace names (security-sensitive)\n */\nconst NAMESPACE_FORBIDDEN_CHARS = [\n \"/\", // Path separator\n \"\\\\\", // Windows path separator\n \":\", // Namespace separator (only one allowed)\n \";\", // Shell metachar\n \"|\", // Shell pipe\n \"&\", // Shell background\n \"`\", // Shell command substitution\n \"$\", // Shell variable (except in $afs prefix)\n \"(\", // Shell subshell\n \")\", // Shell subshell\n \">\", // Shell redirect\n \"<\", // Shell redirect\n \"\\n\", // Newline\n \"\\r\", // Carriage return\n \"\\t\", // Tab\n \"\\x00\", // NUL\n ...FORBIDDEN_CHARS, // All control chars\n];\n\n/**\n * Parsed canonical AFS path\n */\nexport interface ParsedCanonicalPath {\n /** Namespace name, or null for default namespace */\n namespace: string | null;\n /** Path within the namespace (always starts with /) */\n path: string;\n}\n\n/**\n * Check if a string contains any namespace-forbidden characters\n */\nfunction containsNamespaceForbiddenChars(str: string): boolean {\n for (const char of NAMESPACE_FORBIDDEN_CHARS) {\n if (str.includes(char)) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Validate a namespace name\n *\n * @throws AFSPathError if namespace is invalid\n */\nfunction validateNamespace(namespace: string, fullPath: string): void {\n // Check for empty or whitespace-only\n if (!namespace || namespace.trim() === \"\") {\n throw new AFSPathError(\"Namespace cannot be empty or whitespace-only\", fullPath);\n }\n\n // Check for forbidden characters\n if (containsNamespaceForbiddenChars(namespace)) {\n throw new AFSPathError(`Namespace contains forbidden characters: ${namespace}`, fullPath);\n }\n\n // Check for path traversal attempts\n if (namespace === \".\" || namespace === \"..\") {\n throw new AFSPathError(\"Namespace cannot be '.' or '..'\", fullPath);\n }\n\n if (namespace.startsWith(\"..\") || namespace.includes(\"/..\") || namespace.includes(\"\\\\..\")) {\n throw new AFSPathError(\"Namespace cannot contain path traversal\", fullPath);\n }\n}\n\n/**\n * Check if a string is a canonical AFS path\n *\n * Canonical paths have the format:\n * - $afs/path (default namespace)\n * - $afs:namespace/path (named namespace)\n *\n * @param input - String to check\n * @returns true if input is a canonical AFS path\n */\nexport function isCanonicalPath(input: string): boolean {\n if (!input || typeof input !== \"string\") {\n return false;\n }\n\n // Must start with $afs\n if (!input.startsWith(CANONICAL_PREFIX)) {\n return false;\n }\n\n const afterPrefix = input.slice(CANONICAL_PREFIX.length);\n\n // After $afs, must have / or :\n if (afterPrefix.length === 0) {\n return false;\n }\n\n const firstChar = afterPrefix[0];\n if (firstChar !== \"/\" && firstChar !== \":\") {\n return false;\n }\n\n // If starts with :, must have namespace then /\n if (firstChar === \":\") {\n const slashIndex = afterPrefix.indexOf(\"/\", 1);\n if (slashIndex === -1) {\n return false; // No / after namespace\n }\n const namespace = afterPrefix.slice(1, slashIndex);\n if (!namespace || namespace.trim() === \"\") {\n return false; // Empty namespace\n }\n }\n\n return true;\n}\n\n/**\n * Parse a canonical AFS path\n *\n * Canonical paths have the format:\n * - $afs/path (default namespace)\n * - $afs:namespace/path (named namespace)\n *\n * @param canonical - Canonical path string\n * @returns Parsed path with namespace and path\n * @throws AFSPathError if path format is invalid\n */\nexport function parseCanonicalPath(canonical: string): ParsedCanonicalPath {\n // Check for empty or whitespace-only\n if (!canonical || canonical.trim() === \"\") {\n throw new AFSPathError(\"Canonical path cannot be empty\", canonical);\n }\n\n // Check for control characters in entire input\n if (containsForbiddenChars(canonical)) {\n throw new AFSPathError(\"Canonical path contains forbidden control characters\", canonical);\n }\n\n // Must start exactly with $afs (case-sensitive)\n if (!canonical.startsWith(CANONICAL_PREFIX)) {\n throw new AFSPathError(\n `Canonical path must start with '${CANONICAL_PREFIX}', got: ${canonical}`,\n canonical,\n );\n }\n\n const afterPrefix = canonical.slice(CANONICAL_PREFIX.length);\n\n // After $afs, must have / or :\n if (afterPrefix.length === 0) {\n throw new AFSPathError(\"Canonical path must have '/' or ':' after $afs\", canonical);\n }\n\n const firstChar = afterPrefix[0];\n\n // Default namespace: $afs/path\n if (firstChar === \"/\") {\n const pathPart = afterPrefix; // Includes leading /\n const normalizedPath = normalizePath(pathPart);\n return {\n namespace: null,\n path: normalizedPath,\n };\n }\n\n // Named namespace: $afs:namespace/path\n if (firstChar === \":\") {\n const rest = afterPrefix.slice(1); // Skip the :\n\n // Find the / that separates namespace from path\n const slashIndex = rest.indexOf(\"/\");\n if (slashIndex === -1) {\n throw new AFSPathError(\"Named namespace path must have '/' after namespace\", canonical);\n }\n\n const namespace = rest.slice(0, slashIndex);\n const pathPart = rest.slice(slashIndex); // Includes leading /\n\n // Validate namespace\n validateNamespace(namespace, canonical);\n\n // Check for multiple colons in namespace (already forbidden by validateNamespace,\n // but explicit check for clarity)\n if (namespace.includes(\":\")) {\n throw new AFSPathError(\"Namespace cannot contain ':'\", canonical);\n }\n\n // Validate and normalize path\n const normalizedPath = normalizePath(pathPart);\n\n return {\n namespace,\n path: normalizedPath,\n };\n }\n\n // Invalid character after $afs\n throw new AFSPathError(\n `Canonical path must have '/' or ':' after $afs, got: '${firstChar}'`,\n canonical,\n );\n}\n\n/**\n * Create a canonical AFS path from components\n *\n * @param namespace - Namespace name, or null for default namespace\n * @param path - Path within namespace (must start with /)\n * @returns Canonical path string\n * @throws AFSPathError if namespace or path is invalid\n */\nexport function toCanonicalPath(namespace: string | null, path: string): string {\n // Validate path\n if (!path || path.trim() === \"\") {\n throw new AFSPathError(\"Path cannot be empty\", path);\n }\n\n if (!path.startsWith(\"/\")) {\n throw new AFSPathError(\"Path must start with '/'\", path);\n }\n\n if (containsForbiddenChars(path)) {\n throw new AFSPathError(\"Path contains forbidden control characters\", path);\n }\n\n // Normalize path\n const normalizedPath = normalizePath(path);\n\n // Default namespace\n if (namespace === null || namespace === undefined) {\n return `${CANONICAL_PREFIX}${normalizedPath}`;\n }\n\n // Validate namespace\n validateNamespace(namespace, `${CANONICAL_PREFIX}:${namespace}${path}`);\n\n // Named namespace\n return `${CANONICAL_PREFIX}:${namespace}${normalizedPath}`;\n}\n"],"mappings":"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"}