@archlast/server 0.1.1 → 0.1.2

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,959 +1,100 @@
1
1
  # @archlast/server
2
2
 
3
- Type-safe server definitions and runtime helpers for Archlast. This package provides schema definition with validators, function definitions (query, mutation, action, http, webhook, rpc), background job scheduling, and shared types. The runtime server is delivered via Docker image.
3
+ Type-safe server definitions and runtime helpers for Archlast. This package is
4
+ used to define schema, functions, and shared types, and it ships Docker
5
+ templates for runtime deployment.
4
6
 
5
- ## Table of Contents
6
-
7
- - [Installation](#installation)
8
- - [Library vs Runtime](#library-vs-runtime)
9
- - [Schema Definition](#schema-definition)
10
- - [Field Types (Validators)](#field-types-validators)
11
- - [Field Modifiers](#field-modifiers)
12
- - [Relationships](#relationships)
13
- - [Indexes](#indexes)
14
- - [Function Definitions](#function-definitions)
15
- - [Query](#query)
16
- - [Mutation](#mutation)
17
- - [Action](#action)
18
- - [HTTP Routes](#http-routes)
19
- - [Webhooks](#webhooks)
20
- - [RPC (tRPC-style)](#rpc-trpc-style)
21
- - [Context Objects](#context-objects)
22
- - [Authentication & Permissions](#authentication--permissions)
23
- - [Background Jobs](#background-jobs)
24
- - [Storage Types](#storage-types)
25
- - [Subpath Exports](#subpath-exports)
26
- - [Environment Variables](#environment-variables)
27
- - [Docker Templates](#docker-templates)
28
-
29
- ## Installation
7
+ ## Install
30
8
 
31
9
  ```bash
32
10
  npm install -D @archlast/server
33
-
34
- # Or with other package managers
35
- pnpm add -D @archlast/server
36
- yarn add -D @archlast/server
37
- bun add -D @archlast/server
38
11
  ```
39
12
 
40
- ## Library vs Runtime
41
-
42
- This package is a **dev-time library** for schema and function definitions. The actual server runtime is delivered via Docker and managed by the CLI:
43
-
44
- ```bash
45
- # Use the CLI to run the server
46
- archlast start
47
-
48
- # The Docker image contains the full runtime
49
- docker pull archlast/server:latest
50
- ```
13
+ ## Library vs runtime
51
14
 
52
- ---
15
+ This package is a dev-time library for schema and function definitions. The
16
+ runtime server is delivered via the Docker image (`algochad/archlast-server`) and managed
17
+ by the CLI (`archlast start`).
53
18
 
54
- ## Schema Definition
55
-
56
- Define your data model in `src/schema.ts`:
19
+ ## Schema
57
20
 
58
21
  ```ts
59
22
  import { defineSchema, defineTable, v } from "@archlast/server/schema/definition";
60
23
 
61
24
  export default defineSchema({
62
25
  tasks: defineTable({
63
- _id: v.id().primaryKey(),
26
+ id: v.id(),
64
27
  text: v.string(),
65
- completed: v.boolean().default(false),
66
- priority: v.string().default("medium"),
67
- createdAt: v.number().createdNow(),
68
- updatedAt: v.number().updateNow().optional(),
69
- }),
70
-
71
- users: defineTable({
72
- _id: v.id().primaryKey(),
73
- email: v.string().unique(),
74
- name: v.string(),
75
- role: v.string().default("user"),
76
28
  }),
77
29
  });
78
30
  ```
79
31
 
80
- ### Field Types (Validators)
81
-
82
- The `v` object provides all available field types:
83
-
84
- | Validator | TypeScript Type | Description |
85
- |-----------|-----------------|-------------|
86
- | `v.id()` | `string` | CUID2 identifier (auto-generated) |
87
- | `v.uuid()` | `string` | UUID v4 identifier |
88
- | `v.objectId()` | `string` | MongoDB-style ObjectId |
89
- | `v.string()` | `string` | Text field |
90
- | `v.number()` | `number` | Numeric field (integer or float) |
91
- | `v.boolean()` | `boolean` | True/false field |
92
- | `v.date()` | `Date` | Date object |
93
- | `v.datetime()` | `Date` | Date with time |
94
- | `v.bytes()` | `Uint8Array` | Binary data |
95
- | `v.json()` | `unknown` | Arbitrary JSON |
96
- | `v.any()` | `any` | Any type (use sparingly) |
97
- | `v.object(shape)` | `object` | Nested object with schema |
98
- | `v.array(schema)` | `T[]` | Array of items |
99
- | `v.optional(type)` | `T \| undefined` | Optional wrapper |
100
-
101
- ### Examples
102
-
103
- ```ts
104
- import { v } from "@archlast/server/schema/validators";
105
-
106
- // Basic types
107
- v.string() // string
108
- v.number() // number
109
- v.boolean() // boolean
110
-
111
- // IDs
112
- v.id() // CUID2: "clxxxx..."
113
- v.uuid() // UUID: "550e8400-e29b-41d4-a716-446655440000"
114
- v.objectId() // ObjectId: "507f1f77bcf86cd799439011"
115
-
116
- // Dates
117
- v.date() // Date object (date only)
118
- v.datetime() // Date object (with time)
119
-
120
- // Complex types
121
- v.json() // Any JSON-serializable value
122
- v.bytes() // Binary data as Uint8Array
123
-
124
- // Nested object
125
- v.object({
126
- street: v.string(),
127
- city: v.string(),
128
- zip: v.string(),
129
- })
130
-
131
- // Array
132
- v.array(v.string()) // string[]
133
- v.array(v.object({ // Array of objects
134
- name: v.string(),
135
- score: v.number(),
136
- }))
137
- ```
138
-
139
- ### Field Modifiers
140
-
141
- Chain modifiers to add constraints and behaviors:
142
-
143
- | Modifier | Description |
144
- |----------|-------------|
145
- | `.primaryKey()` | Mark as primary key (auto-generates if not provided) |
146
- | `.unique()` | Unique constraint |
147
- | `.index()` | Create index for faster queries |
148
- | `.searchable()` | Enable full-text search |
149
- | `.optional()` | Make field nullable (TypeScript: `T \| undefined`) |
150
- | `.default(value)` | Default value on insert |
151
- | `.createdNow()` | Auto-set to `Date.now()` on insert |
152
- | `.updateNow()` | Auto-set to `Date.now()` on every update |
153
- | `.foreignKey(table, field, options)` | Foreign key reference |
154
-
155
- ### Examples
156
-
157
- ```ts
158
- defineTable({
159
- // Primary key with auto-generation
160
- _id: v.id().primaryKey(),
161
-
162
- // Unique constraint
163
- email: v.string().unique(),
164
-
165
- // Indexed for fast lookups
166
- slug: v.string().index(),
167
-
168
- // Full-text searchable
169
- content: v.string().searchable(),
170
-
171
- // Optional field
172
- bio: v.string().optional(),
173
-
174
- // Default values
175
- role: v.string().default("user"),
176
- count: v.number().default(0),
177
- active: v.boolean().default(true),
178
-
179
- // Automatic timestamps
180
- createdAt: v.number().createdNow(),
181
- updatedAt: v.number().updateNow().optional(),
182
-
183
- // Foreign key
184
- authorId: v.id().foreignKey("users", "_id", {
185
- onDelete: "cascade",
186
- onUpdate: "cascade",
187
- }),
188
- })
189
- ```
190
-
191
- ### Relationships
192
-
193
- Define relationships in the second argument of `defineTable`:
194
-
195
- ```ts
196
- import {
197
- defineTable,
198
- hasOne,
199
- hasMany,
200
- belongsTo,
201
- manyToMany
202
- } from "@archlast/server/schema/definition";
203
-
204
- export default defineSchema({
205
- users: defineTable(
206
- {
207
- _id: v.id().primaryKey(),
208
- name: v.string(),
209
- },
210
- {
211
- relationships: {
212
- // User has many tasks
213
- tasks: hasMany("tasks", "authorId"),
214
-
215
- // User has one profile
216
- profile: hasOne("profiles", "userId"),
217
- },
218
- }
219
- ),
220
-
221
- tasks: defineTable(
222
- {
223
- _id: v.id().primaryKey(),
224
- text: v.string(),
225
- authorId: v.id(),
226
- },
227
- {
228
- relationships: {
229
- // Task belongs to user
230
- author: belongsTo("users", "authorId"),
231
-
232
- // Task has many subtasks
233
- subtasks: hasMany("subtasks", "taskId"),
234
- },
235
- }
236
- ),
237
-
238
- tags: defineTable(
239
- {
240
- _id: v.id().primaryKey(),
241
- name: v.string(),
242
- },
243
- {
244
- relationships: {
245
- // Many-to-many through junction table
246
- tasks: manyToMany("tasks", "task_tags", "tagId", "taskId"),
247
- },
248
- }
249
- ),
250
- });
251
- ```
252
-
253
- ### Indexes
254
-
255
- Create composite indexes for complex queries:
256
-
257
- ```ts
258
- defineTable(
259
- {
260
- _id: v.id().primaryKey(),
261
- completed: v.boolean(),
262
- priority: v.string(),
263
- createdAt: v.number(),
264
- },
265
- {
266
- indexes: [
267
- // Composite index
268
- { fields: ["completed", "createdAt"], name: "idx_tasks_status" },
269
-
270
- // Single field index
271
- { fields: ["priority"], name: "idx_tasks_priority" },
272
- ],
273
- }
274
- )
275
- ```
276
-
277
- ---
278
-
279
- ## Function Definitions
280
-
281
- Functions are the API of your Archlast application. Import from `@archlast/server/functions/definition`.
282
-
283
- ### Query
284
-
285
- Read-only operations. Results are cached and reactive.
286
-
287
- ```ts
288
- import { query } from "@archlast/server/functions/definition";
289
-
290
- // Simple query
291
- export const list = query(async (ctx) => {
292
- return ctx.db.query("tasks").findMany();
293
- });
294
-
295
- // Query with arguments
296
- export const get = query({
297
- args: { id: v.string().zodSchema },
298
- handler: async (ctx, args) => {
299
- return ctx.db.get("tasks", args.id);
300
- },
301
- });
302
-
303
- // Query with filtering
304
- export const listByStatus = query({
305
- args: {
306
- completed: v.boolean().zodSchema,
307
- limit: v.number().optional().zodSchema,
308
- },
309
- handler: async (ctx, args) => {
310
- return ctx.db
311
- .query("tasks")
312
- .where({ completed: args.completed })
313
- .take(args.limit ?? 10)
314
- .findMany();
315
- },
316
- });
317
- ```
318
-
319
- ### Mutation
320
-
321
- Write operations. Automatically invalidate related caches.
322
-
323
- ```ts
324
- import { mutation } from "@archlast/server/functions/definition";
325
-
326
- export const create = mutation({
327
- args: {
328
- text: v.string().zodSchema,
329
- priority: v.string().optional().zodSchema,
330
- },
331
- handler: async (ctx, args) => {
332
- return ctx.db.insert("tasks", {
333
- text: args.text,
334
- priority: args.priority ?? "medium",
335
- completed: false,
336
- });
337
- },
338
- });
339
-
340
- export const update = mutation({
341
- args: {
342
- id: v.string().zodSchema,
343
- text: v.string().optional().zodSchema,
344
- completed: v.boolean().optional().zodSchema,
345
- },
346
- handler: async (ctx, args) => {
347
- const { id, ...data } = args;
348
- return ctx.db.update("tasks", id, data);
349
- },
350
- });
351
-
352
- export const remove = mutation({
353
- args: { id: v.string().zodSchema },
354
- handler: async (ctx, args) => {
355
- await ctx.db.delete("tasks", args.id);
356
- return { success: true };
357
- },
358
- });
359
- ```
360
-
361
- ### Action
362
-
363
- Long-running operations, side effects, external API calls.
364
-
365
- ```ts
366
- import { action } from "@archlast/server/functions/definition";
367
-
368
- export const sendEmail = action({
369
- args: {
370
- to: v.string().zodSchema,
371
- subject: v.string().zodSchema,
372
- body: v.string().zodSchema,
373
- },
374
- handler: async (ctx, args) => {
375
- // Call external email service
376
- const response = await fetch("https://api.sendgrid.com/v3/mail/send", {
377
- method: "POST",
378
- headers: {
379
- "Authorization": `Bearer ${process.env.SENDGRID_API_KEY}`,
380
- "Content-Type": "application/json",
381
- },
382
- body: JSON.stringify({
383
- personalizations: [{ to: [{ email: args.to }] }],
384
- from: { email: "noreply@myapp.com" },
385
- subject: args.subject,
386
- content: [{ type: "text/plain", value: args.body }],
387
- }),
388
- });
389
-
390
- return { sent: response.ok };
391
- },
392
- });
393
-
394
- export const processImage = action({
395
- args: { storageId: v.string().zodSchema },
396
- handler: async (ctx, args) => {
397
- const file = await ctx.storage.get(args.storageId);
398
- // Process image...
399
- const processedId = await ctx.storage.store("processed", processedBuffer);
400
- return { processedId };
401
- },
402
- });
403
- ```
404
-
405
- ### HTTP Routes
406
-
407
- Explicit HTTP endpoints with full control over request/response.
408
-
409
- ```ts
410
- import { http } from "@archlast/server/http/definition";
411
-
412
- // GET route
413
- export const health = http.get({
414
- path: "/health",
415
- handler: async (ctx) => {
416
- return new Response("ok", { status: 200 });
417
- },
418
- });
419
-
420
- // POST route with body parsing
421
- export const createWebhook = http.post({
422
- path: "/api/webhooks",
423
- handler: async (ctx) => {
424
- const body = await ctx.req.json();
425
- // Process webhook...
426
- return Response.json({ received: true });
427
- },
428
- });
429
-
430
- // Route with path parameters
431
- export const getUser = http.get({
432
- path: "/api/users/:id",
433
- handler: async (ctx) => {
434
- const userId = ctx.req.params.id;
435
- const user = await ctx.server.db.get("users", userId);
436
- if (!user) {
437
- return new Response("Not found", { status: 404 });
438
- }
439
- return Response.json(user);
440
- },
441
- });
442
-
443
- // Other methods
444
- export const updateResource = http.put({
445
- path: "/api/resource/:id",
446
- handler: async (ctx) => {
447
- const id = ctx.req.params.id;
448
- const data = await ctx.req.json();
449
- // Update resource...
450
- return Response.json({ success: true });
451
- },
452
- });
453
-
454
- export const patchResource = http.patch({
455
- path: "/api/resource/:id",
456
- handler: async (ctx) => { /* ... */ return Response.json({ success: true }); },
457
- });
458
-
459
- export const deleteResource = http.delete({
460
- path: "/api/resource/:id",
461
- handler: async (ctx) => { /* ... */ return new Response(null, { status: 204 }); },
462
- });
463
-
464
- export const optionsResource = http.options({
465
- path: "/api/resource",
466
- handler: async (ctx) => { return new Response(null, { status: 204 }); },
467
- });
468
- ```
469
-
470
- ### Webhooks
471
-
472
- Webhook handlers with custom security guards.
473
-
474
- ```ts
475
- import { webhook } from "@archlast/server/webhook/definition";
476
- import { createSignatureGuard } from "@archlast/server/webhook/guard";
477
-
478
- // Stripe webhook with signature verification
479
- export const stripeWebhook = webhook({
480
- path: "/webhooks/stripe",
481
- method: "POST",
482
- guards: [
483
- createSignatureGuard({
484
- secret: process.env.STRIPE_WEBHOOK_SECRET!,
485
- header: "stripe-signature",
486
- algorithm: "sha256",
487
- }),
488
- ],
489
- handler: async (ctx, event) => {
490
- switch (event.type) {
491
- case "checkout.session.completed":
492
- await ctx.db.insert("orders", { userId: event.data.customer });
493
- break;
494
- case "customer.subscription.deleted":
495
- await ctx.db.update("users", event.data.customer, { subscribed: false });
496
- break;
497
- }
498
- return { received: true };
499
- },
500
- });
501
-
502
- // GitHub webhook with signature verification
503
- export const githubWebhook = webhook({
504
- path: "/webhooks/github",
505
- guards: [
506
- createSignatureGuard({
507
- secret: process.env.GITHUB_WEBHOOK_SECRET!,
508
- header: "X-Hub-Signature-256",
509
- algorithm: "sha256",
510
- }),
511
- ],
512
- handler: async (ctx, payload) => {
513
- ctx.logger.info("GitHub event received", { action: payload.action });
514
- return { ok: true };
515
- },
516
- });
517
- ```
518
-
519
- ### RPC (tRPC-style)
520
-
521
- Type-safe RPC procedures with automatic router generation.
32
+ ## Functions
522
33
 
523
34
  ```ts
524
- import { rpc } from "@archlast/server/functions/definition";
35
+ import { query, mutation } from "@archlast/server/functions/definition";
525
36
  import { z } from "zod";
526
37
 
527
- // RPC query
528
- export const getTasks = rpc.query({
529
- args: z.object({
530
- completed: z.boolean().optional(),
531
- }),
532
- handler: async (ctx, args) => {
533
- const query = ctx.db.query("tasks");
534
- if (args.completed !== undefined) {
535
- query.where({ completed: args.completed });
536
- }
537
- return query.findMany();
538
- },
38
+ export const list = query({
39
+ handler: async (ctx) => ctx.db.table("tasks").findMany(),
539
40
  });
540
41
 
541
- // RPC mutation
542
- export const createTask = rpc.mutation({
543
- args: z.object({
544
- text: z.string(),
545
- }),
546
- handler: async (ctx, args) => {
547
- return ctx.db.insert("tasks", { text: args.text, completed: false });
548
- },
549
- });
550
- ```
551
-
552
- ---
553
-
554
- ## Context Objects
555
-
556
- Each function receives a context object with available services.
557
-
558
- ### QueryCtx (for queries)
559
-
560
- ```ts
561
- interface QueryCtx {
562
- db: GenericDatabaseReader<DataModel>;
563
- auth: AuthContext;
564
- repository: RepositoryMap<DataModel>;
565
- logger: LogService;
566
- }
567
- ```
568
-
569
- ### MutationCtx (for mutations)
570
-
571
- ```ts
572
- interface MutationCtx extends QueryCtx {
573
- db: GenericDatabaseWriter<DataModel>;
574
- scheduler: JobScheduler;
575
- }
576
- ```
577
-
578
- ### ActionCtx (for actions)
579
-
580
- ```ts
581
- interface ActionCtx extends MutationCtx {
582
- storage: StorageClient;
583
- }
584
- ```
585
-
586
- ### AuthContext
587
-
588
- The auth context is populated from Better-Auth session data:
589
-
590
- ```ts
591
- interface AuthContext {
592
- // User ID from Better-Auth session
593
- userId: string | null;
594
-
595
- // Session ID from Better-Auth
596
- sessionId: string | null;
597
-
598
- // Whether the request is authenticated
599
- isAuthenticated: boolean;
600
-
601
- // Better-Auth user object
602
- user: {
603
- id: string;
604
- email: string;
605
- emailVerified: boolean;
606
- name?: string;
607
- image?: string;
608
- role?: string; // "user", "admin", "super-admin"
609
- banned?: boolean;
610
- createdAt: Date;
611
- updatedAt: Date;
612
- } | null;
613
-
614
- // Tenant/Organization info (if using organizations)
615
- tenant?: {
616
- id: string;
617
- name: string;
618
- ownerId: string;
619
- } | null;
620
-
621
- // Membership info
622
- membership?: {
623
- role: string;
624
- permissions: string[];
625
- } | null;
626
- }
627
- ```
628
-
629
- **Note:** Better-Auth stores users in `system_auth_user` collection with the `id` field (not `_id`). The adapter handles mapping automatically.
630
-
631
- ### Usage Examples
632
-
633
- ```ts
634
- export const myQuery = query(async (ctx) => {
635
- // Database access
636
- const tasks = await ctx.db.query("tasks").findMany();
637
-
638
- // LINQ-style repository
639
- const data = await ctx.repository.tasks
640
- .Where({ completed: false })
641
- .OrderBy({ createdAt: "desc" })
642
- .Take(10)
643
- .ToArrayAsync();
644
-
645
- // Auth info
646
- if (ctx.auth.isAuthenticated) {
647
- console.log("User:", ctx.auth.userId);
648
- }
649
-
650
- // Logging
651
- ctx.logger.info("Query executed", { count: tasks.length });
652
-
653
- return tasks;
654
- });
655
-
656
- export const myMutation = mutation(async (ctx) => {
657
- // Schedule background job
658
- await ctx.scheduler.schedule({
659
- name: "sendNotification",
660
- preset: SchedulePreset.RunNow,
661
- payload: { userId: ctx.auth.userId },
662
- });
663
- });
664
-
665
- export const myAction = action(async (ctx) => {
666
- // File storage
667
- const file = await ctx.storage.get("file_id");
668
- const newId = await ctx.storage.store("bucket", buffer);
42
+ export const create = mutation({
43
+ args: { text: z.string() },
44
+ handler: async (ctx, args) => ctx.db.table("tasks").insert({ text: args.text }),
669
45
  });
670
46
  ```
671
47
 
672
- ---
673
-
674
- ## Authentication & Permissions
675
-
676
- Archlast uses [Better-Auth](https://www.better-auth.com/) for authentication. The server mounts Better-Auth at `/api/auth/*` and includes these plugins:
677
-
678
- - **Email/Password**: Standard credential authentication via `signIn.email()`
679
- - **Username**: Username-based sign-in via `signIn.username()`
680
- - **Anonymous**: Guest user support via `signIn.anonymous()`
681
- - **Admin**: User management, roles, bans, impersonation
682
- - **API Key**: Programmatic access with `arch_` prefixed keys
683
- - **Organization**: Multi-tenancy support
684
-
685
- ### Sign-In Methods
686
-
687
- ```ts
688
- // Email-based sign-in (web app pattern)
689
- await authClient.signIn.email({ email: "user@example.com", password: "..." });
690
-
691
- // Username-based sign-in (dashboard pattern)
692
- await authClient.signIn.username({ username: "admin", password: "..." });
693
-
694
- // Anonymous/guest sign-in
695
- await authClient.signIn.anonymous();
696
- ```
48
+ Other function types:
49
+ - `action` for long running tasks
50
+ - `http` for explicit HTTP routes
51
+ - `webhook` for signed incoming events
52
+ - `rpc` for tRPC-style public procedures
697
53
 
698
- ### Better-Auth Configuration
54
+ ## Auth and permissions
699
55
 
700
- Better-Auth is configured in the server with these features:
56
+ All functions default to `auth: "required"`. You can mark functions public or
57
+ optional, and attach permissions.
701
58
 
702
59
  ```ts
703
- // Environment variables for Better-Auth
704
- BETTER_AUTH_SECRET=your-32-char-secret // Required in production
705
- APP_URL=https://your-app.com // Base URL for redirects
706
- ARCHLAST_ALLOWED_ORIGINS=http://localhost:3000 // Trusted origins (CSV)
707
- ```
708
-
709
- ### Auth Modes
710
-
711
- All functions default to requiring authentication. Override with `auth` option:
712
-
713
- ```ts
714
- // Required (default) - must be authenticated via Better-Auth
715
- export const privateQuery = query({
716
- handler: async (ctx) => {
717
- // ctx.auth.userId is guaranteed to exist
718
- // ctx.auth.user contains Better-Auth user object
719
- },
720
- });
721
-
722
- // Optional - authentication checked but not required
723
- export const optionalAuth = query({
724
- auth: "optional",
725
- handler: async (ctx) => {
726
- if (ctx.auth.isAuthenticated) {
727
- // Authenticated user via Better-Auth
728
- } else {
729
- // Anonymous/guest user
730
- }
731
- },
732
- });
733
-
734
- // Public - no authentication required
735
- export const publicQuery = query({
60
+ export const publicPing = query({
736
61
  auth: "public",
737
- handler: async (ctx) => {
738
- // Anyone can access
739
- },
740
- });
741
- ```
742
-
743
- ### Permissions
744
-
745
- Add permission checks for role-based access:
746
-
747
- ```ts
748
- export const adminOnly = query({
749
- permissions: ["admin"],
750
- handler: async (ctx) => {
751
- // Only users with "admin" permission
752
- },
753
- });
754
-
755
- export const moderatorOrAdmin = mutation({
756
- permissions: ["admin", "moderator"],
757
- handler: async (ctx) => {
758
- // Users with either permission
759
- },
760
- });
761
- ```
762
-
763
- ---
764
-
765
- ## Background Jobs
766
-
767
- Schedule background tasks using the job scheduler.
768
-
769
- ```ts
770
- import { SchedulePreset } from "@archlast/server/jobs";
771
-
772
- export const scheduleCleanup = mutation(async (ctx) => {
773
- // Run immediately
774
- await ctx.scheduler.schedule({
775
- name: "cleanupOldRecords",
776
- preset: SchedulePreset.RunNow,
777
- payload: { olderThanDays: 30 },
778
- });
779
-
780
- // Run every 5 minutes
781
- await ctx.scheduler.schedule({
782
- name: "sendReminder",
783
- preset: SchedulePreset.Every5Minutes,
784
- payload: { userId: ctx.auth.userId },
785
- });
786
-
787
- // Run every 10 minutes
788
- await ctx.scheduler.schedule({
789
- name: "syncData",
790
- preset: SchedulePreset.Every10Minutes,
791
- payload: {},
792
- });
793
-
794
- // Recurring with cron
795
- await ctx.scheduler.schedule({
796
- name: "dailyReport",
797
- cron: "0 9 * * *", // Every day at 9 AM
798
- payload: {},
799
- });
800
- });
801
- ```
802
-
803
- ### Schedule Presets
804
-
805
- | Preset | Cron Expression | Description |
806
- |--------|-----------------|-------------|
807
- | `RunNow` | (immediate) | Execute immediately |
808
- | `EveryMinute` | `* * * * *` | Run every minute |
809
- | `Every5Minutes` | `*/5 * * * *` | Run every 5 minutes |
810
- | `Every10Minutes` | `*/10 * * * *` | Run every 10 minutes |
811
- | `Every30Minutes` | `*/30 * * * *` | Run every 30 minutes |
812
- | `Hourly` | `0 * * * *` | Run every hour |
813
- | `Every8Hours` | `0 */8 * * *` | Run every 8 hours |
814
- | `Every16Hours` | `0 */16 * * *` | Run every 16 hours |
815
- | `DailyMidnight` | `0 0 * * *` | Daily at midnight UTC |
816
- | `Weekly` | `0 0 * * 0` | Weekly on Sunday midnight |
817
- | `Monthly` | `0 0 1 * *` | Monthly on the 1st |
818
- | `Quarterly` | `0 0 1 */3 *` | Quarterly (every 3 months) |
819
-
820
- You can also use custom cron expressions:
821
-
822
- ```ts
823
- await ctx.scheduler.schedule({
824
- name: "customJob",
825
- cron: "0 9 * * 1-5", // Weekdays at 9 AM
826
- payload: {},
62
+ handler: async () => "pong",
827
63
  });
828
64
  ```
829
65
 
830
- ---
831
-
832
- ## Storage Types
66
+ ## Runtime exports
833
67
 
834
- File storage type definitions.
68
+ Common entry points:
69
+ - `@archlast/server/schema/definition` and `@archlast/server/schema/validators`
70
+ - `@archlast/server/functions/definition` and `@archlast/server/functions/types`
71
+ - `@archlast/server/http` and `@archlast/server/webhook`
72
+ - `@archlast/server/jobs`
73
+ - `@archlast/server/storage/types`
74
+ - `@archlast/server/context`
835
75
 
836
- ```ts
837
- import type { StorageFile, StorageMetadata } from "@archlast/server/storage/types";
838
-
839
- interface StorageFile {
840
- id: string;
841
- bucket: string;
842
- filename: string;
843
- contentType: string;
844
- size: number;
845
- createdAt: Date;
846
- }
847
-
848
- interface StorageMetadata {
849
- filename: string;
850
- contentType: string;
851
- size: number;
852
- bucket: string;
853
- createdAt: Date;
854
- url?: string;
855
- }
856
- ```
76
+ ## Docker templates
857
77
 
858
- ---
78
+ Templates live under `templates/` in this package:
79
+ - `docker-compose.yml`, `docker-compose.dev.yml`, `docker-compose.prod.yml`
80
+ - `.env.example`
81
+ - `archlast.config.js`
859
82
 
860
- ## Subpath Exports
83
+ The CLI uses these templates to generate a local Docker setup.
861
84
 
862
- Import only what you need:
863
-
864
- | Import | Description |
865
- |--------|-------------|
866
- | `@archlast/server/schema/definition` | `defineSchema`, `defineTable`, relationship helpers |
867
- | `@archlast/server/schema/validators` | `v` validator object |
868
- | `@archlast/server/functions/definition` | `query`, `mutation`, `action`, `rpc` |
869
- | `@archlast/server/functions/types` | Context types, function types |
870
- | `@archlast/server/http/definition` | `http` route builder |
871
- | `@archlast/server/http/router` | HTTP router utilities |
872
- | `@archlast/server/http` | HTTP utilities |
873
- | `@archlast/server/webhook/definition` | `webhook` function builder |
874
- | `@archlast/server/webhook/guard` | Webhook guard utilities |
875
- | `@archlast/server/webhook/verifier` | Signature verification |
876
- | `@archlast/server/webhook` | Webhook utilities |
877
- | `@archlast/server/jobs` | `SchedulePreset`, `Scheduler`, `JobQueue` |
878
- | `@archlast/server/jobs/scheduler` | Scheduler class and presets |
879
- | `@archlast/server/jobs/queue` | Job queue implementation |
880
- | `@archlast/server/storage/types` | Storage type definitions |
881
- | `@archlast/server/context` | Server context exports |
882
- | `@archlast/server/db/interfaces` | Database interface types |
883
- | `@archlast/server/auth/interfaces` | Auth interface types |
884
- | `@archlast/server/repository/interfaces` | Repository interface types |
885
- | `@archlast/server/repository/factory` | Repository factory |
886
- | `@archlast/server/di/decorators` | DI decorators |
887
- | `@archlast/server/di/container` | DI container |
888
- | `@archlast/server/logging/logger` | Logger service |
889
- | `@archlast/server/docker` | Docker utilities |
890
-
891
- ---
892
-
893
- ## Environment Variables
85
+ ## Environment configuration
894
86
 
895
87
  Key variables used by the server runtime:
896
-
897
- ### Better-Auth Configuration
898
-
899
- | Variable | Default | Description |
900
- |----------|---------|-------------|
901
- | `BETTER_AUTH_SECRET` | - | **Required in production.** Secret for signing tokens |
902
- | `APP_URL` | `http://localhost:4000` | Base URL for auth redirects |
903
- | `BETTER_AUTH_DEBUG` | `false` | Enable debug logging |
904
-
905
- ### Server Configuration
906
-
907
- | Variable | Default | Description |
908
- |----------|---------|-------------|
909
- | `PORT` | `4000` | HTTP server port |
910
- | `NODE_ENV` | `development` | Environment mode |
911
- | `ARCHLAST_DB_ROOT` | `./data` | Database file directory |
912
- | `ARCHLAST_ALLOWED_ORIGINS` | - | CORS allowed origins (CSV) |
913
- | `ARCHLAST_CORS_ALLOW_CREDENTIALS` | `false` | Allow credentials in CORS |
914
-
915
- ### Storage Configuration
916
-
917
- | Variable | Default | Description |
918
- |----------|---------|-------------|
919
- | `STORAGE_ROOT` | `./storage` | Local file storage directory |
920
- | `STORAGE_SIGNING_SECRET` | - | Secret for signed URLs |
921
- | `S3_ENABLED` | `false` | Enable S3 storage |
922
- | `S3_BUCKET` | - | S3 bucket name |
923
- | `S3_REGION` | `us-east-1` | S3 region |
924
- | `AWS_ACCESS_KEY_ID` | - | AWS access key |
925
- | `AWS_SECRET_ACCESS_KEY` | - | AWS secret key |
926
-
927
- ### Dashboard & Store Configuration
928
-
929
- | Variable | Default | Description |
930
- |----------|---------|-------------|
931
- | `ARCHLAST_DASHBOARD_DIR` | - | Dashboard static files path |
932
- | `ARCHLAST_DASHBOARD_URL` | - | Dashboard proxy URL |
933
- | `ARCHLAST_STORE_PORT` | `7001` | Document store port |
934
- | `ARCHLAST_STORE_HOST` | `127.0.0.1` | Document store host |
935
- | `ARCHLAST_STORE_NO_TLS` | `false` | Disable TLS for document store |
936
-
937
- ---
938
-
939
- ## Docker Templates
940
-
941
- Templates for Docker deployment are included in `templates/`:
942
-
943
- - `docker-compose.yml` - Base compose file
944
- - `docker-compose.dev.yml` - Development overrides
945
- - `docker-compose.prod.yml` - Production configuration
946
- - `.env.example` - Example environment variables
947
- - `archlast.config.js` - CLI configuration template
948
-
949
- The CLI uses these templates when running `archlast start`.
950
-
951
- ---
952
-
953
- ## Keywords
954
-
955
- archlast, server, backend, reactive, real-time, websocket, typescript, api, baas
956
-
957
- ## License
958
-
959
- MIT
88
+ - `PORT` (default: 4000)
89
+ - `ARCHLAST_DB_ROOT` (default: `./data`)
90
+ - `ARCHLAST_ALLOWED_ORIGINS` (CSV)
91
+ - `ARCHLAST_CORS_ALLOW_CREDENTIALS` (`true` or `false`)
92
+ - `STORAGE_ROOT` and `STORAGE_SIGNING_SECRET`
93
+ - `S3_ENABLED`, `S3_BUCKET`, `S3_REGION`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`
94
+ - `ARCHLAST_ADMIN_TOKEN` (deprecated), `ARCHLAST_AUTH_TOKEN_PEPPER`
95
+ - `ARCHLAST_DASHBOARD_DIR` or `ARCHLAST_DASHBOARD_URL`
96
+ - `ARCHLAST_STORE_PORT`, `ARCHLAST_STORE_NO_TLS`
97
+
98
+ ## Publishing (maintainers)
99
+
100
+ See `docs/npm-publishing.md` for release and publish steps.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archlast/server",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Archlast server library exports and Docker templates",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -150,7 +150,7 @@
150
150
  "LICENSE"
151
151
  ],
152
152
  "archlast": {
153
- "dockerImage": "archlast/server",
153
+ "dockerImage": "algochad/archlast-server",
154
154
  "defaultTag": "latest",
155
155
  "minDockerVersion": "24.0.0"
156
156
  },
@@ -8,7 +8,7 @@ module.exports = {
8
8
  },
9
9
  docker: {
10
10
  // Docker image to run.
11
- image: "archlast/server",
11
+ image: "algochad/archlast-server",
12
12
  tag: "latest",
13
13
  containerName: "archlast-server",
14
14
  volumeName: "archlast-data",
@@ -2,7 +2,7 @@ version: "3.8"
2
2
 
3
3
  services:
4
4
  archlast:
5
- image: archlast/server:${ARCHLAST_VERSION:-latest}
5
+ image: algochad/archlast-server:${ARCHLAST_VERSION:-latest}
6
6
  container_name: ${ARCHLAST_CONTAINER_NAME:-archlast-server}
7
7
  restart: unless-stopped
8
8
  ports: