@vryzel/file-next 0.5.2 → 0.6.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.
package/README.md CHANGED
@@ -169,7 +169,7 @@ import { createShareRouteHandler } from "@vryzel/file-next/server";
169
169
  export const GET = createShareRouteHandler({ store, fs });
170
170
  ```
171
171
 
172
- The handler streams the object through Next.js. Folders cannot be shared this way. Optional `sharePathPrefix` on `createServerActions` (default `/api/share`).
172
+ The handler streams objects up to 32MB through Next.js and redirects larger ones to a 60-second signed URL. Folders cannot be shared this way. Optional `sharePathPrefix` on `createServerActions` (default `/api/share`).
173
173
 
174
174
  ### 8. Route handlers (upload / download)
175
175
 
@@ -180,6 +180,7 @@ import { getFileSystem } from "@vryzel/file-next";
180
180
 
181
181
  export const PUT = createUploadRouteHandler({
182
182
  fs: getFileSystem(),
183
+ getAuth: async () => session ?? null,
183
184
  maxBytes: 25 * 1024 * 1024,
184
185
  allowedContentTypes: ["image/*", "application/pdf"],
185
186
  });
@@ -206,6 +206,11 @@ interface PresignedUploadInput {
206
206
  contentType?: string;
207
207
  /** Expiry in seconds. Providers enforce a max (e.g. 7 days for S3). */
208
208
  expiresIn?: number;
209
+ /**
210
+ * When set, the signature binds `Content-Length` to this value.
211
+ * A PUT with any other length is rejected by the provider.
212
+ */
213
+ contentLength?: number;
209
214
  }
210
215
  interface PresignedDownloadInput {
211
216
  key: S3Key;
@@ -401,53 +406,19 @@ interface S3CompatibleAdapter {
401
406
  declare const CredentialsSchema: z.ZodObject<{
402
407
  accessKeyId: z.ZodString;
403
408
  secretAccessKey: z.ZodString;
404
- }, "strip", z.ZodTypeAny, {
405
- accessKeyId: string;
406
- secretAccessKey: string;
407
- }, {
408
- accessKeyId: string;
409
- secretAccessKey: string;
410
- }>;
409
+ }, z.core.$strip>;
411
410
  type Credentials = z.infer<typeof CredentialsSchema>;
412
411
  declare const S3ConfigSchema: z.ZodObject<{
413
412
  provider: z.ZodLiteral<"s3">;
414
413
  bucket: z.ZodString;
415
414
  region: z.ZodString;
416
- /** Optional S3-compatible endpoint (MinIO, LocalStack, ...). */
417
415
  endpoint: z.ZodOptional<z.ZodString>;
418
416
  credentials: z.ZodObject<{
419
417
  accessKeyId: z.ZodString;
420
418
  secretAccessKey: z.ZodString;
421
- }, "strip", z.ZodTypeAny, {
422
- accessKeyId: string;
423
- secretAccessKey: string;
424
- }, {
425
- accessKeyId: string;
426
- secretAccessKey: string;
427
- }>;
428
- /** Default false; some S3-compatible providers need path-style. */
419
+ }, z.core.$strip>;
429
420
  forcePathStyle: z.ZodDefault<z.ZodBoolean>;
430
- }, "strip", z.ZodTypeAny, {
431
- provider: "s3";
432
- bucket: string;
433
- region: string;
434
- credentials: {
435
- accessKeyId: string;
436
- secretAccessKey: string;
437
- };
438
- forcePathStyle: boolean;
439
- endpoint?: string | undefined;
440
- }, {
441
- provider: "s3";
442
- bucket: string;
443
- region: string;
444
- credentials: {
445
- accessKeyId: string;
446
- secretAccessKey: string;
447
- };
448
- endpoint?: string | undefined;
449
- forcePathStyle?: boolean | undefined;
450
- }>;
421
+ }, z.core.$strip>;
451
422
  declare const R2ConfigSchema: z.ZodObject<{
452
423
  provider: z.ZodLiteral<"r2">;
453
424
  bucket: z.ZodString;
@@ -455,107 +426,29 @@ declare const R2ConfigSchema: z.ZodObject<{
455
426
  credentials: z.ZodObject<{
456
427
  accessKeyId: z.ZodString;
457
428
  secretAccessKey: z.ZodString;
458
- }, "strip", z.ZodTypeAny, {
459
- accessKeyId: string;
460
- secretAccessKey: string;
461
- }, {
462
- accessKeyId: string;
463
- secretAccessKey: string;
464
- }>;
465
- /** R2 ONLY supports path-style; the literal-true type makes that explicit. */
429
+ }, z.core.$strip>;
466
430
  forcePathStyle: z.ZodLiteral<true>;
467
- }, "strip", z.ZodTypeAny, {
468
- provider: "r2";
469
- bucket: string;
470
- endpoint: string;
471
- credentials: {
472
- accessKeyId: string;
473
- secretAccessKey: string;
474
- };
475
- forcePathStyle: true;
476
- }, {
477
- provider: "r2";
478
- bucket: string;
479
- endpoint: string;
480
- credentials: {
481
- accessKeyId: string;
482
- secretAccessKey: string;
483
- };
484
- forcePathStyle: true;
485
- }>;
486
- declare const FileSystemConfigSchema: z.ZodDiscriminatedUnion<"provider", [z.ZodObject<{
431
+ }, z.core.$strip>;
432
+ declare const FileSystemConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
487
433
  provider: z.ZodLiteral<"s3">;
488
434
  bucket: z.ZodString;
489
435
  region: z.ZodString;
490
- /** Optional S3-compatible endpoint (MinIO, LocalStack, ...). */
491
436
  endpoint: z.ZodOptional<z.ZodString>;
492
437
  credentials: z.ZodObject<{
493
438
  accessKeyId: z.ZodString;
494
439
  secretAccessKey: z.ZodString;
495
- }, "strip", z.ZodTypeAny, {
496
- accessKeyId: string;
497
- secretAccessKey: string;
498
- }, {
499
- accessKeyId: string;
500
- secretAccessKey: string;
501
- }>;
502
- /** Default false; some S3-compatible providers need path-style. */
440
+ }, z.core.$strip>;
503
441
  forcePathStyle: z.ZodDefault<z.ZodBoolean>;
504
- }, "strip", z.ZodTypeAny, {
505
- provider: "s3";
506
- bucket: string;
507
- region: string;
508
- credentials: {
509
- accessKeyId: string;
510
- secretAccessKey: string;
511
- };
512
- forcePathStyle: boolean;
513
- endpoint?: string | undefined;
514
- }, {
515
- provider: "s3";
516
- bucket: string;
517
- region: string;
518
- credentials: {
519
- accessKeyId: string;
520
- secretAccessKey: string;
521
- };
522
- endpoint?: string | undefined;
523
- forcePathStyle?: boolean | undefined;
524
- }>, z.ZodObject<{
442
+ }, z.core.$strip>, z.ZodObject<{
525
443
  provider: z.ZodLiteral<"r2">;
526
444
  bucket: z.ZodString;
527
445
  endpoint: z.ZodString;
528
446
  credentials: z.ZodObject<{
529
447
  accessKeyId: z.ZodString;
530
448
  secretAccessKey: z.ZodString;
531
- }, "strip", z.ZodTypeAny, {
532
- accessKeyId: string;
533
- secretAccessKey: string;
534
- }, {
535
- accessKeyId: string;
536
- secretAccessKey: string;
537
- }>;
538
- /** R2 ONLY supports path-style; the literal-true type makes that explicit. */
449
+ }, z.core.$strip>;
539
450
  forcePathStyle: z.ZodLiteral<true>;
540
- }, "strip", z.ZodTypeAny, {
541
- provider: "r2";
542
- bucket: string;
543
- endpoint: string;
544
- credentials: {
545
- accessKeyId: string;
546
- secretAccessKey: string;
547
- };
548
- forcePathStyle: true;
549
- }, {
550
- provider: "r2";
551
- bucket: string;
552
- endpoint: string;
553
- credentials: {
554
- accessKeyId: string;
555
- secretAccessKey: string;
556
- };
557
- forcePathStyle: true;
558
- }>]>;
451
+ }, z.core.$strip>], "provider">;
559
452
  type S3Config = z.infer<typeof S3ConfigSchema>;
560
453
  type R2Config = z.infer<typeof R2ConfigSchema>;
561
454
  type FileSystemConfig = z.infer<typeof FileSystemConfigSchema>;
@@ -831,11 +724,14 @@ interface FileSystem {
831
724
  readonly tenantId?: string;
832
725
  /** Optional per-tenant byte cap enforced by write-through. */
833
726
  readonly quotaBytes?: number;
727
+ /** Optional per-object byte cap enforced on presigned confirm. */
728
+ readonly maxBytes?: number;
834
729
  forTenant(tenantId: string): FileSystem;
835
730
  }
836
731
  interface CreateFileSystemOptions {
837
732
  readonly store?: MetadataStore;
838
733
  readonly quotaBytes?: number;
734
+ readonly maxBytes?: number;
839
735
  /**
840
736
  * When true (default), `forTenant(id)` prefixes object keys with
841
737
  * `t/{id}/`. Set false for a single-tenant bucket or an R2 token
@@ -206,6 +206,11 @@ interface PresignedUploadInput {
206
206
  contentType?: string;
207
207
  /** Expiry in seconds. Providers enforce a max (e.g. 7 days for S3). */
208
208
  expiresIn?: number;
209
+ /**
210
+ * When set, the signature binds `Content-Length` to this value.
211
+ * A PUT with any other length is rejected by the provider.
212
+ */
213
+ contentLength?: number;
209
214
  }
210
215
  interface PresignedDownloadInput {
211
216
  key: S3Key;
@@ -401,53 +406,19 @@ interface S3CompatibleAdapter {
401
406
  declare const CredentialsSchema: z.ZodObject<{
402
407
  accessKeyId: z.ZodString;
403
408
  secretAccessKey: z.ZodString;
404
- }, "strip", z.ZodTypeAny, {
405
- accessKeyId: string;
406
- secretAccessKey: string;
407
- }, {
408
- accessKeyId: string;
409
- secretAccessKey: string;
410
- }>;
409
+ }, z.core.$strip>;
411
410
  type Credentials = z.infer<typeof CredentialsSchema>;
412
411
  declare const S3ConfigSchema: z.ZodObject<{
413
412
  provider: z.ZodLiteral<"s3">;
414
413
  bucket: z.ZodString;
415
414
  region: z.ZodString;
416
- /** Optional S3-compatible endpoint (MinIO, LocalStack, ...). */
417
415
  endpoint: z.ZodOptional<z.ZodString>;
418
416
  credentials: z.ZodObject<{
419
417
  accessKeyId: z.ZodString;
420
418
  secretAccessKey: z.ZodString;
421
- }, "strip", z.ZodTypeAny, {
422
- accessKeyId: string;
423
- secretAccessKey: string;
424
- }, {
425
- accessKeyId: string;
426
- secretAccessKey: string;
427
- }>;
428
- /** Default false; some S3-compatible providers need path-style. */
419
+ }, z.core.$strip>;
429
420
  forcePathStyle: z.ZodDefault<z.ZodBoolean>;
430
- }, "strip", z.ZodTypeAny, {
431
- provider: "s3";
432
- bucket: string;
433
- region: string;
434
- credentials: {
435
- accessKeyId: string;
436
- secretAccessKey: string;
437
- };
438
- forcePathStyle: boolean;
439
- endpoint?: string | undefined;
440
- }, {
441
- provider: "s3";
442
- bucket: string;
443
- region: string;
444
- credentials: {
445
- accessKeyId: string;
446
- secretAccessKey: string;
447
- };
448
- endpoint?: string | undefined;
449
- forcePathStyle?: boolean | undefined;
450
- }>;
421
+ }, z.core.$strip>;
451
422
  declare const R2ConfigSchema: z.ZodObject<{
452
423
  provider: z.ZodLiteral<"r2">;
453
424
  bucket: z.ZodString;
@@ -455,107 +426,29 @@ declare const R2ConfigSchema: z.ZodObject<{
455
426
  credentials: z.ZodObject<{
456
427
  accessKeyId: z.ZodString;
457
428
  secretAccessKey: z.ZodString;
458
- }, "strip", z.ZodTypeAny, {
459
- accessKeyId: string;
460
- secretAccessKey: string;
461
- }, {
462
- accessKeyId: string;
463
- secretAccessKey: string;
464
- }>;
465
- /** R2 ONLY supports path-style; the literal-true type makes that explicit. */
429
+ }, z.core.$strip>;
466
430
  forcePathStyle: z.ZodLiteral<true>;
467
- }, "strip", z.ZodTypeAny, {
468
- provider: "r2";
469
- bucket: string;
470
- endpoint: string;
471
- credentials: {
472
- accessKeyId: string;
473
- secretAccessKey: string;
474
- };
475
- forcePathStyle: true;
476
- }, {
477
- provider: "r2";
478
- bucket: string;
479
- endpoint: string;
480
- credentials: {
481
- accessKeyId: string;
482
- secretAccessKey: string;
483
- };
484
- forcePathStyle: true;
485
- }>;
486
- declare const FileSystemConfigSchema: z.ZodDiscriminatedUnion<"provider", [z.ZodObject<{
431
+ }, z.core.$strip>;
432
+ declare const FileSystemConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
487
433
  provider: z.ZodLiteral<"s3">;
488
434
  bucket: z.ZodString;
489
435
  region: z.ZodString;
490
- /** Optional S3-compatible endpoint (MinIO, LocalStack, ...). */
491
436
  endpoint: z.ZodOptional<z.ZodString>;
492
437
  credentials: z.ZodObject<{
493
438
  accessKeyId: z.ZodString;
494
439
  secretAccessKey: z.ZodString;
495
- }, "strip", z.ZodTypeAny, {
496
- accessKeyId: string;
497
- secretAccessKey: string;
498
- }, {
499
- accessKeyId: string;
500
- secretAccessKey: string;
501
- }>;
502
- /** Default false; some S3-compatible providers need path-style. */
440
+ }, z.core.$strip>;
503
441
  forcePathStyle: z.ZodDefault<z.ZodBoolean>;
504
- }, "strip", z.ZodTypeAny, {
505
- provider: "s3";
506
- bucket: string;
507
- region: string;
508
- credentials: {
509
- accessKeyId: string;
510
- secretAccessKey: string;
511
- };
512
- forcePathStyle: boolean;
513
- endpoint?: string | undefined;
514
- }, {
515
- provider: "s3";
516
- bucket: string;
517
- region: string;
518
- credentials: {
519
- accessKeyId: string;
520
- secretAccessKey: string;
521
- };
522
- endpoint?: string | undefined;
523
- forcePathStyle?: boolean | undefined;
524
- }>, z.ZodObject<{
442
+ }, z.core.$strip>, z.ZodObject<{
525
443
  provider: z.ZodLiteral<"r2">;
526
444
  bucket: z.ZodString;
527
445
  endpoint: z.ZodString;
528
446
  credentials: z.ZodObject<{
529
447
  accessKeyId: z.ZodString;
530
448
  secretAccessKey: z.ZodString;
531
- }, "strip", z.ZodTypeAny, {
532
- accessKeyId: string;
533
- secretAccessKey: string;
534
- }, {
535
- accessKeyId: string;
536
- secretAccessKey: string;
537
- }>;
538
- /** R2 ONLY supports path-style; the literal-true type makes that explicit. */
449
+ }, z.core.$strip>;
539
450
  forcePathStyle: z.ZodLiteral<true>;
540
- }, "strip", z.ZodTypeAny, {
541
- provider: "r2";
542
- bucket: string;
543
- endpoint: string;
544
- credentials: {
545
- accessKeyId: string;
546
- secretAccessKey: string;
547
- };
548
- forcePathStyle: true;
549
- }, {
550
- provider: "r2";
551
- bucket: string;
552
- endpoint: string;
553
- credentials: {
554
- accessKeyId: string;
555
- secretAccessKey: string;
556
- };
557
- forcePathStyle: true;
558
- }>]>;
451
+ }, z.core.$strip>], "provider">;
559
452
  type S3Config = z.infer<typeof S3ConfigSchema>;
560
453
  type R2Config = z.infer<typeof R2ConfigSchema>;
561
454
  type FileSystemConfig = z.infer<typeof FileSystemConfigSchema>;
@@ -831,11 +724,14 @@ interface FileSystem {
831
724
  readonly tenantId?: string;
832
725
  /** Optional per-tenant byte cap enforced by write-through. */
833
726
  readonly quotaBytes?: number;
727
+ /** Optional per-object byte cap enforced on presigned confirm. */
728
+ readonly maxBytes?: number;
834
729
  forTenant(tenantId: string): FileSystem;
835
730
  }
836
731
  interface CreateFileSystemOptions {
837
732
  readonly store?: MetadataStore;
838
733
  readonly quotaBytes?: number;
734
+ readonly maxBytes?: number;
839
735
  /**
840
736
  * When true (default), `forTenant(id)` prefixes object keys with
841
737
  * `t/{id}/`. Set false for a single-tenant bucket or an R2 token
package/dist/index.cjs CHANGED
@@ -3080,10 +3080,16 @@ var createPresignedUploadUrl = async (client, config, input) => {
3080
3080
  const cmd = new clientS3.PutObjectCommand({
3081
3081
  Bucket: config.bucket,
3082
3082
  Key: input.key,
3083
- ContentType: input.contentType
3083
+ ContentType: input.contentType,
3084
+ ContentLength: input.contentLength
3084
3085
  });
3085
3086
  const url = await s3RequestPresigner.getSignedUrl(client, cmd, { expiresIn: input.expiresIn ?? 900 });
3086
- return ok({ url, method: "PUT" });
3087
+ const requiredHeaders = {};
3088
+ if (input.contentType) requiredHeaders["Content-Type"] = input.contentType;
3089
+ if (input.contentLength != null) {
3090
+ requiredHeaders["Content-Length"] = String(input.contentLength);
3091
+ }
3092
+ return ok({ url, method: "PUT", requiredHeaders });
3087
3093
  } catch (e) {
3088
3094
  return err(fromAws(e));
3089
3095
  }
@@ -3390,11 +3396,17 @@ function createMemoryAdapter(options = {}) {
3390
3396
  };
3391
3397
  const createPresignedUploadUrl2 = async (input) => {
3392
3398
  const expiresIn = input.expiresIn ?? 900;
3399
+ const requiredHeaders = {};
3400
+ if (input.contentType) requiredHeaders["Content-Type"] = input.contentType;
3401
+ if (input.contentLength != null) {
3402
+ requiredHeaders["Content-Length"] = String(input.contentLength);
3403
+ }
3393
3404
  return {
3394
3405
  ok: true,
3395
3406
  value: {
3396
3407
  url: `in-memory://${bucket}/${input.key}?expiresIn=${expiresIn}`,
