@volter/world-core 3.0.147 → 3.0.149

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.
@@ -2296,7 +2296,7 @@
2296
2296
  },
2297
2297
  {
2298
2298
  "host": "dashboard.stripe.com",
2299
- "pathPattern": "^/radar/lists(?:/[^/]+)?/?$|^/test/issuing/balance/?$|^/settings/public/?$"
2299
+ "pathPattern": "^/customers(?:/[^/]+)?/?$|^/radar/lists(?:/[^/]+)?/?$|^/test/issuing/balance/?$|^/settings/public/?$"
2300
2300
  },
2301
2301
  {
2302
2302
  "host": "verify.stripe.com",
@@ -8,6 +8,13 @@ export declare function removeResourceBlob(service: string, key: string, root?:
8
8
  /** A service's resources dir at this branch, then at each retained ancestor's: where a read resolves a payload, and
9
9
  * what a listing that mints ids covers, so a branch serves bytes its parent received and never re-mints a parent's id. */
10
10
  export declare function resourceChain(service: string, root?: string): string[];
11
+ /** Where a resource blob's key is kept under a resources directory. A key is a relative path, its parts joined by '/'.
12
+ * On Windows a part may hold a character no file name can: a ':' there names an NTFS stream of the file before it, so
13
+ * `asset:<id>` wrote into a stream of a file named `asset` and the store's rename failed (EINVAL). There each of
14
+ * `<>:"|?*`, a control character and '%' itself is written as %XX, one name per key; elsewhere the key is the path. */
15
+ export declare function resourceBlobFile(dir: string, key: string): string;
16
+ /** The key a file under a resources directory holds: resourceBlobFile's inverse, its parts joined by '/'. */
17
+ export declare function resourceBlobKey(dir: string, path: string): string;
11
18
  /** Size of a resource payload at this branch or its ancestors, or null. */
12
19
  export declare function resourceBlobSize(service: string, key: string, root?: string): Promise<number | null>;
13
20
  /** A byte range of a resource payload at this branch or its ancestors (a ranged read where the
@@ -1,4 +1,4 @@
1
- import { isAbsolute, join } from 'node:path';
1
+ import { isAbsolute, join, relative } from 'node:path';
2
2
  import { getActiveBlobStore, readBlobRange } from "./blob-store.js";
3
3
  import { readBranchMeta } from "./log.js";
4
4
  import { worldPaths } from "./storage.js";
@@ -6,7 +6,7 @@ import { worldPaths } from "./storage.js";
6
6
  export async function readResourceBlob(service, key, root) {
7
7
  assertResourceKey(key);
8
8
  for (const dir of resourceChain(service, root)) {
9
- const bytes = await getActiveBlobStore().get(join(dir, key));
9
+ const bytes = await getActiveBlobStore().get(resourceBlobFile(dir, key));
10
10
  if (bytes !== null)
11
11
  return bytes;
12
12
  }
@@ -15,13 +15,13 @@ export async function readResourceBlob(service, key, root) {
15
15
  /** Write a resource payload on this branch (a stored object's bytes), where a read finds it first. */
16
16
  export async function writeResourceBlob(service, key, bytes, root) {
17
17
  assertResourceKey(key);
18
- await getActiveBlobStore().put(join(worldPaths(service, root).resources, key), bytes);
18
+ await getActiveBlobStore().put(resourceBlobFile(worldPaths(service, root).resources, key), bytes);
19
19
  }
20
20
  /** Remove this branch's copy of a resource payload; an ancestor's stays its own (the row that named it is what a
21
21
  * deletion ends). */
22
22
  export async function removeResourceBlob(service, key, root) {
23
23
  assertResourceKey(key);
24
- await getActiveBlobStore().remove(join(worldPaths(service, root).resources, key));
24
+ await getActiveBlobStore().remove(resourceBlobFile(worldPaths(service, root).resources, key));
25
25
  }
26
26
  /** A service's resources dir at this branch, then at each retained ancestor's: where a read resolves a payload, and
27
27
  * what a listing that mints ids covers, so a branch serves bytes its parent received and never re-mints a parent's id. */
@@ -40,6 +40,18 @@ export function resourceChain(service, root) {
40
40
  current = parent.at;
41
41
  }
42
42
  }
43
+ /** Where a resource blob's key is kept under a resources directory. A key is a relative path, its parts joined by '/'.
44
+ * On Windows a part may hold a character no file name can: a ':' there names an NTFS stream of the file before it, so
45
+ * `asset:<id>` wrote into a stream of a file named `asset` and the store's rename failed (EINVAL). There each of
46
+ * `<>:"|?*`, a control character and '%' itself is written as %XX, one name per key; elsewhere the key is the path. */
47
+ export function resourceBlobFile(dir, key) {
48
+ return join(dir, process.platform === 'win32' ? key.replace(/[%<>:"|?*\x00-\x1f]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase().padStart(2, '0')}`) : key);
49
+ }
50
+ /** The key a file under a resources directory holds: resourceBlobFile's inverse, its parts joined by '/'. */
51
+ export function resourceBlobKey(dir, path) {
52
+ const key = relative(dir, path).split('\\').join('/');
53
+ return process.platform === 'win32' ? key.replace(/%([0-9A-F]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16))) : key;
54
+ }
43
55
  function assertResourceKey(key) {
44
56
  if (isAbsolute(key) || key.includes('\0') || key.split(/[\\/]/).includes('..'))
45
57
  throw new Error('Invalid relative resource blob key');
@@ -48,7 +60,7 @@ function assertResourceKey(key) {
48
60
  async function resolveResourceBlob(service, key, root) {
49
61
  assertResourceKey(key);
50
62
  for (const dir of resourceChain(service, root)) {
51
- const path = join(dir, key);
63
+ const path = resourceBlobFile(dir, key);
52
64
  if (await getActiveBlobStore().exists(path))
53
65
  return path;
54
66
  }
@@ -78,7 +78,7 @@ export { corsHeaders, withCors, type CorsDecl } from './cors.js';
78
78
  export { setInProcessTwins, vendorFetch, VendorUnreachableError, type InProcessTwin } from './vendor-call.js';
79
79
  export { closeManagedDatabases, databaseSession, EngineDatabaseError, EngineUnavailableError, managedDatabase, quoteIdent, useDatabaseDriver, type DatabaseDriver, type DatabaseSession } from './managed-database.js';
80
80
  export type { Batch as EngineBatch, ManagedDatabase, Statement as EngineStatement, StatementResult as EngineStatementResult } from './managed-database.js';
81
- export { readResourceBlob, readResourceBlobRange, removeResourceBlob, resourceBlobSize, resourceChain, writeResourceBlob } from './resource-blob.js';
81
+ export { readResourceBlob, readResourceBlobRange, removeResourceBlob, resourceBlobFile, resourceBlobKey, resourceBlobSize, resourceChain, writeResourceBlob } from './resource-blob.js';
82
82
  export { assertStateRemovable, checkParent, stateGeneration, withAncestryLock, withHistoryLock, withHistoryLocks, withStateLock, withStateRemoval } from './ancestry.js';
83
83
  export { captureHistory, captureParentHistory, historyChanges, historyAtInstant, historyDigest, historyEntries, historyLength, historyPrefix, inheritedHistory, originHead, publishOriginHistory, readHistoryView } from './history.js';
84
84
  export type { HistoryView, HistoryLayout, HistoryReference, HistoryOrigin } from './history.js';
@@ -98,7 +98,7 @@ export { multipartBoundary, multipartParts, multipartEnvelope, inferMultipartBou
98
98
  export { corsHeaders, withCors } from "./cors.js";
99
99
  export { setInProcessTwins, vendorFetch, VendorUnreachableError } from "./vendor-call.js";
100
100
  export { closeManagedDatabases, databaseSession, EngineDatabaseError, EngineUnavailableError, managedDatabase, quoteIdent, useDatabaseDriver } from "./managed-database.js";
101
- export { readResourceBlob, readResourceBlobRange, removeResourceBlob, resourceBlobSize, resourceChain, writeResourceBlob } from "./resource-blob.js";
101
+ export { readResourceBlob, readResourceBlobRange, removeResourceBlob, resourceBlobFile, resourceBlobKey, resourceBlobSize, resourceChain, writeResourceBlob } from "./resource-blob.js";
102
102
  export { assertStateRemovable, checkParent, stateGeneration, withAncestryLock, withHistoryLock, withHistoryLocks, withStateLock, withStateRemoval } from "./ancestry.js";
103
103
  export { captureHistory, captureParentHistory, historyChanges, historyAtInstant, historyDigest, historyEntries, historyLength, historyPrefix, inheritedHistory, originHead, publishOriginHistory, readHistoryView } from "./history.js";
104
104
  export { foldHistory } from "./log.js";
@@ -2296,7 +2296,7 @@
2296
2296
  },
2297
2297
  {
2298
2298
  "host": "dashboard.stripe.com",
2299
- "pathPattern": "^/radar/lists(?:/[^/]+)?/?$|^/test/issuing/balance/?$|^/settings/public/?$"
2299
+ "pathPattern": "^/customers(?:/[^/]+)?/?$|^/radar/lists(?:/[^/]+)?/?$|^/test/issuing/balance/?$|^/settings/public/?$"
2300
2300
  },
2301
2301
  {
2302
2302
  "host": "verify.stripe.com",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/world-core",
3
- "version": "3.0.147",
3
+ "version": "3.0.149",
4
4
  "description": "The kernel of Volter World: one log per twin, branches as pointers, checkpoints, the fold that keeps a twin current, the head that performs a write against the vendor, references, and the git plane. A twin package builds on it; the runtime serves it.",
5
5
  "keywords": [
6
6
  "twin",
@@ -1,4 +1,4 @@
1
- import { isAbsolute, join } from 'node:path';
1
+ import { isAbsolute, join, relative } from 'node:path';
2
2
  import { getActiveBlobStore, readBlobRange } from './blob-store.ts';
3
3
  import { readBranchMeta } from './log.ts';
4
4
  import { worldPaths } from './storage.ts';
@@ -7,7 +7,7 @@ import { worldPaths } from './storage.ts';
7
7
  export async function readResourceBlob(service: string, key: string, root?: string): Promise<Uint8Array | null> {
8
8
  assertResourceKey(key);
9
9
  for (const dir of resourceChain(service, root)) {
10
- const bytes = await getActiveBlobStore().get(join(dir, key));
10
+ const bytes = await getActiveBlobStore().get(resourceBlobFile(dir, key));
11
11
  if (bytes !== null) return bytes;
12
12
  }
13
13
  return null;
@@ -16,14 +16,14 @@ export async function readResourceBlob(service: string, key: string, root?: stri
16
16
  /** Write a resource payload on this branch (a stored object's bytes), where a read finds it first. */
17
17
  export async function writeResourceBlob(service: string, key: string, bytes: Uint8Array, root?: string): Promise<void> {
18
18
  assertResourceKey(key);
19
- await getActiveBlobStore().put(join(worldPaths(service, root).resources, key), bytes);
19
+ await getActiveBlobStore().put(resourceBlobFile(worldPaths(service, root).resources, key), bytes);
20
20
  }
21
21
 
22
22
  /** Remove this branch's copy of a resource payload; an ancestor's stays its own (the row that named it is what a
23
23
  * deletion ends). */
24
24
  export async function removeResourceBlob(service: string, key: string, root?: string): Promise<void> {
25
25
  assertResourceKey(key);
26
- await getActiveBlobStore().remove(join(worldPaths(service, root).resources, key));
26
+ await getActiveBlobStore().remove(resourceBlobFile(worldPaths(service, root).resources, key));
27
27
  }
28
28
 
29
29
  /** A service's resources dir at this branch, then at each retained ancestor's: where a read resolves a payload, and
@@ -42,6 +42,20 @@ export function resourceChain(service: string, root?: string): string[] {
42
42
  }
43
43
  }
44
44
 
45
+ /** Where a resource blob's key is kept under a resources directory. A key is a relative path, its parts joined by '/'.
46
+ * On Windows a part may hold a character no file name can: a ':' there names an NTFS stream of the file before it, so
47
+ * `asset:<id>` wrote into a stream of a file named `asset` and the store's rename failed (EINVAL). There each of
48
+ * `<>:"|?*`, a control character and '%' itself is written as %XX, one name per key; elsewhere the key is the path. */
49
+ export function resourceBlobFile(dir: string, key: string): string {
50
+ return join(dir, process.platform === 'win32' ? key.replace(/[%<>:"|?*\x00-\x1f]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase().padStart(2, '0')}`) : key);
51
+ }
52
+
53
+ /** The key a file under a resources directory holds: resourceBlobFile's inverse, its parts joined by '/'. */
54
+ export function resourceBlobKey(dir: string, path: string): string {
55
+ const key = relative(dir, path).split('\\').join('/');
56
+ return process.platform === 'win32' ? key.replace(/%([0-9A-F]{2})/g, (_, hex: string) => String.fromCharCode(parseInt(hex, 16))) : key;
57
+ }
58
+
45
59
  function assertResourceKey(key: string): void {
46
60
  if (isAbsolute(key) || key.includes('\0') || key.split(/[\\/]/).includes('..')) throw new Error('Invalid relative resource blob key');
47
61
  }
@@ -50,7 +64,7 @@ function assertResourceKey(key: string): void {
50
64
  async function resolveResourceBlob(service: string, key: string, root?: string): Promise<string | null> {
51
65
  assertResourceKey(key);
52
66
  for (const dir of resourceChain(service, root)) {
53
- const path = join(dir, key);
67
+ const path = resourceBlobFile(dir, key);
54
68
  if (await getActiveBlobStore().exists(path)) return path;
55
69
  }
56
70
  return null;
package/src/runtime.ts CHANGED
@@ -335,7 +335,7 @@ export { corsHeaders, withCors, type CorsDecl } from './cors.ts';
335
335
  export { setInProcessTwins, vendorFetch, VendorUnreachableError, type InProcessTwin } from './vendor-call.ts';
336
336
  export { closeManagedDatabases, databaseSession, EngineDatabaseError, EngineUnavailableError, managedDatabase, quoteIdent, useDatabaseDriver, type DatabaseDriver, type DatabaseSession } from './managed-database.ts';
337
337
  export type { Batch as EngineBatch, ManagedDatabase, Statement as EngineStatement, StatementResult as EngineStatementResult } from './managed-database.ts';
338
- export { readResourceBlob, readResourceBlobRange, removeResourceBlob, resourceBlobSize, resourceChain, writeResourceBlob } from './resource-blob.ts';
338
+ export { readResourceBlob, readResourceBlobRange, removeResourceBlob, resourceBlobFile, resourceBlobKey, resourceBlobSize, resourceChain, writeResourceBlob } from './resource-blob.ts';
339
339
 
340
340
  export { assertStateRemovable, checkParent, stateGeneration, withAncestryLock, withHistoryLock, withHistoryLocks, withStateLock, withStateRemoval } from './ancestry.ts';
341
341