@lokal-server/ts-sdk 0.1.1

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 ADDED
@@ -0,0 +1,129 @@
1
+ # @lokal-server/ts-sdk
2
+
3
+ TypeScript SDK for Lokal external apps. Lokal is a self-hosted file/data hub; this SDK helps apps define a manifest, authenticate a Lokal user, register the manifest, and read/write app-private JSON data.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ npm install @lokal-server/ts-sdk
9
+ ```
10
+
11
+ ## Define an app
12
+
13
+ ```ts
14
+ import { createLokalClient, defineLokalApp } from '@lokal-server/ts-sdk';
15
+
16
+ const recipeSchema = { type: 'object' };
17
+ const settingsSchema = { type: 'object' };
18
+
19
+ const manifest = defineLokalApp({
20
+ name: 'Recipe Box',
21
+ slug: 'recipe-box',
22
+ collections: {
23
+ recipes: recipeSchema,
24
+ settings: settingsSchema,
25
+ },
26
+ });
27
+
28
+ const lokal = createLokalClient({
29
+ instanceUrl: 'https://files.example.com',
30
+ manifest,
31
+ });
32
+ ```
33
+
34
+ `defineLokalApp()` preserves collection names as literal TypeScript keys, so `lokal.collection('recipes')` is accepted and unknown collection names are rejected by TypeScript.
35
+
36
+ ## Sign in
37
+
38
+ ```ts
39
+ await lokal.auth.signIn({
40
+ email: 'user@example.com',
41
+ password: 'password',
42
+ tokenName: 'Recipe Box',
43
+ });
44
+ ```
45
+
46
+ Sign-in posts the manifest to `/api/platform/auth` and stores the returned app token in memory. You can also manage the token yourself:
47
+
48
+ ```ts
49
+ lokal.setToken(savedToken);
50
+ const token = lokal.getToken();
51
+ lokal.clearToken();
52
+ ```
53
+
54
+ The SDK does not persist tokens to `localStorage` or `sessionStorage`; consuming apps should choose their own persistence strategy.
55
+
56
+ ## Collection records
57
+
58
+ ```ts
59
+ const recipes = lokal.collection('recipes');
60
+
61
+ const created = await recipes.create({ title: 'Pancakes' }, { key: 'pancakes' });
62
+ const all = await recipes.list({ limit: 20 });
63
+ const one = await recipes.get(created.id);
64
+ const updated = await recipes.update(created.id, { title: 'Blueberry Pancakes' });
65
+ await recipes.delete(updated.id);
66
+ ```
67
+
68
+ ## Singleton values
69
+
70
+ Use singleton values for one JSON document per collection, such as settings.
71
+
72
+ ```ts
73
+ await lokal.collection('settings').setValue({ theme: 'dark' });
74
+ const settings = await lokal.collection('settings').getValue();
75
+ ```
76
+
77
+ ## Custom fetch
78
+
79
+ By default the SDK uses global `fetch`. Pass a custom implementation for tests or non-standard runtimes:
80
+
81
+ ```ts
82
+ const lokal = createLokalClient({
83
+ instanceUrl: 'https://files.example.com',
84
+ manifest,
85
+ fetch: customFetch,
86
+ });
87
+ ```
88
+
89
+ ## Errors
90
+
91
+ Missing tokens throw a clear `Error`. Non-2xx API responses throw `LokalApiError` with `status`, `message`, and parsed `body` when available.
92
+
93
+ ```ts
94
+ import { LokalApiError } from 'lokal-ts-sdk';
95
+
96
+ try {
97
+ await lokal.collection('recipes').list();
98
+ } catch (error) {
99
+ if (error instanceof LokalApiError) {
100
+ console.error(error.status, error.body);
101
+ }
102
+ }
103
+ ```
104
+
105
+ ## Contracts
106
+
107
+ The main Lokal repo owns the API contracts. Copies are kept in `contracts/`:
108
+
109
+ - `contracts/openapi.json`
110
+ - `contracts/lokal-manifest.schema.json`
111
+
112
+ Update them with:
113
+
114
+ ```sh
115
+ npm run contracts:update
116
+ ```
117
+
118
+ If the OpenAPI contract changes, update SDK types, wrappers, and tests in the same change.
119
+
120
+ ## Publishing
121
+
122
+ The package is configured for public npm publishing with `private: false`, `files: ["dist", "contracts", "README.md"]`, and `publishConfig.access: "public"`.
123
+
124
+ Do not publish automatically. When ready, run checks and publish explicitly:
125
+
126
+ ```sh
127
+ npm run check
128
+ npm publish
129
+ ```
@@ -0,0 +1,34 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://lokal.local/contracts/lokal-manifest.schema.json",
4
+ "title": "Lokal app manifest",
5
+ "description": "Serializable shape sent by external apps from defineLokalApp during platform authentication.",
6
+ "type": "object",
7
+ "required": ["slug", "collections"],
8
+ "properties": {
9
+ "name": {
10
+ "type": "string",
11
+ "minLength": 1
12
+ },
13
+ "slug": {
14
+ "type": "string",
15
+ "pattern": "^[a-z0-9-]+$"
16
+ },
17
+ "description": {
18
+ "type": "string"
19
+ },
20
+ "developerName": {
21
+ "type": "string"
22
+ },
23
+ "collections": {
24
+ "type": "object",
25
+ "minProperties": 1,
26
+ "propertyNames": {
27
+ "type": "string",
28
+ "pattern": "^[A-Za-z0-9_-]+$"
29
+ },
30
+ "additionalProperties": true
31
+ }
32
+ },
33
+ "additionalProperties": true
34
+ }
@@ -0,0 +1,717 @@
1
+ {
2
+ "openapi": "3.1.0",
3
+ "info": {
4
+ "title": "Lokal Platform API",
5
+ "version": "0.1.0",
6
+ "description": "Contract for external apps integrating with Lokal as an app-private JSON data store."
7
+ },
8
+ "servers": [
9
+ {
10
+ "url": "{lokalUrl}",
11
+ "variables": {
12
+ "lokalUrl": {
13
+ "default": "https://files.example.com"
14
+ }
15
+ }
16
+ }
17
+ ],
18
+ "tags": [
19
+ {
20
+ "name": "Discovery"
21
+ },
22
+ {
23
+ "name": "Auth"
24
+ },
25
+ {
26
+ "name": "Records"
27
+ },
28
+ {
29
+ "name": "Values"
30
+ }
31
+ ],
32
+ "paths": {
33
+ "/.well-known/lokal": {
34
+ "get": {
35
+ "tags": ["Discovery"],
36
+ "operationId": "getLokalDiscovery",
37
+ "summary": "Discover Lokal platform capabilities",
38
+ "responses": {
39
+ "200": {
40
+ "description": "Lokal discovery metadata",
41
+ "content": {
42
+ "application/json": {
43
+ "schema": {
44
+ "$ref": "#/components/schemas/DiscoveryResponse"
45
+ }
46
+ }
47
+ }
48
+ }
49
+ }
50
+ }
51
+ },
52
+ "/api/platform/auth": {
53
+ "post": {
54
+ "tags": ["Auth"],
55
+ "operationId": "authenticateApp",
56
+ "summary": "Authenticate a Lokal user and register an app manifest",
57
+ "requestBody": {
58
+ "required": true,
59
+ "content": {
60
+ "application/json": {
61
+ "schema": {
62
+ "$ref": "#/components/schemas/AuthRequest"
63
+ }
64
+ }
65
+ }
66
+ },
67
+ "responses": {
68
+ "200": {
69
+ "description": "User-bound app token and registered app metadata",
70
+ "content": {
71
+ "application/json": {
72
+ "schema": {
73
+ "$ref": "#/components/schemas/AuthResponse"
74
+ }
75
+ }
76
+ }
77
+ },
78
+ "400": {
79
+ "$ref": "#/components/responses/BadRequest"
80
+ },
81
+ "401": {
82
+ "$ref": "#/components/responses/Unauthorized"
83
+ }
84
+ }
85
+ }
86
+ },
87
+ "/api/platform/apps/{appSlug}/collections/{collection}/records": {
88
+ "get": {
89
+ "tags": ["Records"],
90
+ "operationId": "listRecords",
91
+ "summary": "List records in an app-private collection for the authenticated user",
92
+ "security": [
93
+ {
94
+ "bearerAuth": []
95
+ }
96
+ ],
97
+ "parameters": [
98
+ {
99
+ "$ref": "#/components/parameters/AppSlug"
100
+ },
101
+ {
102
+ "$ref": "#/components/parameters/Collection"
103
+ },
104
+ {
105
+ "name": "limit",
106
+ "in": "query",
107
+ "required": false,
108
+ "schema": {
109
+ "type": "integer",
110
+ "minimum": 1,
111
+ "maximum": 100,
112
+ "default": 50
113
+ }
114
+ }
115
+ ],
116
+ "responses": {
117
+ "200": {
118
+ "description": "Collection records",
119
+ "content": {
120
+ "application/json": {
121
+ "schema": {
122
+ "type": "array",
123
+ "items": {
124
+ "$ref": "#/components/schemas/DataRecord"
125
+ }
126
+ }
127
+ }
128
+ }
129
+ },
130
+ "400": {
131
+ "$ref": "#/components/responses/BadRequest"
132
+ },
133
+ "401": {
134
+ "$ref": "#/components/responses/Unauthorized"
135
+ }
136
+ }
137
+ },
138
+ "post": {
139
+ "tags": ["Records"],
140
+ "operationId": "createRecord",
141
+ "summary": "Create a record in an app-private collection for the authenticated user",
142
+ "security": [
143
+ {
144
+ "bearerAuth": []
145
+ }
146
+ ],
147
+ "parameters": [
148
+ {
149
+ "$ref": "#/components/parameters/AppSlug"
150
+ },
151
+ {
152
+ "$ref": "#/components/parameters/Collection"
153
+ }
154
+ ],
155
+ "requestBody": {
156
+ "required": true,
157
+ "content": {
158
+ "application/json": {
159
+ "schema": {
160
+ "$ref": "#/components/schemas/RecordWriteRequest"
161
+ }
162
+ }
163
+ }
164
+ },
165
+ "responses": {
166
+ "201": {
167
+ "description": "Created record",
168
+ "content": {
169
+ "application/json": {
170
+ "schema": {
171
+ "$ref": "#/components/schemas/DataRecord"
172
+ }
173
+ }
174
+ }
175
+ },
176
+ "400": {
177
+ "$ref": "#/components/responses/BadRequest"
178
+ },
179
+ "401": {
180
+ "$ref": "#/components/responses/Unauthorized"
181
+ }
182
+ }
183
+ }
184
+ },
185
+ "/api/platform/apps/{appSlug}/collections/{collection}/records/{recordId}": {
186
+ "get": {
187
+ "tags": ["Records"],
188
+ "operationId": "getRecord",
189
+ "summary": "Read one collection record",
190
+ "security": [
191
+ {
192
+ "bearerAuth": []
193
+ }
194
+ ],
195
+ "parameters": [
196
+ {
197
+ "$ref": "#/components/parameters/AppSlug"
198
+ },
199
+ {
200
+ "$ref": "#/components/parameters/Collection"
201
+ },
202
+ {
203
+ "$ref": "#/components/parameters/RecordId"
204
+ }
205
+ ],
206
+ "responses": {
207
+ "200": {
208
+ "description": "Record",
209
+ "content": {
210
+ "application/json": {
211
+ "schema": {
212
+ "$ref": "#/components/schemas/DataRecord"
213
+ }
214
+ }
215
+ }
216
+ },
217
+ "404": {
218
+ "$ref": "#/components/responses/NotFound"
219
+ }
220
+ }
221
+ },
222
+ "patch": {
223
+ "tags": ["Records"],
224
+ "operationId": "updateRecord",
225
+ "summary": "Update one collection record",
226
+ "security": [
227
+ {
228
+ "bearerAuth": []
229
+ }
230
+ ],
231
+ "parameters": [
232
+ {
233
+ "$ref": "#/components/parameters/AppSlug"
234
+ },
235
+ {
236
+ "$ref": "#/components/parameters/Collection"
237
+ },
238
+ {
239
+ "$ref": "#/components/parameters/RecordId"
240
+ }
241
+ ],
242
+ "requestBody": {
243
+ "required": true,
244
+ "content": {
245
+ "application/json": {
246
+ "schema": {
247
+ "$ref": "#/components/schemas/RecordWriteRequest"
248
+ }
249
+ }
250
+ }
251
+ },
252
+ "responses": {
253
+ "200": {
254
+ "description": "Updated record",
255
+ "content": {
256
+ "application/json": {
257
+ "schema": {
258
+ "$ref": "#/components/schemas/DataRecord"
259
+ }
260
+ }
261
+ }
262
+ },
263
+ "404": {
264
+ "$ref": "#/components/responses/NotFound"
265
+ }
266
+ }
267
+ },
268
+ "delete": {
269
+ "tags": ["Records"],
270
+ "operationId": "deleteRecord",
271
+ "summary": "Soft-delete one collection record",
272
+ "security": [
273
+ {
274
+ "bearerAuth": []
275
+ }
276
+ ],
277
+ "parameters": [
278
+ {
279
+ "$ref": "#/components/parameters/AppSlug"
280
+ },
281
+ {
282
+ "$ref": "#/components/parameters/Collection"
283
+ },
284
+ {
285
+ "$ref": "#/components/parameters/RecordId"
286
+ }
287
+ ],
288
+ "responses": {
289
+ "200": {
290
+ "description": "Delete result",
291
+ "content": {
292
+ "application/json": {
293
+ "schema": {
294
+ "$ref": "#/components/schemas/SuccessResponse"
295
+ }
296
+ }
297
+ }
298
+ },
299
+ "404": {
300
+ "$ref": "#/components/responses/NotFound"
301
+ }
302
+ }
303
+ }
304
+ },
305
+ "/api/platform/apps/{appSlug}/collections/{collection}/value": {
306
+ "get": {
307
+ "tags": ["Values"],
308
+ "operationId": "getCollectionValue",
309
+ "summary": "Read the singleton value for a collection",
310
+ "security": [
311
+ {
312
+ "bearerAuth": []
313
+ }
314
+ ],
315
+ "parameters": [
316
+ {
317
+ "$ref": "#/components/parameters/AppSlug"
318
+ },
319
+ {
320
+ "$ref": "#/components/parameters/Collection"
321
+ }
322
+ ],
323
+ "responses": {
324
+ "200": {
325
+ "description": "Stored JSON value or null",
326
+ "content": {
327
+ "application/json": {
328
+ "schema": {
329
+ "oneOf": [
330
+ {
331
+ "$ref": "#/components/schemas/JsonValue"
332
+ },
333
+ {
334
+ "type": "null"
335
+ }
336
+ ]
337
+ }
338
+ }
339
+ }
340
+ },
341
+ "401": {
342
+ "$ref": "#/components/responses/Unauthorized"
343
+ }
344
+ }
345
+ },
346
+ "put": {
347
+ "tags": ["Values"],
348
+ "operationId": "setCollectionValue",
349
+ "summary": "Set the singleton value for a collection",
350
+ "security": [
351
+ {
352
+ "bearerAuth": []
353
+ }
354
+ ],
355
+ "parameters": [
356
+ {
357
+ "$ref": "#/components/parameters/AppSlug"
358
+ },
359
+ {
360
+ "$ref": "#/components/parameters/Collection"
361
+ }
362
+ ],
363
+ "requestBody": {
364
+ "required": true,
365
+ "content": {
366
+ "application/json": {
367
+ "schema": {
368
+ "$ref": "#/components/schemas/ValueWriteRequest"
369
+ }
370
+ }
371
+ }
372
+ },
373
+ "responses": {
374
+ "200": {
375
+ "description": "Stored singleton record",
376
+ "content": {
377
+ "application/json": {
378
+ "schema": {
379
+ "$ref": "#/components/schemas/DataRecord"
380
+ }
381
+ }
382
+ }
383
+ },
384
+ "401": {
385
+ "$ref": "#/components/responses/Unauthorized"
386
+ }
387
+ }
388
+ }
389
+ }
390
+ },
391
+ "components": {
392
+ "securitySchemes": {
393
+ "bearerAuth": {
394
+ "type": "http",
395
+ "scheme": "bearer",
396
+ "bearerFormat": "lokal_app token"
397
+ }
398
+ },
399
+ "parameters": {
400
+ "AppSlug": {
401
+ "name": "appSlug",
402
+ "in": "path",
403
+ "required": true,
404
+ "schema": {
405
+ "type": "string",
406
+ "pattern": "^[a-z0-9-]+$"
407
+ }
408
+ },
409
+ "Collection": {
410
+ "name": "collection",
411
+ "in": "path",
412
+ "required": true,
413
+ "schema": {
414
+ "type": "string",
415
+ "pattern": "^[A-Za-z0-9_-]+$"
416
+ }
417
+ },
418
+ "RecordId": {
419
+ "name": "recordId",
420
+ "in": "path",
421
+ "required": true,
422
+ "schema": {
423
+ "type": "string"
424
+ }
425
+ }
426
+ },
427
+ "responses": {
428
+ "BadRequest": {
429
+ "description": "Invalid request",
430
+ "content": {
431
+ "application/json": {
432
+ "schema": {
433
+ "$ref": "#/components/schemas/ErrorResponse"
434
+ }
435
+ }
436
+ }
437
+ },
438
+ "Unauthorized": {
439
+ "description": "Missing or invalid credentials"
440
+ },
441
+ "NotFound": {
442
+ "description": "Resource not found"
443
+ }
444
+ },
445
+ "schemas": {
446
+ "JsonValue": {
447
+ "description": "Any JSON value. App-specific shape is validated by the external app or SDK.",
448
+ "nullable": true
449
+ },
450
+ "LokalAppManifest": {
451
+ "$ref": "./lokal-manifest.schema.json"
452
+ },
453
+ "AuthRequest": {
454
+ "type": "object",
455
+ "required": ["email", "password", "manifest"],
456
+ "properties": {
457
+ "email": {
458
+ "type": "string",
459
+ "format": "email"
460
+ },
461
+ "password": {
462
+ "type": "string"
463
+ },
464
+ "manifest": {
465
+ "$ref": "#/components/schemas/LokalAppManifest"
466
+ },
467
+ "lokalManifest": {
468
+ "$ref": "#/components/schemas/LokalAppManifest"
469
+ },
470
+ "app": {
471
+ "$ref": "#/components/schemas/LokalAppManifest"
472
+ },
473
+ "tokenName": {
474
+ "type": "string"
475
+ }
476
+ },
477
+ "additionalProperties": true
478
+ },
479
+ "AuthResponse": {
480
+ "type": "object",
481
+ "required": ["apiBase", "app", "user", "token"],
482
+ "properties": {
483
+ "apiBase": {
484
+ "type": "string",
485
+ "format": "uri"
486
+ },
487
+ "app": {
488
+ "$ref": "#/components/schemas/RegisteredApp"
489
+ },
490
+ "user": {
491
+ "$ref": "#/components/schemas/LokalUser"
492
+ },
493
+ "token": {
494
+ "$ref": "#/components/schemas/AppTokenWithSecret"
495
+ }
496
+ }
497
+ },
498
+ "DiscoveryResponse": {
499
+ "type": "object",
500
+ "required": ["issuer", "apiBase", "features"],
501
+ "properties": {
502
+ "issuer": {
503
+ "type": "string",
504
+ "format": "uri"
505
+ },
506
+ "apiBase": {
507
+ "type": "string",
508
+ "format": "uri"
509
+ },
510
+ "contracts": {
511
+ "type": "object",
512
+ "properties": {
513
+ "openapi": {
514
+ "type": "string"
515
+ },
516
+ "manifestSchema": {
517
+ "type": "string"
518
+ }
519
+ },
520
+ "additionalProperties": false
521
+ },
522
+ "features": {
523
+ "type": "object",
524
+ "properties": {
525
+ "auth": {
526
+ "type": "array",
527
+ "items": {
528
+ "type": "string"
529
+ }
530
+ },
531
+ "data": {
532
+ "type": "array",
533
+ "items": {
534
+ "type": "string"
535
+ }
536
+ },
537
+ "files": {
538
+ "type": "boolean"
539
+ }
540
+ },
541
+ "additionalProperties": true
542
+ }
543
+ },
544
+ "additionalProperties": false
545
+ },
546
+ "RegisteredApp": {
547
+ "type": "object",
548
+ "required": ["id", "name", "slug", "clientId", "manifest"],
549
+ "properties": {
550
+ "id": {
551
+ "type": "string"
552
+ },
553
+ "name": {
554
+ "type": "string"
555
+ },
556
+ "slug": {
557
+ "type": "string"
558
+ },
559
+ "clientId": {
560
+ "type": "string"
561
+ },
562
+ "manifest": {
563
+ "$ref": "#/components/schemas/LokalAppManifest"
564
+ }
565
+ },
566
+ "additionalProperties": true
567
+ },
568
+ "LokalUser": {
569
+ "type": "object",
570
+ "required": ["id", "name", "email", "role"],
571
+ "properties": {
572
+ "id": {
573
+ "type": "string"
574
+ },
575
+ "name": {
576
+ "type": "string"
577
+ },
578
+ "email": {
579
+ "type": "string",
580
+ "format": "email"
581
+ },
582
+ "role": {
583
+ "type": "string"
584
+ }
585
+ }
586
+ },
587
+ "AppTokenWithSecret": {
588
+ "type": "object",
589
+ "required": ["id", "name", "scopes", "createdAt", "type", "rawToken"],
590
+ "properties": {
591
+ "id": {
592
+ "type": "string"
593
+ },
594
+ "name": {
595
+ "type": "string"
596
+ },
597
+ "scopes": {
598
+ "type": "array",
599
+ "items": {
600
+ "type": "string",
601
+ "enum": ["data:read", "data:write"]
602
+ }
603
+ },
604
+ "createdAt": {
605
+ "type": "string",
606
+ "format": "date-time"
607
+ },
608
+ "type": {
609
+ "type": "string",
610
+ "const": "Bearer"
611
+ },
612
+ "rawToken": {
613
+ "type": "string",
614
+ "pattern": "^lokal_app_"
615
+ }
616
+ }
617
+ },
618
+ "DataRecord": {
619
+ "type": "object",
620
+ "required": ["id", "appId", "ownerId", "collection", "value", "version", "createdAt", "updatedAt"],
621
+ "properties": {
622
+ "id": {
623
+ "type": "string"
624
+ },
625
+ "appId": {
626
+ "type": "string"
627
+ },
628
+ "ownerId": {
629
+ "type": "string"
630
+ },
631
+ "collection": {
632
+ "type": "string"
633
+ },
634
+ "key": {
635
+ "type": ["string", "null"]
636
+ },
637
+ "value": {
638
+ "$ref": "#/components/schemas/JsonValue"
639
+ },
640
+ "version": {
641
+ "type": "integer"
642
+ },
643
+ "createdAt": {
644
+ "type": "string",
645
+ "format": "date-time"
646
+ },
647
+ "updatedAt": {
648
+ "type": "string",
649
+ "format": "date-time"
650
+ },
651
+ "deletedAt": {
652
+ "type": ["string", "null"],
653
+ "format": "date-time"
654
+ }
655
+ },
656
+ "additionalProperties": true
657
+ },
658
+ "RecordWriteRequest": {
659
+ "description": "Either send { value, key? } or send any JSON value directly. The SDK should prefer { value }.",
660
+ "oneOf": [
661
+ {
662
+ "type": "object",
663
+ "properties": {
664
+ "key": {
665
+ "type": ["string", "null"]
666
+ },
667
+ "value": {
668
+ "$ref": "#/components/schemas/JsonValue"
669
+ }
670
+ },
671
+ "required": ["value"],
672
+ "additionalProperties": true
673
+ },
674
+ {
675
+ "$ref": "#/components/schemas/JsonValue"
676
+ }
677
+ ]
678
+ },
679
+ "ValueWriteRequest": {
680
+ "description": "Either send { value } or send any JSON value directly. The SDK should prefer { value }.",
681
+ "oneOf": [
682
+ {
683
+ "type": "object",
684
+ "properties": {
685
+ "value": {
686
+ "$ref": "#/components/schemas/JsonValue"
687
+ }
688
+ },
689
+ "required": ["value"],
690
+ "additionalProperties": true
691
+ },
692
+ {
693
+ "$ref": "#/components/schemas/JsonValue"
694
+ }
695
+ ]
696
+ },
697
+ "SuccessResponse": {
698
+ "type": "object",
699
+ "required": ["success"],
700
+ "properties": {
701
+ "success": {
702
+ "type": "boolean"
703
+ }
704
+ }
705
+ },
706
+ "ErrorResponse": {
707
+ "type": "object",
708
+ "required": ["error"],
709
+ "properties": {
710
+ "error": {
711
+ "type": "string"
712
+ }
713
+ }
714
+ }
715
+ }
716
+ }
717
+ }
@@ -0,0 +1,3 @@
1
+ import type { CreateLokalClientOptions, LokalAppManifest, LokalClient } from './types';
2
+ export declare function createLokalClient<Manifest extends LokalAppManifest>(options: CreateLokalClientOptions<Manifest>): LokalClient<Manifest>;
3
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAGV,wBAAwB,EAGxB,gBAAgB,EAChB,WAAW,EAGZ,MAAM,SAAS,CAAC;AAMjB,wBAAgB,iBAAiB,CAAC,QAAQ,SAAS,gBAAgB,EACjE,OAAO,EAAE,wBAAwB,CAAC,QAAQ,CAAC,GAC1C,WAAW,CAAC,QAAQ,CAAC,CA4HvB"}
@@ -0,0 +1,6 @@
1
+ export declare class LokalApiError extends Error {
2
+ readonly status: number;
3
+ readonly body: unknown;
4
+ constructor(status: number, message: string, body?: unknown);
5
+ }
6
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,aAAc,SAAQ,KAAK;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;gBAEX,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO;CAM5D"}
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=class extends Error{status;body;constructor(e,t,n){super(t),this.name=`LokalApiError`,this.status=e,this.body=n}};function t(t){let a=n(t.instanceUrl),o=t.fetch??globalThis.fetch,s=t.token;if(!o)throw Error(`A fetch implementation is required. Pass fetch in createLokalClient options.`);async function c(t,n={}){let{requiresToken:c=!0,headers:l,...u}=n,d=new Headers(l);if(d.set(`Accept`,`application/json`),u.body!==void 0&&!d.has(`Content-Type`)&&d.set(`Content-Type`,`application/json`),c){if(!s)throw Error(`A Lokal app token is required. Sign in first or call setToken().`);d.set(`Authorization`,`Bearer ${s}`)}let f=await o(`${a}${t}`,{...u,headers:d}),p=await r(f);if(!f.ok)throw new e(f.status,i(f,p),p);return p}let l={async signIn(e){let n=await c(`/api/platform/auth`,{method:`POST`,requiresToken:!1,body:JSON.stringify({email:e.email,password:e.password,manifest:t.manifest,...e.tokenName?{tokenName:e.tokenName}:{}})});return s=n.token.rawToken,n}};function u(e){let n=`/api/platform/apps/${encodeURIComponent(t.manifest.slug)}/collections/${encodeURIComponent(e)}`;return{list(e){let t=new URLSearchParams;e?.limit!==void 0&&t.set(`limit`,String(e.limit));let r=t.toString();return c(`${n}/records${r?`?${r}`:``}`)},create(e,t){return c(`${n}/records`,{method:`POST`,body:JSON.stringify({value:e,...t?.key===void 0?{}:{key:t.key}})})},get(e){return c(`${n}/records/${encodeURIComponent(e)}`)},update(e,t,r){return c(`${n}/records/${encodeURIComponent(e)}`,{method:`PATCH`,body:JSON.stringify({value:t,...r&&`key`in r?{key:r.key}:{}})})},delete(e){return c(`${n}/records/${encodeURIComponent(e)}`,{method:`DELETE`})},getValue(){return c(`${n}/value`)},setValue(e){return c(`${n}/value`,{method:`PUT`,body:JSON.stringify({value:e})})}}}return{auth:l,setToken(e){s=e},getToken(){return s},clearToken(){s=void 0},collection:u}}function n(e){let t=e.trim().replace(/\/+$/,``);if(!t)throw Error(`instanceUrl is required.`);return t}async function r(e){let t=await e.text();if(t)try{return JSON.parse(t)}catch{return t}}function i(e,t){if(t&&typeof t==`object`){if(`error`in t&&typeof t.error==`string`)return t.error;if(`message`in t&&typeof t.message==`string`)return t.message}return`Lokal API request failed with status ${e.status}`}function a(e){return e}exports.LokalApiError=e,exports.createLokalClient=t,exports.defineLokalApp=a;
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../src/errors.ts","../src/client.ts","../src/manifest.ts"],"sourcesContent":["export class LokalApiError extends Error {\n readonly status: number;\n readonly body: unknown;\n\n constructor(status: number, message: string, body?: unknown) {\n super(message);\n this.name = 'LokalApiError';\n this.status = status;\n this.body = body;\n }\n}\n","import { LokalApiError } from './errors';\nimport type {\n AuthResponse,\n AuthSignInOptions,\n CreateLokalClientOptions,\n DataRecord,\n JsonValue,\n LokalAppManifest,\n LokalClient,\n LokalCollectionClient,\n SuccessResponse,\n} from './types';\n\ninterface RequestOptions extends RequestInit {\n requiresToken?: boolean;\n}\n\nexport function createLokalClient<Manifest extends LokalAppManifest>(\n options: CreateLokalClientOptions<Manifest>,\n): LokalClient<Manifest> {\n const instanceUrl = normalizeInstanceUrl(options.instanceUrl);\n const fetcher = options.fetch ?? globalThis.fetch;\n let token = options.token;\n\n if (!fetcher) {\n throw new Error('A fetch implementation is required. Pass fetch in createLokalClient options.');\n }\n\n async function request<Result>(path: string, requestOptions: RequestOptions = {}): Promise<Result> {\n const { requiresToken = true, headers, ...init } = requestOptions;\n const requestHeaders = new Headers(headers);\n\n requestHeaders.set('Accept', 'application/json');\n\n if (init.body !== undefined && !requestHeaders.has('Content-Type')) {\n requestHeaders.set('Content-Type', 'application/json');\n }\n\n if (requiresToken) {\n if (!token) {\n throw new Error('A Lokal app token is required. Sign in first or call setToken().');\n }\n requestHeaders.set('Authorization', `Bearer ${token}`);\n }\n\n const response = await fetcher(`${instanceUrl}${path}`, {\n ...init,\n headers: requestHeaders,\n });\n\n const body = await readBody(response);\n\n if (!response.ok) {\n throw new LokalApiError(response.status, getErrorMessage(response, body), body);\n }\n\n return body as Result;\n }\n\n const auth = {\n async signIn(signInOptions: AuthSignInOptions): Promise<AuthResponse<Manifest>> {\n const response = await request<AuthResponse<Manifest>>('/api/platform/auth', {\n method: 'POST',\n requiresToken: false,\n body: JSON.stringify({\n email: signInOptions.email,\n password: signInOptions.password,\n manifest: options.manifest,\n ...(signInOptions.tokenName ? { tokenName: signInOptions.tokenName } : {}),\n }),\n });\n\n token = response.token.rawToken;\n return response;\n },\n };\n\n function collection(name: string): LokalCollectionClient {\n const collectionPath = `/api/platform/apps/${encodeURIComponent(\n options.manifest.slug,\n )}/collections/${encodeURIComponent(name)}`;\n\n return {\n list(listOptions) {\n const search = new URLSearchParams();\n if (listOptions?.limit !== undefined) {\n search.set('limit', String(listOptions.limit));\n }\n\n const query = search.toString();\n return request<DataRecord[]>(`${collectionPath}/records${query ? `?${query}` : ''}`);\n },\n create(value: JsonValue, writeOptions) {\n return request<DataRecord>(`${collectionPath}/records`, {\n method: 'POST',\n body: JSON.stringify({\n value,\n ...(writeOptions?.key !== undefined ? { key: writeOptions.key } : {}),\n }),\n });\n },\n get(recordId: string) {\n return request<DataRecord>(`${collectionPath}/records/${encodeURIComponent(recordId)}`);\n },\n update(recordId: string, value: JsonValue, updateOptions) {\n return request<DataRecord>(`${collectionPath}/records/${encodeURIComponent(recordId)}`, {\n method: 'PATCH',\n body: JSON.stringify({\n value,\n ...(updateOptions && 'key' in updateOptions ? { key: updateOptions.key } : {}),\n }),\n });\n },\n delete(recordId: string) {\n return request<SuccessResponse>(`${collectionPath}/records/${encodeURIComponent(recordId)}`, {\n method: 'DELETE',\n });\n },\n getValue() {\n return request<JsonValue | null>(`${collectionPath}/value`);\n },\n setValue(value: JsonValue) {\n return request<DataRecord>(`${collectionPath}/value`, {\n method: 'PUT',\n body: JSON.stringify({ value }),\n });\n },\n };\n }\n\n return {\n auth,\n setToken(nextToken: string) {\n token = nextToken;\n },\n getToken() {\n return token;\n },\n clearToken() {\n token = undefined;\n },\n collection,\n } as LokalClient<Manifest>;\n}\n\nfunction normalizeInstanceUrl(instanceUrl: string): string {\n const normalized = instanceUrl.trim().replace(/\\/+$/, '');\n if (!normalized) {\n throw new Error('instanceUrl is required.');\n }\n return normalized;\n}\n\nasync function readBody(response: Response): Promise<unknown> {\n const text = await response.text();\n if (!text) {\n return undefined;\n }\n\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n}\n\nfunction getErrorMessage(response: Response, body: unknown): string {\n if (body && typeof body === 'object') {\n if ('error' in body && typeof body.error === 'string') {\n return body.error;\n }\n if ('message' in body && typeof body.message === 'string') {\n return body.message;\n }\n }\n\n return `Lokal API request failed with status ${response.status}`;\n}\n","import type { LokalAppManifest, LokalCollections } from './types';\n\nexport function defineLokalApp<const Collections extends LokalCollections>(\n manifest: LokalAppManifest<Collections>,\n): LokalAppManifest<Collections> {\n return manifest;\n}\n"],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export { createLokalClient } from './client';
2
+ export { LokalApiError } from './errors';
3
+ export { defineLokalApp } from './manifest';
4
+ export type { AppTokenWithSecret, AuthResponse, AuthSignInOptions, CreateLokalClientOptions, DataRecord, JsonValue, LokalAppManifest, LokalClient, LokalCollectionClient, LokalCollections, LokalUser, RegisteredApp, SuccessResponse, } from './types';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,YAAY,EACV,kBAAkB,EAClB,YAAY,EACZ,iBAAiB,EACjB,wBAAwB,EACxB,UAAU,EACV,SAAS,EACT,gBAAgB,EAChB,WAAW,EACX,qBAAqB,EACrB,gBAAgB,EAChB,SAAS,EACT,aAAa,EACb,eAAe,GAChB,MAAM,SAAS,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,126 @@
1
+ //#region src/errors.ts
2
+ var e = class extends Error {
3
+ status;
4
+ body;
5
+ constructor(e, t, n) {
6
+ super(t), this.name = "LokalApiError", this.status = e, this.body = n;
7
+ }
8
+ };
9
+ //#endregion
10
+ //#region src/client.ts
11
+ function t(t) {
12
+ let a = n(t.instanceUrl), o = t.fetch ?? globalThis.fetch, s = t.token;
13
+ if (!o) throw Error("A fetch implementation is required. Pass fetch in createLokalClient options.");
14
+ async function c(t, n = {}) {
15
+ let { requiresToken: c = !0, headers: l, ...u } = n, d = new Headers(l);
16
+ if (d.set("Accept", "application/json"), u.body !== void 0 && !d.has("Content-Type") && d.set("Content-Type", "application/json"), c) {
17
+ if (!s) throw Error("A Lokal app token is required. Sign in first or call setToken().");
18
+ d.set("Authorization", `Bearer ${s}`);
19
+ }
20
+ let f = await o(`${a}${t}`, {
21
+ ...u,
22
+ headers: d
23
+ }), p = await r(f);
24
+ if (!f.ok) throw new e(f.status, i(f, p), p);
25
+ return p;
26
+ }
27
+ let l = { async signIn(e) {
28
+ let n = await c("/api/platform/auth", {
29
+ method: "POST",
30
+ requiresToken: !1,
31
+ body: JSON.stringify({
32
+ email: e.email,
33
+ password: e.password,
34
+ manifest: t.manifest,
35
+ ...e.tokenName ? { tokenName: e.tokenName } : {}
36
+ })
37
+ });
38
+ return s = n.token.rawToken, n;
39
+ } };
40
+ function u(e) {
41
+ let n = `/api/platform/apps/${encodeURIComponent(t.manifest.slug)}/collections/${encodeURIComponent(e)}`;
42
+ return {
43
+ list(e) {
44
+ let t = new URLSearchParams();
45
+ e?.limit !== void 0 && t.set("limit", String(e.limit));
46
+ let r = t.toString();
47
+ return c(`${n}/records${r ? `?${r}` : ""}`);
48
+ },
49
+ create(e, t) {
50
+ return c(`${n}/records`, {
51
+ method: "POST",
52
+ body: JSON.stringify({
53
+ value: e,
54
+ ...t?.key === void 0 ? {} : { key: t.key }
55
+ })
56
+ });
57
+ },
58
+ get(e) {
59
+ return c(`${n}/records/${encodeURIComponent(e)}`);
60
+ },
61
+ update(e, t, r) {
62
+ return c(`${n}/records/${encodeURIComponent(e)}`, {
63
+ method: "PATCH",
64
+ body: JSON.stringify({
65
+ value: t,
66
+ ...r && "key" in r ? { key: r.key } : {}
67
+ })
68
+ });
69
+ },
70
+ delete(e) {
71
+ return c(`${n}/records/${encodeURIComponent(e)}`, { method: "DELETE" });
72
+ },
73
+ getValue() {
74
+ return c(`${n}/value`);
75
+ },
76
+ setValue(e) {
77
+ return c(`${n}/value`, {
78
+ method: "PUT",
79
+ body: JSON.stringify({ value: e })
80
+ });
81
+ }
82
+ };
83
+ }
84
+ return {
85
+ auth: l,
86
+ setToken(e) {
87
+ s = e;
88
+ },
89
+ getToken() {
90
+ return s;
91
+ },
92
+ clearToken() {
93
+ s = void 0;
94
+ },
95
+ collection: u
96
+ };
97
+ }
98
+ function n(e) {
99
+ let t = e.trim().replace(/\/+$/, "");
100
+ if (!t) throw Error("instanceUrl is required.");
101
+ return t;
102
+ }
103
+ async function r(e) {
104
+ let t = await e.text();
105
+ if (t) try {
106
+ return JSON.parse(t);
107
+ } catch {
108
+ return t;
109
+ }
110
+ }
111
+ function i(e, t) {
112
+ if (t && typeof t == "object") {
113
+ if ("error" in t && typeof t.error == "string") return t.error;
114
+ if ("message" in t && typeof t.message == "string") return t.message;
115
+ }
116
+ return `Lokal API request failed with status ${e.status}`;
117
+ }
118
+ //#endregion
119
+ //#region src/manifest.ts
120
+ function a(e) {
121
+ return e;
122
+ }
123
+ //#endregion
124
+ export { e as LokalApiError, t as createLokalClient, a as defineLokalApp };
125
+
126
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/errors.ts","../src/client.ts","../src/manifest.ts"],"sourcesContent":["export class LokalApiError extends Error {\n readonly status: number;\n readonly body: unknown;\n\n constructor(status: number, message: string, body?: unknown) {\n super(message);\n this.name = 'LokalApiError';\n this.status = status;\n this.body = body;\n }\n}\n","import { LokalApiError } from './errors';\nimport type {\n AuthResponse,\n AuthSignInOptions,\n CreateLokalClientOptions,\n DataRecord,\n JsonValue,\n LokalAppManifest,\n LokalClient,\n LokalCollectionClient,\n SuccessResponse,\n} from './types';\n\ninterface RequestOptions extends RequestInit {\n requiresToken?: boolean;\n}\n\nexport function createLokalClient<Manifest extends LokalAppManifest>(\n options: CreateLokalClientOptions<Manifest>,\n): LokalClient<Manifest> {\n const instanceUrl = normalizeInstanceUrl(options.instanceUrl);\n const fetcher = options.fetch ?? globalThis.fetch;\n let token = options.token;\n\n if (!fetcher) {\n throw new Error('A fetch implementation is required. Pass fetch in createLokalClient options.');\n }\n\n async function request<Result>(path: string, requestOptions: RequestOptions = {}): Promise<Result> {\n const { requiresToken = true, headers, ...init } = requestOptions;\n const requestHeaders = new Headers(headers);\n\n requestHeaders.set('Accept', 'application/json');\n\n if (init.body !== undefined && !requestHeaders.has('Content-Type')) {\n requestHeaders.set('Content-Type', 'application/json');\n }\n\n if (requiresToken) {\n if (!token) {\n throw new Error('A Lokal app token is required. Sign in first or call setToken().');\n }\n requestHeaders.set('Authorization', `Bearer ${token}`);\n }\n\n const response = await fetcher(`${instanceUrl}${path}`, {\n ...init,\n headers: requestHeaders,\n });\n\n const body = await readBody(response);\n\n if (!response.ok) {\n throw new LokalApiError(response.status, getErrorMessage(response, body), body);\n }\n\n return body as Result;\n }\n\n const auth = {\n async signIn(signInOptions: AuthSignInOptions): Promise<AuthResponse<Manifest>> {\n const response = await request<AuthResponse<Manifest>>('/api/platform/auth', {\n method: 'POST',\n requiresToken: false,\n body: JSON.stringify({\n email: signInOptions.email,\n password: signInOptions.password,\n manifest: options.manifest,\n ...(signInOptions.tokenName ? { tokenName: signInOptions.tokenName } : {}),\n }),\n });\n\n token = response.token.rawToken;\n return response;\n },\n };\n\n function collection(name: string): LokalCollectionClient {\n const collectionPath = `/api/platform/apps/${encodeURIComponent(\n options.manifest.slug,\n )}/collections/${encodeURIComponent(name)}`;\n\n return {\n list(listOptions) {\n const search = new URLSearchParams();\n if (listOptions?.limit !== undefined) {\n search.set('limit', String(listOptions.limit));\n }\n\n const query = search.toString();\n return request<DataRecord[]>(`${collectionPath}/records${query ? `?${query}` : ''}`);\n },\n create(value: JsonValue, writeOptions) {\n return request<DataRecord>(`${collectionPath}/records`, {\n method: 'POST',\n body: JSON.stringify({\n value,\n ...(writeOptions?.key !== undefined ? { key: writeOptions.key } : {}),\n }),\n });\n },\n get(recordId: string) {\n return request<DataRecord>(`${collectionPath}/records/${encodeURIComponent(recordId)}`);\n },\n update(recordId: string, value: JsonValue, updateOptions) {\n return request<DataRecord>(`${collectionPath}/records/${encodeURIComponent(recordId)}`, {\n method: 'PATCH',\n body: JSON.stringify({\n value,\n ...(updateOptions && 'key' in updateOptions ? { key: updateOptions.key } : {}),\n }),\n });\n },\n delete(recordId: string) {\n return request<SuccessResponse>(`${collectionPath}/records/${encodeURIComponent(recordId)}`, {\n method: 'DELETE',\n });\n },\n getValue() {\n return request<JsonValue | null>(`${collectionPath}/value`);\n },\n setValue(value: JsonValue) {\n return request<DataRecord>(`${collectionPath}/value`, {\n method: 'PUT',\n body: JSON.stringify({ value }),\n });\n },\n };\n }\n\n return {\n auth,\n setToken(nextToken: string) {\n token = nextToken;\n },\n getToken() {\n return token;\n },\n clearToken() {\n token = undefined;\n },\n collection,\n } as LokalClient<Manifest>;\n}\n\nfunction normalizeInstanceUrl(instanceUrl: string): string {\n const normalized = instanceUrl.trim().replace(/\\/+$/, '');\n if (!normalized) {\n throw new Error('instanceUrl is required.');\n }\n return normalized;\n}\n\nasync function readBody(response: Response): Promise<unknown> {\n const text = await response.text();\n if (!text) {\n return undefined;\n }\n\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n}\n\nfunction getErrorMessage(response: Response, body: unknown): string {\n if (body && typeof body === 'object') {\n if ('error' in body && typeof body.error === 'string') {\n return body.error;\n }\n if ('message' in body && typeof body.message === 'string') {\n return body.message;\n }\n }\n\n return `Lokal API request failed with status ${response.status}`;\n}\n","import type { LokalAppManifest, LokalCollections } from './types';\n\nexport function defineLokalApp<const Collections extends LokalCollections>(\n manifest: LokalAppManifest<Collections>,\n): LokalAppManifest<Collections> {\n return manifest;\n}\n"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { LokalAppManifest, LokalCollections } from './types';
2
+ export declare function defineLokalApp<const Collections extends LokalCollections>(manifest: LokalAppManifest<Collections>): LokalAppManifest<Collections>;
3
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAElE,wBAAgB,cAAc,CAAC,KAAK,CAAC,WAAW,SAAS,gBAAgB,EACvE,QAAQ,EAAE,gBAAgB,CAAC,WAAW,CAAC,GACtC,gBAAgB,CAAC,WAAW,CAAC,CAE/B"}
@@ -0,0 +1,96 @@
1
+ export type JsonPrimitive = string | number | boolean | null;
2
+ export type JsonValue = JsonPrimitive | JsonValue[] | {
3
+ [key: string]: JsonValue;
4
+ };
5
+ export type LokalCollections = Record<string, unknown>;
6
+ export interface LokalAppManifest<Collections extends LokalCollections = LokalCollections> {
7
+ name?: string;
8
+ slug: string;
9
+ description?: string;
10
+ developerName?: string;
11
+ collections: Collections;
12
+ }
13
+ export interface LokalUser {
14
+ id: string;
15
+ name: string;
16
+ email: string;
17
+ role: string;
18
+ }
19
+ export interface RegisteredApp<Manifest extends LokalAppManifest = LokalAppManifest> {
20
+ id: string;
21
+ name: string;
22
+ slug: string;
23
+ clientId: string;
24
+ manifest: Manifest;
25
+ [key: string]: unknown;
26
+ }
27
+ export interface AppTokenWithSecret {
28
+ id: string;
29
+ name: string;
30
+ scopes: Array<'data:read' | 'data:write'>;
31
+ createdAt: string;
32
+ type: 'Bearer';
33
+ rawToken: string;
34
+ }
35
+ export interface AuthResponse<Manifest extends LokalAppManifest = LokalAppManifest> {
36
+ apiBase: string;
37
+ app: RegisteredApp<Manifest>;
38
+ user: LokalUser;
39
+ token: AppTokenWithSecret;
40
+ }
41
+ export interface AuthSignInOptions {
42
+ email: string;
43
+ password: string;
44
+ tokenName?: string;
45
+ }
46
+ export interface DataRecord<Value extends JsonValue = JsonValue> {
47
+ id: string;
48
+ appId: string;
49
+ ownerId: string;
50
+ collection: string;
51
+ value: Value;
52
+ version: number;
53
+ createdAt: string;
54
+ updatedAt: string;
55
+ deletedAt?: string | null;
56
+ key?: string | null;
57
+ [key: string]: unknown;
58
+ }
59
+ export interface SuccessResponse {
60
+ success: boolean;
61
+ }
62
+ export interface ListOptions {
63
+ limit?: number;
64
+ }
65
+ export interface WriteOptions {
66
+ key?: string;
67
+ }
68
+ export interface UpdateOptions {
69
+ key?: string | null;
70
+ }
71
+ export interface LokalCollectionClient {
72
+ list(options?: ListOptions): Promise<DataRecord[]>;
73
+ create(value: JsonValue, options?: WriteOptions): Promise<DataRecord>;
74
+ get(recordId: string): Promise<DataRecord>;
75
+ update(recordId: string, value: JsonValue, options?: UpdateOptions): Promise<DataRecord>;
76
+ delete(recordId: string): Promise<SuccessResponse>;
77
+ getValue(): Promise<JsonValue | null>;
78
+ setValue(value: JsonValue): Promise<DataRecord>;
79
+ }
80
+ export type CollectionName<Manifest extends LokalAppManifest> = Extract<keyof Manifest['collections'], string>;
81
+ export interface LokalClient<Manifest extends LokalAppManifest = LokalAppManifest> {
82
+ auth: {
83
+ signIn(options: AuthSignInOptions): Promise<AuthResponse<Manifest>>;
84
+ };
85
+ setToken(token: string): void;
86
+ getToken(): string | undefined;
87
+ clearToken(): void;
88
+ collection(name: CollectionName<Manifest>): LokalCollectionClient;
89
+ }
90
+ export interface CreateLokalClientOptions<Manifest extends LokalAppManifest = LokalAppManifest> {
91
+ instanceUrl: string;
92
+ manifest: Manifest;
93
+ token?: string;
94
+ fetch?: typeof fetch;
95
+ }
96
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@lokal-server/ts-sdk",
3
+ "version": "0.1.1",
4
+ "description": "TypeScript SDK for Lokal external apps.",
5
+ "license": "MIT",
6
+ "private": false,
7
+ "type": "module",
8
+ "main": "./dist/index.cjs",
9
+ "module": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js",
15
+ "require": "./dist/index.cjs"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "contracts",
21
+ "README.md"
22
+ ],
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "scripts": {
27
+ "dev": "vite build --watch",
28
+ "build": "vite build && tsc -p tsconfig.build.json",
29
+ "test": "vitest run",
30
+ "typecheck": "tsc --noEmit",
31
+ "lint": "eslint .",
32
+ "check": "npm run typecheck && npm run lint && npm run test",
33
+ "contracts:update": "node scripts/update-contracts.mjs"
34
+ },
35
+ "devDependencies": {
36
+ "@eslint/js": "^10.0.1",
37
+ "@types/node": "^26.0.0",
38
+ "eslint": "^10.5.0",
39
+ "globals": "^17.7.0",
40
+ "typescript": "^6.0.3",
41
+ "typescript-eslint": "^8.62.0",
42
+ "vite": "^8.1.0",
43
+ "vitest": "^4.1.9"
44
+ }
45
+ }