@warpfx/server 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.d.ts +122 -116
  2. package/dist/index.js +29 -19
  3. package/package.json +1 -1
package/dist/index.d.ts CHANGED
@@ -66,10 +66,20 @@ interface ColumnDef {
66
66
  /** Default value for this column when inserting a row without it. */
67
67
  default?: unknown;
68
68
  }
69
+ /** Table access mode for client sync. */
70
+ type AccessMode = "private" | "public";
69
71
  /** Table definition in a warp.schema.json. */
70
72
  interface TableDef {
71
73
  /** Column definitions keyed by column name. */
72
74
  columns: Record<string, ColumnDef>;
75
+ /**
76
+ * Client access mode — controls automatic sync to players.
77
+ *
78
+ * - `"private"` — Only the owning player receives their rows (scope = primary key).
79
+ * - `"public"` — All connected players receive all rows.
80
+ * - Omitted — Server-only, not synced to clients.
81
+ */
82
+ access?: AccessMode;
73
83
  }
74
84
  /** The full warp.schema.json format. */
75
85
  interface WarpSchema {
@@ -84,22 +94,22 @@ interface WarpSchema {
84
94
  }
85
95
 
86
96
  /**
87
- * @warp/server — Typed SDK for Warp server-side module development.
97
+ * @warpfx/server — Server-side SDK for Warp module development.
88
98
  *
89
- * Provides type-safe access to the Warp core API. Under the hood,
90
- * all calls go through FiveM's resource exports to the 'warp' resource.
99
+ * Clean, minimal API for building FiveM server features with Warp.
100
+ * All calls delegate to the Warp core via FiveM resource exports.
91
101
  *
92
102
  * Usage:
93
- * import { onReady, provide, use, getEvents } from '@warp/server';
103
+ * import { action, fn, command, error, table } from '@warpfx/server';
104
+ * import { accounts } from './db'; // auto-generated from schema
94
105
  *
95
- * onReady(() => {
96
- * const events = getEvents();
97
- * events.on('player:spawned', (data) => { ... });
98
- * provide('economy', new EconomyService());
106
+ * action('deposit', async (player, { amount }) => {
107
+ * const account = accounts.find(player.citizenId);
108
+ * accounts.update(player.citizenId, { balance: account.balance + amount });
99
109
  * });
100
110
  */
101
- /** Player lifecycle event data. */
102
- interface PlayerEventContext {
111
+ /** Player context passed to action/function handlers. */
112
+ interface Player {
103
113
  /** FiveM server-side player ID. */
104
114
  source: number;
105
115
  /** Citizen ID (license identifier). */
@@ -124,63 +134,131 @@ interface PlayerRow {
124
134
  type PlayerState = "connecting" | "connected" | "spawned";
125
135
  /** Player entity with ECS component access. */
126
136
  interface PlayerEntity {
127
- /** FiveM server-side player ID. */
128
137
  readonly source: number;
129
- /** Citizen ID (license identifier). */
130
138
  readonly citizenId: string;
131
- /** Display name. */
132
139
  readonly name: string;
133
- /** Discord identifier (e.g. "discord:123456789"), if linked. */
134
140
  readonly discordId: string | undefined;
135
- /** Steam identifier (e.g. "steam:110000..."), if linked. */
136
141
  readonly steamId: string | undefined;
137
- /** Current lifecycle state. */
138
142
  readonly state: PlayerState;
139
- /** Get a component attached to this player. */
140
143
  getComponent<T>(component: string): T | undefined;
141
- /** Attach or replace a component on this player. */
142
144
  setComponent<T>(component: string, data: T): void;
143
- /** Check if this player has a specific component. */
144
145
  hasComponent(component: string): boolean;
145
- /** Remove a component from this player. */
146
146
  removeComponent(component: string): boolean;
147
147
  }
148
148
  /** Typed event bus for server-side and network communication. */
149
149
  interface ServerEventBus<TMap extends Record<string, any> = Record<string, any>> {
150
- /** Subscribe to a local event. Returns an unsubscribe function. */
151
150
  on<K extends string & keyof TMap>(event: K, handler: (data: TMap[K]) => void): () => void;
152
- /** Unsubscribe from a local event. */
153
151
  off<K extends string & keyof TMap>(event: K, handler: (data: TMap[K]) => void): void;
154
- /** Emit a local event to all subscribers. */
155
152
  emit<K extends string & keyof TMap>(event: K, data: TMap[K]): void;
156
- /** Send an event to a specific client. */
157
153
  emitClient<K extends string & keyof TMap>(event: K, target: number, data: TMap[K]): void;
158
- /** Broadcast an event to all connected clients. */
159
154
  emitAllClients<K extends string & keyof TMap>(event: K, data: TMap[K]): void;
160
- /** Listen for events from clients. Returns an unsubscribe function. */
161
155
  onClient<K extends string & keyof TMap>(event: K, handler: (source: number, data: TMap[K]) => void): () => void;
162
- /** Unsubscribe from a client event. */
163
156
  offClient<K extends string & keyof TMap>(event: K, handler: (source: number, data: TMap[K]) => void): void;
164
157
  }
165
158
  /** ECS-inspired entity component store. */
166
159
  interface EntityStore {
167
- /** Attach a component to an entity. */
168
160
  attach<T>(entityId: string, component: string, data: T): void;
169
- /** Get a component from an entity. Returns undefined if not found. */
170
161
  get<T>(entityId: string, component: string): T | undefined;
171
- /** Remove a component from an entity. */
172
162
  detach(entityId: string, component: string): boolean;
173
- /** Remove all components for an entity. */
174
163
  removeEntity(entityId: string): void;
175
- /** Check if an entity has a specific component. */
176
164
  has(entityId: string, component: string): boolean;
177
- /** Iterate all entities that have a specific component. */
178
165
  query<T>(component: string): IterableIterator<[string, T]>;
179
166
  }
167
+ /** Table handle for typed database operations. */
168
+ interface Table<T = any> {
169
+ /** Find a row by primary key. */
170
+ find(primaryKey: any): T | null;
171
+ /** Get all rows in the table. */
172
+ all(): T[];
173
+ /** Insert a new row. */
174
+ insert(data: T): Promise<void>;
175
+ /** Update an existing row by primary key (partial update). */
176
+ update(primaryKey: any, data: Partial<T>): Promise<void>;
177
+ /** Delete a row by primary key. */
178
+ delete(primaryKey: any): Promise<void>;
179
+ }
180
+ /** Parameter hint shown in command autocomplete. */
181
+ interface CommandParam {
182
+ name: string;
183
+ help?: string;
184
+ }
185
+ /** Options for command(). */
186
+ interface CommandOptions {
187
+ description?: string;
188
+ ace?: string;
189
+ canUse?: (source: number) => boolean;
190
+ params?: CommandParam[];
191
+ }
192
+ /** Action error received from the server. */
193
+ interface ActionError {
194
+ action: string;
195
+ code: string;
196
+ message: string;
197
+ }
198
+ /**
199
+ * Register an action handler that clients can invoke.
200
+ *
201
+ * Actions are fire-and-forget mutations — the client sends data,
202
+ * the server validates and mutates, state syncs back automatically.
203
+ *
204
+ * @example
205
+ * action('deposit', async (player, { amount }) => {
206
+ * const account = accounts.find(player.citizenId);
207
+ * accounts.update(player.citizenId, { balance: account.balance + amount });
208
+ * });
209
+ */
210
+ declare function action(name: string, handler: (player: Player, payload: any) => Promise<void> | void): void;
211
+ /**
212
+ * Register a server function that clients can call and await a result.
213
+ *
214
+ * Unlike actions (fire-and-forget), functions return a value to the caller.
215
+ * Use for request/response patterns like data fetches or validations.
216
+ *
217
+ * @example
218
+ * fn('getBalance', async (player, { targetId }) => {
219
+ * const account = accounts.find(targetId);
220
+ * return { balance: account?.balance ?? 0 };
221
+ * });
222
+ */
223
+ declare function fn<T = any>(name: string, handler: (player: Player, payload: any) => Promise<T> | T): void;
224
+ /**
225
+ * Register a chat command with optional autocomplete.
226
+ *
227
+ * @example
228
+ * command('balance', (player) => {
229
+ * const account = accounts.find(player.citizenId);
230
+ * player.notify(`Balance: $${account.balance}`);
231
+ * });
232
+ *
233
+ * command('give', {
234
+ * description: 'Give money to a player',
235
+ * params: [{ name: 'id' }, { name: 'amount' }],
236
+ * }, (player, args) => { ... });
237
+ */
238
+ declare function command(name: string, handlerOrOptions: CommandOptions | ((player: Player, args: string[], raw: string) => void), handler?: (player: Player, args: string[], raw: string) => void): void;
239
+ /**
240
+ * Create an error to throw from action/function handlers.
241
+ * The error is sent back to the client with a structured code and message.
242
+ *
243
+ * @example
244
+ * throw error('NO_ACCOUNT', 'No account found');
245
+ */
246
+ declare function error(code: string, message?: string): Error;
247
+ /**
248
+ * Get a table handle for database operations.
249
+ *
250
+ * The table name must be the full prefixed name (e.g. 'economy_accounts').
251
+ * For auto-generated handles with types, use the generated `db.ts` instead.
252
+ *
253
+ * @example
254
+ * const accounts = table<Account>('economy_accounts');
255
+ * const account = accounts.find(citizenId);
256
+ * await accounts.update(citizenId, { balance: 500 });
257
+ */
258
+ declare function table<T = any>(name: string): Table<T>;
180
259
  /**
181
260
  * Register a callback to run when Warp is fully booted.
182
261
  * If Warp is already ready, the callback fires immediately.
183
- * All other SDK functions should be called inside this callback.
184
262
  */
185
263
  declare function onReady(cb: () => void): void;
186
264
  /** Check if Warp has finished booting. */
@@ -197,91 +275,19 @@ declare function getEntities(): EntityStore;
197
275
  declare function getPlayer(citizenId: string): PlayerRow | null;
198
276
  /** Get all cached players. */
199
277
  declare function getPlayers(): PlayerRow[];
200
- /**
201
- * Get the player entity for a citizenId.
202
- * Returns the entity with lifecycle state and component access.
203
- */
278
+ /** Get the player entity for a citizenId. */
204
279
  declare function getEntity(citizenId: string): PlayerEntity | null;
205
- /**
206
- * Transition a player to the "spawned" state.
207
- * Call after character selection (e.g. from a multichar module).
208
- * Fires all onPlayerSpawned hooks.
209
- */
280
+ /** Transition a player to the "spawned" state. */
210
281
  declare function spawnPlayer(citizenId: string): void;
211
- /** Register a callback for when a player connects (database confirmed). */
212
- declare function onPlayerConnect(cb: (player: PlayerEventContext) => void): void;
213
- /** Register a callback for when a player spawns (character selected). */
214
- declare function onPlayerSpawned(cb: (player: PlayerEventContext) => void): void;
282
+ /** Register a callback for when a player connects. */
283
+ declare function onPlayerConnect(cb: (player: Player) => void): void;
284
+ /** Register a callback for when a player spawns. */
285
+ declare function onPlayerSpawned(cb: (player: Player) => void): void;
215
286
  /** Register a callback for when a player disconnects. */
216
- declare function onPlayerDisconnect(cb: (player: PlayerEventContext) => void): void;
217
- /**
218
- * Get the database bridge for direct table and reducer access.
219
- * Advanced — most modules should use the event bus and DI instead.
220
- */
287
+ declare function onPlayerDisconnect(cb: (player: Player) => void): void;
288
+ /** Get the database bridge for direct table and reducer access. */
221
289
  declare function getBridge(): any;
222
- /** Subscribe to database tables. Call inside onReady(). */
290
+ /** Subscribe to database tables. */
223
291
  declare function subscribe(queries: string[]): Promise<void>;
224
- /** Sync configuration for a table. */
225
- interface SyncConfig {
226
- /** Who receives updates for this table. */
227
- to: "owner" | "all";
228
- /** Column name for owner filtering (required when to: 'owner'). */
229
- scope?: string;
230
- /** Primary key column for row identity (defaults to 'id'). */
231
- key?: string;
232
- }
233
- /** Context passed to action handlers. */
234
- interface ActionContext {
235
- /** FiveM server-side player ID. */
236
- source: number;
237
- /** Player's citizen ID (license identifier). */
238
- citizenId: string;
239
- /** Player's display name. */
240
- name: string;
241
- /** Discord identifier (e.g. "discord:123456789"), if linked. */
242
- discordId?: string;
243
- /** Steam identifier (e.g. "steam:110000..."), if linked. */
244
- steamId?: string;
245
- /** Database table accessors (read-only). */
246
- db: any;
247
- /** Database reducer proxy (for mutations). */
248
- reducers: any;
249
- }
250
- /** Action handler function. */
251
- type ActionHandler = (ctx: ActionContext, payload: any) => Promise<void> | void;
252
- /**
253
- * Register a table for automatic sync to clients.
254
- *
255
- * The sync engine subscribes to SpacetimeDB table changes and routes
256
- * updates to the correct players based on the config. Call inside onReady().
257
- *
258
- * @example
259
- * registerSync('inventory', { to: 'owner', scope: 'ownerId', key: 'id' });
260
- * registerSync('itemDefinitions', { to: 'all', key: 'name' });
261
- */
262
- declare function registerSync(table: string, config: SyncConfig): void;
263
- /**
264
- * Register an action handler that clients can invoke via useAction().
265
- *
266
- * Actions are the only entry point for client-initiated mutations.
267
- * The sync engine handles security, error routing, and state updates.
268
- *
269
- * @example
270
- * registerAction('inventory:moveItem', async (ctx, { itemId, toSlot }) => {
271
- * await ctx.reducers.moveItem(ctx.citizenId, itemId, toSlot);
272
- * });
273
- */
274
- declare function registerAction(name: string, handler: ActionHandler): void;
275
- /**
276
- * Typed error for action handlers. Thrown errors with a `code` property
277
- * are sent to the client as structured error messages.
278
- *
279
- * @example
280
- * throw new WarpError('ITEM_NOT_FOUND', 'That item no longer exists');
281
- */
282
- declare class WarpError extends Error {
283
- code: string;
284
- constructor(code: string, message?: string);
285
- }
286
292
 
287
- export { type ActionContext, type ActionHandler, type ColumnDef, type ColumnType, type EntityStore, type PlayerEntity, type PlayerEventContext, type PlayerRow, type PlayerState, type ServerEventBus, type SyncConfig, type TableDef, WarpError, type WarpSchema, getBridge, getEntities, getEntity, getEvents, getPlayer, getPlayers, isReady, onPlayerConnect, onPlayerDisconnect, onPlayerSpawned, onReady, provide, registerAction, registerSync, spawnPlayer, subscribe, use };
293
+ export { type AccessMode, type ActionError, type ColumnDef, type ColumnType, type CommandOptions, type CommandParam, type EntityStore, type Player, type PlayerEntity, type PlayerRow, type PlayerState, type ServerEventBus, type Table, type TableDef, type WarpSchema, action, command, error, fn, getBridge, getEntities, getEntity, getEvents, getPlayer, getPlayers, isReady, onPlayerConnect, onPlayerDisconnect, onPlayerSpawned, onReady, provide, spawnPlayer, subscribe, table, use };
package/dist/index.js CHANGED
@@ -20,7 +20,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
- WarpError: () => WarpError,
23
+ action: () => action,
24
+ command: () => command,
25
+ error: () => error,
26
+ fn: () => fn,
24
27
  getBridge: () => getBridge,
25
28
  getEntities: () => getEntities,
26
29
  getEntity: () => getEntity,
@@ -33,16 +36,34 @@ __export(index_exports, {
33
36
  onPlayerSpawned: () => onPlayerSpawned,
34
37
  onReady: () => onReady,
35
38
  provide: () => provide,
36
- registerAction: () => registerAction,
37
- registerSync: () => registerSync,
38
39
  spawnPlayer: () => spawnPlayer,
39
40
  subscribe: () => subscribe,
41
+ table: () => table,
40
42
  use: () => use
41
43
  });
42
44
  module.exports = __toCommonJS(index_exports);
43
45
  function warp() {
44
46
  return globalThis.exports["warp"];
45
47
  }
48
+ function action(name, handler) {
49
+ warp().action(name, handler);
50
+ }
51
+ function fn(name, handler) {
52
+ warp().fn(name, handler);
53
+ }
54
+ function command(name, handlerOrOptions, handler) {
55
+ if (typeof handlerOrOptions === "function") {
56
+ warp().command(name, {}, handlerOrOptions);
57
+ } else {
58
+ warp().command(name, handlerOrOptions, handler);
59
+ }
60
+ }
61
+ function error(code, message) {
62
+ return warp().error(code, message);
63
+ }
64
+ function table(name) {
65
+ return warp().table(name);
66
+ }
46
67
  function onReady(cb) {
47
68
  warp().onReady(cb);
48
69
  }
@@ -88,22 +109,12 @@ function getBridge() {
88
109
  function subscribe(queries) {
89
110
  return warp().subscribe(queries);
90
111
  }
91
- function registerSync(table, config) {
92
- warp().registerSync(table, config);
93
- }
94
- function registerAction(name, handler) {
95
- warp().registerAction(name, handler);
96
- }
97
- var WarpError = class extends Error {
98
- constructor(code, message) {
99
- super(message ?? code);
100
- this.code = code;
101
- this.name = "WarpError";
102
- }
103
- };
104
112
  // Annotate the CommonJS export names for ESM import in node:
105
113
  0 && (module.exports = {
106
- WarpError,
114
+ action,
115
+ command,
116
+ error,
117
+ fn,
107
118
  getBridge,
108
119
  getEntities,
109
120
  getEntity,
@@ -116,9 +127,8 @@ var WarpError = class extends Error {
116
127
  onPlayerSpawned,
117
128
  onReady,
118
129
  provide,
119
- registerAction,
120
- registerSync,
121
130
  spawnPlayer,
122
131
  subscribe,
132
+ table,
123
133
  use
124
134
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@warpfx/server",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Warp Framework SDK for server-side FiveM module development",
5
5
  "keywords": [
6
6
  "fivem",