@quantcdn/edge-kv 0.1.0-rc.0 → 0.1.0-rc.1
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/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// Copyright 2026 QuantCDN Pty Ltd
|
|
3
3
|
// Shared state of the local backend: stores, server time, versions, store-level errors and the storage hook.
|
|
4
|
-
import { randomBytes } from 'node:crypto';
|
|
5
4
|
import { ERROR_CODES } from '../../../lib/error-contract.mjs';
|
|
5
|
+
import { createUlid } from './ulid.mjs';
|
|
6
6
|
|
|
7
7
|
/** A store record. archives and digests belong to the simulated archive (archive.mjs). */
|
|
8
8
|
export function newStore(id, format) {
|
|
@@ -40,9 +40,11 @@ export function createCore({ clock, KVError, storage, org, seed }) {
|
|
|
40
40
|
if (store === undefined) throw core.error('STORE_NOT_FOUND', `store ${storeId} does not exist`);
|
|
41
41
|
return store;
|
|
42
42
|
};
|
|
43
|
+
// A ULID on server time, as the platform gives. versionSeq counts the versions (it is in the snapshot).
|
|
44
|
+
const ulid = createUlid();
|
|
43
45
|
core.newVersion = () => {
|
|
44
46
|
core.versionSeq += 1;
|
|
45
|
-
return
|
|
47
|
+
return ulid(core.now());
|
|
46
48
|
};
|
|
47
49
|
return core;
|
|
48
50
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright 2026 QuantCDN Pty Ltd
|
|
3
|
+
// Item versions in the shape the platform gives: a ULID, 26 characters of Crockford base32 (48-bit millisecond time,
|
|
4
|
+
// then 80 random bits). This one is monotonic in one process: in the same millisecond, or when the clock moves back,
|
|
5
|
+
// the random part goes up by one, so versions sort in the order of the writes.
|
|
6
|
+
import { randomBytes } from 'node:crypto';
|
|
7
|
+
|
|
8
|
+
const ALPHABET = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
|
|
9
|
+
const MAX_TIME = 2 ** 48 - 1;
|
|
10
|
+
const RANDOM_MAX = (1n << 80n) - 1n;
|
|
11
|
+
|
|
12
|
+
function encode(value, length) {
|
|
13
|
+
let out = '';
|
|
14
|
+
for (let v = BigInt(value), i = 0; i < length; i += 1, v >>= 5n) out = ALPHABET[Number(v & 31n)] + out;
|
|
15
|
+
return out;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const randomPart = () => BigInt(`0x${randomBytes(10).toString('hex')}`);
|
|
19
|
+
|
|
20
|
+
/** A function that makes monotonic ULIDs for the time it is given (epoch ms). */
|
|
21
|
+
export function createUlid() {
|
|
22
|
+
let lastTime = -1;
|
|
23
|
+
let lastRandom = 0n;
|
|
24
|
+
return function ulid(nowMs) {
|
|
25
|
+
if (!Number.isSafeInteger(nowMs) || nowMs < 0 || nowMs > MAX_TIME) throw new RangeError('ulid time out of range');
|
|
26
|
+
const repeat = nowMs <= lastTime && lastRandom < RANDOM_MAX;
|
|
27
|
+
lastTime = repeat ? lastTime : nowMs;
|
|
28
|
+
lastRandom = repeat ? lastRandom + 1n : randomPart();
|
|
29
|
+
return encode(lastTime, 10) + encode(lastRandom, 16);
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const ULID_PATTERN = /^[0-9A-HJKMNP-TV-Z]{26}$/;
|
package/src/lib/check-util.mjs
CHANGED
|
@@ -12,11 +12,19 @@ export const isTtl = (v) => isIntIn(v, LIMITS.ttl);
|
|
|
12
12
|
export const isAmount = (v) => Number.isSafeInteger(v) && v !== 0;
|
|
13
13
|
export const byteCompare = (a, b) => Buffer.compare(Buffer.from(a, 'utf8'), Buffer.from(b, 'utf8'));
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
// The Object.prototype of another realm (for example a node:vm context): its own prototype is null and its
|
|
16
|
+
// constructor is a function named Object.
|
|
17
|
+
function isForeignObjectPrototype(proto) {
|
|
18
|
+
return Object.getPrototypeOf(proto) === null && typeof proto.constructor === 'function'
|
|
19
|
+
&& proto.constructor.name === 'Object';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** A plain object of any realm: prototype Object.prototype or null. Arrays, dates and class instances are not. */
|
|
16
23
|
export function isPlainObject(v) {
|
|
17
|
-
if (v === null || typeof v !== 'object') return false;
|
|
24
|
+
if (v === null || typeof v !== 'object' || Array.isArray(v)) return false;
|
|
18
25
|
const proto = Object.getPrototypeOf(v);
|
|
19
|
-
|
|
26
|
+
if (proto === Object.prototype || proto === null) return true;
|
|
27
|
+
return Object.prototype.toString.call(v) === '[object Object]' && isForeignObjectPrototype(proto);
|
|
20
28
|
}
|
|
21
29
|
|
|
22
30
|
export function isScalar(v) {
|
package/src/lib/edge-calls.mjs
CHANGED
|
@@ -37,7 +37,7 @@ export function createEdgeCalls({ backend, checks, KVError, isAllowed, warn = ()
|
|
|
37
37
|
checks.checkStoreId(storeId);
|
|
38
38
|
argumentChecks();
|
|
39
39
|
if (!isAllowed(storeId)) {
|
|
40
|
-
throw new KVError('FORBIDDEN', `
|
|
40
|
+
throw new KVError('FORBIDDEN', `Access to KV store ${storeId} is not allowed`, { status: 403 });
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
|