@lazrus/nukle 0.0.0-stage → 0.2.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
@@ -1,3 +1,21 @@
1
- # Temporary Holding Version
1
+ # @lazrus/nukle
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Typed ESM client for Nukle website content, media, and input forms.
4
+
5
+ ```ts
6
+ import { createNukleClient } from "@lazrus/nukle";
7
+
8
+ // Server-only: keep the website API key out of browser code.
9
+ const nukle = createNukleClient({ apiKey: process.env.NUKLE_API_KEY! });
10
+ const modules = await nukle.getModules();
11
+ ```
12
+
13
+ The browser-safe form client uses a separate `nukle_form_...` public credential.
14
+
15
+ In the source repository:
16
+
17
+ - [Developer reference](../../docs/sdk/README.md)
18
+ - [Development, testing, and npm publishing](../../docs/sdk/development-and-publishing.md)
19
+ - [Runnable examples](../../docs/sdk/examples/README.md)
20
+
21
+ The full guides and examples live in the repository and are not included in the npm tarball. This README's relative links are for source checkouts; use the repository documentation when reading a downloaded package. Build with `pnpm --filter @lazrus/nukle build` from the monorepo root.
@@ -0,0 +1,108 @@
1
+ export type ContentEntry<T = Record<string, unknown>> = {
2
+ id: string;
3
+ data: T;
4
+ position: number;
5
+ createdAt: string;
6
+ updatedAt: string;
7
+ };
8
+ export type PublicModule<T = Record<string, unknown>> = {
9
+ key: string;
10
+ name: string;
11
+ kind: "singleton" | "repeatable";
12
+ position: number;
13
+ entries: ContentEntry<T>[];
14
+ };
15
+ export type PublicMedia = {
16
+ id: string;
17
+ websiteId: string | null;
18
+ originalName: string;
19
+ mimeType: string;
20
+ sizeBytes: number;
21
+ altText: string | null;
22
+ url: string;
23
+ createdAt: string;
24
+ updatedAt: string;
25
+ };
26
+ export type NukleClientOptions = {
27
+ apiKey: string;
28
+ baseUrl?: string;
29
+ fetch?: typeof globalThis.fetch;
30
+ };
31
+ export declare class NukleApiError extends Error {
32
+ readonly status: number;
33
+ readonly code: string;
34
+ readonly details?: unknown | undefined;
35
+ constructor(status: number, code: string, message: string, details?: unknown | undefined);
36
+ }
37
+ export declare function createNukleClient(options: NukleClientOptions): {
38
+ getModules: () => Promise<PublicModule<Record<string, unknown>>[]>;
39
+ getModule: <T = Record<string, unknown>>(moduleKey: string) => Promise<PublicModule<T>>;
40
+ getSingleton<T = Record<string, unknown>>(moduleKey: string): Promise<NonNullable<T> | null>;
41
+ getRepeatable<T = Record<string, unknown>>(moduleKey: string): Promise<T[]>;
42
+ listMedia: () => Promise<PublicMedia[]>;
43
+ getMedia: (mediaId: string) => Promise<PublicMedia>;
44
+ getMediaFile: (mediaId: string) => Promise<ArrayBuffer>;
45
+ };
46
+ export type NukleClient = ReturnType<typeof createNukleClient>;
47
+ export type InputFormField = {
48
+ key: string;
49
+ label: string;
50
+ type: "text" | "email" | "tel" | "textarea" | "checkbox" | "select";
51
+ required?: boolean;
52
+ maxLength?: number;
53
+ options?: string[];
54
+ };
55
+ export type InputFormSchema = {
56
+ name: string;
57
+ allowedOrigins: string[];
58
+ fields: InputFormField[];
59
+ };
60
+ export type InputForm = InputFormSchema & {
61
+ id: string;
62
+ websiteId: string;
63
+ key: string;
64
+ schemaVersion: number;
65
+ active: boolean;
66
+ createdAt: string;
67
+ updatedAt: string;
68
+ };
69
+ export type InputFormWithPublicKey = InputForm & {
70
+ publicKey: string;
71
+ };
72
+ export type InputFormUpsertResult = InputForm & {
73
+ publicKey?: string;
74
+ };
75
+ export type NukleFormManagementClientOptions = {
76
+ apiKey: string;
77
+ websiteId: string;
78
+ baseUrl?: string;
79
+ fetch?: typeof globalThis.fetch;
80
+ };
81
+ export type NukleFormClientOptions = {
82
+ formKey: string;
83
+ baseUrl?: string;
84
+ fetch?: typeof globalThis.fetch;
85
+ };
86
+ /**
87
+ * Manages a single website's input-form schemas. Keep this client and its
88
+ * `nukle_live_...` API key in server-only code.
89
+ */
90
+ export declare function createNukleFormManagementClient(options: NukleFormManagementClientOptions): {
91
+ upsert: (formKey: string, schema: InputFormSchema) => Promise<InputFormUpsertResult>;
92
+ get: (formKey: string) => Promise<InputForm>;
93
+ rotatePublicKey: (formKey: string) => Promise<InputFormWithPublicKey>;
94
+ setActive: (formKey: string, active: boolean) => Promise<InputForm>;
95
+ };
96
+ /**
97
+ * Submits data to exactly one public input form. It is safe to use in browser
98
+ * code because a `nukle_form_...` key cannot read content or manage schemas.
99
+ */
100
+ export declare function createNukleFormClient(options: NukleFormClientOptions): {
101
+ submit(data: Record<string, unknown>): Promise<{
102
+ ok: boolean;
103
+ submissionId?: string;
104
+ }>;
105
+ };
106
+ export type NukleFormManagementClient = ReturnType<typeof createNukleFormManagementClient>;
107
+ export type NukleFormClient = ReturnType<typeof createNukleFormClient>;
108
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,155 @@
1
+ export class NukleApiError extends Error {
2
+ status;
3
+ code;
4
+ details;
5
+ constructor(status, code, message, details) {
6
+ super(message);
7
+ this.status = status;
8
+ this.code = code;
9
+ this.details = details;
10
+ this.name = "NukleApiError";
11
+ }
12
+ }
13
+ const DEFAULT_BASE_URL = "https://api.nukle.in";
14
+ function normalizeBaseUrl(baseUrl) {
15
+ return baseUrl.replace(/\/+$/, "");
16
+ }
17
+ export function createNukleClient(options) {
18
+ if (!options.apiKey.trim())
19
+ throw new Error("Nukle API key is required.");
20
+ const baseUrl = normalizeBaseUrl(options.baseUrl ?? DEFAULT_BASE_URL);
21
+ const requestFetch = options.fetch ?? globalThis.fetch;
22
+ if (!requestFetch)
23
+ throw new Error("A fetch implementation is required.");
24
+ async function request(path, init) {
25
+ const response = await requestFetch(`${baseUrl}/v1${path}`, {
26
+ ...init,
27
+ headers: {
28
+ Accept: "application/json",
29
+ ...init?.headers,
30
+ "x-nukle-api-key": options.apiKey,
31
+ },
32
+ });
33
+ if (!response.ok) {
34
+ let payload = {};
35
+ try {
36
+ payload = (await response.json());
37
+ }
38
+ catch {
39
+ // Preserve the HTTP status when the server returns a non-JSON error.
40
+ }
41
+ throw new NukleApiError(response.status, payload.error?.code ?? "NUKLE_REQUEST_FAILED", payload.error?.message ?? `Nukle request failed (${response.status}).`, payload.error?.details);
42
+ }
43
+ return (await response.json());
44
+ }
45
+ async function getModule(moduleKey) {
46
+ return request(`/public/modules/${encodeURIComponent(moduleKey)}`);
47
+ }
48
+ return {
49
+ getModules: () => request("/public/modules"),
50
+ getModule,
51
+ async getSingleton(moduleKey) {
52
+ const module = await getModule(moduleKey);
53
+ return module.entries[0]?.data ?? null;
54
+ },
55
+ async getRepeatable(moduleKey) {
56
+ const module = await getModule(moduleKey);
57
+ return module.entries.map((entry) => entry.data);
58
+ },
59
+ listMedia: () => request("/public/media"),
60
+ getMedia: (mediaId) => request(`/public/media/${encodeURIComponent(mediaId)}`),
61
+ getMediaFile: async (mediaId) => {
62
+ const response = await requestFetch(`${baseUrl}/v1/public/media/${encodeURIComponent(mediaId)}/file`, {
63
+ headers: { "x-nukle-api-key": options.apiKey },
64
+ });
65
+ if (!response.ok) {
66
+ let payload = {};
67
+ try {
68
+ payload = (await response.json());
69
+ }
70
+ catch {
71
+ // Preserve the HTTP status when the server returns a non-JSON error.
72
+ }
73
+ throw new NukleApiError(response.status, payload.error?.code ?? "NUKLE_REQUEST_FAILED", payload.error?.message ??
74
+ `Nukle request failed (${response.status}).`, payload.error?.details);
75
+ }
76
+ return response.arrayBuffer();
77
+ },
78
+ };
79
+ }
80
+ function createJsonRequest(baseUrl, requestFetch, path, init) {
81
+ return requestFetch(`${baseUrl}/v1${path}`, {
82
+ ...init,
83
+ headers: {
84
+ Accept: "application/json",
85
+ "Content-Type": "application/json",
86
+ ...init.headers,
87
+ },
88
+ });
89
+ }
90
+ async function readJson(response) {
91
+ if (response.ok)
92
+ return (await response.json());
93
+ let payload = {};
94
+ try {
95
+ payload = (await response.json());
96
+ }
97
+ catch {
98
+ // Preserve the HTTP status when the server returns a non-JSON error.
99
+ }
100
+ throw new NukleApiError(response.status, payload.error?.code ?? "NUKLE_REQUEST_FAILED", payload.error?.message ?? `Nukle request failed (${response.status}).`, payload.error?.details);
101
+ }
102
+ /**
103
+ * Manages a single website's input-form schemas. Keep this client and its
104
+ * `nukle_live_...` API key in server-only code.
105
+ */
106
+ export function createNukleFormManagementClient(options) {
107
+ if (!options.apiKey.trim())
108
+ throw new Error("Nukle API key is required.");
109
+ if (!options.websiteId.trim())
110
+ throw new Error("Nukle website ID is required.");
111
+ const baseUrl = normalizeBaseUrl(options.baseUrl ?? DEFAULT_BASE_URL);
112
+ const requestFetch = options.fetch ?? globalThis.fetch;
113
+ if (!requestFetch)
114
+ throw new Error("A fetch implementation is required.");
115
+ const formsPath = `/websites/${encodeURIComponent(options.websiteId)}/forms`;
116
+ function request(path, init) {
117
+ return createJsonRequest(baseUrl, requestFetch, path, {
118
+ ...init,
119
+ headers: { ...init.headers, "x-nukle-api-key": options.apiKey },
120
+ }).then((readJson));
121
+ }
122
+ return {
123
+ upsert: (formKey, schema) => request(`${formsPath}/${encodeURIComponent(formKey)}`, { method: "PUT", body: JSON.stringify(schema) }),
124
+ get: (formKey) => request(`${formsPath}/${encodeURIComponent(formKey)}`, {
125
+ method: "GET",
126
+ }),
127
+ rotatePublicKey: (formKey) => request(`${formsPath}/${encodeURIComponent(formKey)}/rotate-key`, { method: "POST" }),
128
+ setActive: (formKey, active) => request(`${formsPath}/${encodeURIComponent(formKey)}`, {
129
+ method: "PATCH",
130
+ body: JSON.stringify({ active }),
131
+ }),
132
+ };
133
+ }
134
+ /**
135
+ * Submits data to exactly one public input form. It is safe to use in browser
136
+ * code because a `nukle_form_...` key cannot read content or manage schemas.
137
+ */
138
+ export function createNukleFormClient(options) {
139
+ if (!options.formKey.trim())
140
+ throw new Error("Nukle form key is required.");
141
+ const baseUrl = normalizeBaseUrl(options.baseUrl ?? DEFAULT_BASE_URL);
142
+ const requestFetch = options.fetch ?? globalThis.fetch;
143
+ if (!requestFetch)
144
+ throw new Error("A fetch implementation is required.");
145
+ return {
146
+ async submit(data) {
147
+ const response = await createJsonRequest(baseUrl, requestFetch, `/public/forms/${encodeURIComponent(options.formKey)}/submissions`, {
148
+ method: "POST",
149
+ headers: { "x-nukle-form-key": options.formKey },
150
+ body: JSON.stringify({ data, website: "" }),
151
+ });
152
+ return readJson(response);
153
+ },
154
+ };
155
+ }
package/package.json CHANGED
@@ -1,6 +1,32 @@
1
1
  {
2
2
  "name": "@lazrus/nukle",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.2.0",
4
+ "description": "Typed client for Nukle public website delivery APIs.",
5
+ "type": "module",
6
+ "types": "./dist/index.d.ts",
7
+ "main": "./dist/index.js",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "default": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "devDependencies": {
19
+ "@types/node": "^26.6.2",
20
+ "eslint": "^9",
21
+ "tsx": "^4.23.15",
22
+ "typescript": "5.9.2",
23
+ "@repo/typescript-config": "0.0.0",
24
+ "@repo/eslint-config": "0.0.0"
25
+ },
26
+ "scripts": {
27
+ "build": "tsc",
28
+ "check-types": "tsc --noEmit",
29
+ "test": "tsx --test src/**/*.test.ts",
30
+ "lint": "eslint . --max-warnings 0"
31
+ }
6
32
  }