3397
- method: "PUT"
3408
+ method: "PUT",
3409
+ requiredHeaders
3398
3410
  }
3399
3411
  };
3400
3412
  };
@@ -3623,6 +3635,7 @@ var assemble = (adapter, config, options) => {
3623
3635
  metadata: options?.store,
3624
3636
  tenantId,
3625
3637
  quotaBytes: options?.quotaBytes,
3638
+ maxBytes: options?.maxBytes,
3626
3639
  forTenant
3627
3640
  });
3628
3641
  return {
@@ -3630,6 +3643,7 @@ var assemble = (adapter, config, options) => {
3630
3643
  config,
3631
3644
  metadata: options?.store,
3632
3645
  quotaBytes: options?.quotaBytes,
3646
+ maxBytes: options?.maxBytes,
3633
3647
  forTenant
3634
3648
  };
3635
3649
  };
@@ -4479,22 +4493,22 @@ var buildInner = (Database, options) => {
4479
4493
  const stmtUpdateSelfMove = db.prepare(`
4480
4494
  UPDATE nodes
4481
4495
  SET parent_id = ?, name = ?, path = ?, updated_at = ?
4482
- WHERE id = ?
4496
+ WHERE id = ? AND tenant_id = ?
4483
4497
  `);
4484
4498
  const stmtCascadeDescendants = db.prepare(`
4485
4499
  UPDATE nodes
4486
4500
  SET path = ? || SUBSTR(path, LENGTH(?) + 1), updated_at = ?
4487
- WHERE path LIKE ? || '/%'
4501
+ WHERE tenant_id = ? AND path LIKE ? || '/%' ESCAPE '\\'
4488
4502
  `);
4489
4503
  const stmtTombstone = db.prepare(`
4490
4504
  UPDATE nodes
4491
4505
  SET deleted_at = ?, updated_at = ?
4492
- WHERE id = ?
4506
+ WHERE id = ? AND tenant_id = ?
4493
4507
  `);
4494
4508
  const stmtTombstoneSubtree = db.prepare(`
4495
4509
  UPDATE nodes
4496
4510
  SET deleted_at = ?, updated_at = ?
4497
- WHERE (id = ? OR path LIKE ? || '/%')
4511
+ WHERE tenant_id = ? AND (id = ? OR path LIKE ? || '/%' ESCAPE '\\')
4498
4512
  AND deleted_at IS NULL
4499
4513
  `);
4500
4514
  const stmtHasLiveChild = db.prepare(`
@@ -4522,7 +4536,7 @@ var buildInner = (Database, options) => {
4522
4536
  WHERE nodes_fts MATCH ?
4523
4537
  AND n.tenant_id = ?
4524
4538
  AND n.deleted_at IS NULL
4525
- AND (n.id = ? OR n.path LIKE ? || '/%')
4539
+ AND (n.id = ? OR n.path LIKE ? || '/%' ESCAPE '\\')
4526
4540
  ORDER BY bm25(nodes_fts)
4527
4541
  LIMIT ?
4528
4542
  `);
@@ -4559,6 +4573,7 @@ var buildInner = (Database, options) => {
4559
4573
  retryable: false
4560
4574
  })
