@rcrsr/rill 0.17.0 → 0.18.0

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 (83) hide show
  1. package/dist/ast-nodes.d.ts +14 -4
  2. package/dist/ast-unions.d.ts +1 -1
  3. package/dist/constants.d.ts +1 -1
  4. package/dist/constants.js +1 -0
  5. package/dist/error-registry.js +228 -0
  6. package/dist/ext/crypto/index.js +5 -5
  7. package/dist/ext/exec/index.js +3 -3
  8. package/dist/ext/fetch/index.js +4 -4
  9. package/dist/ext/fetch/request.js +1 -1
  10. package/dist/ext/fs/index.js +101 -114
  11. package/dist/ext/fs/sandbox.d.ts +18 -0
  12. package/dist/ext/fs/sandbox.js +33 -0
  13. package/dist/ext/kv/index.js +12 -12
  14. package/dist/ext/kv/store.d.ts +1 -1
  15. package/dist/ext/kv/store.js +1 -1
  16. package/dist/generated/version-data.d.ts +1 -1
  17. package/dist/generated/version-data.js +2 -2
  18. package/dist/highlight-map.js +1 -0
  19. package/dist/index.d.ts +1 -15
  20. package/dist/index.js +1 -14
  21. package/dist/lexer/operators.js +1 -0
  22. package/dist/parser/helpers.js +1 -0
  23. package/dist/parser/parser-expr.js +44 -5
  24. package/dist/parser/parser-literals.js +111 -4
  25. package/dist/parser/parser-shape.js +2 -2
  26. package/dist/parser/parser-use.js +19 -2
  27. package/dist/parser/parser.d.ts +2 -0
  28. package/dist/parser/parser.js +2 -0
  29. package/dist/runtime/core/callable.d.ts +5 -6
  30. package/dist/runtime/core/callable.js +10 -17
  31. package/dist/runtime/core/context.d.ts +2 -2
  32. package/dist/runtime/core/context.js +8 -8
  33. package/dist/runtime/core/eval/base.d.ts +2 -2
  34. package/dist/runtime/core/eval/base.js +2 -0
  35. package/dist/runtime/core/eval/evaluator.d.ts +1 -1
  36. package/dist/runtime/core/eval/index.d.ts +2 -2
  37. package/dist/runtime/core/eval/mixins/closures.js +367 -27
  38. package/dist/runtime/core/eval/mixins/collections.js +81 -6
  39. package/dist/runtime/core/eval/mixins/control-flow.js +1 -1
  40. package/dist/runtime/core/eval/mixins/conversion.js +17 -12
  41. package/dist/runtime/core/eval/mixins/core.js +15 -2
  42. package/dist/runtime/core/eval/mixins/expressions.js +3 -2
  43. package/dist/runtime/core/eval/mixins/extraction.js +2 -3
  44. package/dist/runtime/core/eval/mixins/list-dispatch.js +1 -1
  45. package/dist/runtime/core/eval/mixins/literals.js +14 -3
  46. package/dist/runtime/core/eval/mixins/types.js +30 -1
  47. package/dist/runtime/core/eval/mixins/variables.js +3 -1
  48. package/dist/runtime/core/execute.d.ts +1 -1
  49. package/dist/runtime/core/field-descriptor.d.ts +1 -1
  50. package/dist/runtime/core/introspection.d.ts +2 -2
  51. package/dist/runtime/core/introspection.js +2 -1
  52. package/dist/runtime/core/resolvers.d.ts +1 -1
  53. package/dist/runtime/core/signals.d.ts +6 -1
  54. package/dist/runtime/core/signals.js +9 -0
  55. package/dist/runtime/core/types/constructors.d.ts +54 -0
  56. package/dist/runtime/core/types/constructors.js +201 -0
  57. package/dist/runtime/core/types/guards.d.ts +42 -0
  58. package/dist/runtime/core/types/guards.js +88 -0
  59. package/dist/runtime/core/types/index.d.ts +18 -0
  60. package/dist/runtime/core/types/index.js +19 -0
  61. package/dist/runtime/core/types/operations.d.ts +98 -0
  62. package/dist/runtime/core/types/operations.js +804 -0
  63. package/dist/runtime/core/{type-registrations.d.ts → types/registrations.d.ts} +12 -22
  64. package/dist/runtime/core/{type-registrations.js → types/registrations.js} +94 -92
  65. package/dist/runtime/core/{types.d.ts → types/runtime.d.ts} +8 -8
  66. package/dist/runtime/core/{type-structures.d.ts → types/structures.d.ts} +21 -3
  67. package/dist/runtime/core/values.d.ts +13 -102
  68. package/dist/runtime/core/values.js +26 -722
  69. package/dist/runtime/ext/builtins.js +9 -8
  70. package/dist/runtime/ext/extensions.d.ts +2 -2
  71. package/dist/runtime/ext/extensions.js +2 -1
  72. package/dist/runtime/ext/test-context.d.ts +2 -2
  73. package/dist/runtime/ext/test-context.js +3 -2
  74. package/dist/runtime/index.d.ts +8 -22
  75. package/dist/runtime/index.js +10 -16
  76. package/dist/signature-parser.d.ts +1 -1
  77. package/dist/token-types.d.ts +1 -0
  78. package/dist/token-types.js +1 -0
  79. package/package.json +1 -1
  80. /package/dist/runtime/core/{markers.d.ts → types/markers.d.ts} +0 -0
  81. /package/dist/runtime/core/{markers.js → types/markers.js} +0 -0
  82. /package/dist/runtime/core/{types.js → types/runtime.js} +0 -0
  83. /package/dist/runtime/core/{type-structures.js → types/structures.js} +0 -0
