@gajae-code/natives 0.11.7 → 0.11.9

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/native/index.d.ts CHANGED
@@ -201,6 +201,11 @@ export declare class NotificationServer {
201
201
  * Fails if not started or `frame_json` is not a valid `ServerMessage`.
202
202
  */
203
203
  pushFrame(frameJson: string): void
204
+ /**
205
+ * Deliver a frame through every authenticated connection and wait for each
206
+ * socket writer to settle within `timeout_ms`.
207
+ */
208
+ pushFrameAndWait(frameJson: string, timeoutMs: number): Promise<boolean>
204
209
  /**
205
210
  * Broadcast a TypeScript-constructed turn frame without re-parsing JSON.
206
211
  * External frames must continue through [`Self::push_frame`] for serde
@@ -386,7 +391,7 @@ export declare class RecoveryFsRoot {
386
391
  */
387
392
  appendManaged(relativePath: string, data: Uint8Array, expectedDev: string, expectedIno: string, expectedSize: string, expectedMtimeNs: string, expectedCtimeNs: string, expectedSha256: string): RecoveryFsResult
388
393
  /** Remove one exact managed regular file through retained authority. */
389
- removeManaged(relativePath: string, expectedDev: string, expectedIno: string, expectedSize: string, expectedMtimeNs: string, expectedCtimeNs: string, expectedSha256: string): RecoveryFsResult
394
+ removeManaged(relativePath: string, expectedDev: string, expectedIno: string, expectedSize: string, expectedMtimeNs: string, expectedCtimeNs: string, expectedSha256: string): RecoveryFsRetainedCleanupResult
390
395
  /**
391
396
  * Create each absent directory component beneath the retained root with
392
397
  * owner-only security. Existing components are re-opened no-follow.
@@ -397,22 +402,22 @@ export declare class RecoveryFsRoot {
397
402
  * retained root. The source identity is rechecked after the no-replace
398
403
  * rename, and the move is rolled back on a mismatch.
399
404
  */
400
- renameManagedFileNoReplace(sourceRelativePath: string, destinationRelativePath: string, expectedDev: string, expectedIno: string, expectedSize: string, expectedMtimeNs: string, expectedCtimeNs: string, expectedSha256: string): RecoveryFsResult
405
+ renameManagedFileNoReplace(sourceRelativePath: string, destinationRelativePath: string, expectedDev: string, expectedIno: string, expectedSize: string, expectedMtimeNs: string, expectedCtimeNs: string, expectedSha256: string): RecoveryFsPublishResult
401
406
  /** Snapshot a managed directory tree entirely through the retained root. */
402
407
  snapshotManagedTree(relativePath: string): NativeDirectoryTreeResult
403
408
  /**
404
409
  * Move an exact managed directory tree to an absent name through retained
405
410
  * authority.
406
411
  */
407
- renameManagedTreeNoReplace(sourceRelativePath: string, destinationRelativePath: string, expected: NativeDirectoryTreeSnapshot): RecoveryFsResult
412
+ renameManagedTreeNoReplace(sourceRelativePath: string, destinationRelativePath: string, expected: NativeDirectoryTreeSnapshot): RecoveryFsPublishResult
408
413
  /** Remove an exact managed directory tree through retained authority. */
409
- removeManagedTree(relativePath: string, expected: NativeDirectoryTreeSnapshot): RecoveryFsResult
414
+ removeManagedTree(relativePath: string, expected: NativeDirectoryTreeSnapshot): RecoveryFsRetainedCleanupResult
410
415
  /**
411
416
  * Atomically install an already-created regular file at an absent name.
412
417
  * Both names remain relative to this retained root and are never resolved
413
418
  * through a pathname after their parent descriptors are acquired.
414
419
  */
415
- install(sourceRelativePath: string, destinationRelativePath: string): RecoveryFsResult
420
+ install(sourceRelativePath: string, destinationRelativePath: string): RecoveryFsPublishResult
416
421
  /**
417
422
  * Synchronize the retained root directory, making a preceding create or
418
423
  * install durable when the filesystem supports directory fsync.
@@ -452,6 +457,15 @@ export declare class Shell {
452
457
  abort(): Promise<void>
453
458
  }
454
459
 
460
+ /**
461
+ * Publish-result wire-contract sentinel.
462
+ *
463
+ * The loader requires this in addition to the release sentinel, so a
464
+ * same-version modern artifact built before the retained-publish contract
465
+ * cannot be selected over a compatible baseline.
466
+ */
467
+ export declare function __piNativesPublishOutcomeV1(): void
468
+
455
469
  /**
456
470
  * Version sentinel — exists solely so the JS loader can prove at load time
457
471
  * that the `.node` file on disk is from the same package release as the
@@ -470,7 +484,7 @@ export declare class Shell {
470
484
  * `packages/natives/native/index.js` (which derives the name from
471
485
  * `package.json#version`).
472
486
  */
473
- export declare function __piNativesV0_11_7(): void
487
+ export declare function __piNativesV0_11_9(): void
474
488
 
475
489
  /**
476
490
  * Apply conservative pre-execution rewrites to a bash command.
@@ -1720,6 +1734,18 @@ export interface NativeExactUnlinkResult {
1720
1734
  retainedUnknownPath?: string
1721
1735
  }
1722
1736
 
1737
+ /** Dedicated result for an atomic no-replace namespace publication. */
1738
+ export interface NativeNoReplaceResult {
1739
+ ok: boolean
1740
+ code?: string
1741
+ mutationState: string
1742
+ durabilityState: string
1743
+ reason: string
1744
+ primitive: string
1745
+ phase: string
1746
+ diagnostic: NativePublishDiagnostic
1747
+ }
1748
+
1723
1749
  /** Result of applying or checking owner-only path security. */
1724
1750
  export type NativeOwnerOnlySecurityResult =
1725
1751
  | {
@@ -1950,6 +1976,38 @@ export interface RecoveryFsIdentity {
1950
1976
  sha256?: string
1951
1977
  }
1952
1978
 
1979
+ /** Bounded, path-free diagnostic evidence for one retained publication. */
1980
+ export interface RecoveryFsPublishDiagnostic {
1981
+ schemaVersion: number
1982
+ collectionState: string
1983
+ osCode?: number
1984
+ syncFailures?: Array<RecoveryFsPublishSyncFailure>
1985
+ }
1986
+
1987
+ /**
1988
+ * Explicit mutation and durability outcome for retained no-replace
1989
+ * publication.
1990
+ */
1991
+ export interface RecoveryFsPublishResult {
1992
+ ok: boolean
1993
+ code?: string
1994
+ identity?: RecoveryFsIdentity
1995
+ mutationState: string
1996
+ durabilityState: string
1997
+ reason: string
1998
+ primitive: string
1999
+ phase: string
2000
+ diagnostic: RecoveryFsPublishDiagnostic
2001
+ }
2002
+
2003
+ /** Bounded, path-free diagnostic evidence for one retained publication. */
2004
+ export interface RecoveryFsPublishSyncFailure {
2005
+ phase: string
2006
+ parentRole: string
2007
+ osCode?: number
2008
+ kind: string
2009
+ }
2010
+
1953
2011
  export interface RecoveryFsResult {
1954
2012
  ok: boolean
1955
2013
  code?: string
@@ -1957,7 +2015,20 @@ export interface RecoveryFsResult {
1957
2015
  data?: Uint8Array
1958
2016
  }
1959
2017
 
1960
- export declare function renameNoReplacePath(sourcePath: string, destinationPath: string): NativeExactUnlinkResult
2018
+ /**
2019
+ * Fail-closed outcome for a removal whose detached object remains retained.
2020
+ * `recovery_path` identifies evidence only; it grants no authority to replay
2021
+ * or delete the retained object.
2022
+ */
2023
+ export interface RecoveryFsRetainedCleanupResult {
2024
+ ok: boolean
2025
+ code?: string
2026
+ recoveryPath?: string
2027
+ identity?: RecoveryFsIdentity
2028
+ treeSnapshot?: NativeDirectoryTreeSnapshot
2029
+ }
2030
+
2031
+ export declare function renameNoReplacePath(sourcePath: string, destinationPath: string): NativeNoReplaceResult
1961
2032
 
1962
2033
  /**
1963
2034
  * Repair an owner-only ACL on a retained expected path.
@@ -2232,3 +2303,19 @@ export interface WorkProfile {
2232
2303
  * Returns UTF-16 lines with active SGR codes carried across line boundaries.
2233
2304
  */
2234
2305
  export declare function wrapTextWithAnsi(text: string, width: number, tabWidth: number): Array<string>
2306
+
2307
+ /** Bounded, path-free evidence for a parent-directory durability failure. */
2308
+ export interface NativePublishSyncFailure {
2309
+ phase: string
2310
+ parentRole: string
2311
+ osCode: number
2312
+ kind: string
2313
+ }
2314
+
2315
+ /** Bounded, path-free evidence for one atomic publication. */
2316
+ export interface NativePublishDiagnostic {
2317
+ schemaVersion: number
2318
+ collectionState: string
2319
+ osCode?: number
2320
+ syncFailures?: Array<NativePublishSyncFailure>
2321
+ }
package/native/index.js CHANGED
@@ -29,7 +29,8 @@ export const RecoveryFsRoot = nativeBindings.RecoveryFsRoot;
29
29
  export const Shell = nativeBindings.Shell;
30
30
 
31
31
  // functions
32
- export const __piNativesV0_11_7 = nativeBindings.__piNativesV0_11_7;
32
+ export const __piNativesPublishOutcomeV1 = nativeBindings.__piNativesPublishOutcomeV1;
33
+ export const __piNativesV0_11_9 = nativeBindings.__piNativesV0_11_9;
33
34
  export const applyBashFixups = nativeBindings.applyBashFixups;
34
35
  export const applyOwnerOnlyFdSecurity = nativeBindings.applyOwnerOnlyFdSecurity;
35
36
  export const applyOwnerOnlyPathSecurity = nativeBindings.applyOwnerOnlyPathSecurity;
@@ -79,4 +79,28 @@ export interface CachedEmbeddedExtractionIsFreshInput {
79
79
 
80
80
  export function cachedEmbeddedExtractionIsFresh(input: CachedEmbeddedExtractionIsFreshInput): boolean;
81
81
 
82
- export function loadNative(): Record<string, unknown>;
82
+ export interface LoaderContext {
83
+ isCompiledBinary: boolean;
84
+ platformTag: string;
85
+ packageVersion?: string;
86
+ addonLabel?: string;
87
+ addonFilenames?: string[];
88
+ versionedDir?: string;
89
+ candidates?: string[];
90
+ selectedVariant?: "modern" | "baseline" | null;
91
+ }
92
+
93
+ export function embeddedAddonIsAuthoritative(
94
+ ctx: LoaderContext,
95
+ addon?: EmbeddedAddon | null,
96
+ ): boolean;
97
+
98
+ export interface LoadNativeOptions {
99
+ context?: LoaderContext;
100
+ extractEmbeddedAddons?: (ctx: LoaderContext) => string[];
101
+ stageNodeModulesAddon?: () => string | null;
102
+ requireCandidate?: (candidate: string) => Record<string, unknown>;
103
+ validateCandidate?: (bindings: Record<string, unknown>) => void;
104
+ }
105
+
106
+ export function loadNative(options?: LoadNativeOptions): Record<string, unknown>;
@@ -306,64 +306,56 @@ function resolveCpuVariant(override) {
306
306
  return detectAvx2Support() ? "modern" : "baseline";
307
307
  }
308
308
 
309
- function selectEmbeddedAddonFile(selectedVariant) {
310
- if (!embeddedAddon) return null;
311
- const defaultFile = embeddedAddon.files.find(file => file.variant === "default") || null;
312
- if (process.arch !== "x64") return defaultFile || embeddedAddon.files[0] || null;
313
- if (selectedVariant === "modern") {
314
- return (
315
- embeddedAddon.files.find(file => file.variant === "modern") ||
316
- embeddedAddon.files.find(file => file.variant === "baseline") ||
317
- null
318
- );
319
- }
320
- return embeddedAddon.files.find(file => file.variant === "baseline") || null;
309
+ function embeddedAddonCandidates(selectedVariant) {
310
+ if (!embeddedAddon) return [];
311
+ const files = embeddedAddon.files;
312
+ const candidates = process.arch !== "x64"
313
+ ? [files.find(file => file.variant === "default"), ...files]
314
+ : selectedVariant === "modern"
315
+ ? [files.find(file => file.variant === "modern"), files.find(file => file.variant === "baseline")]
316
+ : [files.find(file => file.variant === "baseline")];
317
+ return [...new Set(candidates.filter(Boolean))];
321
318
  }
322
319
 
323
- function maybeExtractEmbeddedAddon(ctx, errors) {
324
- if (!ctx.isCompiledBinary || !embeddedAddon) return null;
325
- if (embeddedAddon.platformTag !== ctx.platformTag || embeddedAddon.version !== ctx.packageVersion) return null;
326
-
327
- const selectedEmbeddedFile = selectEmbeddedAddonFile(ctx.selectedVariant);
328
- if (!selectedEmbeddedFile) return null;
329
- const targetPath = path.join(ctx.versionedDir, selectedEmbeddedFile.filename);
330
- if (fs.existsSync(targetPath)) {
331
- // Guard against intra-version drift: a cached extraction written by an earlier
332
- // build of the same version carries the same version sentinel but can expose a
333
- // different native surface (e.g. a symbol added mid-cycle). The embedded addon
334
- // is the source of truth, so reuse the cached file only when it matches the
335
- // embedded payload size and re-extract otherwise.
336
- const sizeOf = candidate => {
337
- try {
338
- return fs.statSync(candidate).size;
339
- } catch {
340
- return null;
320
+ function maybeExtractEmbeddedAddons(ctx, errors) {
321
+ if (!ctx.isCompiledBinary || !embeddedAddon) return [];
322
+ if (embeddedAddon.platformTag !== ctx.platformTag || embeddedAddon.version !== ctx.packageVersion) return [];
323
+
324
+ const extracted = [];
325
+ for (const embeddedFile of embeddedAddonCandidates(ctx.selectedVariant)) {
326
+ const targetPath = path.join(ctx.versionedDir, embeddedFile.filename);
327
+ if (fs.existsSync(targetPath)) {
328
+ // Guard against intra-version drift: a cached extraction written by an earlier
329
+ // build of the same version carries the same version sentinel but can expose a
330
+ // different native surface (e.g. a symbol added mid-cycle). The embedded addon
331
+ // is the source of truth, so reuse the cached file only when it matches the
332
+ // embedded payload size and re-extract otherwise.
333
+ const sizeOf = candidate => {
334
+ try {
335
+ return fs.statSync(candidate).size;
336
+ } catch {
337
+ return null;
338
+ }
339
+ };
340
+ if (cachedEmbeddedExtractionIsFresh({ targetPath, embeddedPath: embeddedFile.filePath, sizeOf })) {
341
+ extracted.push(targetPath);
342
+ continue;
341
343
  }
342
- };
343
- if (cachedEmbeddedExtractionIsFresh({ targetPath, embeddedPath: selectedEmbeddedFile.filePath, sizeOf })) {
344
- return targetPath;
345
344
  }
346
- }
347
-
348
- try {
349
- fs.mkdirSync(ctx.versionedDir, { recursive: true });
350
- } catch (err) {
351
- const message = err instanceof Error ? err.message : String(err);
352
- errors.push(`embedded addon dir: ${message}`);
353
- return null;
354
- }
355
345
 
356
- try {
357
- const buffer = fs.readFileSync(selectedEmbeddedFile.filePath);
358
- const tempPath = `${targetPath}.tmp.${process.pid}`;
359
- fs.writeFileSync(tempPath, buffer);
360
- fs.renameSync(tempPath, targetPath);
361
- return targetPath;
362
- } catch (err) {
363
- const message = err instanceof Error ? err.message : String(err);
364
- errors.push(`embedded addon write (${selectedEmbeddedFile.filename}): ${message}`);
365
- return null;
346
+ try {
347
+ fs.mkdirSync(ctx.versionedDir, { recursive: true });
348
+ const buffer = fs.readFileSync(embeddedFile.filePath);
349
+ const tempPath = `${targetPath}.tmp.${process.pid}`;
350
+ fs.writeFileSync(tempPath, buffer);
351
+ fs.renameSync(tempPath, targetPath);
352
+ extracted.push(targetPath);
353
+ } catch (err) {
354
+ const message = err instanceof Error ? err.message : String(err);
355
+ errors.push(`embedded addon write (${embeddedFile.filename}): ${message}`);
356
+ }
366
357
  }
358
+ return extracted;
367
359
  }
368
360
 
369
361
  /**
@@ -410,12 +402,22 @@ function maybeStageNodeModulesAddon(ctx, errors) {
410
402
  }
411
403
 
412
404
  function validateLoadedBindings(ctx, bindings, candidate) {
413
- if (typeof bindings[ctx.versionSentinelExport] === "function") return;
414
- throw new Error(
415
- `Loaded ${candidate} but it does not expose the @gajae-code/natives@${ctx.packageVersion} ` +
416
- `version sentinel \`${ctx.versionSentinelExport}\`. The .node file on disk is from a different ` +
417
- "release than this loader — reinstall to re-sync.",
418
- );
405
+ if (typeof bindings[ctx.versionSentinelExport] !== "function") {
406
+ throw new Error(
407
+ `Loaded ${candidate} but it does not expose the @gajae-code/natives@${ctx.packageVersion} ` +
408
+ `version sentinel \`${ctx.versionSentinelExport}\`. The .node file on disk is from a different ` +
409
+ "release than this loader — reinstall to re-sync.",
410
+ );
411
+ }
412
+ if (typeof bindings.__piNativesPublishOutcomeV1 !== "function") {
413
+ throw new Error(
414
+ `Loaded ${candidate} but it lacks retained-publish capability sentinel ` +
415
+ "`__piNativesPublishOutcomeV1`; trying the next compatible artifact.",
416
+ );
417
+ }
418
+ if (typeof bindings.renameNoReplacePath !== "function") {
419
+ throw new Error(`Loaded ${candidate} but it lacks required atomic publish capability \`renameNoReplacePath\`.`);
420
+ }
419
421
  }
420
422
 
421
423
  function buildHelpMessage(ctx) {
@@ -516,20 +518,30 @@ function initLoaderContext(require_) {
516
518
  };
517
519
  }
518
520
 
519
- export function loadNative() {
520
- const require_ = createRequire(import.meta.url);
521
- const ctx = initLoaderContext(require_);
521
+ /** Embedded standalone payloads are the complete trust boundary for their matching build. */
522
+ export function embeddedAddonIsAuthoritative(ctx, addon = embeddedAddon) {
523
+ return (
524
+ ctx.isCompiledBinary && addon?.platformTag === ctx.platformTag && addon.version === ctx.packageVersion
525
+ );
526
+ }
522
527
 
523
- const errors = [];
524
- const embeddedCandidate = maybeExtractEmbeddedAddon(ctx, errors);
525
- const stagedCandidate = embeddedCandidate ? null : maybeStageNodeModulesAddon(ctx, errors);
526
- const prepended = [embeddedCandidate, stagedCandidate].filter(c => typeof c === "string");
527
- const runtimeCandidates = prepended.length > 0 ? [...prepended, ...ctx.candidates] : ctx.candidates;
528
+ export function loadNative(options = {}) {
529
+ const require_ = options.requireCandidate ? null : createRequire(import.meta.url);
530
+ const ctx = options.context ?? initLoaderContext(require_);
528
531
 
532
+ const errors = [];
533
+ const embeddedCandidates = (options.extractEmbeddedAddons ?? maybeExtractEmbeddedAddons)(ctx, errors);
534
+ const embeddedIsAuthoritative = embeddedAddonIsAuthoritative(ctx);
535
+ const stagedCandidate =
536
+ embeddedCandidates.length > 0 || embeddedIsAuthoritative
537
+ ? null
538
+ : (options.stageNodeModulesAddon ?? maybeStageNodeModulesAddon)(ctx, errors);
539
+ const prepended = [...embeddedCandidates, stagedCandidate].filter(c => typeof c === "string");
540
+ const runtimeCandidates = embeddedIsAuthoritative ? prepended : prepended.length > 0 ? [...prepended, ...ctx.candidates] : ctx.candidates;
529
541
  const loaded = loadFromCandidates({
530
542
  candidates: runtimeCandidates,
531
- requireCandidate: candidate => require_(candidate),
532
- validateCandidate: (bindings, candidate) => validateLoadedBindings(ctx, bindings, candidate),
543
+ requireCandidate: options.requireCandidate ?? (candidate => require_(candidate)),
544
+ validateCandidate: options.validateCandidate ?? ((bindings, candidate) => validateLoadedBindings(ctx, bindings, candidate)),
533
545
  describeCandidate: candidate => candidate,
534
546
  });
535
547
  if (loaded.bindings) return loaded.bindings;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gajae-code/natives",
3
- "version": "0.11.7",
3
+ "version": "0.11.9",
4
4
  "description": "Native Rust bindings for grep, clipboard, image processing, syntax highlighting, PTY, and shell operations via N-API",
5
5
  "type": "module",
6
6
  "homepage": "https://gajae-code.com",
@@ -61,11 +61,11 @@
61
61
  "README.md"
62
62
  ],
63
63
  "optionalDependencies": {
64
- "@gajae-code/natives-darwin-arm64": "0.11.7",
65
- "@gajae-code/natives-darwin-x64": "0.11.7",
66
- "@gajae-code/natives-linux-arm64": "0.11.7",
67
- "@gajae-code/natives-linux-x64": "0.11.7",
68
- "@gajae-code/natives-win32-x64": "0.11.7"
64
+ "@gajae-code/natives-darwin-arm64": "0.11.9",
65
+ "@gajae-code/natives-darwin-x64": "0.11.9",
66
+ "@gajae-code/natives-linux-arm64": "0.11.9",
67
+ "@gajae-code/natives-linux-x64": "0.11.9",
68
+ "@gajae-code/natives-win32-x64": "0.11.9"
69
69
  },
70
70
  "exports": {
71
71
  ".": {