4561
4575
  );
4576
+ const escapeLike = (s) => s.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
4562
4577
  return {
4563
4578
  async createNode(input) {
4564
4579
  const normalized = normalizeNodeName(input.name);
@@ -4611,7 +4626,7 @@ var buildInner = (Database, options) => {
4611
4626
  return err(
4612
4627
  new FileSystemError({
4613
4628
  code: "InternalError",
4614
- message: `createNode failed: ${e.message}`,
4629
+ message: "createNode failed",
4615
4630
  retryable: false
4616
4631
  })
4617
4632
  );
@@ -4698,14 +4713,16 @@ var buildInner = (Database, options) => {
4698
4713
  newName,
4699
4714
  newPath,
4700
4715
  nowIso(),
4701
- input.id
4716
+ input.id,
4717
+ input.tenantId
4702
4718
  );
4703
4719
  if (current.path !== newPath) {
4704
4720
  stmtCascadeDescendants.run(
4705
4721
  newPath,
4706
4722
  current.path,
4707
4723
  nowIso(),
4708
- current.path
4724
+ input.tenantId,
4725
+ escapeLike(current.path)
4709
4726
  );
4710
4727
  }
4711
4728
  });
@@ -4723,7 +4740,7 @@ var buildInner = (Database, options) => {
4723
4740
  return err(
4724
4741
  new FileSystemError({
4725
4742
  code: "InternalError",
4726
- message: `moveNode failed: ${e.message}`,
4743
+ message: "moveNode failed",
4727
4744
  retryable: false
4728
4745
  })
4729
4746
  );
@@ -4757,9 +4774,9 @@ var buildInner = (Database, options) => {
4757
4774
  }
4758
4775
  const now = nowIso();
4759
4776
  if (input.recursive || node.kind === "file") {
4760
- stmtTombstoneSubtree.run(now, now, input.id, node.path);
4777
+ stmtTombstoneSubtree.run(now, now, input.tenantId, input.id, escapeLike(node.path));
4761
4778
  } else {
4762
- stmtTombstone.run(now, now, input.id);
4779
+ stmtTombstone.run(now, now, input.id, input.tenantId);
4763
4780
  }
4764
4781
  return ok(void 0);
4765
4782
  },
@@ -4776,19 +4793,19 @@ var buildInner = (Database, options) => {
4776
4793
  })