@@ -8,8 +8,8 @@ import fs from 'node:fs/promises';
8
8
  import path from 'node:path';
9
9
  import { RuntimeError } from '../../error-classes.js';
10
10
  import { toCallable } from '../../runtime/core/callable.js';
11
- import { rillTypeToTypeValue, } from '../../runtime/core/values.js';
12
- import { resolvePath, matchesGlob, initializeMount, } from './sandbox.js';
11
+ import { structureToTypeValue } from '../../runtime/core/values.js';
12
+ import { resolvePath, matchesGlob, initializeMount, parseMountPath, } from './sandbox.js';
13
13
  export const configSchema = {
14
14
  mounts: { type: 'string', required: true },
15
15
  };
@@ -76,8 +76,7 @@ export function createFsExtension(config) {
76
76
  // ctx and location not used but required by CallableFn signature
77
77
  ) => {
78
78
  await ensureInitialized();
79
- const mountName = args['mount'];
80
- const filePath = args['path'];
79
+ const { mountName, relativePath: filePath } = parseMountPath(args['path'], mounts);
81
80
  // EC-5: Catch file not found from resolvePath
82
81
  let resolvedPath;
83
82
  try {
@@ -102,8 +101,7 @@ export function createFsExtension(config) {
102
101
  */
103
102
  const write = async (args) => {
104
103
  await ensureInitialized();
105
- const mountName = args['mount'];
106
- const filePath = args['path'];
104
+ const { mountName, relativePath: filePath } = parseMountPath(args['path'], mounts);
107
105
  const content = args['content'];
108
106
  const resolvedPath = await resolvePath(mountName, filePath, mounts, 'write', true // createMode: resolve parent directory
109
107
  );
@@ -121,8 +119,7 @@ export function createFsExtension(config) {
121
119
  */
122
120
  const append = async (args) => {
123
121
  await ensureInitialized();
124
- const mountName = args['mount'];
125
- const filePath = args['path'];
122
+ const { mountName, relativePath: filePath } = parseMountPath(args['path'], mounts);
126
123
  const content = args['content'];
127
124
  const resolvedPath = await resolvePath(mountName, filePath, mounts, 'write', true // createMode: allow new files
128
125
  );
@@ -158,8 +155,7 @@ export function createFsExtension(config) {
158
155
  */
159
156
  const list = async (args) => {
160
157
  await ensureInitialized();
161
- const mountName = args['mount'];
162
- const dirPath = args['path'] ?? '';
158
+ const { mountName, relativePath: dirPath } = parseMountPath(args['path'], mounts);
163
159
  const resolvedPath = await resolvePath(mountName, dirPath, mounts, 'read');
164
160
  const entries = await fs.readdir(resolvedPath, { withFileTypes: true });
165
161
  const result = [];
@@ -180,13 +176,20 @@ export function createFsExtension(config) {
180
176
  */
181
177
  const find = async (args) => {
182
178
  await ensureInitialized();
183
- const mountName = args['mount'];
179
+ const { mountName, relativePath: searchBase } = parseMountPath(args['path'], mounts);
184
180
  const pattern = args['pattern'] ?? '*';
185
181
  const mount = mounts[mountName];
186
182
  if (!mount || !mount.resolvedPath) {
187
183
  throw new RuntimeError('RILL-R004', `mount "${mountName}" not configured`, undefined, { mountName });
188
184
  }
189
- const basePath = mount.resolvedPath;
185
+ let basePath;
186
+ if (searchBase) {
187
+ // Validate searchBase through sandbox resolver to prevent path traversal
188
+ basePath = await resolvePath(mountName, searchBase, mounts, 'read');
189
+ }
190
+ else {
191
+ basePath = mount.resolvedPath;
192
+ }
190
193
  const results = [];
191
194
  // Recursive directory traversal
192
195
  const traverse = async (currentPath) => {
@@ -198,7 +201,7 @@ export function createFsExtension(config) {
198
201
  }
199
202
  else if (matchesGlob(entry.name, pattern)) {
200
203
  // Return path relative to mount base
201
- const relativePath = path.relative(basePath, fullPath);
204
+ const relativePath = path.relative(mount.resolvedPath, fullPath);
202
205
  results.push(relativePath);
203
206
  }
204
207
  }
@@ -212,8 +215,7 @@ export function createFsExtension(config) {
212
215
  */
213
216
  const exists = async (args) => {
214
217
  await ensureInitialized();
215
- const mountName = args['mount'];
216
- const filePath = args['path'];
218
+ const { mountName, relativePath: filePath } = parseMountPath(args['path'], mounts);
217
219
  try {
218
220
  await resolvePath(mountName, filePath, mounts, 'read');
219
221
  return true;
@@ -232,8 +234,7 @@ export function createFsExtension(config) {
232
234
  */
233
235
  const remove = async (args) => {
234
236
  await ensureInitialized();
235
- const mountName = args['mount'];
236
- const filePath = args['path'];
237
+ const { mountName, relativePath: filePath } = parseMountPath(args['path'], mounts);
237
238
  // Catch file not found from resolvePath
238
239
  let resolvedPath;
239
240
  try {
@@ -265,8 +266,7 @@ export function createFsExtension(config) {
265
266
  */
266
267
  const stat = async (args) => {
267
268
  await ensureInitialized();
268
- const mountName = args['mount'];
269
- const filePath = args['path'];
269
+ const { mountName, relativePath: filePath } = parseMountPath(args['path'], mounts);
270
270
  // Catch file not found from resolvePath
271
271
  let resolvedPath;
272
272
  try {
@@ -295,8 +295,7 @@ export function createFsExtension(config) {
295
295
  */
296
296
  const mkdir = async (args) => {
297
297
  await ensureInitialized();
298
- const mountName = args['mount'];
299
- const dirPath = args['path'];
298
+ const { mountName, relativePath: dirPath } = parseMountPath(args['path'], mounts);
300
299
  const mount = mounts[mountName];
301
300
  if (!mount || !mount.resolvedPath) {
302
301
  throw new RuntimeError('RILL-R004', `mount "${mountName}" not configured`, undefined, { mountName });
@@ -345,9 +344,13 @@ export function createFsExtension(config) {
345
344
  */
346
345
  const copy = async (args) => {
347
346
  await ensureInitialized();
348
- const mountName = args['mount'];
349
- const srcPath = args['src'];
350
- const destPath = args['dest'];
347
+ const { mountName: srcMountName, relativePath: srcPath } = parseMountPath(args['src'], mounts);
348
+ const { mountName: destMountName, relativePath: destPath } = parseMountPath(args['dest'], mounts);
349
+ const mountName = srcMountName;
350
+ // Verify same mount
351
+ if (srcMountName !== destMountName) {
352
+ throw new RuntimeError('RILL-R004', `copy requires same mount for src and dest`, undefined, { src: args['src'], dest: args['dest'] });
353
+ }
351
354
  const resolvedSrc = await resolvePath(mountName, srcPath, mounts, 'read');
352
355
  const resolvedDest = await resolvePath(mountName, destPath, mounts, 'write', true // createMode
353
356
  );
@@ -374,9 +377,13 @@ export function createFsExtension(config) {
374
377
  */
375
378
  const move = async (args) => {
376
379
  await ensureInitialized();
377
- const mountName = args['mount'];
378
- const srcPath = args['src'];
379
- const destPath = args['dest'];
380
+ const { mountName: srcMountName, relativePath: srcPath } = parseMountPath(args['src'], mounts);
381
+ const { mountName: destMountName, relativePath: destPath } = parseMountPath(args['dest'], mounts);
382
+ const mountName = srcMountName;
383
+ // Verify same mount
384
+ if (srcMountName !== destMountName) {
385
+ throw new RuntimeError('RILL-R004', `move requires same mount for src and dest`, undefined, { src: args['src'], dest: args['dest'] });
386
+ }
380
387
  const resolvedSrc = await resolvePath(mountName, srcPath, mounts, 'read');
381
388
  const resolvedDest = await resolvePath(mountName, destPath, mounts, 'write', true // createMode
382
389
  );
@@ -416,36 +423,28 @@ export function createFsExtension(config) {
416
423
  value: {
417
424
  read: toCallable({
418
425
  params: [
419
- {
420
- name: 'mount',
421
- type: { kind: 'string' },
422
- defaultValue: undefined,
423
- annotations: { description: 'Mount name' },
424
- },
425
426
  {
426
427
  name: 'path',
427
428
  type: { kind: 'string' },
428
429
  defaultValue: undefined,
429
- annotations: { description: 'File path relative to mount' },
430
+ annotations: {
431
+ description: 'Mount-prefixed file path (e.g. "/mount/file.txt")',
432
+ },
430
433
  },
431
434
  ],
432
435
  fn: read,
433
436
  annotations: { description: 'Read file contents' },
434
- returnType: rillTypeToTypeValue({ kind: 'string' }),
437
+ returnType: structureToTypeValue({ kind: 'string' }),
435
438
  }),
436
439
  write: toCallable({
437
440
  params: [
438
- {
439
- name: 'mount',
440
- type: { kind: 'string' },
441
- defaultValue: undefined,
442
- annotations: { description: 'Mount name' },
443
- },
444
441
  {
445
442
  name: 'path',
446
443
  type: { kind: 'string' },
447
444
  defaultValue: undefined,
448
- annotations: { description: 'File path relative to mount' },
445
+ annotations: {
446
+ description: 'Mount-prefixed file path (e.g. "/mount/file.txt")',
447
+ },
449
448
  },
450
449
  {
451
450
  name: 'content',
@@ -456,21 +455,17 @@ export function createFsExtension(config) {
456
455
  ],
457
456
  fn: write,
458
457
  annotations: { description: 'Write file, replacing if exists' },
459
- returnType: rillTypeToTypeValue({ kind: 'string' }),
458
+ returnType: structureToTypeValue({ kind: 'string' }),
460
459
  }),
461
460
  append: toCallable({
462
461
  params: [
463
- {
464
- name: 'mount',
465
- type: { kind: 'string' },
466
- defaultValue: undefined,
467
- annotations: { description: 'Mount name' },
468
- },
469
462
  {
470
463
  name: 'path',
471
464
  type: { kind: 'string' },
472
465
  defaultValue: undefined,
473
- annotations: { description: 'File path relative to mount' },
466
+ annotations: {
467
+ description: 'Mount-prefixed file path (e.g. "/mount/file.txt")',
468
+ },
474
469
  },
475
470
  {
476
471
  name: 'content',
@@ -481,34 +476,42 @@ export function createFsExtension(config) {
481
476
  ],
482
477
  fn: append,
483
478
  annotations: { description: 'Append content to file' },
484
- returnType: rillTypeToTypeValue({ kind: 'string' }),
479
+ returnType: structureToTypeValue({ kind: 'string' }),
485
480
  }),
486
481
  list: toCallable({
487
482
  params: [
488
- {
489
- name: 'mount',
490
- type: { kind: 'string' },
491
- defaultValue: undefined,
492
- annotations: { description: 'Mount name' },
493
- },
494
483
  {
495
484
  name: 'path',
496
485
  type: { kind: 'string' },
497
- defaultValue: '',
498
- annotations: { description: 'Directory path relative to mount' },
486
+ defaultValue: undefined,
487
+ annotations: {
488
+ description: 'Mount-prefixed directory path (e.g. "/mount/subdir")',
489
+ },
499
490
  },
500
491
  ],
501
492
  fn: list,
502
493
  annotations: { description: 'List directory contents' },
503
- returnType: rillTypeToTypeValue({ kind: 'list' }),
494
+ returnType: structureToTypeValue({
495
+ kind: 'list',
496
+ element: {
497
+ kind: 'dict',
498
+ fields: {
499
+ name: { type: { kind: 'string' } },
500
+ type: { type: { kind: 'string' } },
501
+ size: { type: { kind: 'number' } },
502
+ },
503
+ },
504
+ }),
504
505
  }),
505
506
  find: toCallable({
506
507
  params: [
507
508
  {
508
- name: 'mount',
509
+ name: 'path',
509
510
  type: { kind: 'string' },
510
511
  defaultValue: undefined,
511
- annotations: { description: 'Mount name' },
512
+ annotations: {
513
+ description: 'Mount-prefixed base path (e.g. "/mount" or "/mount/subdir")',
514
+ },
512
515
  },
513
516
  {
514
517
  name: 'pattern',
@@ -519,139 +522,123 @@ export function createFsExtension(config) {
519
522
  ],
520
523
  fn: find,
521
524
  annotations: { description: 'Recursive file search' },
522
- returnType: rillTypeToTypeValue({ kind: 'list' }),
525
+ returnType: structureToTypeValue({
526
+ kind: 'list',
527
+ element: { kind: 'string' },
528
+ }),
523
529
  }),
524
530
  exists: toCallable({
525
531
  params: [
526
- {
527
- name: 'mount',
528
- type: { kind: 'string' },
529
- defaultValue: undefined,
530
- annotations: { description: 'Mount name' },
531
- },
532
532
  {
533
533
  name: 'path',
534
534
  type: { kind: 'string' },
535
535
  defaultValue: undefined,
536
- annotations: { description: 'File path relative to mount' },
536
+ annotations: {
537
+ description: 'Mount-prefixed file path (e.g. "/mount/file.txt")',
538
+ },
537
539
  },
538
540
  ],
539
541
  fn: exists,
540
542
  annotations: { description: 'Check file existence' },
541
- returnType: rillTypeToTypeValue({ kind: 'bool' }),
543
+ returnType: structureToTypeValue({ kind: 'bool' }),
542
544
  }),
543
545
  remove: toCallable({
544
546
  params: [
545
- {
546
- name: 'mount',
547
- type: { kind: 'string' },
548
- defaultValue: undefined,
549
- annotations: { description: 'Mount name' },
550
- },
551
547
  {
552
548
  name: 'path',
553
549
  type: { kind: 'string' },
554
550
  defaultValue: undefined,
555
- annotations: { description: 'File path relative to mount' },
551
+ annotations: {
552
+ description: 'Mount-prefixed file path (e.g. "/mount/file.txt")',
553
+ },
556
554
  },
557
555
  ],
558
556
  fn: remove,
559
557
  annotations: { description: 'Delete file' },
560
- returnType: rillTypeToTypeValue({ kind: 'bool' }),
558
+ returnType: structureToTypeValue({ kind: 'bool' }),
561
559
  }),
562
560
  stat: toCallable({
563
561
  params: [
564
- {
565
- name: 'mount',
566
- type: { kind: 'string' },
567
- defaultValue: undefined,
568
- annotations: { description: 'Mount name' },
569
- },
570
562
  {
571
563
  name: 'path',
572
564
  type: { kind: 'string' },
573
565
  defaultValue: undefined,
574
- annotations: { description: 'File path relative to mount' },
566
+ annotations: {
567
+ description: 'Mount-prefixed file path (e.g. "/mount/file.txt")',
568
+ },
575
569
  },
576
570
  ],
577
571
  fn: stat,
578
572
  annotations: { description: 'Get file metadata' },
579
- returnType: rillTypeToTypeValue({ kind: 'dict' }),
573
+ returnType: structureToTypeValue({
574
+ kind: 'dict',
575
+ fields: {
576
+ name: { type: { kind: 'string' } },
577
+ type: { type: { kind: 'string' } },
578
+ size: { type: { kind: 'number' } },
579
+ created: { type: { kind: 'string' } },
580
+ modified: { type: { kind: 'string' } },
581
+ },
582
+ }),
580
583
  }),
581
584
  mkdir: toCallable({
582
585
  params: [
583
- {
584
- name: 'mount',
585
- type: { kind: 'string' },
586
- defaultValue: undefined,
587
- annotations: { description: 'Mount name' },
588
- },
589
586
  {
590
587
  name: 'path',
591
588
  type: { kind: 'string' },
592
589
  defaultValue: undefined,
593
- annotations: { description: 'Directory path relative to mount' },
590
+ annotations: {
591
+ description: 'Mount-prefixed directory path (e.g. "/mount/subdir")',
592
+ },
594
593
  },
595
594
  ],
596
595
  fn: mkdir,
597
596
  annotations: { description: 'Create directory' },
598
- returnType: rillTypeToTypeValue({ kind: 'bool' }),
597
+ returnType: structureToTypeValue({ kind: 'bool' }),
599
598
  }),
600
599
  copy: toCallable({
601
600
  params: [
602
- {
603
- name: 'mount',
604
- type: { kind: 'string' },
605
- defaultValue: undefined,
606
- annotations: { description: 'Mount name' },
607
- },
608
601
  {
609
602
  name: 'src',
610
603
  type: { kind: 'string' },
611
604
  defaultValue: undefined,
612
- annotations: { description: 'Source file path' },
605
+ annotations: { description: 'Mount-prefixed source path' },
613
606
  },
614
607
  {
615
608
  name: 'dest',
616
609
  type: { kind: 'string' },
617
610
  defaultValue: undefined,
618
- annotations: { description: 'Destination file path' },
611
+ annotations: { description: 'Mount-prefixed destination path' },
619
612
  },
620
613
  ],
621
614
  fn: copy,
622
615
  annotations: { description: 'Copy file within mount' },
623
- returnType: rillTypeToTypeValue({ kind: 'bool' }),
616
+ returnType: structureToTypeValue({ kind: 'bool' }),
624
617
  }),
625
618
  move: toCallable({
626
619
  params: [
627
- {
628
- name: 'mount',
629
- type: { kind: 'string' },
630
- defaultValue: undefined,
631
- annotations: { description: 'Mount name' },
632
- },
633
620
  {
634
621
  name: 'src',
635
622
  type: { kind: 'string' },
636
623
  defaultValue: undefined,
637
- annotations: { description: 'Source file path' },
624
+ annotations: { description: 'Mount-prefixed source path' },
638
625
  },
639
626
  {
640
627
  name: 'dest',
641
628
  type: { kind: 'string' },
642
629
  defaultValue: undefined,
643
- annotations: { description: 'Destination file path' },
630
+ annotations: { description: 'Mount-prefixed destination path' },
644
631
  },
645
632
  ],
646
633
  fn: move,
647
634
  annotations: { description: 'Move file within mount' },
648
- returnType: rillTypeToTypeValue({ kind: 'bool' }),
635
+ returnType: structureToTypeValue({ kind: 'bool' }),
649
636
  }),
650
637
  mounts: toCallable({
651
638
  params: [],
652
639
  fn: mountsList,
653
640
  annotations: { description: 'List configured mounts' },
654
- returnType: rillTypeToTypeValue({ kind: 'list' }),
641
+ returnType: structureToTypeValue({ kind: 'list' }),
655
642
  }),
656
643
  },
657
644
  };
@@ -75,3 +75,21 @@ export declare function checkMode(mode: 'read' | 'write' | 'read-write', operati
75
75
  * @throws RuntimeError - If mount path invalid or inaccessible
76
76
  */
77
77
  export declare function initializeMount(mount: MountConfig): Promise<void>;
78
+ /**
79
+ * Parses a mount-prefixed path into mount name and relative path.
80
+ *
81
+ * Uses longest-first prefix matching to support mount names containing slashes.
82
+ * Strips a leading `/` before matching.
83
+ * Example: "/workspace/my/file.txt" → { mountName: "workspace", relativePath: "my/file.txt" }
84
+ * Example: "/the/mount/file.txt" → { mountName: "the/mount", relativePath: "file.txt" }
85
+ * Example: "/workspace" → { mountName: "workspace", relativePath: "" }
86
+ *
87
+ * @param fullPath - Path with leading `/` and mount prefix
88
+ * @param mounts - Mount configuration map
89
+ * @returns Parsed mount name and relative path
90
+ * @throws RuntimeError - If no mount matches the path prefix
91
+ */
92
+ export declare function parseMountPath(fullPath: string, mounts: Record<string, MountConfig>): {
93
+ mountName: string;
94
+ relativePath: string;
95
+ };
@@ -205,3 +205,36 @@ export async function initializeMount(mount) {
205
205
  throw error;
206
206
  }
207
207
  }
208
+ // ============================================================
209
+ // MOUNT PATH PARSING
210
+ // ============================================================
211
+ /**
212
+ * Parses a mount-prefixed path into mount name and relative path.
213
+ *
214
+ * Uses longest-first prefix matching to support mount names containing slashes.
215
+ * Strips a leading `/` before matching.
216
+ * Example: "/workspace/my/file.txt" → { mountName: "workspace", relativePath: "my/file.txt" }
217
+ * Example: "/the/mount/file.txt" → { mountName: "the/mount", relativePath: "file.txt" }
218
+ * Example: "/workspace" → { mountName: "workspace", relativePath: "" }
219
+ *
220
+ * @param fullPath - Path with leading `/` and mount prefix
221
+ * @param mounts - Mount configuration map
222
+ * @returns Parsed mount name and relative path
223
+ * @throws RuntimeError - If no mount matches the path prefix
224
+ */
225
+ export function parseMountPath(fullPath, mounts) {
226
+ const normalized = fullPath.startsWith('/') ? fullPath.slice(1) : fullPath;
227
+ const sortedNames = Object.keys(mounts).sort((a, b) => b.length - a.length);
228
+ for (const mountName of sortedNames) {
229
+ if (normalized === mountName) {
230
+ return { mountName, relativePath: '' };
231
+ }
232
+ if (normalized.startsWith(mountName + '/')) {
233
+ return {
234
+ mountName,
235
+ relativePath: normalized.slice(mountName.length + 1),
236
+ };
237
+ }
238
+ }
239
+ throw new RuntimeError('RILL-R017', `no mount matches path "${fullPath}"`, undefined, { path: fullPath });
240
+ }
@@ -8,7 +8,7 @@ import { RuntimeError } from '../../error-classes.js';
8
8
  import { toCallable } from '../../runtime/core/callable.js';
9
9
  import { isDict } from '../../runtime/core/callable.js';
10
10
  import { createStore } from './store.js';
11
- import { rillTypeToTypeValue, } from '../../runtime/core/values.js';
11
+ import { structureToTypeValue } from '../../runtime/core/values.js';
12
12
  export const configSchema = {
13
13
  store: { type: 'string' },
14
14
  mounts: { type: 'string' },
@@ -299,7 +299,7 @@ export function createKvExtension(config) {
299
299
  ],
300
300
  fn: get,
301
301
  annotations: { description: 'Get value or schema default' },
302
- returnType: rillTypeToTypeValue({ kind: 'any' }),
302
+ returnType: structureToTypeValue({ kind: 'any' }),
303
303
  }),
304
304
  get_or: toCallable({
305
305
  params: [
@@ -326,7 +326,7 @@ export function createKvExtension(config) {
326
326
  annotations: {
327
327
  description: 'Get value or return fallback if key missing',
328
328
  },
329
- returnType: rillTypeToTypeValue({ kind: 'any' }),
329
+ returnType: structureToTypeValue({ kind: 'any' }),
330
330
  }),
331
331
  set: toCallable({
332
332
  params: [
@@ -351,7 +351,7 @@ export function createKvExtension(config) {
351
351
  ],
352
352
  fn: set,
353
353
  annotations: { description: 'Set value with validation' },
354
- returnType: rillTypeToTypeValue({ kind: 'bool' }),
354
+ returnType: structureToTypeValue({ kind: 'bool' }),
355
355
  }),
356
356
  merge: toCallable({
357
357
  params: [
@@ -378,7 +378,7 @@ export function createKvExtension(config) {
378
378
  annotations: {
379
379
  description: 'Merge partial dict into existing dict value',
380
380
  },
381
- returnType: rillTypeToTypeValue({ kind: 'bool' }),
381
+ returnType: structureToTypeValue({ kind: 'bool' }),
382
382
  }),
383
383
  delete: toCallable({
384
384
  params: [
@@ -397,7 +397,7 @@ export function createKvExtension(config) {
397
397
  ],
398
398
  fn: deleteKey,
399
399
  annotations: { description: 'Delete key' },
400
- returnType: rillTypeToTypeValue({ kind: 'bool' }),
400
+ returnType: structureToTypeValue({ kind: 'bool' }),
401
401
  }),
402
402
  keys: toCallable({
403
403
  params: [
@@ -410,7 +410,7 @@ export function createKvExtension(config) {
410
410
  ],
411
411
  fn: keys,
412
412
  annotations: { description: 'Get all keys in mount' },
413
- returnType: rillTypeToTypeValue({ kind: 'list' }),
413
+ returnType: structureToTypeValue({ kind: 'list' }),
414
414
  }),
415
415
  has: toCallable({
416
416
  params: [
@@ -429,7 +429,7 @@ export function createKvExtension(config) {
429
429
  ],
430
430
  fn: has,
431
431
  annotations: { description: 'Check key existence' },
432
- returnType: rillTypeToTypeValue({ kind: 'bool' }),
432
+ returnType: structureToTypeValue({ kind: 'bool' }),
433
433
  }),
434
434
  clear: toCallable({
435
435
  params: [
@@ -442,7 +442,7 @@ export function createKvExtension(config) {
442
442
  ],
443
443
  fn: clear,
444
444
  annotations: { description: 'Clear all keys in mount' },
445
- returnType: rillTypeToTypeValue({ kind: 'bool' }),
445
+ returnType: structureToTypeValue({ kind: 'bool' }),
446
446
  }),
447
447
  getAll: toCallable({
448
448
  params: [
@@ -455,7 +455,7 @@ export function createKvExtension(config) {
455
455
  ],
456
456
  fn: getAll,
457
457
  annotations: { description: 'Get all entries as dict' },
458
- returnType: rillTypeToTypeValue({ kind: 'dict' }),
458
+ returnType: structureToTypeValue({ kind: 'dict' }),
459
459
  }),
460
460
  schema: toCallable({
461
461
  params: [
@@ -468,13 +468,13 @@ export function createKvExtension(config) {
468
468
  ],
469
469
  fn: schema,
470
470
  annotations: { description: 'Get schema information' },
471
- returnType: rillTypeToTypeValue({ kind: 'list' }),
471
+ returnType: structureToTypeValue({ kind: 'list' }),
472
472
  }),
473
473
  mounts: toCallable({
474
474
  params: [],
475
475
  fn: mountsList,
476
476
  annotations: { description: 'Get list of mount metadata' },
477
- returnType: rillTypeToTypeValue({ kind: 'list' }),
477
+ returnType: structureToTypeValue({ kind: 'list' }),
478
478
  }),
479
479
  },
480
480
  dispose,
@@ -4,7 +4,7 @@
4
4
  * Provides JSON-based key-value persistence with schema validation.
5
5
  * Lifecycle: Load (read store file) -> Execute (in-memory operations) -> Flush (atomic write on dispose)
6
6
  */
7
- import type { RillValue } from '../../runtime/core/values.js';
7
+ import type { RillValue } from '../../runtime/core/types/structures.js';
8
8
  /** Schema entry defining type and default for a key */
9
9
  export interface SchemaEntry {
10
10
  type: 'string' | 'number' | 'bool' | 'list' | 'dict';
@@ -7,7 +7,7 @@
7
7
  import fs from 'node:fs/promises';
8
8
  import path from 'node:path';
9
9
  import { RuntimeError } from '../../error-classes.js';
10
- import { deserializeValue } from '../../runtime/core/type-registrations.js';
10
+ import { deserializeValue } from '../../runtime/core/types/registrations.js';
11
11
  // ============================================================
12
12
  // STORE IMPLEMENTATION
13
13
  // ============================================================
@@ -10,7 +10,7 @@ export interface VersionInfo {
10
10
  /**
11
11
  * Version string from package.json
12
12
  */
13
- export declare const VERSION = "0.17.0";
13
+ export declare const VERSION = "0.18.0";
14
14
  /**
15
15
  * Parsed version components
16
16
  */
@@ -3,13 +3,13 @@
3
3
  /**
4
4
  * Version string from package.json
5
5
  */
6
- export const VERSION = '0.17.0';
6
+ export const VERSION = '0.18.0';
7
7
  /**
8
8
  * Parsed version components
9
9
  */
10
10
  export const VERSION_INFO = {
11
11
  major: 0,
12
- minor: 17,
12
+ minor: 18,
13
13
  patch: 0,
14
14
  prerelease: undefined,
15
15
  };