@synap-core/sdk 1.1.0 → 1.3.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 (63) hide show
  1. package/README.md +138 -6
  2. package/dist/chunk-J7XHAAFJ.js +79 -0
  3. package/dist/chunk-J7XHAAFJ.js.map +1 -0
  4. package/dist/client-CVUT1Mcf.d.cts +253 -0
  5. package/dist/client-CVUT1Mcf.d.ts +253 -0
  6. package/dist/connect.browser.cjs +22 -0
  7. package/dist/connect.browser.cjs.map +1 -0
  8. package/dist/connect.browser.d.cts +2 -0
  9. package/dist/connect.browser.d.ts +2 -0
  10. package/dist/connect.browser.js +5 -0
  11. package/dist/connect.browser.js.map +1 -0
  12. package/dist/connect.cjs +1206 -0
  13. package/dist/connect.cjs.map +1 -0
  14. package/dist/connect.d.cts +442 -0
  15. package/dist/connect.d.ts +442 -0
  16. package/dist/connect.js +1092 -0
  17. package/dist/connect.js.map +1 -0
  18. package/dist/index.cjs +48 -6
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.cts +118 -7
  21. package/dist/index.d.ts +118 -7
  22. package/dist/index.js +41 -2
  23. package/dist/index.js.map +1 -1
  24. package/dist/public.cjs +627 -0
  25. package/dist/public.cjs.map +1 -0
  26. package/dist/public.d.cts +228 -0
  27. package/dist/public.d.ts +228 -0
  28. package/dist/public.js +578 -0
  29. package/dist/public.js.map +1 -0
  30. package/dist/testing.cjs +671 -0
  31. package/dist/testing.cjs.map +1 -0
  32. package/dist/testing.d.cts +181 -0
  33. package/dist/testing.d.ts +181 -0
  34. package/dist/testing.js +614 -0
  35. package/dist/testing.js.map +1 -0
  36. package/dist/types-DpqPNaaa.d.cts +76 -0
  37. package/dist/types-DpqPNaaa.d.ts +76 -0
  38. package/package.json +16 -3
  39. package/src/client.ts +24 -7
  40. package/src/connect/auth.ts +405 -0
  41. package/src/connect/browser.ts +10 -0
  42. package/src/connect/client.ts +193 -0
  43. package/src/connect/discovery.ts +262 -0
  44. package/src/connect/encoding.ts +93 -0
  45. package/src/connect/errors.ts +115 -0
  46. package/src/connect/flow.ts +449 -0
  47. package/src/connect/guard.ts +25 -0
  48. package/src/connect/index.ts +59 -0
  49. package/src/connect/seal.ts +119 -0
  50. package/src/connect/store.ts +151 -0
  51. package/src/connect/types.ts +80 -0
  52. package/src/index.ts +13 -0
  53. package/src/public/client.ts +579 -0
  54. package/src/public/form-model.ts +210 -0
  55. package/src/public/index.ts +12 -0
  56. package/src/public/safe-markdown.ts +114 -0
  57. package/src/public/share-model.ts +47 -0
  58. package/src/public/site-model.ts +217 -0
  59. package/src/templates.ts +122 -0
  60. package/src/testing/auth-server.ts +380 -0
  61. package/src/testing/fixtures.ts +132 -0
  62. package/src/testing/index.ts +359 -0
  63. package/src/public.ts +0 -262
