@synap-core/sdk 0.2.0 → 1.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 (48) hide show
  1. package/README.md +61 -135
  2. package/dist/index.cjs +115 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +80 -0
  5. package/dist/index.d.ts +72 -47
  6. package/dist/index.js +80 -47
  7. package/dist/index.js.map +1 -1
  8. package/package.json +39 -28
  9. package/src/client.ts +193 -0
  10. package/src/index.ts +2 -0
  11. package/src/public.ts +262 -0
  12. package/dist/client.d.ts +0 -105
  13. package/dist/client.d.ts.map +0 -1
  14. package/dist/client.js +0 -95
  15. package/dist/client.js.map +0 -1
  16. package/dist/index.d.ts.map +0 -1
  17. package/dist/resources/entities.d.ts +0 -90
  18. package/dist/resources/entities.d.ts.map +0 -1
  19. package/dist/resources/entities.js +0 -126
  20. package/dist/resources/entities.js.map +0 -1
  21. package/dist/resources/events.d.ts +0 -51
  22. package/dist/resources/events.d.ts.map +0 -1
  23. package/dist/resources/events.js +0 -92
  24. package/dist/resources/events.js.map +0 -1
  25. package/dist/resources/preferences.d.ts +0 -24
  26. package/dist/resources/preferences.d.ts.map +0 -1
  27. package/dist/resources/preferences.js +0 -30
  28. package/dist/resources/preferences.js.map +0 -1
  29. package/dist/resources/relations.d.ts +0 -71
  30. package/dist/resources/relations.d.ts.map +0 -1
  31. package/dist/resources/relations.js +0 -121
  32. package/dist/resources/relations.js.map +0 -1
  33. package/dist/resources/views.d.ts +0 -43
  34. package/dist/resources/views.d.ts.map +0 -1
  35. package/dist/resources/views.js +0 -54
  36. package/dist/resources/views.js.map +0 -1
  37. package/dist/resources/workspaces.d.ts +0 -50
  38. package/dist/resources/workspaces.d.ts.map +0 -1
  39. package/dist/resources/workspaces.js +0 -66
  40. package/dist/resources/workspaces.js.map +0 -1
  41. package/dist/types/events.d.ts +0 -24
  42. package/dist/types/events.d.ts.map +0 -1
  43. package/dist/types/events.js +0 -5
  44. package/dist/types/events.js.map +0 -1
  45. package/dist/types/relations.d.ts +0 -26
  46. package/dist/types/relations.d.ts.map +0 -1
  47. package/dist/types/relations.js +0 -5
  48. package/dist/types/relations.js.map +0 -1
package/README.md CHANGED
@@ -1,173 +1,99 @@
1
- # @synap/sdk
1
+ # @synap-core/sdk
2
2
 
