@newschools/sdk 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 New Schools
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,289 @@
1
+ # @newschools/nuxt
2
+
3
+ Nuxt module for integrating New Schools learning content into your applications.
4
+
5
+ ## Features
6
+
7
+ ✨ **Auto-imported composables** - `useAsyncNewSchools` available everywhere
8
+ 🔒 **Type-safe** - Full TypeScript support with entity types
9
+ ⚡ **SSR-compatible** - Built on Nuxt's `useAsyncData`
10
+ 🎯 **Simple API** - Fetch journeys, waypoints, and activities with ease
11
+ 🔑 **API key authentication** - Secure access to your organisation's content
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ # Using npm
17
+ npm install @newschools/nuxt
18
+
19
+ # Using yarn
20
+ yarn add @newschools/nuxt
21
+
22
+ # Using pnpm
23
+ pnpm add @newschools/nuxt
24
+
25
+ # Using bun
26
+ bun add @newschools/nuxt
27
+ ```
28
+
29
+ ## Setup
30
+
31
+ ### 1. Add module to `nuxt.config.ts`
32
+
33
+ ```typescript
34
+ export default defineNuxtConfig({
35
+ modules: ["@newschools/nuxt"],
36
+ });
37
+ ```
38
+
39
+ ### 2. Configure API key
40
+
41
+ Add your New Schools API key to `.env`:
42
+
43
+ ```env
44
+ NUXT_PUBLIC_NEWSCHOOLS_API_KEY=ns_live_xxxxxxxxxxxxx
45
+ ```
46
+
47
+ Or configure directly in `nuxt.config.ts`:
48
+
49
+ ```typescript
50
+ export default defineNuxtConfig({
51
+ modules: ["@newschools/nuxt"],
52
+
53
+ newschools: {
54
+ apiKey: "ns_live_xxxxxxxxxxxxx",
55
+ baseUrl: "https://newschools.ai", // optional, defaults to production
56
+ },
57
+ });
58
+ ```
59
+
60
+ ## Usage
61
+
62
+ The `useAsyncNewSchools` composable is auto-imported and available in all components, pages, and composables.
63
+
64
+ ### List Journeys
65
+
66
+ ```vue
67
+ <script setup lang="ts">
68
+ const {
69
+ data: journeys,
70
+ pending,
71
+ error,
72
+ refresh,
73
+ } = await useAsyncNewSchools("journeys");
74
+ </script>
75
+
76
+ <template>
77
+ <div>
78
+ <div v-if="pending">Loading journeys...</div>
79
+ <div v-else-if="error">Error: {{ error.message }}</div>
80
+ <div v-else>
81
+ <div v-for="journey in journeys" :key="journey.id">
82
+ <h2>{{ journey.title }}</h2>
83
+ <p>{{ journey.description }}</p>
84
+ </div>
85
+ </div>
86
+ </div>
87
+ </template>
88
+ ```
89
+
90
+ ### Get Single Journey
91
+
92
+ ```vue
93
+ <script setup lang="ts">
94
+ const route = useRoute();
95
+ const slug = route.params.slug as string;
96
+
97
+ const { data: journey } = await useAsyncNewSchools("journey", { id: slug });
98
+ </script>
99
+
100
+ <template>
101
+ <div>
102
+ <h1>{{ journey?.title }}</h1>
103
+ <p>{{ journey?.summary }}</p>
104
+ <img v-if="journey?.coverImageUrl" :src="journey.coverImageUrl" />
105
+ </div>
106
+ </template>
107
+ ```
108
+
109
+ ### List Waypoints for Journey
110
+
111
+ ```vue
112
+ <script setup lang="ts">
113
+ const route = useRoute();
114
+ const journeyId = route.params.journeyId as string;
115
+
116
+ const { data: waypoints } = await useAsyncNewSchools("waypoints", {
117
+ journeyId,
118
+ });
119
+ </script>
120
+
121
+ <template>
122
+ <div>
123
+ <div v-for="waypoint in waypoints" :key="waypoint.id">
124
+ <h3>{{ waypoint.title }}</h3>
125
+ </div>
126
+ </div>
127
+ </template>
128
+ ```
129
+
130
+ ### Get Single Waypoint
131
+
132
+ ```vue
133
+ <script setup lang="ts">
134
+ const { data: waypoint } = await useAsyncNewSchools("waypoint", {
135
+ journeyId: "journey-123",
136
+ waypointId: "waypoint-456",
137
+ });
138
+ </script>
139
+ ```
140
+
141
+ ### List Activities
142
+
143
+ ```vue
144
+ <script setup lang="ts">
145
+ const { data: activities } = await useAsyncNewSchools("activities", {
146
+ journeyId: "journey-123",
147
+ waypointId: "waypoint-456",
148
+ });
149
+ </script>
150
+ ```
151
+
152
+ ### Get Single Activity
153
+
154
+ ```vue
155
+ <script setup lang="ts">
156
+ const { data: activity } = await useAsyncNewSchools("activity", {
157
+ journeyId: "journey-123",
158
+ waypointId: "waypoint-456",
159
+ activityId: "activity-789",
160
+ });
161
+ </script>
162
+ ```
163
+
164
+ ## API Reference
165
+
166
+ ### `useAsyncNewSchools(resource, params?)`
167
+
168
+ Fetch data from New Schools v1 API with SSR support.
169
+
170
+ **Parameters:**
171
+
172
+ - `resource` - Resource type to fetch:
173
+ - `'journeys'` - List all journeys
174
+ - `'journey'` - Get single journey
175
+ - `'waypoints'` - List waypoints for journey
176
+ - `'waypoint'` - Get single waypoint
177
+ - `'activities'` - List activities for waypoint
178
+ - `'activity'` - Get single activity
179
+
180
+ - `params` - Resource-specific parameters (optional for lists):
181
+ - Journey: `{ id: string }`
182
+ - Waypoint: `{ journeyId: string, waypointId?: string }`
183
+ - Activity: `{ journeyId: string, waypointId: string, activityId?: string }`
184
+
185
+ **Returns:**
186
+
187
+ `AsyncData` object with:
188
+
189
+ - `data` - Fetched data (reactive)
190
+ - `pending` - Loading state (reactive)
191
+ - `error` - Error object if request failed (reactive)
192
+ - `refresh()` - Function to refetch data
193
+
194
+ ## TypeScript Support
195
+
196
+ Full type safety for all entities:
197
+
198
+ ```typescript
199
+ import type {
200
+ Journey,
201
+ Waypoint,
202
+ Activity,
203
+ JourneyTheme,
204
+ WaypointType,
205
+ LayoutMode,
206
+ } from "@newschools/nuxt";
207
+
208
+ // Types are automatically inferred in useAsyncNewSchools
209
+ const { data: journeys } = await useAsyncNewSchools("journeys");
210
+ // journeys is typed as Journey[] | null
211
+
212
+ const { data: journey } = await useAsyncNewSchools("journey", { id: "slug" });
213
+ // journey is typed as Journey | null
214
+ ```
215
+
216
+ ## Advanced Usage
217
+
218
+ ### Manual Client (Without Nuxt Composables)
219
+
220
+ For use in server routes, plugins, or non-component contexts:
221
+
222
+ ```typescript
223
+ import { createClient } from "@newschools/nuxt";
224
+
225
+ const client = createClient({
226
+ apiKey: "ns_live_xxxxxxxxxxxxx",
227
+ baseUrl: "https://newschools.ai", // optional
228
+ });
229
+
230
+ const journeys = await client.get<Journey[]>("/journeys");
231
+ const journey = await client.get<Journey>("/journeys/my-slug");
232
+ ```
233
+
234
+ ### Custom Path Resolution
235
+
236
+ For advanced integrations:
237
+
238
+ ```typescript
239
+ import { NewSchoolsPathResolver } from "@newschools/nuxt";
240
+
241
+ const path = NewSchoolsPathResolver.resolve("journey", "list");
242
+ // Returns: '/journeys'
243
+
244
+ const path2 = NewSchoolsPathResolver.resolve("waypoint", "get", {
245
+ journeyId: "123",
246
+ waypointId: "456",
247
+ });
248
+ // Returns: '/journeys/123/waypoints/456'
249
+ ```
250
+
251
+ ## Local Development (Testing with bun link)
252
+
253
+ For testing the SDK locally before publishing:
254
+
255
+ ```bash
256
+ # In the SDK package directory
257
+ cd packages/nuxt
258
+ bun link
259
+
260
+ # In your test project
261
+ cd ~/my-test-project
262
+ bun link @newschools/nuxt
263
+ ```
264
+
265
+ Add to your test project's `nuxt.config.ts`:
266
+
267
+ ```typescript
268
+ export default defineNuxtConfig({
269
+ modules: ["@newschools/nuxt"],
270
+ });
271
+ ```
272
+
273
+ ## Environment Variables
274
+
275
+ | Variable | Description | Required |
276
+ | --------------------------------- | ------------------------------------- | -------- |
277
+ | `NUXT_PUBLIC_NEWSCHOOLS_API_KEY` | Your New Schools API key | Yes |
278
+ | `NUXT_PUBLIC_NEWSCHOOLS_BASE_URL` | API base URL (defaults to production) | No |
279
+
280
+ ## License
281
+
282
+ MIT
283
+
284
+ ## Support
285
+
286
+ For issues or questions:
287
+
288
+ - API documentation: https://newschools.ai/docs
289
+ - Contact: support@newschools.ai
@@ -0,0 +1,46 @@
1
+ /**
2
+ * New Schools SDK - Main Entry Point
3
+ *
4
+ * Exports module, types, and utilities
5
+ */
6
+
7
+ // Export Nuxt module
8
+ export { default } from "./module";
9
+
10
+ // Export types
11
+ export type {
12
+ Journey,
13
+ JourneyTheme,
14
+ JourneyStatus,
15
+ Waypoint,
16
+ StandardWaypoint,
17
+ ProfileEnrichmentWaypoint,
18
+ WaypointType,
19
+ WaypointStatus,
20
+ LayoutMode,
21
+ Question,
22
+ Activity,
23
+ ActivityStatus,
24
+ GridPosition,
25
+ SuccessResponse,
26
+ ErrorResponse,
27
+ ApiResponse,
28
+ } from "./types";
29
+
30
+ // Export composable type for advanced usage
31
+ export type { useAsyncNewSchools } from "./runtime/composables/useAsyncNewSchools";
32
+
33
+ // Export client for manual usage (advanced)
34
+ export { createClient, NewSchoolsClient } from "./runtime/client";
35
+ export type { NewSchoolsClientConfig, RequestOptions } from "./runtime/client";
36
+
37
+ // Export path resolver for custom integrations (advanced)
38
+ export { NewSchoolsPathResolver } from "./runtime/utils/pathResolver";
39
+ export type {
40
+ ResourceAction,
41
+ ResourceType,
42
+ ResourceParams,
43
+ JourneyParams,
44
+ WaypointParams,
45
+ ActivityParams,
46
+ } from "./runtime/utils/pathResolver";
@@ -0,0 +1,98 @@
1
+ /**
2
+ * New Schools Nuxt Module
3
+ *
4
+ * Nuxt module that provides composables and configuration for the New Schools SDK
5
+ * Auto-registers plugin and makes composables available globally
6
+ */
7
+
8
+ import {
9
+ defineNuxtModule,
10
+ addPlugin,
11
+ createResolver,
12
+ addImportsDir,
13
+ addComponentsDir,
14
+ } from "@nuxt/kit";
15
+
16
+ export interface ModuleOptions {
17
+ /**
18
+ * New Schools API key
19
+ * Can also be set via NUXT_PUBLIC_NEWSCHOOLS_API_KEY env variable
20
+ */
21
+ apiKey?: string;
22
+
23
+ /**
24
+ * Base URL for New Schools API
25
+ * Defaults to https://newschools.ai
26
+ */
27
+ baseUrl?: string;
28
+
29
+ /**
30
+ * Organization slug for auto-fetch and theming
31
+ * When set, SDK will automatically fetch organization data on init
32
+ * and apply brand colors as CSS variables
33
+ *
34
+ * @example 'my-organization'
35
+ */
36
+ organizationSlug?: string;
37
+ }
38
+
39
+ export default defineNuxtModule<ModuleOptions>({
40
+ meta: {
41
+ name: "@newschools/nuxt",
42
+ configKey: "newschools",
43
+ compatibility: {
44
+ nuxt: "^3.0.0 || ^4.0.0",
45
+ },
46
+ },
47
+
48
+ defaults: {
49
+ baseUrl: "https://newschools.ai",
50
+ },
51
+
52
+ setup(options, nuxt) {
53
+ const resolver = createResolver(import.meta.url);
54
+
55
+ // Vite optimizations (following Storyblok pattern)
56
+ if (nuxt.options.vite.optimizeDeps) {
57
+ nuxt.options.vite.optimizeDeps.exclude =
58
+ nuxt.options.vite.optimizeDeps.exclude || [];
59
+ nuxt.options.vite.optimizeDeps.exclude.push("fsevents");
60
+ }
61
+
62
+ // Transpile runtime for proper bundling
63
+ nuxt.options.build.transpile.push(resolver.resolve("./runtime"));
64
+ nuxt.options.build.transpile.push("@newschools/nuxt");
65
+
66
+ // Add runtime config
67
+ nuxt.options.runtimeConfig.public.newschools = {
68
+ apiKey:
69
+ options.apiKey || process.env.NUXT_PUBLIC_NEWSCHOOLS_API_KEY || "",
70
+ baseUrl: options.baseUrl || "https://newschools.ai",
71
+ organizationSlug: options.organizationSlug || "",
72
+ };
73
+
74
+ // Register plugin
75
+ addPlugin(resolver.resolve("./runtime/plugin"));
76
+
77
+ // Auto-import all composables from directory (Storyblok pattern)
78
+ addImportsDir(resolver.resolve("./runtime/composables"));
79
+
80
+ // Auto-register public components globally
81
+ // Private components: prefix with _ or place in internal/ subdirectory
82
+ addComponentsDir({
83
+ path: resolver.resolve("./runtime/components"),
84
+ global: true,
85
+ pattern: "**/*.vue", // All .vue files
86
+ ignore: ["**/internal/**", "**/_*.vue"], // Exclude internal/ and _-prefixed
87
+ });
88
+
89
+ // Add CSS tokens to Nuxt's CSS array
90
+ nuxt.options.css.push(resolver.resolve("./runtime/styles/tokens.css"));
91
+
92
+ // Log configuration in development
93
+ if (nuxt.options.dev) {
94
+ console.log("[NewSchools Module] Registered");
95
+ console.log("[NewSchools Module] CSS tokens loaded");
96
+ }
97
+ },
98
+ });
@@ -0,0 +1,150 @@
1
+ /**
2
+ * New Schools API Client
3
+ *
4
+ * Core client for making authenticated requests to the New Schools v1 API
5
+ * Handles API key authentication, error handling, and response parsing
6
+ */
7
+
8
+ import { ofetch } from "ofetch";
9
+ import type { SuccessResponse, ErrorResponse } from "../types";
10
+
11
+ /**
12
+ * Client configuration options
13
+ */
14
+ export interface NewSchoolsClientConfig {
15
+ /** API key for authentication (e.g., ns_live_xxxxx) */
16
+ apiKey: string;
17
+
18
+ /** Base URL for API requests (defaults to production) */
19
+ baseUrl?: string;
20
+
21
+ /** Optional origin for CORS validation */
22
+ origin?: string;
23
+ }
24
+
25
+ /**
26
+ * Request options for API calls
27
+ */
28
+ export interface RequestOptions {
29
+ /** HTTP method (defaults to GET) */
30
+ method?: "GET" | "POST" | "PUT" | "DELETE";
31
+
32
+ /** Request body for POST/PUT requests */
33
+ body?: unknown;
34
+
35
+ /** Additional headers */
36
+ headers?: Record<string, string>;
37
+ }
38
+
39
+ /**
40
+ * New Schools API Client
41
+ * Provides type-safe methods for fetching learning content
42
+ */
43
+ export class NewSchoolsClient {
44
+ private apiKey: string;
45
+ private baseUrl: string;
46
+ private origin?: string;
47
+
48
+ constructor(config: NewSchoolsClientConfig) {
49
+ this.apiKey = config.apiKey;
50
+ this.baseUrl = config.baseUrl || "https://newschools.ai";
51
+ this.origin = config.origin;
52
+
53
+ if (!this.apiKey) {
54
+ throw new Error("[NewSchools] API key is required");
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Make authenticated request to v1 API
60
+ *
61
+ * @param endpoint - API endpoint path (e.g., '/journeys' or '/journeys/123')
62
+ * @param options - Request options
63
+ * @returns Unwrapped data from SuccessResponse
64
+ * @throws Error with message from ErrorResponse
65
+ */
66
+ async request<T>(endpoint: string, options: RequestOptions = {}): Promise<T> {
67
+ const url = `${this.baseUrl}/api/v1${endpoint}`;
68
+
69
+ const headers: Record<string, string> = {
70
+ Authorization: `Bearer ${this.apiKey}`,
71
+ "Content-Type": "application/json",
72
+ ...options.headers,
73
+ };
74
+
75
+ // Add origin if provided (for CORS validation)
76
+ if (this.origin) {
77
+ headers["Origin"] = this.origin;
78
+ }
79
+
80
+ try {
81
+ const response = await ofetch<SuccessResponse<T> | ErrorResponse>(url, {
82
+ method: options.method || "GET",
83
+ headers,
84
+ body: options.body ? JSON.stringify(options.body) : undefined,
85
+ });
86
+
87
+ // Handle error response
88
+ if (!response.success) {
89
+ const errorResponse = response as ErrorResponse;
90
+ throw new Error(errorResponse.error.message);
91
+ }
92
+
93
+ // Return unwrapped data from success response
94
+ return (response as SuccessResponse<T>).data;
95
+ } catch (error: any) {
96
+ // Re-throw with improved error message
97
+ if (error.data && !error.data.success) {
98
+ const errorResponse = error.data as ErrorResponse;
99
+ throw new Error(`[NewSchools API] ${errorResponse.error.message}`);
100
+ }
101
+
102
+ throw new Error(`[NewSchools API] ${error.message || "Request failed"}`);
103
+ }
104
+ }
105
+
106
+ /**
107
+ * GET request helper
108
+ */
109
+ async get<T>(endpoint: string): Promise<T> {
110
+ return this.request<T>(endpoint, { method: "GET" });
111
+ }
112
+
113
+ /**
114
+ * POST request helper
115
+ */
116
+ async post<T>(endpoint: string, body: unknown): Promise<T> {
117
+ return this.request<T>(endpoint, { method: "POST", body });
118
+ }
119
+
120
+ /**
121
+ * PUT request helper
122
+ */
123
+ async put<T>(endpoint: string, body: unknown): Promise<T> {
124
+ return this.request<T>(endpoint, { method: "PUT", body });
125
+ }
126
+
127
+ /**
128
+ * DELETE request helper
129
+ */
130
+ async delete<T>(endpoint: string): Promise<T> {
131
+ return this.request<T>(endpoint, { method: "DELETE" });
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Create a new New Schools API client instance
137
+ *
138
+ * @example
139
+ * ```typescript
140
+ * const client = createClient({
141
+ * apiKey: 'ns_live_xxxxx',
142
+ * baseUrl: 'https://newschools.ai' // optional
143
+ * });
144
+ *
145
+ * const journeys = await client.get('/journeys');
146
+ * ```
147
+ */
148
+ export function createClient(config: NewSchoolsClientConfig): NewSchoolsClient {
149
+ return new NewSchoolsClient(config);
150
+ }