@@ -0,0 +1,253 @@
1
+ /**
2
+ * @synap-core/sdk/public — a thin, CREDENTIALLESS fetch client for the pod's
3
+ * public doors (Sites W3/W4, collection door C1a/C1b):
4
+ *
5
+ * GET <pod>/api/hub/public/shares/:token read a published share
6
+ * GET <pod>/api/hub/public/forms/:token a form's public definition + ticket
7
+ * POST <pod>/api/hub/public/forms/:token a guest submission (202 / 422 / 503)
8
+ * GET <pod>/api/hub/public/sites/:token a site manifest
9
+ * GET <pod>/api/hub/public/sites/:token/collections/:key a page of records
10
+ * GET <pod>/api/hub/public/sites/:token/collections/:key/:slugOrPublicId a record
11
+ *
12
+ * Invariants (pinned by src/__tests__/public.guard.test.ts):
13
+ * - every request is `credentials: 'omit'`; no Authorization / Cookie / session
14
+ * header is ever set (only `Accept`, `If-None-Match` on a site revalidation,
15
+ * and `Content-Type` on POST);
16
+ * - imports NOTHING (no tRPC, superjson, zod, authenticated client);
17
+ * - the uniform 404 is one typed `not_found` (revoked / expired / unknown are
18
+ * indistinguishable by design); the constant 202 is `{ received: true }`;
19
+ * - a submit has six typed non-success outcomes (`invalid` with the field keys,
20
+ * `retry`, `unavailable`, `rate_limited`, `network`, `server`); a 422 `retry`
21
+ * (ticket missing / too early / stale) is retried ONCE automatically: the
22
+ * form is fetched again, `minSubmitMs` is waited out (capped) and the same
23
+ * submission (same idempotency key) is resent;
24
+ * - server detail (bodies, messages) never reaches the caller.
25
+ *
26
+ * DRIFT RISK: the response types below are HAND-WRITTEN from the backend wire
27
+ * contracts (synap-backend: `_codecs/public-shares.ts` PublicShareResponseSchema,
28
+ * `services/forms/form-definition.ts` publicFormView + the public-forms route,
29
+ * `SubmissionEnvelopeSchema`, `_codecs/public-sites.ts` + the bounds in
30
+ * `services/sharing/public-site-read.ts` parseCollectionQuery).
31
+ * `src/__tests__/fixtures.ts` copies those shapes; when the backend changes,
32
+ * update both.
33
+ */
34
+ interface PublicShare {
35
+ resourceType: "entity";
36
+ /** Present only when the publisher allowlisted `title`. */
37
+ title?: string;
38
+ properties: Record<string, string | number | boolean>;
39
+ body: {
40
+ format: "markdown";
41
+ content: string;
42
+ } | null;
43
+ /** Publication day, YYYY-MM-DD. */
44
+ publishedOn: string;
45
+ }
46
+ type PublicFormFieldType = "text" | "richtext" | "email" | "phone" | "url" | "number" | "boolean" | "date" | "enum";
47
+ interface PublicFormField {
48
+ key: string;
49
+ label: string;
50
+ type: PublicFormFieldType;
51
+ required?: true;
52
+ help?: string;
53
+ constraints?: {
54
+ enum?: string[];
55
+ min?: number;
56
+ max?: number;
57
+ maxLength?: number;
58
+ };
59
+ }
60
+ interface PublicForm {
61
+ title: string;
62
+ fields: PublicFormField[];
63
+ successMessage: string;
64
+ /** The envelope key of the honeypot (always "hp" today). */
65
+ honeypotField: string;
66
+ /** Time-to-submit ticket, minted by this GET; pass it back to `submitForm`. */
67
+ ticket: string;
68
+ /**
69
+ * The POST is refused with `retry` until this many ms have passed since the
70
+ * ticket was minted. Hold the submit button until then. 0 = no wait.
71
+ */
72
+ minSubmitMs: number;
73
+ captcha: {
74
+ required: true;
75
+ siteKey: string | null;
76
+ } | {
77
+ required: false;
78
+ };
79
+ }
80
+ /** A published value: a publish-time snapshot, scalars only (C1b). */
81
+ type PublicValue = string | number | boolean;
82
+ interface PublicSiteCollection {
83
+ /** The collection key (a profile slug); pass it to `listCollection`. */
84
+ key: string;
85
+ /** Live published records in it. */
86
+ count: number;
87
+ /** The default sort keys (`-` = descending). */
88
+ sort: string[];
89
+ /** The default page size. */
90
+ pageSize: number;
91
+ /** How a record of this collection is addressed in URLs. */
92
+ addressing: "slug" | "publicId";
93
+ }
94
+ /** A published site's manifest. */
95
+ interface PublicSite {
96
+ /** Present only when the owner's policy publishes `title`. */
97
+ title?: string;
98
+ /** Present only when the owner's policy publishes `description`. */
99
+ description?: string;
100
+ /** Day the site went live, YYYY-MM-DD. */
101
+ publishedOn: string;
102
+ collections: PublicSiteCollection[];
103
+ }
104
+ /** One published record as a list item (no body). */
105
+ interface PublicRecordSummary {
106
+ collection: string;
107
+ /** The stable public address: `p_` + 16 random base62 chars. */
108
+ publicId: string;
109
+ /** The record's slug in its collection, or null. */
110
+ slug: string | null;
111
+ /** Present only when the owner's policy publishes `title`. */
112
+ title?: string;
113
+ properties: Record<string, PublicValue>;
114
+ /** Publication day, YYYY-MM-DD. */
115
+ publishedOn: string;
116
+ }
117
+ interface PublicCollectionPage {
118
+ items: PublicRecordSummary[];
119
+ /** Live published records in the collection. */
120
+ total: number;
121
+ offset: number;
122
+ limit: number;
123
+ }
124
+ /** One published record, with its pinned body. */
125
+ interface PublicRecord extends PublicRecordSummary {
126
+ body: {
127
+ format: "markdown";
128
+ content: string;
129
+ } | null;
130
+ }
131
+ /** The collection door's query bounds (backend `parseCollectionQuery`). */
132
+ declare const SITE_MAX_OFFSET = 10000;
133
+ declare const SITE_MAX_LIMIT = 100;
134
+ declare const SITE_MAX_SORT_KEYS = 3;
135
+ /** The sort key every collection accepts, besides its configured ones. */
136
+ declare const SITE_PUBLISHED_AT_SORT = "publishedAt";
137
+ /** `not_found` is the uniform 404 (never says revoked vs expired vs unknown). */
138
+ type PublicReadError = "not_found" | "rate_limited"
139
+ /** The owner has not turned public pages/forms on for this workspace. */
140
+ | "doors_disabled" | "network" | "server";
141
+ /** A submit never yields `not_found`: the door answers 202 to everything. */
142
+ type PublicSubmitError = "invalid"
143
+ /** The ticket was refused, even after the one automatic retry. */
144
+ | "retry"
145
+ /** 503: the pod failed to file it; resubmitting with the same key is safe. */
146
+ | "unavailable" | "rate_limited"
147
+ /** The owner has not turned public pages/forms on for this workspace. */
148
+ | "doors_disabled" | "network" | "server";
149
+ /**
150
+ * A site read's failure. `not_found` is the uniform 404 (an unknown, revoked
151
+ * or unpublished site, collection or record are indistinguishable).
152
+ */
153
+ type PublicSiteError = "not_found" | "rate_limited"
154
+ /** The site is live but the owner switched its space's public pages off. */
155
+ | "doors_disabled"
156
+ /** 400: the query is out of bounds or names a sort key the collection does not offer. */
157
+ | "bad_request"
158
+ /** No answer (network, DNS, CORS) or a gateway 502/503/504. */
159
+ | "pod_unreachable" | "server";
160
+ /**
161
+ * A failure. `rate_limited` carries `retryAfterSec` only when the response
162
+ * had a `Retry-After` header the caller can read (see README: the pod's public
163
+ * doors do not send one today, and a browser cannot read it cross-origin).
164
+ */
165
+ type PublicFailure<E extends string> = E extends "rate_limited" ? {
166
+ ok: false;
167
+ error: E;
168
+ retryAfterSec?: number;
169
+ } : {
170
+ ok: false;
171
+ error: E;
172
+ };
173
+ type PublicResult<T, E extends string = PublicReadError> = {
174
+ ok: true;
175
+ data: T;
176
+ } | PublicFailure<E>;
177
+ /** A site read. `etag` is the door's weak validator, for `ifNoneMatch`. */
178
+ type PublicSiteResult<T> = {
179
+ ok: true;
180
+ data: T;
181
+ etag: string | null;
182
+ } | PublicFailure<PublicSiteError>;
183
+ /** 304: what the caller holds for `ifNoneMatch` is still current. */
184
+ interface PublicNotModified {
185
+ ok: true;
186
+ notModified: true;
187
+ etag: string;
188
+ }
189
+ /**
190
+ * Revalidate with the ETag of an earlier answer. SERVER-SIDE ONLY: the public
191
+ * doors allow no request header but `Content-Type` cross-origin, so a browser
192
+ * fails the preflight (→ `pod_unreachable`). Browsers revalidate on their own
193
+ * through their HTTP cache (`Cache-Control: no-cache` + ETag).
194
+ */
195
+ interface Revalidate {
196
+ ifNoneMatch: string;
197
+ }
198
+ interface CollectionQuery {
199
+ /** 0..10 000 (default 0). Anything else is `bad_request`, without a request. */
200
+ offset?: number;
201
+ /** An integer, clamped to 1..100 (default: the collection's page size). */
202
+ limit?: number;
203
+ /**
204
+ * ≤ 3 keys, `-` = descending, out of the collection's `sort` keys and
205
+ * `publishedAt` (default: the collection's sort). A string is comma-split.
206
+ */
207
+ sort?: string | readonly string[];
208
+ }
209
+ type SubmitResult = {
210
+ ok: true;
211
+ received: true;
212
+ }
213
+ /** 422: the answers do not satisfy the form. `fields` are field keys; empty = not a submission. */
214
+ | {
215
+ ok: false;
216
+ error: "invalid";
217
+ fields: string[];
218
+ } | PublicFailure<Exclude<PublicSubmitError, "invalid">>;
219
+ interface SubmitFormInput {
220
+ /** Answers keyed by the form's field `key`s. */
221
+ fields: Record<string, unknown>;
222
+ /** The `ticket` from `getForm` (waiting out the form's min time is the caller's job). */
223
+ ticket?: string;
224
+ /** Turnstile-style token, when `captcha.required`. */
225
+ captchaToken?: string;
226
+ /** Optional replay key; a repeated key collapses onto one submission. */
227
+ idempotencyKey?: string;
228
+ }
229
+ interface PublicClientOptions {
230
+ /** Pod origin, e.g. `https://you.synap.live` (a trailing slash is ignored). */
231
+ podUrl: string;
232
+ /** Injectable fetch; defaults to the global one. */
233
+ fetch?: typeof fetch;
234
+ /** Injectable wait (tests); defaults to `setTimeout`. */
235
+ sleep?: (ms: number) => Promise<void>;
236
+ }
237
+ interface PublicClient {
238
+ readShare(token: string): Promise<PublicResult<PublicShare>>;
239
+ getForm(token: string): Promise<PublicResult<PublicForm>>;
240
+ submitForm(token: string, input: SubmitFormInput): Promise<SubmitResult>;
241
+ /** A published site's manifest: its metadata and configured collections. */
242
+ getSite(siteToken: string): Promise<PublicSiteResult<PublicSite>>;
243
+ getSite(siteToken: string, opts: Revalidate): Promise<PublicSiteResult<PublicSite> | PublicNotModified>;
244
+ /** One page of a collection's live published records (no bodies). */
245
+ listCollection(siteToken: string, key: string, query?: CollectionQuery): Promise<PublicSiteResult<PublicCollectionPage>>;
246
+ listCollection(siteToken: string, key: string, query: CollectionQuery & Revalidate): Promise<PublicSiteResult<PublicCollectionPage> | PublicNotModified>;
247
+ /** One record by its public id (`p_…`) or its slug, with its pinned body. */
248
+ getRecord(siteToken: string, key: string, slugOrPublicId: string): Promise<PublicSiteResult<PublicRecord>>;
249
+ getRecord(siteToken: string, key: string, slugOrPublicId: string, opts: Revalidate): Promise<PublicSiteResult<PublicRecord> | PublicNotModified>;
250
+ }
251
+ declare function createPublicClient(opts: PublicClientOptions): PublicClient;
252
+
253
+ export { type CollectionQuery as C, type PublicForm as P, type Revalidate as R, type SubmitResult as S, type PublicShare as a, type PublicRecord as b, type PublicReadError as c, type PublicRecordSummary as d, type PublicValue as e, type PublicCollectionPage as f, type PublicResult as g, type PublicClient as h, type PublicClientOptions as i, type PublicFailure as j, type PublicFormField as k, type PublicFormFieldType as l, type PublicNotModified as m, type PublicSite as n, type PublicSiteCollection as o, type PublicSiteError as p, type PublicSiteResult as q, type PublicSubmitError as r, SITE_MAX_LIMIT as s, SITE_MAX_OFFSET as t, SITE_MAX_SORT_KEYS as u, SITE_PUBLISHED_AT_SORT as v, type SubmitFormInput as w, createPublicClient as x };
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __copyProps = (to, from, except, desc) => {
7
+ if (from && typeof from === "object" || typeof from === "function") {
8
+ for (let key of __getOwnPropNames(from))
9
+ if (!__hasOwnProp.call(to, key) && key !== except)
10
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
11
+ }
12
+ return to;
13
+ };
14
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
15
+
16
+ // src/connect/browser.ts
17
+ var browser_exports = {};
18
+ module.exports = __toCommonJS(browser_exports);
19
+ throw new Error(
20
+ "@synap-core/sdk/connect is server-only: it holds the pod's tokens and the app secret. Import it from route handlers, proxy.ts or server code, never from a client component."
21
+ );
22
+ //# sourceMappingURL=connect.browser.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/connect/browser.ts"],"sourcesContent":["/**\n * What a BROWSER bundle gets for `@synap-core/sdk/connect` (the `browser`\n * export condition): no exports, and a throw on load. See ./guard.ts.\n */\nthrow new Error(\n \"@synap-core/sdk/connect is server-only: it holds the pod's tokens and the app secret. \" +\n \"Import it from route handlers, proxy.ts or server code, never from a client component.\",\n);\n\nexport {};\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;AAIA,MAAM,IAAI;AAAA,EACR;AAEF;","names":[]}
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -0,0 +1,5 @@
1
+ // src/connect/browser.ts
2
+ throw new Error(
3
+ "@synap-core/sdk/connect is server-only: it holds the pod's tokens and the app secret. Import it from route handlers, proxy.ts or server code, never from a client component."
4
+ );
5
+ //# sourceMappingURL=connect.browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/connect/browser.ts"],"sourcesContent":["/**\n * What a BROWSER bundle gets for `@synap-core/sdk/connect` (the `browser`\n * export condition): no exports, and a throw on load. See ./guard.ts.\n */\nthrow new Error(\n \"@synap-core/sdk/connect is server-only: it holds the pod's tokens and the app secret. \" +\n \"Import it from route handlers, proxy.ts or server code, never from a client component.\",\n);\n\nexport {};\n"],"mappings":";AAIA,MAAM,IAAI;AAAA,EACR;AAEF;","names":[]}