4777
4794
  );
4778
4795
  }
4779
- const like = `${node.path}/%`;
4796
+ const like = `${escapeLike(node.path)}/%`;
4780
4797
  const files = db.prepare(
4781
4798
  `SELECT s3_key FROM nodes
4782
4799
  WHERE tenant_id = ? AND deleted_at IS NOT NULL AND kind = 'file' AND s3_key != ''
4783
- AND (id = ? OR path LIKE ?)`
4800
+ AND (id = ? OR path LIKE ? ESCAPE '\\')`
4784
4801
  ).all(input.tenantId, input.id, like);
4785
4802
  db.prepare(
4786
4803
  `DELETE FROM shares WHERE tenant_id = ? AND node_id IN (
4787
- SELECT id FROM nodes WHERE tenant_id = ? AND deleted_at IS NOT NULL AND (id = ? OR path LIKE ?)
4804
+ SELECT id FROM nodes WHERE tenant_id = ? AND deleted_at IS NOT NULL AND (id = ? OR path LIKE ? ESCAPE '\\')
4788
4805
  )`
4789
4806
  ).run(input.tenantId, input.tenantId, input.id, like);
4790
4807
  db.prepare(
4791
- `DELETE FROM nodes WHERE tenant_id = ? AND deleted_at IS NOT NULL AND (id = ? OR path LIKE ?)`
4808
+ `DELETE FROM nodes WHERE tenant_id = ? AND deleted_at IS NOT NULL AND (id = ? OR path LIKE ? ESCAPE '\\')`
4792
4809
  ).run(input.tenantId, input.id, like);
4793
4810
  return ok({ s3Keys: files.map((row) => row.s3_key) });
4794
4811
  },
@@ -4828,7 +4845,7 @@ var buildInner = (Database, options) => {
4828
4845
  ftsQuery,
4829
4846
  input.tenantId,
4830
4847
  input.parentId,
4831
- parent.path,
4848
+ escapeLike(parent.path),
4832
4849
  limit
4833
4850
  );
4834
4851
  }
@@ -5300,7 +5317,7 @@ var buildInner2 = (pg, options) => {
5300
5317
  return err(
5301
5318
  new FileSystemError({
5302
5319
  code: "InternalError",
5303
- message: `createNode failed: ${e.message}`,
5320
+ message: "createNode failed",
5304
5321
  retryable: false
5305
5322
  })
5306
5323
  );
@@ -5417,16 +5434,16 @@ var buildInner2 = (pg, options) => {
5417
5434
  await client.query(
5418
5435
  `UPDATE "${schema}".nodes
5419
5436
  SET parent_id = $1, name = $2, path = $3, updated_at = now()
5420
- WHERE id = $4`,
5421
- [input.newParentId, newName, newPath, input.id]
5437
+ WHERE id = $4 AND tenant_id = $5`,
5438
+ [input.newParentId, newName, newPath, input.id, input.tenantId]
5422
5439
  );
