@adep/runtime 0.1.0 → 0.1.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/dist/database/builder/ulid.d.ts +16 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +746 -11
- package/dist/shared/capability-keys.d.ts +16 -0
- package/dist/sim/realtime.d.ts +64 -0
- package/dist/sim/sql-engine.d.ts +82 -0
- package/dist/storage/hmac-sha256.d.ts +23 -0
- package/package.json +11 -25
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ULID 生成器(DB-006 owned 表主键)。
|
|
3
|
+
*
|
|
4
|
+
* 128 位 = 48 位毫秒时间戳 + 80 位随机,Crockford Base32 编码,26 字符。
|
|
5
|
+
* 性质(Gherkin Scenario:owned 表行主键为 ULID 且本地可生成):
|
|
6
|
+
* - **时间有序**:高位是毫秒时间戳,新生成的 ULID 字典序严格 ≥ 旧值;
|
|
7
|
+
* - **单调**:同一毫秒内对随机段 +1 递增,保证同毫秒生成不冲突;
|
|
8
|
+
* - **离线可生成**:纯本地随机,无中心协调(对比 UUID v4 需中心分配)。
|
|
9
|
+
*
|
|
10
|
+
* 纯函数模块,无运行时依赖;离线/本地节点可用相同逻辑复现(M8 同步客户端)。
|
|
11
|
+
*
|
|
12
|
+
* FN-023 起随机源走 `crypto.getRandomValues`(浏览器 / Node19+ / Bun 全 realm 同形),
|
|
13
|
+
* 不再 import `node:crypto`——`@adep/runtime` 的 builder 模块要进浏览器 bundle
|
|
14
|
+
* (离线模拟运行时复用 `createDbCapability`),node-only import 会让打包直接失败。
|
|
15
|
+
* 退化分支(非安全上下文无 crypto)仅覆盖模拟/开发场景,主键唯一性由单调段兜底。
|
|
16
|
+
*/
|
|
1
17
|
/** ULID 合法字符集(用于校验:`^[0-9A-HJKMNP-TV-Z]{26}$`)。 */
|
|
2
18
|
export declare const ULID_PATTERN: RegExp;
|
|
3
19
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -32,4 +32,9 @@ export type { StorageDriver, StoredFileMeta, FileVisibility } from './storage/dr
|
|
|
32
32
|
export { StorageError, STORAGE_CODES } from './storage/driver';
|
|
33
33
|
export { signDownloadUrl, verifyDownloadSignature } from './storage/signature';
|
|
34
34
|
export type { DownloadSignerConfig } from './storage/signature';
|
|
35
|
+
export { hmacSha256, hmacSha256Hex, sha256, constantTimeEqual } from './storage/hmac-sha256';
|
|
36
|
+
export { SimSqlEngine, SimDbError } from './sim/sql-engine';
|
|
37
|
+
export type { SimTable, EngineStorage } from './sim/sql-engine';
|
|
38
|
+
export { SimRealtime, createSimRealtimeCapability, REALTIME_CODES } from './sim/realtime';
|
|
39
|
+
export type { SimRealtimeSubscription } from './sim/realtime';
|
|
35
40
|
export { resolveFunctionSource, parseSnapshot } from './shared/function-source';
|
package/dist/index.js
CHANGED
|
@@ -35,6 +35,20 @@ var DB_RPC = {
|
|
|
35
35
|
/** 执行一条链:`chain, args=[ChainRequest]`。 */
|
|
36
36
|
chain: "chain"
|
|
37
37
|
};
|
|
38
|
+
var REALTIME_CAPABILITY_CODES = {
|
|
39
|
+
invalidMethod: "REALTIME_INVALID_METHOD",
|
|
40
|
+
unsupportedArg: "REALTIME_UNSUPPORTED_ARG",
|
|
41
|
+
subscriptionNotFound: "REALTIME_SUBSCRIPTION_NOT_FOUND",
|
|
42
|
+
tooManySubscriptions: "REALTIME_TOO_MANY_SUBSCRIPTIONS",
|
|
43
|
+
/** 线上独有:订阅授权策略(RT-002 `SubscriptionPolicy`)拒绝该 channel。 */
|
|
44
|
+
channelDenied: "REALTIME_CHANNEL_DENIED"
|
|
45
|
+
};
|
|
46
|
+
var REALTIME_CAPABILITY_METHODS = [
|
|
47
|
+
"publish",
|
|
48
|
+
"subscribe",
|
|
49
|
+
"receive",
|
|
50
|
+
"unsubscribe"
|
|
51
|
+
];
|
|
38
52
|
|
|
39
53
|
// packages/runtime/src/functions/runtime/worker-executor.ts
|
|
40
54
|
import { readFileSync } from "node:fs";
|
|
@@ -301,9 +315,6 @@ var WorkerFunctionExecutor = class {
|
|
|
301
315
|
}
|
|
302
316
|
};
|
|
303
317
|
|
|
304
|
-
// packages/runtime/src/database/sdk/cloud.ts
|
|
305
|
-
import { randomUUID } from "node:crypto";
|
|
306
|
-
|
|
307
318
|
// packages/runtime/src/database/builder/dialect.ts
|
|
308
319
|
var sqliteDialect = {
|
|
309
320
|
name: "sqlite",
|
|
@@ -485,7 +496,6 @@ async function readChanges(driver, table, query = {}) {
|
|
|
485
496
|
}
|
|
486
497
|
|
|
487
498
|
// packages/runtime/src/database/builder/ulid.ts
|
|
488
|
-
import { randomFillSync } from "node:crypto";
|
|
489
499
|
var ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
490
500
|
var TIME_LEN = 10;
|
|
491
501
|
var RANDOM_LEN = 16;
|
|
@@ -526,6 +536,16 @@ function incrBase32(prev) {
|
|
|
526
536
|
}
|
|
527
537
|
return chars.join("");
|
|
528
538
|
}
|
|
539
|
+
function randomFill(bytes) {
|
|
540
|
+
const source = globalThis.crypto;
|
|
541
|
+
if (source !== void 0 && typeof source.getRandomValues === "function") {
|
|
542
|
+
return source.getRandomValues(bytes);
|
|
543
|
+
}
|
|
544
|
+
for (let i = 0; i < bytes.length; i += 1) {
|
|
545
|
+
bytes[i] = Math.floor(Math.random() * 256);
|
|
546
|
+
}
|
|
547
|
+
return bytes;
|
|
548
|
+
}
|
|
529
549
|
function ulid(now = Date.now()) {
|
|
530
550
|
const time = encodeTime(now);
|
|
531
551
|
let random;
|
|
@@ -534,7 +554,7 @@ function ulid(now = Date.now()) {
|
|
|
534
554
|
} else {
|
|
535
555
|
lastTime = now;
|
|
536
556
|
const bytes = new Uint8Array(10);
|
|
537
|
-
|
|
557
|
+
randomFill(bytes);
|
|
538
558
|
random = encodeRandom(bytes);
|
|
539
559
|
}
|
|
540
560
|
lastRandom = random;
|
|
@@ -982,7 +1002,7 @@ function createDbCapability(driver, options = {}) {
|
|
|
982
1002
|
case DB_RPC.begin: {
|
|
983
1003
|
if (activeTxs.size > 0) throw unsafeOperation("\u4E0D\u5141\u8BB8\u5D4C\u5957\u4E8B\u52A1");
|
|
984
1004
|
await driver.run("BEGIN");
|
|
985
|
-
const txId = randomUUID();
|
|
1005
|
+
const txId = crypto.randomUUID();
|
|
986
1006
|
activeTxs.set(txId, true);
|
|
987
1007
|
return txId;
|
|
988
1008
|
}
|
|
@@ -1059,14 +1079,182 @@ function assertSafeStoragePath(path) {
|
|
|
1059
1079
|
return path;
|
|
1060
1080
|
}
|
|
1061
1081
|
|
|
1082
|
+
// packages/runtime/src/storage/hmac-sha256.ts
|
|
1083
|
+
var K = new Uint32Array([
|
|
1084
|
+
1116352408,
|
|
1085
|
+
1899447441,
|
|
1086
|
+
3049323471,
|
|
1087
|
+
3921009573,
|
|
1088
|
+
961987163,
|
|
1089
|
+
1508970993,
|
|
1090
|
+
2453635748,
|
|
1091
|
+
2870763221,
|
|
1092
|
+
3624381080,
|
|
1093
|
+
310598401,
|
|
1094
|
+
607225278,
|
|
1095
|
+
1426881987,
|
|
1096
|
+
1925078388,
|
|
1097
|
+
2162078206,
|
|
1098
|
+
2614888103,
|
|
1099
|
+
3248222580,
|
|
1100
|
+
3835390401,
|
|
1101
|
+
4022224774,
|
|
1102
|
+
264347078,
|
|
1103
|
+
604807628,
|
|
1104
|
+
770255983,
|
|
1105
|
+
1249150122,
|
|
1106
|
+
1555081692,
|
|
1107
|
+
1996064986,
|
|
1108
|
+
2554220882,
|
|
1109
|
+
2821834349,
|
|
1110
|
+
2952996808,
|
|
1111
|
+
3210313671,
|
|
1112
|
+
3336571891,
|
|
1113
|
+
3584528711,
|
|
1114
|
+
113926993,
|
|
1115
|
+
338241895,
|
|
1116
|
+
666307205,
|
|
1117
|
+
773529912,
|
|
1118
|
+
1294757372,
|
|
1119
|
+
1396182291,
|
|
1120
|
+
1695183700,
|
|
1121
|
+
1986661051,
|
|
1122
|
+
2177026350,
|
|
1123
|
+
2456956037,
|
|
1124
|
+
2730485921,
|
|
1125
|
+
2820302411,
|
|
1126
|
+
3259730800,
|
|
1127
|
+
3345764771,
|
|
1128
|
+
3516065817,
|
|
1129
|
+
3600352804,
|
|
1130
|
+
4094571909,
|
|
1131
|
+
275423344,
|
|
1132
|
+
430227734,
|
|
1133
|
+
506948616,
|
|
1134
|
+
659060556,
|
|
1135
|
+
883997877,
|
|
1136
|
+
958139571,
|
|
1137
|
+
1322822218,
|
|
1138
|
+
1537002063,
|
|
1139
|
+
1747873779,
|
|
1140
|
+
1955562222,
|
|
1141
|
+
2024104815,
|
|
1142
|
+
2227730452,
|
|
1143
|
+
2361852424,
|
|
1144
|
+
2428436474,
|
|
1145
|
+
2756734187,
|
|
1146
|
+
3204031479,
|
|
1147
|
+
3329325298
|
|
1148
|
+
]);
|
|
1149
|
+
var rotr = (x, n) => x >>> n | x << 32 - n;
|
|
1150
|
+
function compress(h, block, w) {
|
|
1151
|
+
for (let i = 0; i < 16; i += 1) {
|
|
1152
|
+
const j = i * 4;
|
|
1153
|
+
w[i] = (block[j] ?? 0) << 24 | (block[j + 1] ?? 0) << 16 | (block[j + 2] ?? 0) << 8 | (block[j + 3] ?? 0);
|
|
1154
|
+
}
|
|
1155
|
+
for (let i = 16; i < 64; i += 1) {
|
|
1156
|
+
const s0 = rotr(w[i - 15], 7) ^ rotr(w[i - 15], 18) ^ w[i - 15] >>> 3;
|
|
1157
|
+
const s1 = rotr(w[i - 2], 17) ^ rotr(w[i - 2], 19) ^ w[i - 2] >>> 10;
|
|
1158
|
+
w[i] = w[i - 16] + s0 + w[i - 7] + s1 | 0;
|
|
1159
|
+
}
|
|
1160
|
+
let [a, b, c, d, e, f, g, hh] = [h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7]];
|
|
1161
|
+
for (let i = 0; i < 64; i += 1) {
|
|
1162
|
+
const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
|
|
1163
|
+
const ch = e & f ^ ~e & g;
|
|
1164
|
+
const t1 = hh + S1 + ch + K[i] + w[i] | 0;
|
|
1165
|
+
const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
|
|
1166
|
+
const maj = a & b ^ a & c ^ b & c;
|
|
1167
|
+
const t2 = S0 + maj | 0;
|
|
1168
|
+
hh = g;
|
|
1169
|
+
g = f;
|
|
1170
|
+
f = e;
|
|
1171
|
+
e = d + t1 | 0;
|
|
1172
|
+
d = c;
|
|
1173
|
+
c = b;
|
|
1174
|
+
b = a;
|
|
1175
|
+
a = t1 + t2 | 0;
|
|
1176
|
+
}
|
|
1177
|
+
h[0] = h[0] + a | 0;
|
|
1178
|
+
h[1] = h[1] + b | 0;
|
|
1179
|
+
h[2] = h[2] + c | 0;
|
|
1180
|
+
h[3] = h[3] + d | 0;
|
|
1181
|
+
h[4] = h[4] + e | 0;
|
|
1182
|
+
h[5] = h[5] + f | 0;
|
|
1183
|
+
h[6] = h[6] + g | 0;
|
|
1184
|
+
h[7] = h[7] + hh | 0;
|
|
1185
|
+
}
|
|
1186
|
+
function sha256(data) {
|
|
1187
|
+
const h = new Uint32Array([
|
|
1188
|
+
1779033703,
|
|
1189
|
+
3144134277,
|
|
1190
|
+
1013904242,
|
|
1191
|
+
2773480762,
|
|
1192
|
+
1359893119,
|
|
1193
|
+
2600822924,
|
|
1194
|
+
528734635,
|
|
1195
|
+
1541459225
|
|
1196
|
+
]);
|
|
1197
|
+
const bitLength = data.length * 8;
|
|
1198
|
+
const paddedLength = Math.ceil((data.length + 9) / 64) * 64;
|
|
1199
|
+
const padded = new Uint8Array(paddedLength);
|
|
1200
|
+
padded.set(data);
|
|
1201
|
+
padded[data.length] = 128;
|
|
1202
|
+
const view = new DataView(padded.buffer);
|
|
1203
|
+
view.setUint32(paddedLength - 4, bitLength >>> 0, false);
|
|
1204
|
+
view.setUint32(paddedLength - 8, Math.floor(bitLength / 4294967296), false);
|
|
1205
|
+
const w = new Uint32Array(64);
|
|
1206
|
+
for (let offset = 0; offset < paddedLength; offset += 64) {
|
|
1207
|
+
compress(h, padded.subarray(offset, offset + 64), w);
|
|
1208
|
+
}
|
|
1209
|
+
const out = new Uint8Array(32);
|
|
1210
|
+
const outView = new DataView(out.buffer);
|
|
1211
|
+
for (let i = 0; i < 8; i += 1) outView.setUint32(i * 4, h[i], false);
|
|
1212
|
+
return out;
|
|
1213
|
+
}
|
|
1214
|
+
function toBytes(input) {
|
|
1215
|
+
if (typeof input !== "string") return input;
|
|
1216
|
+
return new TextEncoder().encode(input);
|
|
1217
|
+
}
|
|
1218
|
+
function hmacSha256(key, message) {
|
|
1219
|
+
const blockSize = 64;
|
|
1220
|
+
let keyBytes = toBytes(key);
|
|
1221
|
+
if (keyBytes.length > blockSize) keyBytes = sha256(keyBytes);
|
|
1222
|
+
const padded = new Uint8Array(blockSize);
|
|
1223
|
+
padded.set(keyBytes);
|
|
1224
|
+
const inner = new Uint8Array(blockSize);
|
|
1225
|
+
const outer = new Uint8Array(blockSize);
|
|
1226
|
+
for (let i = 0; i < blockSize; i += 1) {
|
|
1227
|
+
inner[i] = padded[i] ^ 54;
|
|
1228
|
+
outer[i] = padded[i] ^ 92;
|
|
1229
|
+
}
|
|
1230
|
+
const innerInput = new Uint8Array(blockSize + toBytes(message).length);
|
|
1231
|
+
innerInput.set(inner);
|
|
1232
|
+
innerInput.set(toBytes(message), blockSize);
|
|
1233
|
+
const innerHash = sha256(innerInput);
|
|
1234
|
+
const outerInput = new Uint8Array(blockSize + 32);
|
|
1235
|
+
outerInput.set(outer);
|
|
1236
|
+
outerInput.set(innerHash, blockSize);
|
|
1237
|
+
return sha256(outerInput);
|
|
1238
|
+
}
|
|
1239
|
+
function hmacSha256Hex(key, message) {
|
|
1240
|
+
return [...hmacSha256(key, message)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1241
|
+
}
|
|
1242
|
+
function constantTimeEqual(a, b) {
|
|
1243
|
+
if (a.length !== b.length) return false;
|
|
1244
|
+
let diff = 0;
|
|
1245
|
+
for (let i = 0; i < a.length; i += 1) {
|
|
1246
|
+
diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
1247
|
+
}
|
|
1248
|
+
return diff === 0;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1062
1251
|
// packages/runtime/src/storage/signature.ts
|
|
1063
|
-
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
1064
1252
|
var DEFAULT_SIGN_TTL_SECONDS = 15 * 60;
|
|
1065
1253
|
var SIGN_EXPIRES_KEY = "x-expires";
|
|
1066
1254
|
var SIGN_SIGNATURE_KEY = "x-signature";
|
|
1067
1255
|
function sign(secret, projectId, path, expires) {
|
|
1068
1256
|
const payload = `${projectId}|${path}|${expires}`;
|
|
1069
|
-
return
|
|
1257
|
+
return hmacSha256Hex(secret, payload);
|
|
1070
1258
|
}
|
|
1071
1259
|
function signDownloadUrl(config, projectId, path, ttlSeconds = DEFAULT_SIGN_TTL_SECONDS) {
|
|
1072
1260
|
const expires = Math.floor(Date.now() / 1e3) + ttlSeconds;
|
|
@@ -1085,9 +1273,7 @@ function verifyDownloadSignature(config, projectId, path, query, nowSeconds = Ma
|
|
|
1085
1273
|
if (!Number.isFinite(expires)) return { ok: false, reason: "malformed" };
|
|
1086
1274
|
if (nowSeconds > expires) return { ok: false, reason: "expired" };
|
|
1087
1275
|
const expected = sign(config.secret, projectId, path, expires);
|
|
1088
|
-
|
|
1089
|
-
const candidate = Buffer.from(expected, "utf8");
|
|
1090
|
-
if (actual.length !== candidate.length || !timingSafeEqual(actual, candidate)) {
|
|
1276
|
+
if (!constantTimeEqual(signatureRaw, expected)) {
|
|
1091
1277
|
return { ok: false, reason: "invalid" };
|
|
1092
1278
|
}
|
|
1093
1279
|
return { ok: true };
|
|
@@ -1359,6 +1545,546 @@ function createLocalStorageDriver(options) {
|
|
|
1359
1545
|
};
|
|
1360
1546
|
}
|
|
1361
1547
|
|
|
1548
|
+
// packages/runtime/src/sim/sql-engine.ts
|
|
1549
|
+
var SimDbError = class extends Error {
|
|
1550
|
+
constructor(code, message) {
|
|
1551
|
+
super(message);
|
|
1552
|
+
this.code = code;
|
|
1553
|
+
this.name = "SimDbError";
|
|
1554
|
+
}
|
|
1555
|
+
};
|
|
1556
|
+
var ParamCursor = class {
|
|
1557
|
+
constructor(params) {
|
|
1558
|
+
this.params = params;
|
|
1559
|
+
}
|
|
1560
|
+
index = 0;
|
|
1561
|
+
take() {
|
|
1562
|
+
const v = this.params[this.index];
|
|
1563
|
+
this.index += 1;
|
|
1564
|
+
return v ?? null;
|
|
1565
|
+
}
|
|
1566
|
+
};
|
|
1567
|
+
function ident(raw) {
|
|
1568
|
+
return raw.trim().replace(/^"|"$/g, "").trim();
|
|
1569
|
+
}
|
|
1570
|
+
function toNumber(raw) {
|
|
1571
|
+
const t = raw.trim();
|
|
1572
|
+
if (t === "?") return null;
|
|
1573
|
+
const n = Number(t);
|
|
1574
|
+
return Number.isNaN(n) ? null : n;
|
|
1575
|
+
}
|
|
1576
|
+
function stripQuotes(raw) {
|
|
1577
|
+
const t = raw.trim();
|
|
1578
|
+
if (/^'.*'$/.test(t)) return t.slice(1, -1);
|
|
1579
|
+
if (/^".*"$/.test(t)) return t.slice(1, -1);
|
|
1580
|
+
return t;
|
|
1581
|
+
}
|
|
1582
|
+
function parseCreateTable(ddl) {
|
|
1583
|
+
const m = /^CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?((?:"[^"]+")|([A-Za-z_][A-Za-z0-9_]*))\s*\(([\s\S]*)\)$/i.exec(
|
|
1584
|
+
ddl.trim()
|
|
1585
|
+
);
|
|
1586
|
+
if (m === null) return null;
|
|
1587
|
+
const name = ident(m[1]);
|
|
1588
|
+
const body = m[3];
|
|
1589
|
+
const columns = [];
|
|
1590
|
+
for (const part of body.split(",")) {
|
|
1591
|
+
const tokens = part.trim().split(/\s+/);
|
|
1592
|
+
const colName = ident(tokens[0] ?? "");
|
|
1593
|
+
const type = (tokens[1] ?? "TEXT").toUpperCase();
|
|
1594
|
+
const primaryKey = tokens.includes("PRIMARY") && tokens.includes("KEY");
|
|
1595
|
+
const autoincrement = primaryKey && tokens.includes("AUTOINCREMENT");
|
|
1596
|
+
columns.push({ name: colName, type, primaryKey, autoincrement });
|
|
1597
|
+
}
|
|
1598
|
+
return { name, columns };
|
|
1599
|
+
}
|
|
1600
|
+
function parseWhereClauses(whereRaw, cursor) {
|
|
1601
|
+
const clauses = [];
|
|
1602
|
+
for (const part of whereRaw.split(/\s+AND\s+/i)) {
|
|
1603
|
+
const t = part.trim();
|
|
1604
|
+
if (t.length === 0) continue;
|
|
1605
|
+
if (/\bIS\s+NULL\b/i.test(t)) {
|
|
1606
|
+
const col2 = ident(t.split(/\s+IS\s+NULL\b/i)[0] ?? "");
|
|
1607
|
+
clauses.push({ column: col2, operator: "IS NULL", value: null, list: false });
|
|
1608
|
+
continue;
|
|
1609
|
+
}
|
|
1610
|
+
if (/\bIS\s+NOT\s+NULL\b/i.test(t)) {
|
|
1611
|
+
const col2 = ident(t.split(/\s+IS\s+NOT\s+NULL\b/i)[0] ?? "");
|
|
1612
|
+
clauses.push({ column: col2, operator: "IS NOT NULL", value: null, list: false });
|
|
1613
|
+
continue;
|
|
1614
|
+
}
|
|
1615
|
+
const opMatch = /^\s*((?:"[^"]+")|([A-Za-z_][A-Za-z0-9_]*))\s*(=|!=|>=|<=|>|<|like|in|not\s+in)\s*(.+)$/i.exec(
|
|
1616
|
+
t
|
|
1617
|
+
);
|
|
1618
|
+
if (opMatch === null) continue;
|
|
1619
|
+
const col = ident(opMatch[1] ?? "");
|
|
1620
|
+
const op = (opMatch[3] ?? opMatch[2] ?? "").toLowerCase();
|
|
1621
|
+
const rhs = (opMatch[4] ?? "").trim();
|
|
1622
|
+
if (op === "in" || op === "not in") {
|
|
1623
|
+
const inner = rhs.replace(/^\(|\)$/g, "");
|
|
1624
|
+
const items = splitListItems(inner);
|
|
1625
|
+
const list = items.map((item) => {
|
|
1626
|
+
if (item.trim() === "?") return cursor.take();
|
|
1627
|
+
return stripQuotes(item);
|
|
1628
|
+
});
|
|
1629
|
+
clauses.push({ column: col, operator: op, value: list, list: true });
|
|
1630
|
+
continue;
|
|
1631
|
+
}
|
|
1632
|
+
let value;
|
|
1633
|
+
if (rhs === "?") {
|
|
1634
|
+
value = cursor.take();
|
|
1635
|
+
} else {
|
|
1636
|
+
value = stripQuotes(rhs);
|
|
1637
|
+
}
|
|
1638
|
+
clauses.push({ column: col, operator: op, value, list: false });
|
|
1639
|
+
}
|
|
1640
|
+
return clauses;
|
|
1641
|
+
}
|
|
1642
|
+
function matchWhere(clauses, row) {
|
|
1643
|
+
return clauses.every((c) => matchValue(row[c.column], c));
|
|
1644
|
+
}
|
|
1645
|
+
function splitListItems(inner) {
|
|
1646
|
+
const items = [];
|
|
1647
|
+
let depth = 0;
|
|
1648
|
+
let buffer = "";
|
|
1649
|
+
for (let i = 0; i < inner.length; i += 1) {
|
|
1650
|
+
const ch = inner[i];
|
|
1651
|
+
if (ch === "(") depth += 1;
|
|
1652
|
+
else if (ch === ")") depth -= 1;
|
|
1653
|
+
if (ch === "," && depth === 0) {
|
|
1654
|
+
items.push(buffer);
|
|
1655
|
+
buffer = "";
|
|
1656
|
+
} else {
|
|
1657
|
+
buffer += ch;
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
items.push(buffer);
|
|
1661
|
+
return items;
|
|
1662
|
+
}
|
|
1663
|
+
function matchValue(value, clause) {
|
|
1664
|
+
const op = clause.operator;
|
|
1665
|
+
if (op === "IS NULL") return value === null || value === void 0;
|
|
1666
|
+
if (op === "IS NOT NULL") return value !== null && value !== void 0;
|
|
1667
|
+
if (op === "in") return clause.value.some((item) => value === item);
|
|
1668
|
+
if (op === "not in") return !clause.value.some((item) => value === item);
|
|
1669
|
+
if (op === "like") {
|
|
1670
|
+
if (typeof value !== "string") return false;
|
|
1671
|
+
const pattern = String(clause.value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/%/g, ".*").replace(/_/g, ".");
|
|
1672
|
+
return new RegExp(`^${pattern}$`, "s").test(value);
|
|
1673
|
+
}
|
|
1674
|
+
const left = value;
|
|
1675
|
+
const right = clause.value;
|
|
1676
|
+
switch (op) {
|
|
1677
|
+
case "=":
|
|
1678
|
+
return left !== null && right !== null && left === right;
|
|
1679
|
+
case "!=":
|
|
1680
|
+
return left !== null && right !== null && left !== right;
|
|
1681
|
+
case ">":
|
|
1682
|
+
return left !== null && right !== null && left > right;
|
|
1683
|
+
case ">=":
|
|
1684
|
+
return left !== null && right !== null && left >= right;
|
|
1685
|
+
case "<":
|
|
1686
|
+
return left !== null && right !== null && left < right;
|
|
1687
|
+
case "<=":
|
|
1688
|
+
return left !== null && right !== null && left <= right;
|
|
1689
|
+
default:
|
|
1690
|
+
return false;
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
function assertReadOnly(sql) {
|
|
1694
|
+
const stripped = sql.replace(/'[^']*(?:''[^']*)*'/g, "").replace(/"[^"]*(?:""[^"]*)*"/g, "").replace(/--[^\n]*/g, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
1695
|
+
if (stripped.includes(";")) throw new SimDbError("DB_UNSAFE_OP", "\u4EC5\u5141\u8BB8\u5355\u6761\u8BED\u53E5\uFF0C\u7981\u6B62\u591A\u8BED\u53E5\u5806\u53E0");
|
|
1696
|
+
const head = stripped.trim().replace(/^\(+/, "").trim().toUpperCase();
|
|
1697
|
+
if (!head.startsWith("SELECT") && !head.startsWith("WITH")) {
|
|
1698
|
+
throw new SimDbError("DB_UNSAFE_OP", "cloud.db.query \u4EC5\u5141\u8BB8\u53EA\u8BFB\u67E5\u8BE2\uFF08SELECT / WITH\uFF09");
|
|
1699
|
+
}
|
|
1700
|
+
if (/ATTACH|DETACH/.test(stripped.toUpperCase())) {
|
|
1701
|
+
throw new SimDbError("DB_UNSAFE_OP", "\u7981\u6B62\u8DE8\u5E93\u64CD\u4F5C\uFF08ATTACH / DETACH\uFF09");
|
|
1702
|
+
}
|
|
1703
|
+
if (/SQLITE_\w+/i.test(stripped) && !/SQLITE_MASTER\b/i.test(stripped)) {
|
|
1704
|
+
throw new SimDbError("DB_UNSAFE_OP", "\u7981\u6B62\u8BBF\u95EE\u7CFB\u7EDF\u8868\uFF08sqlite_*\uFF09");
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
var SimSqlEngine = class {
|
|
1708
|
+
tables = {};
|
|
1709
|
+
storage;
|
|
1710
|
+
memoryOnly;
|
|
1711
|
+
savepointDepth = 0;
|
|
1712
|
+
txSnapshot = null;
|
|
1713
|
+
constructor(options = {}) {
|
|
1714
|
+
this.storage = options.storage ?? { load: async () => null, save: async () => void 0 };
|
|
1715
|
+
this.memoryOnly = options.memoryOnly ?? false;
|
|
1716
|
+
}
|
|
1717
|
+
async load() {
|
|
1718
|
+
if (this.memoryOnly) return;
|
|
1719
|
+
const persisted = await this.storage.load();
|
|
1720
|
+
if (persisted !== null) this.tables = persisted;
|
|
1721
|
+
}
|
|
1722
|
+
async persist() {
|
|
1723
|
+
if (this.memoryOnly) return;
|
|
1724
|
+
await this.storage.save(this.tables);
|
|
1725
|
+
}
|
|
1726
|
+
ensureTable(name) {
|
|
1727
|
+
let table = this.tables[name];
|
|
1728
|
+
if (table === void 0) {
|
|
1729
|
+
table = { columns: [], rows: [], nextAutoincrement: 1 };
|
|
1730
|
+
this.tables[name] = table;
|
|
1731
|
+
}
|
|
1732
|
+
return table;
|
|
1733
|
+
}
|
|
1734
|
+
applyAutoincrement(table, row) {
|
|
1735
|
+
const pk = table.columns.find((c) => c.primaryKey && c.autoincrement);
|
|
1736
|
+
if (pk === void 0) return;
|
|
1737
|
+
const val = row[pk.name];
|
|
1738
|
+
if (val !== null && val !== void 0) {
|
|
1739
|
+
const n = Number(val);
|
|
1740
|
+
if (Number.isInteger(n) && n >= table.nextAutoincrement) table.nextAutoincrement = n + 1;
|
|
1741
|
+
return;
|
|
1742
|
+
}
|
|
1743
|
+
row[pk.name] = table.nextAutoincrement;
|
|
1744
|
+
table.nextAutoincrement += 1;
|
|
1745
|
+
}
|
|
1746
|
+
catalog() {
|
|
1747
|
+
const out = [];
|
|
1748
|
+
for (const [name] of Object.entries(this.tables)) {
|
|
1749
|
+
out.push({ type: "table", name, tbl_name: name });
|
|
1750
|
+
}
|
|
1751
|
+
return out;
|
|
1752
|
+
}
|
|
1753
|
+
async run(sql, params = []) {
|
|
1754
|
+
const stmt = sql.trim();
|
|
1755
|
+
if (/^BEGIN\s*$/i.test(stmt)) {
|
|
1756
|
+
this.txSnapshot = structuredClone(this.tables);
|
|
1757
|
+
return { changes: 0 };
|
|
1758
|
+
}
|
|
1759
|
+
if (/^COMMIT\s*$/i.test(stmt)) {
|
|
1760
|
+
this.txSnapshot = null;
|
|
1761
|
+
await this.persist();
|
|
1762
|
+
return { changes: 0 };
|
|
1763
|
+
}
|
|
1764
|
+
if (/^ROLLBACK\s*$/i.test(stmt)) {
|
|
1765
|
+
if (this.txSnapshot !== null) {
|
|
1766
|
+
this.tables = this.txSnapshot;
|
|
1767
|
+
this.txSnapshot = null;
|
|
1768
|
+
}
|
|
1769
|
+
await this.persist();
|
|
1770
|
+
return { changes: 0 };
|
|
1771
|
+
}
|
|
1772
|
+
if (/^SAVEPOINT\s+/i.test(stmt)) {
|
|
1773
|
+
this.savepointDepth += 1;
|
|
1774
|
+
return { changes: 0 };
|
|
1775
|
+
}
|
|
1776
|
+
if (/^RELEASE\s+SAVEPOINT\s+/i.test(stmt)) {
|
|
1777
|
+
this.savepointDepth = Math.max(0, this.savepointDepth - 1);
|
|
1778
|
+
return { changes: 0 };
|
|
1779
|
+
}
|
|
1780
|
+
if (/^ROLLBACK\s+TO\s+SAVEPOINT\s+/i.test(stmt)) return { changes: 0 };
|
|
1781
|
+
if (/^CREATE\s+TABLE/i.test(stmt)) {
|
|
1782
|
+
const parsed = parseCreateTable(stmt);
|
|
1783
|
+
if (parsed === null) throw new SimDbError("DB_UNSAFE_OP", `\u65E0\u6CD5\u89E3\u6790\u5EFA\u8868\u8BED\u53E5`);
|
|
1784
|
+
if (this.tables[parsed.name] === void 0) {
|
|
1785
|
+
this.tables[parsed.name] = { columns: parsed.columns, rows: [], nextAutoincrement: 1 };
|
|
1786
|
+
await this.persist();
|
|
1787
|
+
}
|
|
1788
|
+
return { changes: 0 };
|
|
1789
|
+
}
|
|
1790
|
+
if (/^INSERT\s+INTO/i.test(stmt)) return this.execInsert(stmt, params);
|
|
1791
|
+
if (/^UPDATE\s+/i.test(stmt)) return this.execUpdate(stmt, params);
|
|
1792
|
+
if (/^DELETE\s+FROM/i.test(stmt)) return this.execDelete(stmt, params);
|
|
1793
|
+
throw new SimDbError("DB_UNSAFE_OP", `sim SQL \u5F15\u64CE\u4E0D\u652F\u6301\u8BE5\u8BED\u53E5`);
|
|
1794
|
+
}
|
|
1795
|
+
execInsert(sql, params) {
|
|
1796
|
+
const match = /^INSERT\s+INTO\s+((?:"[^"]+")|([A-Za-z_][A-Za-z0-9_]*))\s*\(([^)]*)\)\s*VALUES\s*([\s\S]+)$/i.exec(
|
|
1797
|
+
sql
|
|
1798
|
+
);
|
|
1799
|
+
if (match === null) throw new SimDbError("DB_UNSAFE_OP", "\u65E0\u6CD5\u89E3\u6790 INSERT");
|
|
1800
|
+
const tableName = ident(match[1]);
|
|
1801
|
+
const cols = match[3].split(",").map(ident);
|
|
1802
|
+
const valueBody = match[4].trim();
|
|
1803
|
+
const cursor = new ParamCursor(params);
|
|
1804
|
+
const table = this.ensureTable(tableName);
|
|
1805
|
+
let inserted = 0;
|
|
1806
|
+
for (const group of splitListItems(valueBody)) {
|
|
1807
|
+
const inner = group.trim().replace(/^\(|\)$/g, "");
|
|
1808
|
+
const values = splitListItems(inner).map(
|
|
1809
|
+
(item) => item.trim() === "?" ? cursor.take() : stripQuotes(item)
|
|
1810
|
+
);
|
|
1811
|
+
const row = {};
|
|
1812
|
+
cols.forEach((col, i) => {
|
|
1813
|
+
row[col] = values[i] ?? null;
|
|
1814
|
+
});
|
|
1815
|
+
this.applyAutoincrement(table, row);
|
|
1816
|
+
table.rows.push(row);
|
|
1817
|
+
inserted += 1;
|
|
1818
|
+
}
|
|
1819
|
+
void this.persist();
|
|
1820
|
+
return { changes: inserted };
|
|
1821
|
+
}
|
|
1822
|
+
execUpdate(sql, params) {
|
|
1823
|
+
const match = /^UPDATE\s+((?:"[^"]+")|([A-Za-z_][A-Za-z0-9_]*))\s+SET\s+([\s\S]+?)\s+WHERE\s+([\s\S]+)$/i.exec(
|
|
1824
|
+
sql
|
|
1825
|
+
);
|
|
1826
|
+
if (match === null) throw new SimDbError("DB_UNSAFE_OP", "UPDATE \u5FC5\u987B\u5E26 WHERE");
|
|
1827
|
+
const tableName = ident(match[1]);
|
|
1828
|
+
const setRaw = match[3];
|
|
1829
|
+
const whereRaw = match[4];
|
|
1830
|
+
const cursor = new ParamCursor(params);
|
|
1831
|
+
const sets = splitListItems(setRaw).map((part) => {
|
|
1832
|
+
const [col, , mark] = part.trim().split(/\s+/);
|
|
1833
|
+
return { col: ident(col ?? ""), mark: mark ?? "?" };
|
|
1834
|
+
});
|
|
1835
|
+
const table = this.ensureTable(tableName);
|
|
1836
|
+
const boundSets = sets.map((set) => ({
|
|
1837
|
+
col: set.col,
|
|
1838
|
+
value: set.mark === "?" ? cursor.take() : stripQuotes(set.mark)
|
|
1839
|
+
}));
|
|
1840
|
+
const whereClauses = parseWhereClauses(whereRaw, cursor);
|
|
1841
|
+
let changes = 0;
|
|
1842
|
+
for (const row of table.rows) {
|
|
1843
|
+
if (!matchWhere(whereClauses, row)) continue;
|
|
1844
|
+
for (const set of boundSets) {
|
|
1845
|
+
row[set.col] = set.value;
|
|
1846
|
+
}
|
|
1847
|
+
changes += 1;
|
|
1848
|
+
}
|
|
1849
|
+
void this.persist();
|
|
1850
|
+
return { changes };
|
|
1851
|
+
}
|
|
1852
|
+
execDelete(sql, params) {
|
|
1853
|
+
const match = /^DELETE\s+FROM\s+((?:"[^"]+")|([A-Za-z_][A-Za-z0-9_]*))\s+WHERE\s+([\s\S]+)$/i.exec(sql);
|
|
1854
|
+
if (match === null) throw new SimDbError("DB_UNSAFE_OP", "DELETE \u5FC5\u987B\u5E26 WHERE");
|
|
1855
|
+
const tableName = ident(match[1]);
|
|
1856
|
+
const whereRaw = match[3];
|
|
1857
|
+
const cursor = new ParamCursor(params);
|
|
1858
|
+
const table = this.ensureTable(tableName);
|
|
1859
|
+
const whereClauses = parseWhereClauses(whereRaw, cursor);
|
|
1860
|
+
const keep = [];
|
|
1861
|
+
let changes = 0;
|
|
1862
|
+
for (const row of table.rows) {
|
|
1863
|
+
if (!matchWhere(whereClauses, row)) keep.push(row);
|
|
1864
|
+
else changes += 1;
|
|
1865
|
+
}
|
|
1866
|
+
table.rows = keep;
|
|
1867
|
+
void this.persist();
|
|
1868
|
+
return { changes };
|
|
1869
|
+
}
|
|
1870
|
+
select(sql, params) {
|
|
1871
|
+
const m = /^SELECT\s+([\s\S]+?)\s+FROM\s+((?:"[^"]+")|([A-Za-z_][A-Za-z0-9_]*))([\s\S]*)$/i.exec(
|
|
1872
|
+
sql.trim()
|
|
1873
|
+
);
|
|
1874
|
+
if (m === null) throw new SimDbError("DB_UNSAFE_OP", "\u65E0\u6CD5\u89E3\u6790 SELECT");
|
|
1875
|
+
const selectRaw = m[1].trim();
|
|
1876
|
+
const tableName = ident(m[2]);
|
|
1877
|
+
const tail = m[4] ?? "";
|
|
1878
|
+
const cursor = new ParamCursor(params);
|
|
1879
|
+
if (tableName.toLowerCase() === "sqlite_master") {
|
|
1880
|
+
return project(this.catalog(), selectRaw);
|
|
1881
|
+
}
|
|
1882
|
+
const table = this.ensureTable(tableName);
|
|
1883
|
+
const whereMatch = /\bWHERE\b/i.exec(tail);
|
|
1884
|
+
const orderMatch = /\bORDER\s+BY\b/i.exec(tail);
|
|
1885
|
+
const limitMatch = /\bLIMIT\b/i.exec(tail);
|
|
1886
|
+
const offsetMatch = /\bOFFSET\b/i.exec(tail);
|
|
1887
|
+
const whereRaw = whereMatch === null ? "" : tail.slice(
|
|
1888
|
+
whereMatch.index + whereMatch[0].length,
|
|
1889
|
+
indexAfter(whereMatch.index, [orderMatch, limitMatch, offsetMatch], tail)
|
|
1890
|
+
);
|
|
1891
|
+
let rows = table.rows;
|
|
1892
|
+
if (whereRaw.trim().length > 0) {
|
|
1893
|
+
const clauses = parseWhereClauses(whereRaw, cursor);
|
|
1894
|
+
rows = rows.filter((row) => matchWhere(clauses, row));
|
|
1895
|
+
}
|
|
1896
|
+
if (orderMatch !== null) {
|
|
1897
|
+
const orderRaw = tail.slice(
|
|
1898
|
+
orderMatch.index + orderMatch[0].length,
|
|
1899
|
+
indexAfter(orderMatch.index, [limitMatch, offsetMatch], tail)
|
|
1900
|
+
);
|
|
1901
|
+
const oc = /^\s*((?:"[^"]+")|([A-Za-z_][A-Za-z0-9_]*))\s+(asc|desc)/i.exec(orderRaw);
|
|
1902
|
+
if (oc !== null) {
|
|
1903
|
+
const col = ident(oc[1]);
|
|
1904
|
+
const dir = oc[3].toLowerCase();
|
|
1905
|
+
rows = [...rows].toSorted((a, b) => {
|
|
1906
|
+
const av = a[col];
|
|
1907
|
+
const bv = b[col];
|
|
1908
|
+
const cmp = av === bv ? 0 : av === null ? -1 : bv === null ? 1 : av > bv ? 1 : -1;
|
|
1909
|
+
return dir === "desc" ? -cmp : cmp;
|
|
1910
|
+
});
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
if (limitMatch !== null) {
|
|
1914
|
+
const limRaw = tail.slice(
|
|
1915
|
+
limitMatch.index + limitMatch[0].length,
|
|
1916
|
+
indexAfter(limitMatch.index, [offsetMatch], tail)
|
|
1917
|
+
);
|
|
1918
|
+
const lim = limRaw.trim() === "?" ? cursor.take() : toNumber(limRaw);
|
|
1919
|
+
if (typeof lim === "number") rows = rows.slice(0, lim);
|
|
1920
|
+
}
|
|
1921
|
+
if (offsetMatch !== null) {
|
|
1922
|
+
const offRaw = tail.slice(
|
|
1923
|
+
offsetMatch.index + offsetMatch[0].length,
|
|
1924
|
+
indexAfter(offsetMatch.index, [limitMatch], tail)
|
|
1925
|
+
);
|
|
1926
|
+
const off = offRaw.trim() === "?" ? cursor.take() : toNumber(offRaw);
|
|
1927
|
+
if (typeof off === "number") rows = rows.slice(off);
|
|
1928
|
+
}
|
|
1929
|
+
return project(rows, selectRaw);
|
|
1930
|
+
}
|
|
1931
|
+
async all(sql, params = []) {
|
|
1932
|
+
assertReadOnly(sql);
|
|
1933
|
+
return this.select(sql, params);
|
|
1934
|
+
}
|
|
1935
|
+
async get(sql, params = []) {
|
|
1936
|
+
assertReadOnly(sql);
|
|
1937
|
+
return this.select(sql, params)[0] ?? null;
|
|
1938
|
+
}
|
|
1939
|
+
async close() {
|
|
1940
|
+
void this.savepointDepth;
|
|
1941
|
+
}
|
|
1942
|
+
};
|
|
1943
|
+
function indexAfter(start, matches, tail) {
|
|
1944
|
+
const candidates = matches.filter((x) => x !== null && x.index > start).map((x) => x.index);
|
|
1945
|
+
const end = candidates.length === 0 ? -1 : Math.min(...candidates);
|
|
1946
|
+
return end === -1 ? tail.length : end;
|
|
1947
|
+
}
|
|
1948
|
+
function project(rows, selectRaw) {
|
|
1949
|
+
if (/^count\s*\(\s*\*/i.test(selectRaw) || /^count\s*\(\s*1\)/i.test(selectRaw)) {
|
|
1950
|
+
const alias = selectRaw.match(/\bAS\s+([A-Za-z_][A-Za-z0-9_]*)/i)?.[1] ?? "n";
|
|
1951
|
+
return [{ [alias]: rows.length }];
|
|
1952
|
+
}
|
|
1953
|
+
const constMatch = /^(\d+)\s+AS\s+([A-Za-z_][A-Za-z0-9_]*)/i.exec(selectRaw);
|
|
1954
|
+
if (constMatch !== null) {
|
|
1955
|
+
return [{ [constMatch[2]]: Number(constMatch[1]) }];
|
|
1956
|
+
}
|
|
1957
|
+
if (selectRaw.trim() === "*") return rows;
|
|
1958
|
+
const cols = selectRaw.split(",").map((s) => ident(s.trim()));
|
|
1959
|
+
return rows.map((row) => {
|
|
1960
|
+
const out = {};
|
|
1961
|
+
for (const col of cols) out[col] = row[col];
|
|
1962
|
+
return out;
|
|
1963
|
+
});
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
// packages/runtime/src/sim/realtime.ts
|
|
1967
|
+
var SimRealtime = class {
|
|
1968
|
+
seq = 0;
|
|
1969
|
+
subscribers = /* @__PURE__ */ new Map();
|
|
1970
|
+
/** 向某 channel 广播一条消息;返回被投递的连接数(单进程内)。 */
|
|
1971
|
+
publish(channel, data, publisher) {
|
|
1972
|
+
this.seq += 1;
|
|
1973
|
+
const message = {
|
|
1974
|
+
channel,
|
|
1975
|
+
seq: this.seq,
|
|
1976
|
+
data,
|
|
1977
|
+
publishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1978
|
+
...publisher === void 0 ? {} : { publisher }
|
|
1979
|
+
};
|
|
1980
|
+
const set = this.subscribers.get(channel);
|
|
1981
|
+
if (set === void 0) return { delivered: 0 };
|
|
1982
|
+
let delivered = 0;
|
|
1983
|
+
for (const listener of set) {
|
|
1984
|
+
listener(message);
|
|
1985
|
+
delivered += 1;
|
|
1986
|
+
}
|
|
1987
|
+
return { delivered };
|
|
1988
|
+
}
|
|
1989
|
+
/** 订阅某 channel;返回退订函数。 */
|
|
1990
|
+
subscribe(channel, listener) {
|
|
1991
|
+
let set = this.subscribers.get(channel);
|
|
1992
|
+
if (set === void 0) {
|
|
1993
|
+
set = /* @__PURE__ */ new Set();
|
|
1994
|
+
this.subscribers.set(channel, set);
|
|
1995
|
+
}
|
|
1996
|
+
set.add(listener);
|
|
1997
|
+
return () => {
|
|
1998
|
+
set?.delete(listener);
|
|
1999
|
+
};
|
|
2000
|
+
}
|
|
2001
|
+
/** 当前有订阅者的 channel 数(调试用)。 */
|
|
2002
|
+
channelCount() {
|
|
2003
|
+
return this.subscribers.size;
|
|
2004
|
+
}
|
|
2005
|
+
};
|
|
2006
|
+
var MAX_BUFFERED_MESSAGES = 64;
|
|
2007
|
+
var MAX_SUBSCRIPTIONS = 64;
|
|
2008
|
+
var REALTIME_CODES = REALTIME_CAPABILITY_CODES;
|
|
2009
|
+
function realtimeError(code, message) {
|
|
2010
|
+
return new Error(`[${code}] ${message}`);
|
|
2011
|
+
}
|
|
2012
|
+
function createSimRealtimeCapability() {
|
|
2013
|
+
const sim = new SimRealtime();
|
|
2014
|
+
const buffers = /* @__PURE__ */ new Map();
|
|
2015
|
+
const disposers = /* @__PURE__ */ new Map();
|
|
2016
|
+
let seq = 0;
|
|
2017
|
+
const handler = async (method, args) => {
|
|
2018
|
+
switch (method) {
|
|
2019
|
+
case "publish": {
|
|
2020
|
+
if (args.length > 2) {
|
|
2021
|
+
throw realtimeError(
|
|
2022
|
+
REALTIME_CODES.unsupportedArg,
|
|
2023
|
+
"\u672C\u5730\u5355\u8FDB\u7A0B\u5185\u5B58\u5E7F\u64AD\u65E0\u8FDE\u63A5\u8EAB\u4EFD\uFF0C\u4E0D\u652F\u6301 publish \u7684 except \u5B9E\u53C2"
|
|
2024
|
+
);
|
|
2025
|
+
}
|
|
2026
|
+
const [channel, data] = args;
|
|
2027
|
+
return sim.publish(channel, data);
|
|
2028
|
+
}
|
|
2029
|
+
case "subscribe": {
|
|
2030
|
+
if (buffers.size >= MAX_SUBSCRIPTIONS) {
|
|
2031
|
+
throw realtimeError(
|
|
2032
|
+
REALTIME_CODES.tooManySubscriptions,
|
|
2033
|
+
`\u672C\u5730\u8BA2\u9605\u6570\u5DF2\u8FBE\u4E0A\u9650 ${String(MAX_SUBSCRIPTIONS)}\uFF0C\u8BF7\u5148 unsubscribe`
|
|
2034
|
+
);
|
|
2035
|
+
}
|
|
2036
|
+
const [channel] = args;
|
|
2037
|
+
seq += 1;
|
|
2038
|
+
const id = `sub-${String(seq)}`;
|
|
2039
|
+
const buffer = [];
|
|
2040
|
+
const off = sim.subscribe(channel, (msg) => {
|
|
2041
|
+
buffer.push(msg);
|
|
2042
|
+
if (buffer.length > MAX_BUFFERED_MESSAGES) buffer.shift();
|
|
2043
|
+
});
|
|
2044
|
+
buffers.set(id, buffer);
|
|
2045
|
+
disposers.set(id, off);
|
|
2046
|
+
return { subscription: id, channel };
|
|
2047
|
+
}
|
|
2048
|
+
case "receive": {
|
|
2049
|
+
const buffer = buffers.get(args[0]);
|
|
2050
|
+
if (buffer === void 0) {
|
|
2051
|
+
throw realtimeError(
|
|
2052
|
+
REALTIME_CODES.subscriptionNotFound,
|
|
2053
|
+
`\u672C\u5730\u8BA2\u9605 "${String(args[0])}" \u4E0D\u5B58\u5728\u6216\u5DF2\u9000\u8BA2`
|
|
2054
|
+
);
|
|
2055
|
+
}
|
|
2056
|
+
return buffer.splice(0, buffer.length);
|
|
2057
|
+
}
|
|
2058
|
+
case "unsubscribe": {
|
|
2059
|
+
const id = args[0];
|
|
2060
|
+
const off = disposers.get(id);
|
|
2061
|
+
if (off === void 0) {
|
|
2062
|
+
throw realtimeError(
|
|
2063
|
+
REALTIME_CODES.subscriptionNotFound,
|
|
2064
|
+
`\u672C\u5730\u8BA2\u9605 "${id}" \u4E0D\u5B58\u5728\u6216\u5DF2\u9000\u8BA2`
|
|
2065
|
+
);
|
|
2066
|
+
}
|
|
2067
|
+
off();
|
|
2068
|
+
disposers.delete(id);
|
|
2069
|
+
buffers.delete(id);
|
|
2070
|
+
return { removed: true };
|
|
2071
|
+
}
|
|
2072
|
+
default:
|
|
2073
|
+
throw realtimeError(
|
|
2074
|
+
REALTIME_CODES.invalidMethod,
|
|
2075
|
+
`\u672A\u77E5\u7684 cloud.realtime \u65B9\u6CD5 "${method}"\uFF08\u53EF\u7528\uFF1A${REALTIME_CAPABILITY_METHODS.join(" / ")}\uFF09`
|
|
2076
|
+
);
|
|
2077
|
+
}
|
|
2078
|
+
};
|
|
2079
|
+
return {
|
|
2080
|
+
bundle: {
|
|
2081
|
+
capabilities: [{ name: "realtime", value: { [RPC_CAPABILITY_KEY]: true } }],
|
|
2082
|
+
rpcHandlers: { realtime: handler }
|
|
2083
|
+
},
|
|
2084
|
+
realtime: sim
|
|
2085
|
+
};
|
|
2086
|
+
}
|
|
2087
|
+
|
|
1362
2088
|
// packages/runtime/src/shared/function-source.ts
|
|
1363
2089
|
async function resolveFunctionSource(db, fn) {
|
|
1364
2090
|
const readDraft = async () => {
|
|
@@ -1381,17 +2107,26 @@ export {
|
|
|
1381
2107
|
DEFAULT_TIMEOUT_MS,
|
|
1382
2108
|
ExecutorError,
|
|
1383
2109
|
OOM_CODE,
|
|
2110
|
+
REALTIME_CODES,
|
|
1384
2111
|
RPC_CAPABILITY_KEY,
|
|
1385
2112
|
STORAGE_CODES,
|
|
2113
|
+
SimDbError,
|
|
2114
|
+
SimRealtime,
|
|
2115
|
+
SimSqlEngine,
|
|
1386
2116
|
StorageError,
|
|
1387
2117
|
TIMEOUT_CODE,
|
|
1388
2118
|
WorkerFunctionExecutor,
|
|
2119
|
+
constantTimeEqual,
|
|
1389
2120
|
createCloudDb,
|
|
1390
2121
|
createDbCapability,
|
|
1391
2122
|
createLocalStorageDriver,
|
|
2123
|
+
createSimRealtimeCapability,
|
|
1392
2124
|
createStorageCapability,
|
|
2125
|
+
hmacSha256,
|
|
2126
|
+
hmacSha256Hex,
|
|
1393
2127
|
parseSnapshot,
|
|
1394
2128
|
resolveFunctionSource,
|
|
2129
|
+
sha256,
|
|
1395
2130
|
signDownloadUrl,
|
|
1396
2131
|
verifyDownloadSignature
|
|
1397
2132
|
};
|
|
@@ -25,3 +25,19 @@ export declare const DB_RPC: {
|
|
|
25
25
|
/** 执行一条链:`chain, args=[ChainRequest]`。 */
|
|
26
26
|
readonly chain: "chain";
|
|
27
27
|
};
|
|
28
|
+
/**
|
|
29
|
+
* `cloud.realtime` 的跨 realm 错误码(FN-020):与 `STORAGE_*` / `DB_*` 同族,装在 message 前缀里
|
|
30
|
+
* (worker 只回传 `error.message`)。本地 sim(CLI-007)与线上提供方**共用这一份**——
|
|
31
|
+
* 两侧各写一份时,函数里 `catch` 的错误码分支会在上线后静默走空。
|
|
32
|
+
*/
|
|
33
|
+
export declare const REALTIME_CAPABILITY_CODES: {
|
|
34
|
+
readonly invalidMethod: "REALTIME_INVALID_METHOD";
|
|
35
|
+
readonly unsupportedArg: "REALTIME_UNSUPPORTED_ARG";
|
|
36
|
+
readonly subscriptionNotFound: "REALTIME_SUBSCRIPTION_NOT_FOUND";
|
|
37
|
+
readonly tooManySubscriptions: "REALTIME_TOO_MANY_SUBSCRIPTIONS";
|
|
38
|
+
/** 线上独有:订阅授权策略(RT-002 `SubscriptionPolicy`)拒绝该 channel。 */
|
|
39
|
+
readonly channelDenied: "REALTIME_CHANNEL_DENIED";
|
|
40
|
+
};
|
|
41
|
+
/** 命令通道可派发的 realtime 方法名(未知方法一律 fail loud)。 */
|
|
42
|
+
export declare const REALTIME_CAPABILITY_METHODS: readonly ["publish", "subscribe", "receive", "unsubscribe"];
|
|
43
|
+
export type RealtimeCapabilityMethod = (typeof REALTIME_CAPABILITY_METHODS)[number];
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SimRealtime(CLI-004)+ `cloud.realtime` 能力装配(CLI-007):单进程内存广播。
|
|
3
|
+
*
|
|
4
|
+
* 以 `RealtimeChannelMessage`(`@adep/types`)为对外形状;单实例语义与 PRD §3.2.9
|
|
5
|
+
* 硬约束 5 一致:**不支持跨进程 / 跨实例广播**。这是模拟运行时的**能力边界**之一。
|
|
6
|
+
*
|
|
7
|
+
* `createSimRealtimeCapability` 把本类装成 `CapabilityBundle`(与 sim db / storage 同款形状:
|
|
8
|
+
* 能力值以 `RPC_CAPABILITY_KEY` 占位、实现在宿主侧 `rpcHandlers.realtime`),因此函数里
|
|
9
|
+
* `await ctx.cloud.realtime.publish(...)` 在 `adep dev` 下可达。本地不建 WS 握手(无连接身份),
|
|
10
|
+
* 函数侧订阅采用**拉取式缓冲**:`subscribe` 注册一条进程内监听并把命中消息留在宿主缓冲里,
|
|
11
|
+
* 函数用 `receive` 取回,`unsubscribe` 退订。测试 / CLI 也可直接调用类上的 `publish` / `subscribe`。
|
|
12
|
+
*
|
|
13
|
+
* **FN-023 移植说明**:本文件自 `packages/cli/src/sim/realtime.ts` 复制进 runtime(CLI 域不在
|
|
14
|
+
* 该单 touches,CLI 侧暂留自有副本)。两份副本的语义一致性由 `shared/sim-contract.ts` 的
|
|
15
|
+
* realtime 期望表双向钉死(CLI 测试 + 浏览器驱动测试各跑一遍);CLI 换口消费本模块留给
|
|
16
|
+
* CLI 域后续单。浏览器侧(`@adep/web-container`)在本装配之上叠加「回网切真实 WS」的
|
|
17
|
+
* 传输切换,语义层(广播 / 缓冲 / 错误码)不动。
|
|
18
|
+
*/
|
|
19
|
+
import type { CapabilityBundle, RealtimeChannelMessage } from '@adep/types';
|
|
20
|
+
export declare class SimRealtime {
|
|
21
|
+
private seq;
|
|
22
|
+
private readonly subscribers;
|
|
23
|
+
/** 向某 channel 广播一条消息;返回被投递的连接数(单进程内)。 */
|
|
24
|
+
publish(channel: string, data: unknown, publisher?: string): {
|
|
25
|
+
delivered: number;
|
|
26
|
+
};
|
|
27
|
+
/** 订阅某 channel;返回退订函数。 */
|
|
28
|
+
subscribe(channel: string, listener: (msg: RealtimeChannelMessage) => void): () => void;
|
|
29
|
+
/** 当前有订阅者的 channel 数(调试用)。 */
|
|
30
|
+
channelCount(): number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* 错误码自 FN-020 起取自 `@adep/runtime/shared/capability-keys`(本地与线上共用同一份)。
|
|
34
|
+
* 保留 `REALTIME_CODES` 这个名字与导出,既有 sim 测试与调用点无需改动。
|
|
35
|
+
*/
|
|
36
|
+
export declare const REALTIME_CODES: {
|
|
37
|
+
readonly invalidMethod: "REALTIME_INVALID_METHOD";
|
|
38
|
+
readonly unsupportedArg: "REALTIME_UNSUPPORTED_ARG";
|
|
39
|
+
readonly subscriptionNotFound: "REALTIME_SUBSCRIPTION_NOT_FOUND";
|
|
40
|
+
readonly tooManySubscriptions: "REALTIME_TOO_MANY_SUBSCRIPTIONS";
|
|
41
|
+
readonly channelDenied: "REALTIME_CHANNEL_DENIED";
|
|
42
|
+
};
|
|
43
|
+
/** `subscribe` 的回执(函数侧只拿到订阅号,拿不到监听器 / 总线内部对象)。 */
|
|
44
|
+
export interface SimRealtimeSubscription {
|
|
45
|
+
subscription: string;
|
|
46
|
+
channel: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* 装配 `cloud.realtime` 能力 bundle(`capabilities[].name = 'realtime'` + `rpcHandlers.realtime`),
|
|
50
|
+
* 并暴露其背后的进程内广播实例(宿主可直接 `subscribe` 观察函数广播)。
|
|
51
|
+
*
|
|
52
|
+
* 方法面(全部经命令通道转发回宿主,返回 Promise):
|
|
53
|
+
* - `publish(channel, data)` → `{ delivered }`:向 channel 内存广播,返回命中订阅者数;
|
|
54
|
+
* - `subscribe(channel)` → `{ subscription, channel }`:注册进程内监听,命中消息留在宿主缓冲;
|
|
55
|
+
* - `receive(subscription)` → `RealtimeChannelMessage[]`:取回并清空缓冲(非阻塞);
|
|
56
|
+
* - `unsubscribe(subscription)` → `{ removed: true }`:退订并丢弃缓冲。
|
|
57
|
+
*
|
|
58
|
+
* 本地**没有**线上的连接身份,故 PRD §2.12.3 的 `except`(不回给发送者)无从实现——
|
|
59
|
+
* 传第三个实参即 fail loud 报错,不静默改变语义。
|
|
60
|
+
*/
|
|
61
|
+
export declare function createSimRealtimeCapability(): {
|
|
62
|
+
bundle: CapabilityBundle;
|
|
63
|
+
realtime: SimRealtime;
|
|
64
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 本地模拟 SQL 引擎(CLI-004):进程内、零外部依赖的 MINI-SQL。
|
|
3
|
+
*
|
|
4
|
+
* 设计动因:`adep dev` 要把 `cloud.db` 能力装进函数执行环境,而 `createDbCapability`
|
|
5
|
+
* (线上 Provider,只读复用)消费一个 `ProjectDriver`(run/all/get/close + engine)。
|
|
6
|
+
* 本引擎实现该驱动所需的「有界 SQL 子集」,恰好覆盖线上 `createCloudDb` 编译器产出的语句形态。
|
|
7
|
+
*
|
|
8
|
+
* **参数化一致性核心**:线上 `compileUpdate`/`compileDelete`/`compileInsert` 的 params 数组顺序
|
|
9
|
+
* 与 SQL 文本里 `?` 占位符的**出现顺序**严格一致(编译器边拼文本边 push 参数)。本引擎统一采用
|
|
10
|
+
* 「从左到右扫描文本、按顺序消费 params」的方式,因此无论 WHERE / SET / VALUES 谁先谁后,
|
|
11
|
+
* 解析顺序都与 params 数组天然对齐——删掉了按子句手工记账的出错面。
|
|
12
|
+
*
|
|
13
|
+
* 覆盖语句:
|
|
14
|
+
* - DDL:`CREATE TABLE [IF NOT EXISTS] name (col TYPE [PRIMARY KEY] [AUTOINCREMENT] [, ...])`;
|
|
15
|
+
* - SELECT:`SELECT cols|count(*)|常量 FROM t [WHERE ...] [ORDER BY col asc|desc] [LIMIT ?] [OFFSET ?]`;
|
|
16
|
+
* `sqlite_master` 探测(owned 变更流表存在性)由特殊分支返回目录;
|
|
17
|
+
* - INSERT:`INSERT INTO t (cols) VALUES (?,...) [, (?,...)]`;
|
|
18
|
+
* - UPDATE:`UPDATE t SET col = ? [...] WHERE ...`;
|
|
19
|
+
* - DELETE:`DELETE FROM t WHERE ...`;
|
|
20
|
+
* - 事务:`BEGIN` / `COMMIT` / `ROLLBACK` / `SAVEPOINT x` / `RELEASE SAVEPOINT x` / `ROLLBACK TO SAVEPOINT x`;
|
|
21
|
+
* - WHERE:`col op ?`(op ∈ = != > >= < <= like in not in);`= ?(null)` → IS NULL、`!= ?(null)` → IS NOT NULL。
|
|
22
|
+
*
|
|
23
|
+
* 一致性承诺:本引擎只在「持久化落点」与线上 sqlite 不同,SQL 语义(CRUD、where 操作符、
|
|
24
|
+
* orderBy/limit、owned 变更流、事务回滚)逐条对齐线上 builder 编译产物——契约表由
|
|
25
|
+
* `shared/sim-contract.ts` 钉死,sim 与线上 Provider 跑同一张表。
|
|
26
|
+
*
|
|
27
|
+
* **FN-023 移植说明**:本文件自 `packages/cli/src/sim/sql-engine.ts` 复制进 runtime
|
|
28
|
+
* (CLI 域不在该单 touches,CLI 侧暂留自有副本)。两份副本的语义一致性由契约表双向钉死
|
|
29
|
+
* (CLI 测试 + 浏览器驱动测试各跑一遍);CLI 换口消费本模块留给 CLI 域后续单。
|
|
30
|
+
* 移植至此的原因:浏览器侧模拟运行时(`@adep/web-container`)复用 `createDbCapability`
|
|
31
|
+
* 时需要同一份「进程内 SQL 引擎」,而 CLI 包不能进浏览器 bundle(`adep` 命令族 / node 依赖)。
|
|
32
|
+
*/
|
|
33
|
+
/** 表列定义(从 CREATE TABLE 语句解析)。 */
|
|
34
|
+
interface SimColumn {
|
|
35
|
+
name: string;
|
|
36
|
+
type: string;
|
|
37
|
+
primaryKey: boolean;
|
|
38
|
+
autoincrement: boolean;
|
|
39
|
+
}
|
|
40
|
+
/** 单表状态:列 + 行。 */
|
|
41
|
+
export interface SimTable {
|
|
42
|
+
columns: SimColumn[];
|
|
43
|
+
rows: Array<Record<string, unknown>>;
|
|
44
|
+
nextAutoincrement: number;
|
|
45
|
+
}
|
|
46
|
+
/** 持久层宿主接口(JSON 文件 / 内存)。 */
|
|
47
|
+
export interface EngineStorage {
|
|
48
|
+
load(): Promise<Record<string, SimTable> | null>;
|
|
49
|
+
save(tables: Record<string, SimTable>): Promise<void>;
|
|
50
|
+
}
|
|
51
|
+
/** 本地模拟 DB 统一错误(code 用平台码,供错误信封一致)。 */
|
|
52
|
+
export declare class SimDbError extends Error {
|
|
53
|
+
readonly code: string;
|
|
54
|
+
constructor(code: string, message: string);
|
|
55
|
+
}
|
|
56
|
+
export declare class SimSqlEngine {
|
|
57
|
+
private tables;
|
|
58
|
+
private storage;
|
|
59
|
+
private memoryOnly;
|
|
60
|
+
private savepointDepth;
|
|
61
|
+
private txSnapshot;
|
|
62
|
+
constructor(options?: {
|
|
63
|
+
storage?: EngineStorage;
|
|
64
|
+
memoryOnly?: boolean;
|
|
65
|
+
});
|
|
66
|
+
load(): Promise<void>;
|
|
67
|
+
private persist;
|
|
68
|
+
private ensureTable;
|
|
69
|
+
private applyAutoincrement;
|
|
70
|
+
private catalog;
|
|
71
|
+
run(sql: string, params?: readonly unknown[]): Promise<{
|
|
72
|
+
changes: number;
|
|
73
|
+
}>;
|
|
74
|
+
private execInsert;
|
|
75
|
+
private execUpdate;
|
|
76
|
+
private execDelete;
|
|
77
|
+
private select;
|
|
78
|
+
all(sql: string, params?: readonly unknown[]): Promise<Array<Record<string, unknown>>>;
|
|
79
|
+
get(sql: string, params?: readonly unknown[]): Promise<Record<string, unknown> | null>;
|
|
80
|
+
close(): Promise<void>;
|
|
81
|
+
}
|
|
82
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 纯 TS HMAC-SHA256(FN-023):跨 realm(浏览器 / Node / Bun)同形,零 node 依赖。
|
|
3
|
+
*
|
|
4
|
+
* 动因:`storage/signature.ts` 的签名 URL 算法原走 `node:crypto.createHmac`,浏览器侧
|
|
5
|
+
* 模拟运行时(`@adep/web-container` 的 sim storage)复用 `createStorageCapability` 时
|
|
6
|
+
* 打包直接失败。与其在浏览器再造一份「形状一致但算法不同」的签名,不如把 HMAC 本体
|
|
7
|
+
* 收敛为单一实现——所有 realm 跑同一份代码,签名逐字节可比(对拍测试钉住与
|
|
8
|
+
* `node:crypto` 的一致性,见 `__tests__/signature.test.ts`)。
|
|
9
|
+
*
|
|
10
|
+
* 实现是教科书式 FIPS 180-4 / RFC 2104:无 SIMD 优化、无流式 API——本模块的调用面只有
|
|
11
|
+
* 「一次签一个短 payload(项目 + 路径 + 过期时间)」,常数级正确性优先于大数据吞吐。
|
|
12
|
+
*/
|
|
13
|
+
/** SHA-256 摘要(32 字节)。输入一次性给全(本模块的调用面没有流式需求)。 */
|
|
14
|
+
export declare function sha256(data: Uint8Array): Uint8Array;
|
|
15
|
+
/** HMAC-SHA256(RFC 2104):key 超过块长先哈希,短 key 左侧补零。 */
|
|
16
|
+
export declare function hmacSha256(key: string | Uint8Array, message: string | Uint8Array): Uint8Array;
|
|
17
|
+
/** HMAC-SHA256 的小写 hex 摘要(签名 URL 的 `x-signature` 取此形态)。 */
|
|
18
|
+
export declare function hmacSha256Hex(key: string | Uint8Array, message: string | Uint8Array): string;
|
|
19
|
+
/**
|
|
20
|
+
* 恒时字符串比较(替代 `node:crypto.timingSafeEqual`):先比长度、再逐字符累加差值。
|
|
21
|
+
* 签名校验的侧信道面是「签名串本身」,该形态恒时性足够(比较对象是 hex 串,无密钥直接暴露)。
|
|
22
|
+
*/
|
|
23
|
+
export declare function constantTimeEqual(a: string, b: string): boolean;
|
package/package.json
CHANGED
|
@@ -1,44 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adep/runtime",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"description": "AgentDeploy 运行时内核:云函数执行器 + 本地模拟运行时 + cloud.db/cloud.storage 能力装配,零平台重栈依赖(无 elysia/drizzle/better-auth/nuxt/bun:sqlite/postgres/ioredis),供 server / CLI(adep dev) / 浏览器离线工作区三方复用。",
|
|
8
|
-
"main": "./
|
|
9
|
-
"types": "./
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
10
|
"exports": {
|
|
11
11
|
".": {
|
|
12
|
-
"types": "./
|
|
13
|
-
"default": "./
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
14
|
},
|
|
15
15
|
"./*": {
|
|
16
|
-
"types": "./
|
|
17
|
-
"default": "./
|
|
16
|
+
"types": "./dist/*.d.ts",
|
|
17
|
+
"default": "./dist/*.js"
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
20
|
"files": [
|
|
21
21
|
"dist"
|
|
22
22
|
],
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@adep/types": "0.1.1"
|
|
25
|
+
},
|
|
23
26
|
"scripts": {
|
|
24
27
|
"test": "vitest run",
|
|
25
28
|
"build": "bun run ../../scripts/build-package.ts runtime"
|
|
26
|
-
},
|
|
27
|
-
"dependencies": {
|
|
28
|
-
"@adep/types": "workspace:*"
|
|
29
|
-
},
|
|
30
|
-
"publishConfig": {
|
|
31
|
-
"main": "./dist/index.js",
|
|
32
|
-
"types": "./dist/index.d.ts",
|
|
33
|
-
"exports": {
|
|
34
|
-
".": {
|
|
35
|
-
"types": "./dist/index.d.ts",
|
|
36
|
-
"default": "./dist/index.js"
|
|
37
|
-
},
|
|
38
|
-
"./*": {
|
|
39
|
-
"types": "./dist/*.d.ts",
|
|
40
|
-
"default": "./dist/*.js"
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
29
|
}
|
|
44
|
-
}
|
|
30
|
+
}
|