@mapled/next 0.2.0 → 0.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.
package/README.md CHANGED
@@ -72,6 +72,27 @@ export const GET = createExitPreviewHandler();
72
72
 
73
73
  The **Preview** button in Mapled opens your site through a one-time link: the route turns on Next.js draft mode, and every `getRecords`/`getRecord`/`getSingle` call automatically switches to live drafts, uncached. No code changes in your components — the client detects draft mode on its own. Sessions last two hours; `/api/mapled/preview/exit` leaves preview.
74
74
 
75
+ ## Live data
76
+
77
+ Operational collections skip the publish cycle — saves take effect immediately. Your site's backend reads **and writes** them through the Application Data API with the project's **server key** (Mapled: Integrations → Your site). Server-side only; the key must never reach the browser.
78
+
79
+ ```bash
80
+ MAPLED_SERVER_KEY=msk_live_…
81
+ ```
82
+
83
+ ```ts
84
+ import { createAppClient } from "@mapled/next";
85
+
86
+ const db = createAppClient({ serverKey: process.env.MAPLED_SERVER_KEY! });
87
+
88
+ await db.create("orders", { customer: "Ada Lovelace", total: 120 });
89
+ const { records } = await db.list("orders", { filter: { status: "new" }, sort: "-total" });
90
+ await db.update("orders", id, { ...data, status: "shipped" });
91
+ await db.remove("orders", id);
92
+ ```
93
+
94
+ The server key reaches only operational collections with access class `public`/`server` — editorial content, internal and sensitive collections stay out of reach, and it grants nothing on the Management API.
95
+
75
96
  ## API
76
97
 
77
98
  - `createClient({ key, apiUrl? })` → `getRecords(collection, opts?)`, `getRecord(collection, id, opts?)`, `getSingle(key, opts?)`
package/dist/index.d.ts CHANGED
@@ -28,6 +28,19 @@ export declare class MapledError extends Error {
28
28
  constructor(status: number, message: string);
29
29
  }
30
30
  export type MapledClient = ReturnType<typeof createClient>;
31
+ export type MapledAppClient = ReturnType<typeof createAppClient>;
32
+ export type AppRecord<T = Record<string, unknown>> = {
33
+ id: string;
34
+ data: T;
35
+ createdAt: string;
36
+ updatedAt: string;
37
+ };
38
+ export type AppListOptions = {
39
+ filter?: Record<string, string | number | boolean>;
40
+ sort?: string;
41
+ limit?: number;
42
+ offset?: number;
43
+ };
31
44
  export declare function createClient(config: {
32
45
  key: string;
33
46
  apiUrl?: string;
@@ -39,3 +52,19 @@ export declare function createClient(config: {
39
52
  /** The record of a single (e.g. "homepage"), or null before publish. */
40
53
  getSingle<T = Record<string, unknown>>(key: string, opts?: Pick<ListOptions, "revalidate" | "tags">): Promise<MapledRecord<T> | null>;
41
54
  };
55
+ /** Live data (operational collections): the site's backend reads and
56
+ writes records that take effect immediately — no publish involved.
57
+ Server-side only: the server key must never reach the browser. */
58
+ export declare function createAppClient(config: {
59
+ serverKey: string;
60
+ apiUrl?: string;
61
+ }): {
62
+ list<T = Record<string, unknown>>(collection: string, opts?: AppListOptions): Promise<{
63
+ records: AppRecord<T>[];
64
+ total: number;
65
+ }>;
66
+ get<T = Record<string, unknown>>(collection: string, id: string): Promise<AppRecord<T> | null>;
67
+ create<T = Record<string, unknown>>(collection: string, data: T): Promise<AppRecord<T>>;
68
+ update<T = Record<string, unknown>>(collection: string, id: string, data: T): Promise<AppRecord<T>>;
69
+ remove(collection: string, id: string): Promise<void>;
70
+ };
package/dist/index.js CHANGED
@@ -100,3 +100,74 @@ export function createClient(config) {
100
100
  },
101
101
  };
102
102
  }
103
+ /** Live data (operational collections): the site's backend reads and
104
+ writes records that take effect immediately — no publish involved.
105
+ Server-side only: the server key must never reach the browser. */
106
+ export function createAppClient(config) {
107
+ const base = (config.apiUrl ?? "https://api.mapled.io").replace(/\/+$/, "");
108
+ if (!config.serverKey)
109
+ throw new Error("Mapled: a server key is required.");
110
+ async function call(method, path, search, data) {
111
+ const qs = search?.toString();
112
+ const res = await fetch(`${base}${path}${qs ? `?${qs}` : ""}`, {
113
+ method,
114
+ headers: {
115
+ "x-mapled-server-key": config.serverKey,
116
+ ...(data !== undefined ? { "content-type": "application/json" } : {}),
117
+ },
118
+ ...(data !== undefined ? { body: JSON.stringify({ data }) } : {}),
119
+ cache: "no-store",
120
+ });
121
+ if (!res.ok) {
122
+ let message = `Mapled request failed (${res.status}).`;
123
+ try {
124
+ const parsed = (await res.json());
125
+ if (parsed.error?.message)
126
+ message = parsed.error.message;
127
+ }
128
+ catch {
129
+ /* non-JSON error body */
130
+ }
131
+ throw new MapledError(res.status, message);
132
+ }
133
+ return (await res.json());
134
+ }
135
+ const collectionPath = (c) => `/v1/app/collections/${encodeURIComponent(c)}/records`;
136
+ return {
137
+ async list(collection, opts = {}) {
138
+ const search = new URLSearchParams();
139
+ for (const [k, v] of Object.entries(opts.filter ?? {})) {
140
+ search.set(`filter[${k}]`, String(v));
141
+ }
142
+ if (opts.sort)
143
+ search.set("sort", opts.sort);
144
+ if (opts.limit !== undefined)
145
+ search.set("limit", String(opts.limit));
146
+ if (opts.offset !== undefined)
147
+ search.set("offset", String(opts.offset));
148
+ return call("GET", collectionPath(collection), search);
149
+ },
150
+ async get(collection, id) {
151
+ try {
152
+ const body = await call("GET", `${collectionPath(collection)}/${encodeURIComponent(id)}`);
153
+ return body.record;
154
+ }
155
+ catch (err) {
156
+ if (err instanceof MapledError && err.status === 404)
157
+ return null;
158
+ throw err;
159
+ }
160
+ },
161
+ async create(collection, data) {
162
+ const body = await call("POST", collectionPath(collection), undefined, data);
163
+ return body.record;
164
+ },
165
+ async update(collection, id, data) {
166
+ const body = await call("PATCH", `${collectionPath(collection)}/${encodeURIComponent(id)}`, undefined, data);
167
+ return body.record;
168
+ },
169
+ async remove(collection, id) {
170
+ await call("DELETE", `${collectionPath(collection)}/${encodeURIComponent(id)}`);
171
+ },
172
+ };
173
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mapled/next",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Read published Mapled content in a Next.js site — delivery client, ISR tags, and a revalidation webhook handler.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://mapled.io",