5423
5440
  if (current.path !== newPath) {
5424
5441
  await client.query(
5425
5442
  `UPDATE "${schema}".nodes
5426
5443
  SET path = $1 || substring(path FROM length($2) + 1),
5427
5444
  updated_at = now()
5428
- WHERE path LIKE $2 || '/%'`,
5429
- [newPath, current.path]
5445
+ WHERE tenant_id = $3 AND path LIKE $4 || '/%' ESCAPE '\\'`,
5446
+ [newPath, current.path, input.tenantId, escapeLike(current.path)]
5430
5447
  );
5431
5448
  }
5432
5449
  });
@@ -5443,7 +5460,7 @@ var buildInner2 = (pg, options) => {
5443
5460
  return err(
5444
5461
  new FileSystemError({
5445
5462
  code: "InternalError",
5446
- message: `moveNode failed: ${e.message}`,
5463
+ message: "moveNode failed",
5447
5464
  retryable: false
5448
5465
  })
5449
5466
  );
@@ -5498,17 +5515,17 @@ var buildInner2 = (pg, options) => {
5498
5515
  input.tenantId,
5499
5516
  `UPDATE "${schema}".nodes
5500
5517
  SET deleted_at = now(), updated_at = now()
5501
- WHERE (id = $1 OR path LIKE $2 || '/%')
5518
+ WHERE tenant_id = $1 AND (id = $2 OR path LIKE $3 || '/%' ESCAPE '\\')
5502
5519
  AND deleted_at IS NULL`,
5503
- [input.id, node.path]
5520
+ [input.tenantId, input.id, escapeLike(node.path)]
5504
5521
  );
5505
5522
  } else {
5506
5523
  await q(
5507
5524
  input.tenantId,
5508
5525
  `UPDATE "${schema}".nodes
5509
5526
  SET deleted_at = now(), updated_at = now()
5510
- WHERE id = $1`,
5511
- [input.id]
5527
+ WHERE id = $1 AND tenant_id = $2`,
5528
+ [input.id, input.tenantId]
5512
5529
  );
5513
5530
  }