3
- High-level SDK for Synap - Event-sourced personal data platform
3
+ Type-safe tRPC client for [Synap](https://synap.live) data pods. Connect any JavaScript/TypeScript app to your personal data pod with full autocomplete on all 47+ routers.
4
4
 
5
- ## Installation
5
+ ## Install
6
6
 
7
7
  ```bash
8
- pnpm add @synap/sdk
8
+ npm install @synap-core/sdk
9
+ # or
10
+ pnpm add @synap-core/sdk
9
11
  ```
10
12
 
11
- ## Quick Start
13
+ ## Quick start
12
14
 
13
15
  ```typescript
14
- import { SynapSDK } from '@synap/sdk';
16
+ import { createSynapClient } from "@synap-core/sdk";
15
17
 
16
- // Initialize SDK
17
- const synap = new SynapSDK({
18
- url: 'https://api.synap.app',
19
- apiKey: 'your-api-key'
18
+ const synap = createSynapClient({
19
+ podUrl: "https://your-pod.synap.live", // your data pod URL
20
+ apiKey: "sk_live_...", // from Workspace → Settings → API Keys
21
+ workspaceId: "ws_...", // your workspace ID
20
22
  });
21
23
 
22
- // Create entity (event-sourced)
23
- const { entityId } = await synap.entities.create({
24
- type: 'task',
25
- title: 'Call Marie tomorrow',
26
- metadata: {
27
- dueDate: '2024-12-20',
28
- priority: 'high'
29
- }
30
- });
31
-
32
- // Create relationship (event-sourced)
33
- await synap.relations.create(entityId, personId, 'assigned_to');
24
+ // Fully typed — autocomplete works on all procedures
25
+ const entities = await synap.entities.list.query({ limit: 20 });
26
+ const workspace = await synap.workspaces.get.query();
34
27
 
35
- // Query data (direct reads)
36
- const tasks = await synap.entities.list({ type: 'task' });
37
- const history = await synap.events.getHistory(entityId);
28
+ // Create an entity
29
+ await synap.entities.create.mutate({
30
+ profileSlug: "note",
31
+ title: "My first note",
32
+ properties: { content: "Hello from the SDK!" },
33
+ });
38
34
  ```
39
35
 
40
- ## Architecture
41
-
42
- ### Event Sourcing
43
-
44
- All **mutations** go through event sourcing:
45
- - `entities.create/update/delete` → `events.log` → Inngest worker → Database
46
- - `relations.create/delete` → `events.log` → Inngest worker → Database
47
-
48
- All **queries** are direct database reads:
49
- - `entities.get/list/search` → Direct query
50
- - `relations.get/getRelated` → Direct query
51
- - `events.getHistory` → Direct query
52
-
53
- ### Benefits
54
-
55
- - **Audit trail**: Every change logged as immutable event
56
- - **Time travel**: Replay events to see past states
57
- - **Reliability**: Workers handle async processing with retries
58
- - **Real-time**: Events trigger webhooks and SSE broadcasts
36
+ ## Authentication
59
37
 
60
- ## API Reference
61
-
62
- ### Entities API
38
+ **API key (server-side / external apps)**
63
39
 
64
40
  ```typescript
65
- // Create
66
- const { entityId } = await synap.entities.create({
67
- type: 'note',
68
- title: 'Meeting Notes',
69
- content: '# Discussion points...'
70
- });
71
-
72
- // Update
73
- await synap.entities.update(entityId, {
74
- title: 'Updated Title'
75
- });
76
-
77
- // Delete (soft delete)
78
- await synap.entities.delete(entityId);
79
-
80
- // Get
81
- const entity = await synap.entities.get(entityId);
82
-
83
- // List
84
- const notes = await synap.entities.list({
85
- type: 'note',
86
- limit: 20
87
- });
88
-
89
- // Search
90
- const results = await synap.entities.search({
91
- query: 'project planning',
92
- types: ['note', 'task']
41
+ const synap = createSynapClient({
42
+ podUrl: "https://your-pod.synap.live",
43
+ apiKey: "sk_live_...", // Bearer token — generate in workspace settings
44
+ workspaceId: "ws_...",
93
45
  });
94
46
  ```
95
47
 
96
- ### Relations API
48
+ **Session cookie (browser — after Kratos login)**
97
49
 
98
50
  ```typescript
99
- // Create relationship
100
- await synap.relations.create(
101
- sourceEntityId,
102
- targetEntityId,
103
- 'assigned_to'
104
- );
105
-
106
- // Get relations
107
- const relations = await synap.relations.get(entityId, {
108
- type: 'assigned_to',
109
- direction: 'both' // 'source' | 'target' | 'both'
51
+ const synap = createSynapClient({
52
+ podUrl: "https://your-pod.synap.live",
53
+ workspaceId: "ws_...",
54
+ // omit apiKey — credentials: "include" is set automatically
110
55
  });
56
+ ```
111
57
 
112
- // Get related entities
113
- const people = await synap.relations.getRelated(taskId, {
114
- type: 'assigned_to',
115
- direction: 'source'
116
- });
58
+ ## Type helpers
117
59
 
118
- // Get statistics
119
- const stats = await synap.relations.getStats(entityId);
120
- // Returns: { total, outgoing, incoming, byType }
60
+ ```typescript
61
+ import type { RouterInputs, RouterOutputs } from "@synap-core/sdk";
121
62
 
122
- // Delete relationship
123
- await synap.relations.delete(relationId);
63
+ type EntityListInput = RouterInputs["entities"]["list"];
64
+ type EntityListOutput = RouterOutputs["entities"]["list"];
124
65
  ```
125
66
 
126
- ### Events API
67
+ ## Public doors (no credentials): `@synap-core/sdk/public`
68
+
69
+ A tiny, dependency-free fetch client for the pod's credentialless public doors
70
+ (published shares, guest forms). It never sends credentials (`credentials: 'omit'`,
71
+ no Authorization/Cookie) and works from any origin, so it is safe in a public site.
127
72
 
128
73
  ```typescript
129
- // Get entity history
130
- const history = await synap.events.getHistory(entityId, {
131
- eventTypes: ['entities.create.validated'],
132
- limit: 100
133
- });
74
+ import { createPublicClient } from "@synap-core/sdk/public";
134
75
 
135
- // Get user timeline
136
- const timeline = await synap.events.getTimeline({
137
- limit: 50
138
- });
76
+ const pub = createPublicClient({ podUrl: "https://your-pod.synap.live" });
139
77
 
140
- // Replay events to rebuild state
141
- const state = await synap.events.replay(entityId);
78
+ const share = await pub.readShare(token); // { ok: true, data } | { ok: false, error }
79
+ const form = await pub.getForm(formToken); // fields, honeypotField, ticket, captcha
80
+ const sent = await pub.submitForm(formToken, {
81
+ fields: { email: "a@b.co" },
82
+ ticket: form.ok ? form.data.ticket : undefined,
83
+ captchaToken, // when form.data.captcha.required
84
+ });
142
85
  ```
143
86
 
144
- ## Entity Types
87
+ Errors are typed: `not_found` (the uniform 404: unknown, revoked and expired are
88
+ indistinguishable), `rate_limited`, `network`, `server`. A submit always answers
89
+ `{ ok: true, received: true }` on the pod's constant 202; the pod silently drops
90
+ too-fast, honeypot, captcha-failed and over-cap submissions, so wait out the form's
91
+ minimum time before submitting. Pass `fetch` to inject your own.
145
92
 
146
- ```typescript
147
- type EntityType =
148
- | 'task'
149
- | 'note'
150
- | 'person'
151
- | 'event'
152
- | 'file';
153
- ```
93
+ ## React
154
94
 
155
- ## Relation Types
156
-
157
- ```typescript
158
- type RelationType =
159
- | 'assigned_to' // Task → Person
160
- | 'mentions' // Note → Entity
161
- | 'links_to' // Note → Note
162
- | 'parent_of' // Project → Task
163
- | 'relates_to' // Generic
164
- | 'tagged_with' // Entity → Tag
165
- | 'created_by' // Entity → Person
166
- | 'attended_by' // Event → Person
167
- | 'depends_on' // Task → Task
168
- | 'blocks'; // Task → Task
169
- ```
95
+ For React apps use [`@synap-core/react`](https://www.npmjs.com/package/@synap-core/react) which wraps this SDK with TanStack Query hooks.
170
96
 
171
- ## License
97
+ ## API reference
172
98
 
173
- AGPL-3.0
99
+ Full procedure reference: [docs.synap.live/reference/api-reference](https://docs.synap.live/reference/api-reference)
package/dist/index.cjs ADDED
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ createSynapClient: () => createSynapClient
34
+ });
35
+ module.exports = __toCommonJS(index_exports);
36
+
37
+ // src/client.ts
38
+ var import_client = require("@trpc/client");
39
+ var import_superjson = __toESM(require("superjson"), 1);
40
+ function createSynapClient(options) {
41
+ const { podUrl, apiKey, workspaceId, extraHeaders, onAuthError } = options;
42
+ const url = `${podUrl.replace(/\/$/, "")}/trpc`;
43
+ const baseFetch = (input, init) => fetch(input, { ...init, credentials: "include" });
44
+ const wrappedFetch = onAuthError ? async (input, init) => {
45
+ const response = await baseFetch(input, init);
46
+ if (response.ok) return response;
47
+ const cloned = response.clone();
48
+ let body = "";
49
+ try {
50
+ body = await cloned.text();
51
+ } catch {
52
+ body = "";
53
+ }
54
+ let isTrpcFormat = false;
55
+ try {
56
+ isTrpcFormat = Array.isArray(JSON.parse(body));
57
+ } catch {
58
+ isTrpcFormat = false;
59
+ }
60
+ if (isTrpcFormat) return response;
61
+ const parsedMessage = (() => {
62
+ try {
63
+ const parsed = JSON.parse(body);
64
+ return parsed.error ?? parsed.message ?? null;
65
+ } catch {
66
+ return null;
67
+ }
68
+ })();
69
+ const message = response.status === 401 ? "Session expired. Please sign in again." : `Request failed (${response.status}).`;
70
+ if (response.status === 401) {
71
+ onAuthError({ status: 401, message });
72
+ }
73
+ const trpcError = [
74
+ {
75
+ error: {
76
+ message,
77
+ code: response.status === 401 ? -32001 : -32e3,
78
+ data: {
79
+ code: response.status === 401 ? "UNAUTHORIZED" : "INTERNAL_SERVER_ERROR",
80
+ httpStatus: response.status,
81
+ // Server-supplied detail — untrusted; consumers must escape it
82
+ // before rendering (never inject as HTML).
83
+ detail: parsedMessage ?? void 0
84
+ }
85
+ }
86
+ }
87
+ ];
88
+ return new Response(JSON.stringify(trpcError), {
89
+ status: response.status,
90
+ headers: { "content-type": "application/json" }
91
+ });
92
+ } : baseFetch;
93
+ return (0, import_client.createTRPCClient)({
94
+ links: [
95
+ (0, import_client.httpBatchLink)({
96
+ url,
97
+ transformer: import_superjson.default,
98
+ fetch: wrappedFetch,
99
+ headers: () => ({
100
+ // Session token mode (Kratos, preferred for non-browser clients)
101
+ ...options.sessionToken ? { "x-session-token": options.sessionToken } : {},
102
+ // API key mode (Bearer, for legacy SDK usage)
103
+ ...apiKey && !options.sessionToken ? { Authorization: `Bearer ${apiKey}` } : {},
104
+ ...workspaceId ? { "x-workspace-id": workspaceId } : {},
105
+ ...extraHeaders
106
+ })
107
+ })
108
+ ]
109
+ });
110
+ }
111
+ // Annotate the CommonJS export names for ESM import in node:
112
+ 0 && (module.exports = {
113
+ createSynapClient
114
+ });
115
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/client.ts"],"sourcesContent":["export { createSynapClient } from \"./client\";\nexport type { SynapClient, SynapClientOptions, AppRouter } from \"./client\";\n","/**\n * @synap-core/sdk — Type-safe tRPC client for Synap data pods.\n *\n * Usage:\n * ```typescript\n * import { createSynapClient } from \"@synap-core/sdk\";\n *\n * const synap = createSynapClient({\n * podUrl: \"https://pod.example.com\",\n * apiKey: \"sk_live_...\",\n * workspaceId: \"ws_...\",\n * });\n *\n * // Fully typed — autocomplete works on all 47+ routers\n * const entities = await synap.entities.list.query({ limit: 20 });\n * const workspace = await synap.workspaces.get.query();\n * ```\n */\n\nimport {\n createTRPCClient,\n httpBatchLink,\n type CreateTRPCClient,\n} from \"@trpc/client\";\nimport superjson from \"superjson\";\nimport type { AppRouter } from \"@synap-core/api-types\";\n\nexport interface SynapClientOptions {\n /**\n * Base URL of the data pod (no trailing slash, no `/trpc` suffix).\n * @example \"https://pod.example.com\"\n */\n podUrl: string;\n\n /**\n * Ory Kratos session token (for example, from the Pod's\n * `/api/federation/exchange` endpoint).\n * Sent as `X-Session-Token` header — preferred for non-browser clients\n * like extensions, CLI tools, and mobile apps.\n * @example \"oathkeeper_token_...\"\n */\n sessionToken?: string;\n\n /**\n * API key for authentication (Bearer token).\n * Generate one in your workspace developer settings.\n * If omitted, the client falls back to cookie-based Kratos session auth.\n * @example \"sk_live_abc123...\"\n */\n apiKey?: string;\n\n /**\n * Workspace ID to scope all requests to.\n * Pass empty string for procedures that don't require workspace context\n * (e.g. workspaces.list, which is protectedProcedure only).\n * @example \"ws_01jk...\"\n */\n workspaceId: string;\n\n /**\n * Additional headers to include in every request.\n */\n extraHeaders?: Record<string, string>;\n\n /**\n * Called when the pod returns a `401 Unauthorized` (expired/invalid session\n * or API key). The app should sign out and re-authenticate (re-handshake).\n *\n * The pod's auth middleware can reply with a plain-JSON body\n * (`{\"error\":\"Invalid session\"}`) that is NOT in tRPC batch format and would\n * otherwise crash the superjson transformer; when an `onAuthError` handler is\n * provided the client rewrites such responses into a well-formed tRPC error so\n * the query rejects gracefully instead of throwing a parse error.\n */\n onAuthError?: (error: { status: number; message: string }) => void;\n}\n\n/**\n * Creates a fully-typed tRPC client connected to a Synap data pod.\n *\n * All 47+ tRPC routers are available with full TypeScript autocomplete\n * and compile-time safety. Auth is handled via Bearer token + workspace header.\n */\nexport function createSynapClient(\n options: SynapClientOptions,\n): CreateTRPCClient<AppRouter> {\n const { podUrl, apiKey, workspaceId, extraHeaders, onAuthError } = options;\n\n const url = `${podUrl.replace(/\\/$/, \"\")}/trpc`;\n\n // Base fetch: always include credentials so session cookies are sent in\n // cookie-auth mode. When apiKey is present, the Bearer token takes precedence\n // on the pod.\n const baseFetch: typeof fetch = (input, init) =>\n fetch(input, { ...init, credentials: \"include\" });\n\n // When an onAuthError handler is provided, wrap fetch to catch 401 (and other\n // non-tRPC error) responses. The pod's auth middleware replies with a plain\n // JSON body that is not a tRPC batch array; left untouched it crashes the\n // superjson transformer. We notify the handler and rewrite the body into a\n // valid tRPC error envelope so the query rejects cleanly.\n const wrappedFetch: typeof fetch = onAuthError\n ? async (input, init) => {\n const response = await baseFetch(input, init);\n if (response.ok) return response;\n\n const cloned = response.clone();\n let body = \"\";\n try {\n body = await cloned.text();\n } catch {\n body = \"\";\n }\n\n let isTrpcFormat = false;\n try {\n isTrpcFormat = Array.isArray(JSON.parse(body));\n } catch {\n isTrpcFormat = false;\n }\n if (isTrpcFormat) return response;\n\n const parsedMessage = (() => {\n try {\n const parsed = JSON.parse(body) as {\n error?: string;\n message?: string;\n };\n return parsed.error ?? parsed.message ?? null;\n } catch {\n return null;\n }\n })();\n // Never forward a server-controlled string as user-facing copy — map by\n // status. The raw server detail is preserved (untrusted) on `data.detail`.\n const message =\n response.status === 401\n ? \"Session expired. Please sign in again.\"\n : `Request failed (${response.status}).`;\n\n if (response.status === 401) {\n onAuthError({ status: 401, message });\n }\n\n const trpcError = [\n {\n error: {\n message,\n code: response.status === 401 ? -32001 : -32000,\n data: {\n code:\n response.status === 401\n ? \"UNAUTHORIZED\"\n : \"INTERNAL_SERVER_ERROR\",\n httpStatus: response.status,\n // Server-supplied detail — untrusted; consumers must escape it\n // before rendering (never inject as HTML).\n detail: parsedMessage ?? undefined,\n },\n\n },\n },\n ];\n return new Response(JSON.stringify(trpcError), {\n status: response.status,\n headers: { \"content-type\": \"application/json\" },\n });\n }\n : baseFetch;\n\n return createTRPCClient<AppRouter>({\n links: [\n httpBatchLink({\n url,\n transformer: superjson,\n fetch: wrappedFetch,\n headers: () => ({\n // Session token mode (Kratos, preferred for non-browser clients)\n ...(options.sessionToken ? { \"x-session-token\": options.sessionToken } : {}),\n // API key mode (Bearer, for legacy SDK usage)\n ...(apiKey && !options.sessionToken ? { Authorization: `Bearer ${apiKey}` } : {}),\n ...(workspaceId ? { \"x-workspace-id\": workspaceId } : {}),\n ...extraHeaders,\n }),\n }),\n ],\n });\n}\n\nexport type SynapClient = ReturnType<typeof createSynapClient>;\n\n// Re-export type helpers so consumers don't need @trpc/server\nexport type { AppRouter };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACmBA,oBAIO;AACP,uBAAsB;AA2Df,SAAS,kBACd,SAC6B;AAC7B,QAAM,EAAE,QAAQ,QAAQ,aAAa,cAAc,YAAY,IAAI;AAEnE,QAAM,MAAM,GAAG,OAAO,QAAQ,OAAO,EAAE,CAAC;AAKxC,QAAM,YAA0B,CAAC,OAAO,SACtC,MAAM,OAAO,EAAE,GAAG,MAAM,aAAa,UAAU,CAAC;AAOlD,QAAM,eAA6B,cAC/B,OAAO,OAAO,SAAS;AACrB,UAAM,WAAW,MAAM,UAAU,OAAO,IAAI;AAC5C,QAAI,SAAS,GAAI,QAAO;AAExB,UAAM,SAAS,SAAS,MAAM;AAC9B,QAAI,OAAO;AACX,QAAI;AACF,aAAO,MAAM,OAAO,KAAK;AAAA,IAC3B,QAAQ;AACN,aAAO;AAAA,IACT;AAEA,QAAI,eAAe;AACnB,QAAI;AACF,qBAAe,MAAM,QAAQ,KAAK,MAAM,IAAI,CAAC;AAAA,IAC/C,QAAQ;AACN,qBAAe;AAAA,IACjB;AACA,QAAI,aAAc,QAAO;AAEzB,UAAM,iBAAiB,MAAM;AAC3B,UAAI;AACF,cAAM,SAAS,KAAK,MAAM,IAAI;AAI9B,eAAO,OAAO,SAAS,OAAO,WAAW;AAAA,MAC3C,QAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF,GAAG;AAGH,UAAM,UACJ,SAAS,WAAW,MAChB,2CACA,mBAAmB,SAAS,MAAM;AAExC,QAAI,SAAS,WAAW,KAAK;AAC3B,kBAAY,EAAE,QAAQ,KAAK,QAAQ,CAAC;AAAA,IACtC;AAEA,UAAM,YAAY;AAAA,MAChB;AAAA,QACE,OAAO;AAAA,UACL;AAAA,UACA,MAAM,SAAS,WAAW,MAAM,SAAS;AAAA,UACzC,MAAM;AAAA,YACJ,MACE,SAAS,WAAW,MAChB,iBACA;AAAA,YACN,YAAY,SAAS;AAAA;AAAA;AAAA,YAGrB,QAAQ,iBAAiB;AAAA,UAC3B;AAAA,QAEF;AAAA,MACF;AAAA,IACF;AACA,WAAO,IAAI,SAAS,KAAK,UAAU,SAAS,GAAG;AAAA,MAC7C,QAAQ,SAAS;AAAA,MACjB,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAChD,CAAC;AAAA,EACH,IACA;AAEJ,aAAO,gCAA4B;AAAA,IACjC,OAAO;AAAA,UACL,6BAAc;AAAA,QACZ;AAAA,QACA,aAAa,iBAAAA;AAAA,QACb,OAAO;AAAA,QACP,SAAS,OAAO;AAAA;AAAA,UAEd,GAAI,QAAQ,eAAe,EAAE,mBAAmB,QAAQ,aAAa,IAAI,CAAC;AAAA;AAAA,UAE1E,GAAI,UAAU,CAAC,QAAQ,eAAe,EAAE,eAAe,UAAU,MAAM,GAAG,IAAI,CAAC;AAAA,UAC/E,GAAI,cAAc,EAAE,kBAAkB,YAAY,IAAI,CAAC;AAAA,UACvD,GAAG;AAAA,QACL;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF,CAAC;AACH;","names":["superjson"]}
@@ -0,0 +1,80 @@
1
+ import { CreateTRPCClient } from '@trpc/client';
2
+ import { AppRouter } from '@synap-core/api-types';
3
+ export { AppRouter } from '@synap-core/api-types';
4
+
5
+ /**
6
+ * @synap-core/sdk — Type-safe tRPC client for Synap data pods.
7
+ *
8
+ * Usage:
9
+ * ```typescript
10
+ * import { createSynapClient } from "@synap-core/sdk";
11
+ *
12
+ * const synap = createSynapClient({
13
+ * podUrl: "https://pod.example.com",
14
+ * apiKey: "sk_live_...",
15
+ * workspaceId: "ws_...",
16
+ * });
17
+ *
18
+ * // Fully typed — autocomplete works on all 47+ routers
19
+ * const entities = await synap.entities.list.query({ limit: 20 });
20
+ * const workspace = await synap.workspaces.get.query();
21
+ * ```
22
+ */
23
+
24
+ interface SynapClientOptions {
25
+ /**
26
+ * Base URL of the data pod (no trailing slash, no `/trpc` suffix).
27
+ * @example "https://pod.example.com"
28
+ */
29
+ podUrl: string;
30
+ /**
31
+ * Ory Kratos session token (for example, from the Pod's
32
+ * `/api/federation/exchange` endpoint).
33
+ * Sent as `X-Session-Token` header — preferred for non-browser clients
34
+ * like extensions, CLI tools, and mobile apps.
35
+ * @example "oathkeeper_token_..."
36
+ */
37
+ sessionToken?: string;
38
+ /**
39
+ * API key for authentication (Bearer token).
40
+ * Generate one in your workspace developer settings.
41
+ * If omitted, the client falls back to cookie-based Kratos session auth.
42
+ * @example "sk_live_abc123..."
43
+ */
44
+ apiKey?: string;
45
+ /**
46
+ * Workspace ID to scope all requests to.
47
+ * Pass empty string for procedures that don't require workspace context
48
+ * (e.g. workspaces.list, which is protectedProcedure only).
49
+ * @example "ws_01jk..."
50
+ */
51
+ workspaceId: string;
52
+ /**
53
+ * Additional headers to include in every request.
54
+ */
55
+ extraHeaders?: Record<string, string>;
56
+ /**
57
+ * Called when the pod returns a `401 Unauthorized` (expired/invalid session
58
+ * or API key). The app should sign out and re-authenticate (re-handshake).
59
+ *
60
+ * The pod's auth middleware can reply with a plain-JSON body
61
+ * (`{"error":"Invalid session"}`) that is NOT in tRPC batch format and would
62
+ * otherwise crash the superjson transformer; when an `onAuthError` handler is
63
+ * provided the client rewrites such responses into a well-formed tRPC error so
64
+ * the query rejects gracefully instead of throwing a parse error.
65
+ */
66
+ onAuthError?: (error: {
67
+ status: number;
68
+ message: string;
69
+ }) => void;
70
+ }
71
+ /**
72
+ * Creates a fully-typed tRPC client connected to a Synap data pod.
73
+ *
74
+ * All 47+ tRPC routers are available with full TypeScript autocomplete
75
+ * and compile-time safety. Auth is handled via Bearer token + workspace header.
76
+ */
77
+ declare function createSynapClient(options: SynapClientOptions): CreateTRPCClient<AppRouter>;
78
+ type SynapClient = ReturnType<typeof createSynapClient>;
79
+
80
+ export { type SynapClient, type SynapClientOptions, createSynapClient };
package/dist/index.d.ts CHANGED
@@ -1,55 +1,80 @@
1
+ import { CreateTRPCClient } from '@trpc/client';
2
+ import { AppRouter } from '@synap-core/api-types';
3
+ export { AppRouter } from '@synap-core/api-types';
4
+
1
5
  /**
2
- * @synap/sdk - High-Level SDK for Synap
6
+ * @synap-core/sdk — Type-safe tRPC client for Synap data pods.
3
7
  *
4
- * Event-sourced personal data platform SDK with type-safe APIs.
5
- *
6
- * @example
8
+ * Usage:
7
9
  * ```typescript
8
- * import { SynapSDK } from '@synap/sdk';
9
- *
10
- * const synap = new SynapSDK({
11
- * url: 'https://api.synap.app',
12
- * apiKey: 'your-api-key',
13
- * realtime: {
14
- * userId: 'user-123',
15
- * userName: 'Alice',
16
- * },
17
- * });
10
+ * import { createSynapClient } from "@synap-core/sdk";
18
11
  *
19
- * // Create entity (event-sourced)
20
- * const task = await synap.entities.create({
21
- * type: 'task',
22
- * title: 'Call Marie',
23
- * metadata: { dueDate: '2024-12-20', priority: 'high' }
12
+ * const synap = createSynapClient({
13
+ * podUrl: "https://pod.example.com",
14
+ * apiKey: "sk_live_...",
15
+ * workspaceId: "ws_...",
24
16
  * });
25
17
  *
26
- * // Create workspace
27
- * const workspace = await synap.workspaces.create({ name: 'Team' });
28
- *
29
- * // Create whiteboard
30
- * const board = await synap.views.create({
31
- * type: 'whiteboard',
32
- * name: 'Brainstorm',
33
- * workspaceId: workspace.id,
34
- * });
35
- *
36
- * // Real-time collaboration
37
- * synap.realtime?.connectPresence(board.id);
38
- * const ydoc = synap.realtime?.connectYjs(board.id);
18
+ * // Fully typed — autocomplete works on all 47+ routers
19
+ * const entities = await synap.entities.list.query({ limit: 20 });
20
+ * const workspace = await synap.workspaces.get.query();
39
21
  * ```
40
22
  */
