@objy/server 0.0.0-stage → 0.1.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 (55) hide show
  1. package/API.md +1469 -0
  2. package/LICENSE +21 -0
  3. package/NOTICE.md +9 -0
  4. package/README.md +21 -2
  5. package/objydb.config.json +48 -0
  6. package/package.json +42 -4
  7. package/public/index.html +4134 -0
  8. package/public/objy-db-logo.png +0 -0
  9. package/src/aggregate.js +438 -0
  10. package/src/api.js +15 -0
  11. package/src/auth.js +472 -0
  12. package/src/clock.js +18 -0
  13. package/src/cluster-secret.js +25 -0
  14. package/src/cluster-status.js +562 -0
  15. package/src/compression-stats.js +32 -0
  16. package/src/config.js +360 -0
  17. package/src/crdt.js +1076 -0
  18. package/src/derived-state-scheduler.js +128 -0
  19. package/src/files.js +324 -0
  20. package/src/hnsw.js +269 -0
  21. package/src/http-cors.js +31 -0
  22. package/src/http-router.js +162 -0
  23. package/src/index.js +137 -0
  24. package/src/interfaces/auth.js +123 -0
  25. package/src/interfaces/cluster.js +296 -0
  26. package/src/interfaces/public-object.js +2452 -0
  27. package/src/interfaces/ui.js +38 -0
  28. package/src/lib/crypto.cjs +54 -0
  29. package/src/lib/jwt.cjs +231 -0
  30. package/src/local-wal.js +122 -0
  31. package/src/memory-guard.js +152 -0
  32. package/src/ndjson.js +117 -0
  33. package/src/node-runtime.js +29 -0
  34. package/src/object-hooks.js +282 -0
  35. package/src/object-id.js +7 -0
  36. package/src/object-storage.js +263 -0
  37. package/src/object-store.js +288 -0
  38. package/src/patch.js +87 -0
  39. package/src/permissions.js +178 -0
  40. package/src/persistence.js +1918 -0
  41. package/src/plugin-loader.js +21 -0
  42. package/src/query-planner.js +266 -0
  43. package/src/query.js +416 -0
  44. package/src/references.js +349 -0
  45. package/src/replication-scheduler.js +303 -0
  46. package/src/restore-status.js +77 -0
  47. package/src/runtime.js +47 -0
  48. package/src/server.js +242 -0
  49. package/src/shape.js +286 -0
  50. package/src/shard-map-store.js +85 -0
  51. package/src/shard-map.js +333 -0
  52. package/src/shard-router.js +737 -0
  53. package/src/store.js +3410 -0
  54. package/src/system-secrets.js +107 -0
  55. package/src/vector.js +83 -0
