@zeronsh/orbit 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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../server/src/pg.ts"],"names":[],"mappings":";;;AAgBO,SAAS,OAAO,EAAA,EAAgE;AACrF,EAAA,MAAM,IAAA,GACJ,OAAO,EAAA,KAAO,QAAA,GACV,IAAI,EAAA,CAAG,IAAA,CAAK,EAAE,gBAAA,EAAkB,EAAA,EAAI,IACpC,EAAA,YAAc,EAAA,CAAG,QAAQ,EAAA,YAAc,EAAA,CAAG,SACxC,EAAA,GACA,IAAI,EAAA,CAAG,IAAA,CAAK,EAAE,CAAA;AAEtB,EAAA,OAAO;AAAA,IACL,MAAM,YAAe,EAAA,EAAmD;AAEtE,MAAA,MAAM,SAAS,IAAA,YAAgB,EAAA,CAAG,OAAO,MAAM,IAAA,CAAK,SAAQ,GAAI,IAAA;AAChE,MAAA,MAAM,EAAA,GAAoB;AAAA,QACxB,MAAM,KAAA,CAAM,GAAA,EAAK,MAAA,EAAQ;AACvB,UAAA,MAAM,GAAA,GAAM,MAAM,MAAA,CAAO,KAAA,CAAM,KAAK,MAAmB,CAAA;AACvD,UAAA,OAAO,GAAA,CAAI,IAAA;AAAA,QACb;AAAA,OACF;AACA,MAAA,IAAI;AACF,QAAA,MAAM,MAAA,CAAO,MAAM,OAAO,CAAA;AAC1B,QAAA,MAAM,MAAA,GAAS,MAAM,EAAA,CAAG,EAAE,CAAA;AAC1B,QAAA,MAAM,MAAA,CAAO,MAAM,QAAQ,CAAA;AAC3B,QAAA,OAAO,MAAA;AAAA,MACT,SAAS,CAAA,EAAG;AACV,QAAA,IAAI;AACF,UAAA,MAAM,MAAA,CAAO,MAAM,UAAU,CAAA;AAAA,QAC/B,CAAA,CAAA,MAAQ;AAAA,QAER;AACA,QAAA,MAAM,CAAA;AAAA,MACR,CAAA,SAAE;AACA,QAAA,IAAI,IAAA,YAAgB,EAAA,CAAG,IAAA,EAAO,OAAyB,OAAA,EAAQ;AAAA,MACjE;AAAA,IACF;AAAA,GACF;AACF","file":"pg.js","sourcesContent":["// The built-in node-postgres adapter. A different backend (Drizzle, Kysely,\n// postgres.js, …) just implements `DBConnection` the same way — see the comment\n// at the bottom.\n\nimport pg from 'pg';\nimport type { DBConnection, DBTransaction } from './index.ts';\n\n/**\n * Wrap a node-postgres `Pool`/`Client` (or a connection string / config) as a\n * `DBConnection` for `PushProcessor`.\n *\n * ```ts\n * import { nodePg } from '@zeronsh/orbit/server/pg';\n * const connection = nodePg(process.env.DATABASE_URL!);\n * ```\n */\nexport function nodePg(db: pg.Pool | pg.Client | pg.PoolConfig | string): DBConnection {\n const pool: pg.Pool | pg.Client =\n typeof db === 'string'\n ? new pg.Pool({ connectionString: db })\n : db instanceof pg.Pool || db instanceof pg.Client\n ? db\n : new pg.Pool(db);\n\n return {\n async transaction<R>(fn: (tx: DBTransaction) => Promise<R>): Promise<R> {\n // Use a dedicated client for the transaction when pooled.\n const client = pool instanceof pg.Pool ? await pool.connect() : pool;\n const tx: DBTransaction = {\n async query(sql, params) {\n const res = await client.query(sql, params as unknown[]);\n return res.rows;\n },\n };\n try {\n await client.query('BEGIN');\n const result = await fn(tx);\n await client.query('COMMIT');\n return result;\n } catch (e) {\n try {\n await client.query('ROLLBACK');\n } catch {\n // ignore; original error is thrown\n }\n throw e;\n } finally {\n if (pool instanceof pg.Pool) (client as pg.PoolClient).release();\n }\n },\n };\n}\n\n// Adding another adapter is just this shape:\n//\n// export function drizzle(db: DrizzleDb): DBConnection {\n// return {\n// transaction: (fn) => db.transaction((dtx) =>\n// fn({ query: (sql, params) => dtx.execute(sql, params).then((r) => r.rows) })),\n// };\n// }\n"]}
@@ -0,0 +1,41 @@
1
+ import { C as CrudOp } from './query-BMK1cXAS.js';
2
+ import { S as SchemaDef } from './schema-BNM6bks7.js';
3
+ import { M as MutatorDef, Q as QueryDef } from './custom-DzMQ-nY2.js';
4
+
5
+ /** A transaction handle: run a parameterized SQL statement. */
6
+ interface DBTransaction {
7
+ query(sql: string, params: unknown[]): Promise<unknown[]>;
8
+ }
9
+ /** A database connection: run work inside a transaction. Implement this to add
10
+ * a new adapter (the built-in `@orbit/server/pg` adapter implements it). */
11
+ interface DBConnection {
12
+ transaction<R>(fn: (tx: DBTransaction) => Promise<R>): Promise<R>;
13
+ }
14
+ /** Derive the authenticated context from the forwarded request (return `null`
15
+ * to reject with 401). This is where auth lives. */
16
+ type ContextFn<Ctx> = (request: Request) => Ctx | null | Promise<Ctx | null>;
17
+ /** Apply one CRUD op as parameterized SQL via the adapter's `query`. The SQL is
18
+ * generated here (shared); the adapter only executes it. */
19
+ declare function applyCrudOp(tx: DBTransaction, op: CrudOp): Promise<void>;
20
+ /** Runs custom mutators forwarded to your push endpoint. One line per route:
21
+ * `POST: ({request}) => pushProcessor.process(mutators, request)`. */
22
+ declare class PushProcessor<Ctx = unknown> {
23
+ #private;
24
+ constructor(opts: {
25
+ connection: DBConnection;
26
+ schema: SchemaDef;
27
+ context: ContextFn<Ctx>;
28
+ });
29
+ process(mutators: Record<string, MutatorDef>, request: Request): Promise<Response>;
30
+ }
31
+ /** Resolves named queries forwarded to your query endpoint. One line per route:
32
+ * `POST: ({request}) => queryProcessor.process(queries, request)`. */
33
+ declare class QueryProcessor<Ctx = unknown> {
34
+ #private;
35
+ constructor(opts: {
36
+ context: ContextFn<Ctx>;
37
+ });
38
+ process(queries: Record<string, QueryDef>, request: Request): Promise<Response>;
39
+ }
40
+
41
+ export { type ContextFn, type DBConnection, type DBTransaction, PushProcessor, QueryProcessor, applyCrudOp };
package/dist/server.js ADDED
@@ -0,0 +1,81 @@
1
+ import { collectOps } from './chunk-N2NAKHMU.js';
2
+
3
+ // server/src/index.ts
4
+ var ident = (s) => '"' + s.replace(/"/g, '""') + '"';
5
+ async function applyCrudOp(tx, op) {
6
+ const value = op.value;
7
+ if (op.op === "insert" || op.op === "upsert") {
8
+ const cols = Object.keys(value);
9
+ const placeholders = cols.map((_, i) => `$${i + 1}`).join(", ");
10
+ let sql = `INSERT INTO ${ident(op.tableName)} (${cols.map(ident).join(", ")}) VALUES (${placeholders})`;
11
+ if (op.op === "upsert") {
12
+ const set = cols.filter((c) => !op.primaryKey.includes(c));
13
+ sql += ` ON CONFLICT (${op.primaryKey.map(ident).join(", ")}) DO ` + (set.length ? `UPDATE SET ${set.map((c) => `${ident(c)}=EXCLUDED.${ident(c)}`).join(", ")}` : "NOTHING");
14
+ }
15
+ await tx.query(sql, cols.map((c) => value[c]));
16
+ } else if (op.op === "update") {
17
+ const set = Object.keys(value).filter((c) => !op.primaryKey.includes(c));
18
+ const sql = `UPDATE ${ident(op.tableName)} SET ${set.map((c, i) => `${ident(c)}=$${i + 1}`).join(", ")} WHERE ${op.primaryKey.map((c, i) => `${ident(c)}=$${set.length + i + 1}`).join(" AND ")}`;
19
+ await tx.query(sql, [...set.map((c) => value[c]), ...op.primaryKey.map((c) => value[c])]);
20
+ } else if (op.op === "delete") {
21
+ const sql = `DELETE FROM ${ident(op.tableName)} WHERE ${op.primaryKey.map((c, i) => `${ident(c)}=$${i + 1}`).join(" AND ")}`;
22
+ await tx.query(sql, op.primaryKey.map((c) => value[c]));
23
+ }
24
+ }
25
+ var PushProcessor = class {
26
+ #connection;
27
+ #schema;
28
+ #context;
29
+ #schemaReady = false;
30
+ constructor(opts) {
31
+ this.#connection = opts.connection;
32
+ this.#schema = opts.schema;
33
+ this.#context = opts.context;
34
+ }
35
+ async process(mutators, request) {
36
+ const ctx = await this.#context(request);
37
+ if (ctx == null) return new Response("unauthorized", { status: 401 });
38
+ const body = await request.json();
39
+ await this.#connection.transaction(async (tx) => {
40
+ if (!this.#schemaReady) {
41
+ await tx.query(
42
+ "CREATE TABLE IF NOT EXISTS orbit_client_mutations (client_id text PRIMARY KEY, last_mutation_id bigint NOT NULL)",
43
+ []
44
+ );
45
+ this.#schemaReady = true;
46
+ }
47
+ for (const m of body.mutations ?? []) {
48
+ const def = mutators[m.name];
49
+ if (!def) continue;
50
+ const advanced = await tx.query(
51
+ "INSERT INTO orbit_client_mutations (client_id, last_mutation_id) VALUES ($1, $2) ON CONFLICT (client_id) DO UPDATE SET last_mutation_id = EXCLUDED.last_mutation_id WHERE orbit_client_mutations.last_mutation_id < EXCLUDED.last_mutation_id RETURNING client_id",
52
+ [m.clientID, m.id]
53
+ );
54
+ if (advanced.length === 0) continue;
55
+ for (const op of collectOps(this.#schema, def, m.args?.[0], ctx)) {
56
+ await applyCrudOp(tx, op);
57
+ }
58
+ }
59
+ });
60
+ return Response.json({});
61
+ }
62
+ };
63
+ var QueryProcessor = class {
64
+ #context;
65
+ constructor(opts) {
66
+ this.#context = opts.context;
67
+ }
68
+ async process(queries, request) {
69
+ const ctx = await this.#context(request);
70
+ if (ctx == null) return new Response("unauthorized", { status: 401 });
71
+ const body = await request.json();
72
+ const def = queries[body.name];
73
+ if (!def) return new Response("unknown query", { status: 404 });
74
+ const ast = def({ args: (body.args ?? [])[0], ctx }).ast();
75
+ return Response.json({ ast });
76
+ }
77
+ };
78
+
79
+ export { PushProcessor, QueryProcessor, applyCrudOp };
80
+ //# sourceMappingURL=server.js.map
81
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../server/src/index.ts"],"names":[],"mappings":"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","file":"server.js","sourcesContent":["// @orbit/server — server-side helpers for the endpoints orbit-cache forwards to\n// (push + query). Mirrors Zero's `@rocicorp/zero/server`: a `PushProcessor`\n// runs your custom mutators against a pluggable database `connection`, and a\n// `QueryProcessor` resolves named queries. The database is abstracted behind a\n// small `DBConnection` interface, so any adapter works (a `pg` one ships in\n// `@orbit/server/pg`; a Drizzle/Kysely/etc. adapter just implements the same\n// interface).\n\nimport { collectOps, type CrudOp, type MutatorDef, type QueryDef, type SchemaDef } from '../../client/src/index.ts';\n\n/** A transaction handle: run a parameterized SQL statement. */\nexport interface DBTransaction {\n query(sql: string, params: unknown[]): Promise<unknown[]>;\n}\n\n/** A database connection: run work inside a transaction. Implement this to add\n * a new adapter (the built-in `@orbit/server/pg` adapter implements it). */\nexport interface DBConnection {\n transaction<R>(fn: (tx: DBTransaction) => Promise<R>): Promise<R>;\n}\n\n/** Derive the authenticated context from the forwarded request (return `null`\n * to reject with 401). This is where auth lives. */\nexport type ContextFn<Ctx> = (request: Request) => Ctx | null | Promise<Ctx | null>;\n\nconst ident = (s: string) => '\"' + s.replace(/\"/g, '\"\"') + '\"';\n\n/** Apply one CRUD op as parameterized SQL via the adapter's `query`. The SQL is\n * generated here (shared); the adapter only executes it. */\nexport async function applyCrudOp(tx: DBTransaction, op: CrudOp): Promise<void> {\n const value = op.value as Record<string, unknown>;\n if (op.op === 'insert' || op.op === 'upsert') {\n const cols = Object.keys(value);\n const placeholders = cols.map((_, i) => `$${i + 1}`).join(', ');\n let sql = `INSERT INTO ${ident(op.tableName)} (${cols.map(ident).join(', ')}) VALUES (${placeholders})`;\n if (op.op === 'upsert') {\n const set = cols.filter((c) => !op.primaryKey.includes(c));\n sql +=\n ` ON CONFLICT (${op.primaryKey.map(ident).join(', ')}) DO ` +\n (set.length ? `UPDATE SET ${set.map((c) => `${ident(c)}=EXCLUDED.${ident(c)}`).join(', ')}` : 'NOTHING');\n }\n await tx.query(sql, cols.map((c) => value[c]));\n } else if (op.op === 'update') {\n const set = Object.keys(value).filter((c) => !op.primaryKey.includes(c));\n const sql =\n `UPDATE ${ident(op.tableName)} SET ${set.map((c, i) => `${ident(c)}=$${i + 1}`).join(', ')} ` +\n `WHERE ${op.primaryKey.map((c, i) => `${ident(c)}=$${set.length + i + 1}`).join(' AND ')}`;\n await tx.query(sql, [...set.map((c) => value[c]), ...op.primaryKey.map((c) => value[c])]);\n } else if (op.op === 'delete') {\n const sql = `DELETE FROM ${ident(op.tableName)} WHERE ${op.primaryKey.map((c, i) => `${ident(c)}=$${i + 1}`).join(' AND ')}`;\n await tx.query(sql, op.primaryKey.map((c) => value[c]));\n }\n}\n\n/** Runs custom mutators forwarded to your push endpoint. One line per route:\n * `POST: ({request}) => pushProcessor.process(mutators, request)`. */\nexport class PushProcessor<Ctx = unknown> {\n readonly #connection: DBConnection;\n readonly #schema: SchemaDef;\n readonly #context: ContextFn<Ctx>;\n #schemaReady = false;\n\n constructor(opts: { connection: DBConnection; schema: SchemaDef; context: ContextFn<Ctx> }) {\n this.#connection = opts.connection;\n this.#schema = opts.schema;\n this.#context = opts.context;\n }\n\n async process(mutators: Record<string, MutatorDef>, request: Request): Promise<Response> {\n const ctx = await this.#context(request);\n if (ctx == null) return new Response('unauthorized', { status: 401 });\n const body = await request.json();\n await this.#connection.transaction(async (tx) => {\n if (!this.#schemaReady) {\n await tx.query(\n 'CREATE TABLE IF NOT EXISTS orbit_client_mutations (' +\n 'client_id text PRIMARY KEY, last_mutation_id bigint NOT NULL)',\n [],\n );\n this.#schemaReady = true;\n }\n for (const m of (body.mutations ?? []) as { name: string; args?: unknown[]; id: number; clientID: string }[]) {\n const def = mutators[m.name];\n if (!def) continue;\n // Exactly-once: advance the client's lastMutationID, skipping mutations\n // already applied (a client re-sends unconfirmed mutations on reconnect —\n // to this or, in multinode, any other node). The WHERE guard makes the\n // advance + skip atomic in this transaction.\n const advanced = await tx.query(\n 'INSERT INTO orbit_client_mutations (client_id, last_mutation_id) VALUES ($1, $2) ' +\n 'ON CONFLICT (client_id) DO UPDATE SET last_mutation_id = EXCLUDED.last_mutation_id ' +\n 'WHERE orbit_client_mutations.last_mutation_id < EXCLUDED.last_mutation_id RETURNING client_id',\n [m.clientID, m.id],\n );\n if (advanced.length === 0) continue; // already applied — skip\n for (const op of collectOps(this.#schema, def, m.args?.[0], ctx)) {\n await applyCrudOp(tx, op);\n }\n }\n });\n return Response.json({});\n }\n}\n\n/** Resolves named queries forwarded to your query endpoint. One line per route:\n * `POST: ({request}) => queryProcessor.process(queries, request)`. */\nexport class QueryProcessor<Ctx = unknown> {\n readonly #context: ContextFn<Ctx>;\n\n constructor(opts: { context: ContextFn<Ctx> }) {\n this.#context = opts.context;\n }\n\n async process(queries: Record<string, QueryDef>, request: Request): Promise<Response> {\n const ctx = await this.#context(request);\n if (ctx == null) return new Response('unauthorized', { status: 401 });\n const body = await request.json();\n const def = queries[body.name];\n if (!def) return new Response('unknown query', { status: 404 });\n const ast = def({ args: (body.args ?? [])[0], ctx }).ast();\n return Response.json({ ast });\n }\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,96 @@
1
+ {
2
+ "name": "@zeronsh/orbit",
3
+ "version": "0.1.0",
4
+ "description": "Orbit — a Rust rebuild of Zero. Type-safe sync client, React bindings, server helpers, and Drizzle schema generation, all under one tree-shakeable package.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/zeronsh/orbit.git",
10
+ "directory": "packages/orbit"
11
+ },
12
+ "homepage": "https://github.com/zeronsh/orbit#readme",
13
+ "sideEffects": false,
14
+ "exports": {
15
+ "./client": {
16
+ "types": "./dist/client.d.ts",
17
+ "import": "./dist/client.js"
18
+ },
19
+ "./react": {
20
+ "types": "./dist/react.d.ts",
21
+ "import": "./dist/react.js"
22
+ },
23
+ "./server": {
24
+ "types": "./dist/server.d.ts",
25
+ "import": "./dist/server.js"
26
+ },
27
+ "./server/pg": {
28
+ "types": "./dist/server/pg.d.ts",
29
+ "import": "./dist/server/pg.js"
30
+ },
31
+ "./orm-core": {
32
+ "types": "./dist/orm-core.d.ts",
33
+ "import": "./dist/orm-core.js"
34
+ },
35
+ "./drizzle": {
36
+ "types": "./dist/drizzle.d.ts",
37
+ "import": "./dist/drizzle.js"
38
+ },
39
+ "./drizzle/cli": {
40
+ "types": "./dist/drizzle/cli.d.ts",
41
+ "import": "./dist/drizzle/cli.js"
42
+ },
43
+ "./package.json": "./package.json"
44
+ },
45
+ "bin": {
46
+ "orbit-drizzle": "./dist/drizzle/cli/bin.js"
47
+ },
48
+ "files": [
49
+ "dist",
50
+ "README.md"
51
+ ],
52
+ "dependencies": {
53
+ "commander": "^14.0.0"
54
+ },
55
+ "peerDependencies": {
56
+ "drizzle-orm": ">=0.36.0",
57
+ "pg": "^8",
58
+ "prettier": ">=3.0.0",
59
+ "react": ">=18",
60
+ "ts-morph": ">=24.0.0"
61
+ },
62
+ "peerDependenciesMeta": {
63
+ "drizzle-orm": {
64
+ "optional": true
65
+ },
66
+ "pg": {
67
+ "optional": true
68
+ },
69
+ "prettier": {
70
+ "optional": true
71
+ },
72
+ "react": {
73
+ "optional": true
74
+ },
75
+ "ts-morph": {
76
+ "optional": true
77
+ }
78
+ },
79
+ "devDependencies": {
80
+ "@types/node": "^22",
81
+ "@types/pg": "^8",
82
+ "@types/react": "^19",
83
+ "drizzle-orm": "1.0.0-rc.3",
84
+ "pg": "^8",
85
+ "react": "^19",
86
+ "react-test-renderer": "^19",
87
+ "ts-morph": "^27.0.0",
88
+ "tsup": "^8.5.0",
89
+ "typescript": "^5.7.0"
90
+ },
91
+ "scripts": {
92
+ "build": "tsup",
93
+ "typecheck": "tsc --noEmit",
94
+ "test": "node --test --experimental-strip-types"
95
+ }
96
+ }