5514
5531
  return ok(void 0);
@@ -5531,26 +5548,26 @@ var buildInner2 = (pg, options) => {
5531
5548
  })
5532
5549
  );
5533
5550
  }
5534
- const like = `${node.path}/%`;
5551
+ const like = `${escapeLike(node.path)}/%`;
5535
5552
  const files = await q(
5536
5553
  input.tenantId,
5537
5554
  `SELECT s3_key FROM "${schema}".nodes
5538
5555
  WHERE tenant_id = $1 AND deleted_at IS NOT NULL AND kind = 'file' AND s3_key <> ''
5539
- AND (id = $2 OR path LIKE $3)`,
5556
+ AND (id = $2 OR path LIKE $3 ESCAPE '\\')`,
5540
5557
  [input.tenantId, input.id, like]
5541
5558
  );
5542
5559
  await q(
5543
5560
  input.tenantId,
5544
5561
  `DELETE FROM "${schema}".shares WHERE tenant_id = $1 AND node_id IN (
5545
5562
  SELECT id FROM "${schema}".nodes
5546
- WHERE tenant_id = $1 AND deleted_at IS NOT NULL AND (id = $2 OR path LIKE $3)
5563
+ WHERE tenant_id = $1 AND deleted_at IS NOT NULL AND (id = $2 OR path LIKE $3 ESCAPE '\\')
5547
5564
  )`,
5548
5565
  [input.tenantId, input.id, like]
5549
5566
  );
5550
5567
  await q(
5551
5568
  input.tenantId,
5552
5569
  `DELETE FROM "${schema}".nodes
5553
- WHERE tenant_id = $1 AND deleted_at IS NOT NULL AND (id = $2 OR path LIKE $3)`,
5570
+ WHERE tenant_id = $1 AND deleted_at IS NOT NULL AND (id = $2 OR path LIKE $3 ESCAPE '\\')`,
5554
5571
  [input.tenantId, input.id, like]
5555
5572
  );
5556
5573
  return ok({ s3Keys: files.rows.map((row) => row.s3_key) });
@@ -5624,11 +5641,11 @@ var buildInner2 = (pg, options) => {
5624
5641
  input.tenantId,
5625
5642
  `SELECT * FROM "${schema}".nodes
5626
5643
  WHERE tenant_id = $1 AND deleted_at IS NULL
5627
- AND (id = $2 OR path LIKE $3 || '/%')
5644
+ AND (id = $2 OR path LIKE $3 || '/%' ESCAPE '\\')
5628
5645
  AND name ILIKE $4 ESCAPE '\\'
5629
5646
  ORDER BY lower(name) ASC
5630
5647
  LIMIT $5`,
5631
- [input.tenantId, input.parentId, parent.path, pattern, limit]
5648
+ [input.tenantId, input.parentId, escapeLike(parent.path), pattern, limit]
5632
5649
  );
5633
5650
  }
5634
5651
  }