@@ -0,0 +1,107 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
3
+ import { dirname } from "node:path";
4
+
5
+ const DEFAULT_KEY = "system/secrets.json";
6
+ const DEFAULT_PREFIX = "objydb";
7
+
8
+ export async function resolveSystemSecrets({
9
+ jwtSecret,
10
+ clusterSecret,
11
+ storage,
12
+ prefix = DEFAULT_PREFIX,
13
+ localPath,
14
+ } = {}) {
15
+ const configured = {
16
+ jwtSecret: optionalSecret(jwtSecret),
17
+ clusterSecret: optionalSecret(clusterSecret),
18
+ };
19
+ if (configured.jwtSecret && configured.clusterSecret) return configured;
20
+
21
+ const store = storage
22
+ ? new ObjectSecretStore(storage, `${normalizePrefix(prefix)}/${DEFAULT_KEY}`)
23
+ : new FileSecretStore(localPath);
24
+ let stored = await store.load();
25
+ if (!stored) {
26
+ const candidate = {
27
+ version: 1,
28
+ jwtSecret: configured.jwtSecret || randomSecret(),
29
+ clusterSecret: configured.clusterSecret || randomSecret(),
30
+ createdAt: new Date().toISOString(),
31
+ };
32
+ await store.create(candidate);
33
+ stored = await store.load();
34
+ }
35
+
36
+ const resolved = {
37
+ jwtSecret: configured.jwtSecret || stored?.jwtSecret,
38
+ clusterSecret: configured.clusterSecret || stored?.clusterSecret,
39
+ };
40
+ if (!resolved.jwtSecret || !resolved.clusterSecret) {
41
+ throw new Error("stored OBJY DB secrets are invalid");
42
+ }
43
+ return resolved;
44
+ }
45
+
46
+ export function randomSecret(bytes = 32) {
47
+ return randomBytes(bytes).toString("base64url");
48
+ }
49
+
50
+ class ObjectSecretStore {
51
+ constructor(storage, key) {
52
+ this.storage = storage;
53
+ this.key = key;
54
+ }
55
+
56
+ async load() {
57
+ const raw = await this.storage.get(this.key);
58
+ return raw ? parseSecrets(raw) : null;
59
+ }
60
+
61
+ async create(value) {
62
+ return this.storage.putIfAbsent(this.key, JSON.stringify(value), "application/json");
63
+ }
64
+ }
65
+
66
+ class FileSecretStore {
67
+ constructor(path) {
68
+ if (!path) throw new Error("local system secret path is required");
69
+ this.path = path;
70
+ }
71
+
72
+ async load() {
73
+ try {
74
+ return parseSecrets(await readFile(this.path, "utf8"));
75
+ } catch (err) {
76
+ if (err.code === "ENOENT") return null;
77
+ throw err;
78
+ }
79
+ }
80
+
81
+ async create(value) {
82
+ await mkdir(dirname(this.path), { recursive: true, mode: 0o700 });
83
+ try {
84
+ await writeFile(this.path, `${JSON.stringify(value, null, 2)}\n`, { flag: "wx", mode: 0o600 });
85
+ return true;
86
+ } catch (err) {
87
+ if (err.code === "EEXIST") return false;
88
+ throw err;
89
+ }
90
+ }
91
+ }
92
+
93
+ function parseSecrets(raw) {
94
+ const value = JSON.parse(raw);
95
+ if (value?.version !== 1 || !optionalSecret(value.jwtSecret) || !optionalSecret(value.clusterSecret)) {
96
+ throw new Error("stored OBJY DB secrets are invalid");
97
+ }
98
+ return value;
99
+ }
100
+
101
+ function optionalSecret(value) {
102
+ return typeof value === "string" && value.length > 0 ? value : null;
103
+ }
104
+
105
+ function normalizePrefix(value) {
106
+ return String(value || "").replace(/^\/+|\/+$/g, "");
107
+ }
package/src/vector.js ADDED
@@ -0,0 +1,83 @@
1
+ export const DEFAULT_VECTOR_FIELD = "_vec";
2
+
3
+ export class VectorIndexWarmingError extends Error {
4
+ constructor({ database, collection, indexKey = "all", indexStatus = "warming" } = {}) {
5
+ super("vector index warming");
6
+ this.name = "VectorIndexWarmingError";
7
+ this.code = "VECTOR_INDEX_WARMING";
8
+ this.status = 503;
9
+ this.body = {
10
+ error: "vector index warming",
11
+ ...(database ? { database } : {}),
12
+ ...(collection ? { collection } : {}),
13
+ indexKey,
14
+ indexStatus,
15
+ };
16
+ }
17
+ }
18
+
19
+ export function isValidVector(value) {
20
+ return Array.isArray(value) &&
21
+ value.length > 0 &&
22
+ value.every((entry) => typeof entry === "number" && Number.isFinite(entry));
23
+ }
24
+
25
+ export function cloneVector(value) {
26
+ return isValidVector(value) ? [...value] : null;
27
+ }
28
+
29
+ export function cosine(a, b) {
30
+ if (!isValidVector(a) || !isValidVector(b) || a.length !== b.length) return 0;
31
+ let dot = 0;
32
+ let na = 0;
33
+ let nb = 0;
34
+ for (let i = 0; i < a.length; i++) {
35
+ dot += a[i] * b[i];
36
+ na += a[i] * a[i];
37
+ nb += b[i] * b[i];
38
+ }
39
+ if (na === 0 || nb === 0) return 0;
40
+ return dot / (Math.sqrt(na) * Math.sqrt(nb));
41
+ }
42
+
43
+ export function rankBySimilarity(rows, queryVec, {
44
+ limit = 20,
45
+ getVec,
46
+ idOf = (row) => row?.id,
47
+ } = {}) {
48
+ if (!isValidVector(queryVec) || typeof getVec !== "function") return [];
49
+ const scored = [];
50
+ for (const row of rows || []) {
51
+ const id = idOf(row);
52
+ if (id === undefined || id === null) continue;
53
+ const vec = getVec(String(id));
54
+ if (!isValidVector(vec) || vec.length !== queryVec.length) continue;
55
+ scored.push({ row, score: cosine(queryVec, vec) });
56
+ }
57
+ scored.sort((left, right) => right.score - left.score);
58
+ return scored.slice(0, Math.max(0, limit)).map(({ row, score }) => ({ ...row, _score: score }));
59
+ }
60
+
61
+ export function normalizeVectorConfig(config = {}) {
62
+ if (config === false || config.enabled === false) {
63
+ return { enabled: false, field: null, hnsw: normalizeHnswConfig({}) };
64
+ }
65
+ return {
66
+ enabled: true,
67
+ field: typeof config.field === "string" && config.field.length > 0 ? config.field : DEFAULT_VECTOR_FIELD,
68
+ hnsw: normalizeHnswConfig(config.hnsw),
69
+ };
70
+ }
71
+
72
+ function normalizeHnswConfig(config = {}) {
73
+ return {
74
+ M: positiveInt(config.M, 16),
75
+ efConstruction: positiveInt(config.efConstruction, 200),
76
+ efSearch: positiveInt(config.efSearch, 0),
77
+ };
78
+ }
79
+
80
+ function positiveInt(value, fallback) {
81
+ const n = Number(value);
82
+ return Number.isInteger(n) && n > 0 ? n : fallback;
83
+ }