41
- export { SynapSDK, type SynapSDKConfig } from './client.js';
42
- export { EntitiesAPI } from './resources/entities.js';
43
- export { RelationsAPI } from './resources/relations.js';
44
- export { EventsAPI } from './resources/events.js';
45
- export { WorkspacesAPI } from './resources/workspaces.js';
46
- export { ViewsAPI } from './resources/views.js';
47
- export { PreferencesAPI } from './resources/preferences.js';
48
- export type { EntityType, EntityMetadata, Entity, NewEntity, UpdateEntity, } from '@synap-core/types/entities';
49
- export type { Workspace, WorkspaceMember, CreateWorkspaceInput, UpdateWorkspaceInput, } from '@synap-core/types/workspaces';
50
- export type { View, CreateViewInput, UpdateViewInput, } from '@synap-core/types/views';
51
- export type { UserPreferences, UpdatePreferencesInput, } from '@synap-core/types/preferences';
52
- export type { UserPresence, YDoc, } from '@synap-core/types/realtime';
53
- export type { Relation, RelationType, } from './types/relations.js';
54
- export type { Event, EventFilter, } from './types/events.js';
55
- //# sourceMappingURL=index.d.ts.map
23
+
24
+ interface SynapClientOptions {
25
+ /**
26
+ * Base URL of the data pod (no trailing slash, no `/trpc` suffix).
27
+ * @example "https://pod.example.com"
28
+ */
29
+ podUrl: string;
30
+ /**
31
+ * Ory Kratos session token (for example, from the Pod's
32
+ * `/api/federation/exchange` endpoint).
33
+ * Sent as `X-Session-Token` header — preferred for non-browser clients
34
+ * like extensions, CLI tools, and mobile apps.
35
+ * @example "oathkeeper_token_..."
36
+ */
37
+ sessionToken?: string;
38
+ /**
39
+ * API key for authentication (Bearer token).
40
+ * Generate one in your workspace developer settings.
41
+ * If omitted, the client falls back to cookie-based Kratos session auth.
42
+ * @example "sk_live_abc123..."
43
+ */
44
+ apiKey?: string;
45
+ /**
46
+ * Workspace ID to scope all requests to.
47
+ * Pass empty string for procedures that don't require workspace context
48
+ * (e.g. workspaces.list, which is protectedProcedure only).
49
+ * @example "ws_01jk..."
50
+ */
51
+ workspaceId: string;
52
+ /**
53
+ * Additional headers to include in every request.
54
+ */
55
+ extraHeaders?: Record<string, string>;
56
+ /**
57
+ * Called when the pod returns a `401 Unauthorized` (expired/invalid session
58
+ * or API key). The app should sign out and re-authenticate (re-handshake).
59
+ *
60
+ * The pod's auth middleware can reply with a plain-JSON body
61
+ * (`{"error":"Invalid session"}`) that is NOT in tRPC batch format and would
62
+ * otherwise crash the superjson transformer; when an `onAuthError` handler is
63
+ * provided the client rewrites such responses into a well-formed tRPC error so
64
+ * the query rejects gracefully instead of throwing a parse error.
65
+ */
66
+ onAuthError?: (error: {
67
+ status: number;
68
+ message: string;
69
+ }) => void;
70
+ }
71
+ /**
72
+ * Creates a fully-typed tRPC client connected to a Synap data pod.
73
+ *
74
+ * All 47+ tRPC routers are available with full TypeScript autocomplete
75
+ * and compile-time safety. Auth is handled via Bearer token + workspace header.
76
+ */
77
+ declare function createSynapClient(options: SynapClientOptions): CreateTRPCClient<AppRouter>;
78
+ type SynapClient = ReturnType<typeof createSynapClient>;
79
+
80
+ export { type SynapClient, type SynapClientOptions, createSynapClient };