@papillon6814/copi 0.1.0 → 0.2.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 (53) hide show
  1. package/LICENSE.unicode +39 -0
  2. package/README.md +57 -5
  3. package/dist/native/rename-exclusive-arm64 +0 -0
  4. package/dist/native/rename-exclusive-arm64.manifest.json +12 -0
  5. package/dist/src/apply.d.ts +9 -0
  6. package/dist/src/apply.js +110 -9
  7. package/dist/src/catalog.d.ts +21 -4
  8. package/dist/src/catalog.js +265 -133
  9. package/dist/src/cli.d.ts +2 -1
  10. package/dist/src/cli.js +73 -3
  11. package/dist/src/groups.js +8 -3
  12. package/dist/src/inspect.d.ts +3 -1
  13. package/dist/src/inspect.js +16 -6
  14. package/dist/src/instruction-reference.d.ts +6 -0
  15. package/dist/src/instruction-reference.js +21 -0
  16. package/dist/src/manage.d.ts +2 -0
  17. package/dist/src/manage.js +329 -0
  18. package/dist/src/migrate.d.ts +25 -0
  19. package/dist/src/migrate.js +171 -0
  20. package/dist/src/migration-backup.d.ts +22 -0
  21. package/dist/src/migration-backup.js +403 -0
  22. package/dist/src/migration-inventory.d.ts +9 -0
  23. package/dist/src/migration-inventory.js +237 -0
  24. package/dist/src/migration-resolution.d.ts +28 -0
  25. package/dist/src/migration-resolution.js +1618 -0
  26. package/dist/src/migration.d.ts +49 -0
  27. package/dist/src/migration.js +1004 -0
  28. package/dist/src/names.d.ts +11 -0
  29. package/dist/src/names.js +40 -0
  30. package/dist/src/native-placement.d.ts +16 -0
  31. package/dist/src/native-placement.js +557 -0
  32. package/dist/src/refresh.d.ts +20 -0
  33. package/dist/src/refresh.js +143 -0
  34. package/dist/src/resource-mutations.d.ts +45 -0
  35. package/dist/src/resource-mutations.js +225 -0
  36. package/dist/src/source-content.d.ts +4 -0
  37. package/dist/src/source-content.js +208 -0
  38. package/dist/src/storage-journal.d.ts +111 -0
  39. package/dist/src/storage-journal.js +613 -0
  40. package/dist/src/storage-root-lease.d.ts +7 -0
  41. package/dist/src/storage-root-lease.js +92 -0
  42. package/dist/src/storage-transaction.d.ts +100 -0
  43. package/dist/src/storage-transaction.js +3951 -0
  44. package/dist/src/terminal-editor.d.ts +8 -0
  45. package/dist/src/terminal-editor.js +391 -0
  46. package/dist/src/types.d.ts +18 -2
  47. package/dist/src/unicode-case-folding-data.d.ts +1 -0
  48. package/dist/src/unicode-case-folding-data.js +1596 -0
  49. package/dist/src/unicode-case-folding.d.ts +2 -0
  50. package/dist/src/unicode-case-folding.js +9 -0
  51. package/dist/src/wizard.d.ts +11 -3
  52. package/dist/src/wizard.js +274 -18
  53. package/package.json +5 -3
@@ -0,0 +1,92 @@
1
+ import { constants } from "node:fs";
2
+ import { lstat, open, realpath } from "node:fs/promises";
3
+ import { setTimeout as delay } from "node:timers/promises";
4
+ import path from "node:path";
5
+ const macOsExclusiveLock = 0x20;
6
+ function sameRootIdentity(left, right) {
7
+ return left.dev === right.dev && left.ino === right.ino && left.isDirectory() && right.isDirectory() &&
8
+ !left.isSymbolicLink() && !right.isSymbolicLink();
9
+ }
10
+ function sameRoot(left, right) {
11
+ return sameRootIdentity(left, right) && left.mode === right.mode;
12
+ }
13
+ /** Holds a crash-released macOS O_EXLOCK on the library root directory descriptor. */
14
+ export async function acquireRootLease(root, waitMilliseconds = 0) {
15
+ if (process.platform !== "darwin") {
16
+ throw new Error("Copi storage mutation requires the macOS root O_EXLOCK lease; no fallback is available.");
17
+ }
18
+ if (!path.isAbsolute(root) || path.resolve(root) !== root || await realpath(root) !== root) {
19
+ throw new Error("Copi storage root must be a canonical absolute directory for its O_EXLOCK lease.");
20
+ }
21
+ const before = await lstat(root, { bigint: true });
22
+ if (!before.isDirectory() || before.isSymbolicLink()) {
23
+ throw new Error("Copi storage root is unsafe; refusing to acquire its O_EXLOCK lease.");
24
+ }
25
+ if (!Number.isSafeInteger(waitMilliseconds) || waitMilliseconds < 0)
26
+ throw new Error("O_EXLOCK wait duration must be a nonnegative safe integer.");
27
+ let handle;
28
+ const deadline = Date.now() + waitMilliseconds;
29
+ while (!handle) {
30
+ try {
31
+ handle = await open(root, constants.O_RDONLY | macOsExclusiveLock | constants.O_NONBLOCK);
32
+ }
33
+ catch (error) {
34
+ const code = error.code;
35
+ if ((code === "EAGAIN" || code === "EWOULDBLOCK") && Date.now() < deadline) {
36
+ await delay(Math.min(10, deadline - Date.now()));
37
+ continue;
38
+ }
39
+ if (code === "EAGAIN" || code === "EWOULDBLOCK") {
40
+ throw new Error("Copi storage root remained leased by another process until the acquisition deadline; refusing mutation.", { cause: error });
41
+ }
42
+ throw new Error("Copi storage root O_EXLOCK lease is unavailable; refusing mutation without fallback.", { cause: error });
43
+ }
44
+ }
45
+ try {
46
+ const [opened, after, canonical] = await Promise.all([
47
+ handle.stat({ bigint: true }),
48
+ lstat(root, { bigint: true }),
49
+ realpath(root),
50
+ ]);
51
+ if (canonical !== root || !sameRoot(before, opened) || !sameRoot(opened, after)) {
52
+ throw new Error("Copi storage root identity changed while acquiring its O_EXLOCK lease.");
53
+ }
54
+ let expectedRoot = opened;
55
+ let closed = false;
56
+ return {
57
+ async assertCurrent() {
58
+ if (closed)
59
+ throw new Error("Copi storage root O_EXLOCK lease is closed.");
60
+ const [currentHandle, currentPath, currentCanonical] = await Promise.all([
61
+ handle.stat({ bigint: true }), lstat(root, { bigint: true }), realpath(root),
62
+ ]);
63
+ if (currentCanonical !== root || !sameRoot(expectedRoot, currentHandle) || !sameRoot(expectedRoot, currentPath)) {
64
+ throw new Error("Copi storage root identity changed while its O_EXLOCK lease was held.");
65
+ }
66
+ },
67
+ async updateExpectedMode(mode) {
68
+ if (closed || !Number.isSafeInteger(mode) || mode < 0 || mode > 0o7777) {
69
+ throw new Error("Copi storage root lease expected mode is invalid or closed.");
70
+ }
71
+ const [currentHandle, currentPath, currentCanonical] = await Promise.all([
72
+ handle.stat({ bigint: true }), lstat(root, { bigint: true }), realpath(root),
73
+ ]);
74
+ if (currentCanonical !== root || !sameRootIdentity(expectedRoot, currentHandle) || !sameRootIdentity(expectedRoot, currentPath) ||
75
+ currentHandle.mode !== currentPath.mode || Number(currentHandle.mode & 4095n) !== mode) {
76
+ throw new Error("Copi storage root identity or requested mode changed before its lease baseline update.");
77
+ }
78
+ expectedRoot = currentHandle;
79
+ },
80
+ async close() {
81
+ if (closed)
82
+ return;
83
+ closed = true;
84
+ await handle.close();
85
+ },
86
+ };
87
+ }
88
+ catch (error) {
89
+ await handle.close().catch(() => undefined);
90
+ throw error;
91
+ }
92
+ }
@@ -0,0 +1,100 @@
1
+ import type { InspectionWarning } from "./types.ts";
2
+ import type { PreparedMigration } from "./migration.ts";
3
+ export interface StorageFile {
4
+ readonly path: string;
5
+ readonly bytes: Buffer;
6
+ readonly mode: number;
7
+ }
8
+ export interface StorageReplacement {
9
+ readonly itemId: string;
10
+ readonly files: readonly StorageFile[];
11
+ }
12
+ export interface LegacyMigrationInput extends StorageFile {
13
+ readonly sha256: string;
14
+ readonly dev: string;
15
+ readonly ino: string;
16
+ }
17
+ declare const legacyMigrationReviewBrand: unique symbol;
18
+ export interface LegacyMigrationReview {
19
+ readonly [legacyMigrationReviewBrand]: true;
20
+ readonly reviewId: string;
21
+ }
22
+ export interface LegacyMigrationApproval {
23
+ readonly reviewId: string;
24
+ readonly confirmed: boolean;
25
+ readonly approvedSensitivePaths: readonly string[];
26
+ }
27
+ declare const storageReviewBrand: unique symbol;
28
+ export interface StorageReview {
29
+ readonly [storageReviewBrand]: true;
30
+ readonly reviewId: string;
31
+ }
32
+ export interface StorageReviewView {
33
+ readonly sensitivePaths: readonly string[];
34
+ readonly warnings: readonly InspectionWarning[];
35
+ }
36
+ export interface StorageAuthorization {
37
+ readonly review: StorageReview;
38
+ readonly approval: {
39
+ readonly reviewId: string;
40
+ readonly confirmed: boolean;
41
+ readonly approvedSensitivePaths: readonly string[];
42
+ };
43
+ }
44
+ export interface StorageRevision {
45
+ readonly root: string;
46
+ readonly catalog: string | null;
47
+ readonly catalogIdentity: string | null;
48
+ readonly format: "v1" | "v2";
49
+ readonly tree: string;
50
+ }
51
+ export type StorageCheckpoint = "stage-write" | "stage-sync" | "journal" | "before-swap" | "old-renamed" | "new-renamed" | "before-catalog" | "catalog-held" | "catalog-renamed" | "directory-sync" | "committed" | "rollback-started" | "rollback-displaced" | "rollback-restored" | "rollback-catalog-held" | "finalized" | "rollback-finalized";
52
+ export interface StorageOptions {
53
+ checkpoint?: (point: StorageCheckpoint, root: string) => void | Promise<void>;
54
+ waitMilliseconds?: number;
55
+ }
56
+ interface StorageRunOptions {
57
+ requiresNativePlacement: boolean;
58
+ }
59
+ export declare function safeStoragePath(value: string): boolean;
60
+ export declare function privateStoragePath(value: string): boolean;
61
+ /** @internal Opaque review for callers of the generalized transaction adapter. Fixed bytes, no approval bypass flags. */
62
+ export declare function prepareStorageReview(catalog: Buffer, replacements: readonly StorageReplacement[]): StorageReview;
63
+ export declare function readStorageReview(review: StorageReview): StorageReviewView;
64
+ /** @internal Returns a detached copy for the caller's private diff state, never a public review view. */
65
+ export declare function readStorageBeforeReviewFiles(review: StorageReview, itemId: string): readonly StorageFile[];
66
+ /** @internal Plan-bound bridge; the request is checked against the opaque public preview before issuance. */
67
+ export declare function prepareLegacyMigrationReview(plan: PreparedMigration, value: unknown): Promise<LegacyMigrationReview>;
68
+ export declare function absentStorageRevision(libraryDir: string): Promise<StorageRevision>;
69
+ /** @internal Generalized fixed-byte persistence adapter. Inspection/approval are enforced here too; instance-bound faults cannot disable checks. */
70
+ export declare class StorageTransactions {
71
+ private readonly options;
72
+ constructor(options?: StorageOptions);
73
+ run<T>(libraryDir: string, create: boolean, operation: (session: StorageSession | undefined) => Promise<T>, runOptions: StorageRunOptions): Promise<T>;
74
+ private recoverDead;
75
+ }
76
+ export declare class StorageSession {
77
+ #private;
78
+ readonly root: string;
79
+ private readonly lock;
80
+ private readonly token;
81
+ private readonly checkpoint?;
82
+ private readonly checkLegacyItems;
83
+ constructor(root: string, lock: string, token: string, checkpoint?: StorageOptions["checkpoint"], checkLegacyItems?: boolean);
84
+ private assertLease;
85
+ capture(): Promise<StorageRevision>;
86
+ assertRevision(expected: StorageRevision): Promise<void>;
87
+ /** @internal Captures a private, lease-bound before state for an explicitly confirmed sources review. */
88
+ prepareSourcesReview(expected: StorageRevision, catalog: Buffer, replacements: readonly StorageReplacement[]): Promise<StorageReview>;
89
+ commit(expected: StorageRevision, catalog: Buffer, replacements?: readonly StorageReplacement[], authorization?: StorageAuthorization): Promise<void>;
90
+ /** @internal Dedicated v1-to-v2 adopt-only path. Ordinary commit() deliberately remains v2-only. */
91
+ commitLegacyMigration(review: LegacyMigrationReview, approvalValue: LegacyMigrationApproval): Promise<void>;
92
+ private markFinalized;
93
+ private verifyFinalized;
94
+ private recoverNewSources;
95
+ private recoverCatalogOnly;
96
+ private recoverSources;
97
+ recover(rollback: boolean): Promise<void>;
98
+ }
99
+ export declare const storageTransactions: StorageTransactions;
100
+ export {};