@kernhq/module-inventory 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/LICENSE +662 -0
  2. package/README.md +32 -0
  3. package/dist/contract.d.ts +387 -0
  4. package/dist/contract.d.ts.map +1 -0
  5. package/dist/contract.js +119 -0
  6. package/dist/contract.js.map +1 -0
  7. package/dist/server/_impl.d.ts +427 -0
  8. package/dist/server/_impl.d.ts.map +1 -0
  9. package/dist/server/_impl.js +204 -0
  10. package/dist/server/_impl.js.map +1 -0
  11. package/dist/server/index.d.ts +3 -0
  12. package/dist/server/index.d.ts.map +1 -0
  13. package/dist/server/index.js +32 -0
  14. package/dist/server/index.js.map +1 -0
  15. package/dist/server/schema.d.ts +1552 -0
  16. package/dist/server/schema.d.ts.map +1 -0
  17. package/dist/server/schema.js +175 -0
  18. package/dist/server/schema.js.map +1 -0
  19. package/drizzle.config.ts +8 -0
  20. package/migrations/0000_init.sql +129 -0
  21. package/migrations/0001_rls.sql +62 -0
  22. package/migrations/meta/0000_snapshot.json +994 -0
  23. package/migrations/meta/_journal.json +20 -0
  24. package/package.json +106 -0
  25. package/src/client/api-instance.ts +28 -0
  26. package/src/client/api.ts +10 -0
  27. package/src/client/components/AssetFormDialog.svelte +185 -0
  28. package/src/client/i18n.ts +169 -0
  29. package/src/client/index.ts +26 -0
  30. package/src/client/mock.ts +95 -0
  31. package/src/client/module.ts +96 -0
  32. package/src/client/pages/AssetsPage.svelte +266 -0
  33. package/src/client/permissions.ts +30 -0
  34. package/src/client/query.ts +12 -0
  35. package/src/client/widgets/OverviewWidget.svelte +92 -0
  36. package/src/contract.ts +143 -0
  37. package/src/module.test.ts +79 -0
  38. package/src/server/_impl.ts +275 -0
  39. package/src/server/index.ts +33 -0
  40. package/src/server/schema.ts +228 -0
  41. package/tsconfig.client.json +10 -0
  42. package/tsconfig.json +10 -0
  43. package/vitest.config.ts +5 -0
@@ -0,0 +1,427 @@
1
+ import { defineModule, defineServerModule, type Kernel, packageVersion, type RequestContext } from '@kernhq/kernel';
2
+ /**
3
+ * The router, kept apart from `index.ts` so `module.test.ts` can walk it without booting a kernel.
4
+ *
5
+ * Two middlewares on every procedure, and the test fails if either is missing:
6
+ * `workspaceScoped` (a real membership, and the module switched on for that workspace) and
7
+ * `requires` (the permission this particular call needs).
8
+ */
9
+ export { defineModule, defineServerModule, packageVersion };
10
+ export declare function implement_(kernel: Kernel): {
11
+ assets: {
12
+ list: import("@orpc/server").Procedure<import("@orpc/server").MergedInitialContext<RequestContext & Record<never, never>, RequestContext & Record<never, never> & Omit<RequestContext & Record<never, never>, keyof RequestContext> & Omit<RequestContext & Record<never, never>, "workspaceId" | "kernel" | "principal" | "requestId" | "ip" | "headers">, RequestContext>, Omit<import("@orpc/server").MergedCurrentContext<RequestContext, {
13
+ workspaceId: string;
14
+ kernel: Kernel;
15
+ principal: {
16
+ kind: "service" | "user" | "api_key" | "anonymous";
17
+ userId: (string & import("zod").$brand<"UserId">) | null;
18
+ email: string | null;
19
+ name: string | null;
20
+ locale: "en" | "fa" | "ar" | "de";
21
+ instanceAdmin: boolean;
22
+ service: string | null;
23
+ memberships: {
24
+ workspaceId: string & import("zod").$brand<"WorkspaceId">;
25
+ role: "owner" | "admin" | "member" | "guest";
26
+ roleIds: string[];
27
+ groupIds: string[];
28
+ status: "active" | "invited" | "suspended";
29
+ }[];
30
+ permissionVersion: number;
31
+ };
32
+ requestId: string;
33
+ ip: string;
34
+ headers: Record<string, string | string[] | undefined>;
35
+ }>, never> & Record<never, never>, import("zod").ZodObject<{
36
+ workspaceId: import("zod/v4/core").$ZodBranded<import("zod").ZodUUID, "WorkspaceId", "out">;
37
+ q: import("zod").ZodOptional<import("zod").ZodString>;
38
+ categoryId: import("zod").ZodOptional<import("zod").ZodUUID>;
39
+ status: import("zod").ZodOptional<import("zod").ZodEnum<{
40
+ in_stock: "in_stock";
41
+ assigned: "assigned";
42
+ under_repair: "under_repair";
43
+ retired: "retired";
44
+ }>>;
45
+ sort: import("zod").ZodDefault<import("zod").ZodEnum<{
46
+ code: "code";
47
+ name: "name";
48
+ recent: "recent";
49
+ }>>;
50
+ cursor: import("zod").ZodOptional<import("zod").ZodString>;
51
+ limit: import("zod").ZodDefault<import("zod").ZodNumber>;
52
+ }, import("zod/v4/core").$strip>, import("zod").ZodObject<{
53
+ items: import("zod").ZodArray<import("zod").ZodObject<{
54
+ id: import("zod").ZodUUID;
55
+ workspaceId: import("zod/v4/core").$ZodBranded<import("zod").ZodUUID, "WorkspaceId", "out">;
56
+ code: import("zod").ZodString;
57
+ name: import("zod").ZodString;
58
+ description: import("zod").ZodString;
59
+ categoryId: import("zod").ZodNullable<import("zod").ZodUUID>;
60
+ status: import("zod").ZodEnum<{
61
+ in_stock: "in_stock";
62
+ assigned: "assigned";
63
+ under_repair: "under_repair";
64
+ retired: "retired";
65
+ }>;
66
+ custodianUserId: import("zod").ZodNullable<import("zod").ZodUUID>;
67
+ custodySince: import("zod").ZodNullable<import("zod").ZodString>;
68
+ serialNumber: import("zod").ZodNullable<import("zod").ZodString>;
69
+ location: import("zod").ZodNullable<import("zod").ZodString>;
70
+ purchasedOn: import("zod").ZodNullable<import("zod").ZodString>;
71
+ purchasedFrom: import("zod").ZodNullable<import("zod").ZodString>;
72
+ priceMinor: import("zod").ZodNullable<import("zod").ZodNumber>;
73
+ currency: import("zod").ZodNullable<import("zod").ZodString>;
74
+ warrantyUntil: import("zod").ZodNullable<import("zod").ZodString>;
75
+ photoFileId: import("zod").ZodNullable<import("zod").ZodUUID>;
76
+ createdAt: import("zod").ZodString;
77
+ updatedAt: import("zod").ZodString;
78
+ archivedAt: import("zod").ZodNullable<import("zod").ZodString>;
79
+ }, import("zod/v4/core").$strip>>;
80
+ nextCursor: import("zod").ZodNullable<import("zod").ZodString>;
81
+ total: import("zod").ZodOptional<import("zod").ZodNumber>;
82
+ }, import("zod/v4/core").$strip>, import("@orpc/server").MergedErrorMap<Record<never, never>, import("@orpc/server").MergedErrorMap<Record<never, never>, {
83
+ BAD_REQUEST: {
84
+ data: import("zod").ZodOptional<import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodUnknown>>>;
85
+ };
86
+ UNAUTHORIZED: {};
87
+ FORBIDDEN: {
88
+ data: import("zod").ZodOptional<import("zod").ZodObject<{
89
+ permission: import("zod").ZodOptional<import("zod").ZodString>;
90
+ }, import("zod/v4/core").$strip>>;
91
+ };
92
+ NOT_FOUND: {};
93
+ CONFLICT: {
94
+ data: import("zod").ZodOptional<import("zod").ZodObject<{
95
+ reason: import("zod").ZodOptional<import("zod").ZodString>;
96
+ }, import("zod/v4/core").$strip>>;
97
+ };
98
+ RATE_LIMITED: {};
99
+ MODULE_DISABLED: {
100
+ data: import("zod").ZodObject<{
101
+ module: import("zod").ZodString;
102
+ }, import("zod/v4/core").$strip>;
103
+ };
104
+ }>>, Record<never, never>>;
105
+ get: import("@orpc/server").Procedure<import("@orpc/server").MergedInitialContext<RequestContext & Record<never, never>, RequestContext & Record<never, never> & Omit<RequestContext & Record<never, never>, keyof RequestContext> & Omit<RequestContext & Record<never, never>, "workspaceId" | "kernel" | "principal" | "requestId" | "ip" | "headers">, RequestContext>, Omit<import("@orpc/server").MergedCurrentContext<RequestContext, {
106
+ workspaceId: string;
107
+ kernel: Kernel;
108
+ principal: {
109
+ kind: "service" | "user" | "api_key" | "anonymous";
110
+ userId: (string & import("zod").$brand<"UserId">) | null;
111
+ email: string | null;
112
+ name: string | null;
113
+ locale: "en" | "fa" | "ar" | "de";
114
+ instanceAdmin: boolean;
115
+ service: string | null;
116
+ memberships: {
117
+ workspaceId: string & import("zod").$brand<"WorkspaceId">;
118
+ role: "owner" | "admin" | "member" | "guest";
119
+ roleIds: string[];
120
+ groupIds: string[];
121
+ status: "active" | "invited" | "suspended";
122
+ }[];
123
+ permissionVersion: number;
124
+ };
125
+ requestId: string;
126
+ ip: string;
127
+ headers: Record<string, string | string[] | undefined>;
128
+ }>, never> & Record<never, never>, import("zod").ZodObject<{
129
+ workspaceId: import("zod/v4/core").$ZodBranded<import("zod").ZodUUID, "WorkspaceId", "out">;
130
+ assetId: import("zod").ZodUUID;
131
+ }, import("zod/v4/core").$strip>, import("zod").ZodObject<{
132
+ id: import("zod").ZodUUID;
133
+ workspaceId: import("zod/v4/core").$ZodBranded<import("zod").ZodUUID, "WorkspaceId", "out">;
134
+ code: import("zod").ZodString;
135
+ name: import("zod").ZodString;
136
+ description: import("zod").ZodString;
137
+ categoryId: import("zod").ZodNullable<import("zod").ZodUUID>;
138
+ status: import("zod").ZodEnum<{
139
+ in_stock: "in_stock";
140
+ assigned: "assigned";
141
+ under_repair: "under_repair";
142
+ retired: "retired";
143
+ }>;
144
+ custodianUserId: import("zod").ZodNullable<import("zod").ZodUUID>;
145
+ custodySince: import("zod").ZodNullable<import("zod").ZodString>;
146
+ serialNumber: import("zod").ZodNullable<import("zod").ZodString>;
147
+ location: import("zod").ZodNullable<import("zod").ZodString>;
148
+ purchasedOn: import("zod").ZodNullable<import("zod").ZodString>;
149
+ purchasedFrom: import("zod").ZodNullable<import("zod").ZodString>;
150
+ priceMinor: import("zod").ZodNullable<import("zod").ZodNumber>;
151
+ currency: import("zod").ZodNullable<import("zod").ZodString>;
152
+ warrantyUntil: import("zod").ZodNullable<import("zod").ZodString>;
153
+ photoFileId: import("zod").ZodNullable<import("zod").ZodUUID>;
154
+ createdAt: import("zod").ZodString;
155
+ updatedAt: import("zod").ZodString;
156
+ archivedAt: import("zod").ZodNullable<import("zod").ZodString>;
157
+ }, import("zod/v4/core").$strip>, import("@orpc/server").MergedErrorMap<Record<never, never>, import("@orpc/server").MergedErrorMap<Record<never, never>, {
158
+ BAD_REQUEST: {
159
+ data: import("zod").ZodOptional<import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodUnknown>>>;
160
+ };
161
+ UNAUTHORIZED: {};
162
+ FORBIDDEN: {
163
+ data: import("zod").ZodOptional<import("zod").ZodObject<{
164
+ permission: import("zod").ZodOptional<import("zod").ZodString>;
165
+ }, import("zod/v4/core").$strip>>;
166
+ };
167
+ NOT_FOUND: {};
168
+ CONFLICT: {
169
+ data: import("zod").ZodOptional<import("zod").ZodObject<{
170
+ reason: import("zod").ZodOptional<import("zod").ZodString>;
171
+ }, import("zod/v4/core").$strip>>;
172
+ };
173
+ RATE_LIMITED: {};
174
+ MODULE_DISABLED: {
175
+ data: import("zod").ZodObject<{
176
+ module: import("zod").ZodString;
177
+ }, import("zod/v4/core").$strip>;
178
+ };
179
+ }>>, Record<never, never>>;
180
+ create: import("@orpc/server").Procedure<import("@orpc/server").MergedInitialContext<RequestContext & Record<never, never>, RequestContext & Record<never, never> & Omit<RequestContext & Record<never, never>, keyof RequestContext> & Omit<RequestContext & Record<never, never>, "workspaceId" | "kernel" | "principal" | "requestId" | "ip" | "headers">, RequestContext>, Omit<import("@orpc/server").MergedCurrentContext<RequestContext, {
181
+ workspaceId: string;
182
+ kernel: Kernel;
183
+ principal: {
184
+ kind: "service" | "user" | "api_key" | "anonymous";
185
+ userId: (string & import("zod").$brand<"UserId">) | null;
186
+ email: string | null;
187
+ name: string | null;
188
+ locale: "en" | "fa" | "ar" | "de";
189
+ instanceAdmin: boolean;
190
+ service: string | null;
191
+ memberships: {
192
+ workspaceId: string & import("zod").$brand<"WorkspaceId">;
193
+ role: "owner" | "admin" | "member" | "guest";
194
+ roleIds: string[];
195
+ groupIds: string[];
196
+ status: "active" | "invited" | "suspended";
197
+ }[];
198
+ permissionVersion: number;
199
+ };
200
+ requestId: string;
201
+ ip: string;
202
+ headers: Record<string, string | string[] | undefined>;
203
+ }>, never> & Record<never, never>, import("zod").ZodObject<{
204
+ workspaceId: import("zod/v4/core").$ZodBranded<import("zod").ZodUUID, "WorkspaceId", "out">;
205
+ name: import("zod").ZodString;
206
+ description: import("zod").ZodDefault<import("zod").ZodString>;
207
+ categoryId: import("zod").ZodOptional<import("zod").ZodNullable<import("zod").ZodUUID>>;
208
+ serialNumber: import("zod").ZodOptional<import("zod").ZodNullable<import("zod").ZodString>>;
209
+ location: import("zod").ZodOptional<import("zod").ZodNullable<import("zod").ZodString>>;
210
+ purchasedFrom: import("zod").ZodOptional<import("zod").ZodNullable<import("zod").ZodString>>;
211
+ purchasedOn: import("zod").ZodOptional<import("zod").ZodNullable<import("zod").ZodISODate>>;
212
+ warrantyUntil: import("zod").ZodOptional<import("zod").ZodNullable<import("zod").ZodISODate>>;
213
+ priceMinor: import("zod").ZodOptional<import("zod").ZodNullable<import("zod").ZodNumber>>;
214
+ currency: import("zod").ZodOptional<import("zod").ZodNullable<import("zod").ZodString>>;
215
+ }, import("zod/v4/core").$strip>, import("zod").ZodObject<{
216
+ id: import("zod").ZodUUID;
217
+ workspaceId: import("zod/v4/core").$ZodBranded<import("zod").ZodUUID, "WorkspaceId", "out">;
218
+ code: import("zod").ZodString;
219
+ name: import("zod").ZodString;
220
+ description: import("zod").ZodString;
221
+ categoryId: import("zod").ZodNullable<import("zod").ZodUUID>;
222
+ status: import("zod").ZodEnum<{
223
+ in_stock: "in_stock";
224
+ assigned: "assigned";
225
+ under_repair: "under_repair";
226
+ retired: "retired";
227
+ }>;
228
+ custodianUserId: import("zod").ZodNullable<import("zod").ZodUUID>;
229
+ custodySince: import("zod").ZodNullable<import("zod").ZodString>;
230
+ serialNumber: import("zod").ZodNullable<import("zod").ZodString>;
231
+ location: import("zod").ZodNullable<import("zod").ZodString>;
232
+ purchasedOn: import("zod").ZodNullable<import("zod").ZodString>;
233
+ purchasedFrom: import("zod").ZodNullable<import("zod").ZodString>;
234
+ priceMinor: import("zod").ZodNullable<import("zod").ZodNumber>;
235
+ currency: import("zod").ZodNullable<import("zod").ZodString>;
236
+ warrantyUntil: import("zod").ZodNullable<import("zod").ZodString>;
237
+ photoFileId: import("zod").ZodNullable<import("zod").ZodUUID>;
238
+ createdAt: import("zod").ZodString;
239
+ updatedAt: import("zod").ZodString;
240
+ archivedAt: import("zod").ZodNullable<import("zod").ZodString>;
241
+ }, import("zod/v4/core").$strip>, import("@orpc/server").MergedErrorMap<Record<never, never>, import("@orpc/server").MergedErrorMap<Record<never, never>, {
242
+ BAD_REQUEST: {
243
+ data: import("zod").ZodOptional<import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodUnknown>>>;
244
+ };
245
+ UNAUTHORIZED: {};
246
+ FORBIDDEN: {
247
+ data: import("zod").ZodOptional<import("zod").ZodObject<{
248
+ permission: import("zod").ZodOptional<import("zod").ZodString>;
249
+ }, import("zod/v4/core").$strip>>;
250
+ };
251
+ NOT_FOUND: {};
252
+ CONFLICT: {
253
+ data: import("zod").ZodOptional<import("zod").ZodObject<{
254
+ reason: import("zod").ZodOptional<import("zod").ZodString>;
255
+ }, import("zod/v4/core").$strip>>;
256
+ };
257
+ RATE_LIMITED: {};
258
+ MODULE_DISABLED: {
259
+ data: import("zod").ZodObject<{
260
+ module: import("zod").ZodString;
261
+ }, import("zod/v4/core").$strip>;
262
+ };
263
+ }>>, Record<never, never>>;
264
+ update: import("@orpc/server").Procedure<import("@orpc/server").MergedInitialContext<RequestContext & Record<never, never>, RequestContext & Record<never, never> & Omit<RequestContext & Record<never, never>, keyof RequestContext> & Omit<RequestContext & Record<never, never>, "workspaceId" | "kernel" | "principal" | "requestId" | "ip" | "headers">, RequestContext>, Omit<import("@orpc/server").MergedCurrentContext<RequestContext, {
265
+ workspaceId: string;
266
+ kernel: Kernel;
267
+ principal: {
268
+ kind: "service" | "user" | "api_key" | "anonymous";
269
+ userId: (string & import("zod").$brand<"UserId">) | null;
270
+ email: string | null;
271
+ name: string | null;
272
+ locale: "en" | "fa" | "ar" | "de";
273
+ instanceAdmin: boolean;
274
+ service: string | null;
275
+ memberships: {
276
+ workspaceId: string & import("zod").$brand<"WorkspaceId">;
277
+ role: "owner" | "admin" | "member" | "guest";
278
+ roleIds: string[];
279
+ groupIds: string[];
280
+ status: "active" | "invited" | "suspended";
281
+ }[];
282
+ permissionVersion: number;
283
+ };
284
+ requestId: string;
285
+ ip: string;
286
+ headers: Record<string, string | string[] | undefined>;
287
+ }>, never> & Record<never, never>, import("zod").ZodObject<{
288
+ workspaceId: import("zod/v4/core").$ZodBranded<import("zod").ZodUUID, "WorkspaceId", "out">;
289
+ name: import("zod").ZodOptional<import("zod").ZodString>;
290
+ description: import("zod").ZodOptional<import("zod").ZodDefault<import("zod").ZodString>>;
291
+ categoryId: import("zod").ZodOptional<import("zod").ZodOptional<import("zod").ZodNullable<import("zod").ZodUUID>>>;
292
+ serialNumber: import("zod").ZodOptional<import("zod").ZodOptional<import("zod").ZodNullable<import("zod").ZodString>>>;
293
+ location: import("zod").ZodOptional<import("zod").ZodOptional<import("zod").ZodNullable<import("zod").ZodString>>>;
294
+ purchasedFrom: import("zod").ZodOptional<import("zod").ZodOptional<import("zod").ZodNullable<import("zod").ZodString>>>;
295
+ purchasedOn: import("zod").ZodOptional<import("zod").ZodOptional<import("zod").ZodNullable<import("zod").ZodISODate>>>;
296
+ warrantyUntil: import("zod").ZodOptional<import("zod").ZodOptional<import("zod").ZodNullable<import("zod").ZodISODate>>>;
297
+ priceMinor: import("zod").ZodOptional<import("zod").ZodOptional<import("zod").ZodNullable<import("zod").ZodNumber>>>;
298
+ currency: import("zod").ZodOptional<import("zod").ZodOptional<import("zod").ZodNullable<import("zod").ZodString>>>;
299
+ assetId: import("zod").ZodUUID;
300
+ }, import("zod/v4/core").$strip>, import("zod").ZodObject<{
301
+ id: import("zod").ZodUUID;
302
+ workspaceId: import("zod/v4/core").$ZodBranded<import("zod").ZodUUID, "WorkspaceId", "out">;
303
+ code: import("zod").ZodString;
304
+ name: import("zod").ZodString;
305
+ description: import("zod").ZodString;
306
+ categoryId: import("zod").ZodNullable<import("zod").ZodUUID>;
307
+ status: import("zod").ZodEnum<{
308
+ in_stock: "in_stock";
309
+ assigned: "assigned";
310
+ under_repair: "under_repair";
311
+ retired: "retired";
312
+ }>;
313
+ custodianUserId: import("zod").ZodNullable<import("zod").ZodUUID>;
314
+ custodySince: import("zod").ZodNullable<import("zod").ZodString>;
315
+ serialNumber: import("zod").ZodNullable<import("zod").ZodString>;
316
+ location: import("zod").ZodNullable<import("zod").ZodString>;
317
+ purchasedOn: import("zod").ZodNullable<import("zod").ZodString>;
318
+ purchasedFrom: import("zod").ZodNullable<import("zod").ZodString>;
319
+ priceMinor: import("zod").ZodNullable<import("zod").ZodNumber>;
320
+ currency: import("zod").ZodNullable<import("zod").ZodString>;
321
+ warrantyUntil: import("zod").ZodNullable<import("zod").ZodString>;
322
+ photoFileId: import("zod").ZodNullable<import("zod").ZodUUID>;
323
+ createdAt: import("zod").ZodString;
324
+ updatedAt: import("zod").ZodString;
325
+ archivedAt: import("zod").ZodNullable<import("zod").ZodString>;
326
+ }, import("zod/v4/core").$strip>, import("@orpc/server").MergedErrorMap<Record<never, never>, import("@orpc/server").MergedErrorMap<Record<never, never>, {
327
+ BAD_REQUEST: {
328
+ data: import("zod").ZodOptional<import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodUnknown>>>;
329
+ };
330
+ UNAUTHORIZED: {};
331
+ FORBIDDEN: {
332
+ data: import("zod").ZodOptional<import("zod").ZodObject<{
333
+ permission: import("zod").ZodOptional<import("zod").ZodString>;
334
+ }, import("zod/v4/core").$strip>>;
335
+ };
336
+ NOT_FOUND: {};
337
+ CONFLICT: {
338
+ data: import("zod").ZodOptional<import("zod").ZodObject<{
339
+ reason: import("zod").ZodOptional<import("zod").ZodString>;
340
+ }, import("zod/v4/core").$strip>>;
341
+ };
342
+ RATE_LIMITED: {};
343
+ MODULE_DISABLED: {
344
+ data: import("zod").ZodObject<{
345
+ module: import("zod").ZodString;
346
+ }, import("zod/v4/core").$strip>;
347
+ };
348
+ }>>, Record<never, never>>;
349
+ archive: import("@orpc/server").Procedure<import("@orpc/server").MergedInitialContext<RequestContext & Record<never, never>, RequestContext & Record<never, never> & Omit<RequestContext & Record<never, never>, keyof RequestContext> & Omit<RequestContext & Record<never, never>, "workspaceId" | "kernel" | "principal" | "requestId" | "ip" | "headers">, RequestContext>, Omit<import("@orpc/server").MergedCurrentContext<RequestContext, {
350
+ workspaceId: string;
351
+ kernel: Kernel;
352
+ principal: {
353
+ kind: "service" | "user" | "api_key" | "anonymous";
354
+ userId: (string & import("zod").$brand<"UserId">) | null;
355
+ email: string | null;
356
+ name: string | null;
357
+ locale: "en" | "fa" | "ar" | "de";
358
+ instanceAdmin: boolean;
359
+ service: string | null;
360
+ memberships: {
361
+ workspaceId: string & import("zod").$brand<"WorkspaceId">;
362
+ role: "owner" | "admin" | "member" | "guest";
363
+ roleIds: string[];
364
+ groupIds: string[];
365
+ status: "active" | "invited" | "suspended";
366
+ }[];
367
+ permissionVersion: number;
368
+ };
369
+ requestId: string;
370
+ ip: string;
371
+ headers: Record<string, string | string[] | undefined>;
372
+ }>, never> & Record<never, never>, import("zod").ZodObject<{
373
+ workspaceId: import("zod/v4/core").$ZodBranded<import("zod").ZodUUID, "WorkspaceId", "out">;
374
+ assetId: import("zod").ZodUUID;
375
+ archived: import("zod").ZodDefault<import("zod").ZodBoolean>;
376
+ }, import("zod/v4/core").$strip>, import("zod").ZodObject<{
377
+ id: import("zod").ZodUUID;
378
+ workspaceId: import("zod/v4/core").$ZodBranded<import("zod").ZodUUID, "WorkspaceId", "out">;
379
+ code: import("zod").ZodString;
380
+ name: import("zod").ZodString;
381
+ description: import("zod").ZodString;
382
+ categoryId: import("zod").ZodNullable<import("zod").ZodUUID>;
383
+ status: import("zod").ZodEnum<{
384
+ in_stock: "in_stock";
385
+ assigned: "assigned";
386
+ under_repair: "under_repair";
387
+ retired: "retired";
388
+ }>;
389
+ custodianUserId: import("zod").ZodNullable<import("zod").ZodUUID>;
390
+ custodySince: import("zod").ZodNullable<import("zod").ZodString>;
391
+ serialNumber: import("zod").ZodNullable<import("zod").ZodString>;
392
+ location: import("zod").ZodNullable<import("zod").ZodString>;
393
+ purchasedOn: import("zod").ZodNullable<import("zod").ZodString>;
394
+ purchasedFrom: import("zod").ZodNullable<import("zod").ZodString>;
395
+ priceMinor: import("zod").ZodNullable<import("zod").ZodNumber>;
396
+ currency: import("zod").ZodNullable<import("zod").ZodString>;
397
+ warrantyUntil: import("zod").ZodNullable<import("zod").ZodString>;
398
+ photoFileId: import("zod").ZodNullable<import("zod").ZodUUID>;
399
+ createdAt: import("zod").ZodString;
400
+ updatedAt: import("zod").ZodString;
401
+ archivedAt: import("zod").ZodNullable<import("zod").ZodString>;
402
+ }, import("zod/v4/core").$strip>, import("@orpc/server").MergedErrorMap<Record<never, never>, import("@orpc/server").MergedErrorMap<Record<never, never>, {
403
+ BAD_REQUEST: {
404
+ data: import("zod").ZodOptional<import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodUnknown>>>;
405
+ };
406
+ UNAUTHORIZED: {};
407
+ FORBIDDEN: {
408
+ data: import("zod").ZodOptional<import("zod").ZodObject<{
409
+ permission: import("zod").ZodOptional<import("zod").ZodString>;
410
+ }, import("zod/v4/core").$strip>>;
411
+ };
412
+ NOT_FOUND: {};
413
+ CONFLICT: {
414
+ data: import("zod").ZodOptional<import("zod").ZodObject<{
415
+ reason: import("zod").ZodOptional<import("zod").ZodString>;
416
+ }, import("zod/v4/core").$strip>>;
417
+ };
418
+ RATE_LIMITED: {};
419
+ MODULE_DISABLED: {
420
+ data: import("zod").ZodObject<{
421
+ module: import("zod").ZodString;
422
+ }, import("zod/v4/core").$strip>;
423
+ };
424
+ }>>, Record<never, never>>;
425
+ };
426
+ };
427
+ //# sourceMappingURL=_impl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_impl.d.ts","sourceRoot":"","sources":["../../src/server/_impl.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,kBAAkB,EAElB,KAAK,MAAM,EACX,cAAc,EACd,KAAK,cAAc,EAKpB,MAAM,gBAAgB,CAAA;AAMvB;;;;;;GAMG;AACH,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,cAAc,EAAE,CAAA;AAkD3D,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAwMxC"}
@@ -0,0 +1,204 @@
1
+ import { defineModule, defineServerModule, KernError, packageVersion, requires, uuidv7, workspaceScoped, } from '@kernhq/kernel';
2
+ import { implement } from '@orpc/server';
3
+ import { and, desc, eq, ilike, or, sql } from 'drizzle-orm';
4
+ import { inventoryContract, inventoryEvents, MODULE_ID } from '../contract.js';
5
+ import { assetHistory, assets, counters } from './schema.js';
6
+ /**
7
+ * The router, kept apart from `index.ts` so `module.test.ts` can walk it without booting a kernel.
8
+ *
9
+ * Two middlewares on every procedure, and the test fails if either is missing:
10
+ * `workspaceScoped` (a real membership, and the module switched on for that workspace) and
11
+ * `requires` (the permission this particular call needs).
12
+ */
13
+ export { defineModule, defineServerModule, packageVersion };
14
+ const os = implement(inventoryContract).$context();
15
+ /** The wire shape: drizzle gives Date objects for timestamps, the contract promises ISO strings. */
16
+ function toAsset(row) {
17
+ return {
18
+ id: row.id,
19
+ workspaceId: row.workspaceId,
20
+ code: row.code,
21
+ name: row.name,
22
+ description: row.description,
23
+ categoryId: row.categoryId,
24
+ status: row.status,
25
+ custodianUserId: row.custodianUserId,
26
+ custodySince: row.custodySince?.toISOString() ?? null,
27
+ serialNumber: row.serialNumber,
28
+ location: row.location,
29
+ purchasedOn: row.purchasedOn ?? null,
30
+ purchasedFrom: row.purchasedFrom,
31
+ priceMinor: row.priceMinor,
32
+ currency: row.currency,
33
+ warrantyUntil: row.warrantyUntil ?? null,
34
+ photoFileId: row.photoFileId,
35
+ createdAt: row.createdAt.toISOString(),
36
+ updatedAt: row.updatedAt.toISOString(),
37
+ archivedAt: row.archivedAt?.toISOString() ?? null,
38
+ };
39
+ }
40
+ /**
41
+ * The next asset tag for a workspace. One narrow row per workspace and key, incremented under the
42
+ * insert's conflict lock — two concurrent creates each read the value their own update returned, so
43
+ * codes stay unique without a retry loop. `INV-0042`, because people say asset tags out loud.
44
+ */
45
+ async function nextCode(tx, workspaceId) {
46
+ const [row] = await tx
47
+ .insert(counters)
48
+ .values({ workspaceId, key: 'asset_code', value: 1 })
49
+ .onConflictDoUpdate({
50
+ target: [counters.workspaceId, counters.key],
51
+ set: { value: sql `${counters.value} + 1` },
52
+ })
53
+ .returning();
54
+ return `INV-${String(row.value).padStart(4, '0')}`;
55
+ }
56
+ export function implement_(kernel) {
57
+ const scoped = os.use(workspaceScoped(MODULE_ID));
58
+ const changed = (workspaceId, id, op) => kernel.realtime.change(workspaceId, { module: MODULE_ID, entity: 'asset', id, op });
59
+ async function record(tx, input, assetId, actorId, action, changes = [], data) {
60
+ await tx.insert(assetHistory).values({
61
+ id: uuidv7(),
62
+ workspaceId: input.workspaceId,
63
+ assetId,
64
+ actorId: actorId ?? null,
65
+ action,
66
+ changes,
67
+ data: data ?? null,
68
+ });
69
+ }
70
+ return os.router({
71
+ assets: {
72
+ list: scoped.assets.list.use(requires('inventory.asset.view')).handler(({ input }) => kernel.database.withWorkspace(input.workspaceId, async (tx) => {
73
+ const filters = [eq(assets.workspaceId, input.workspaceId)];
74
+ if (input.q) {
75
+ // A code is something somebody reads off a sticker; matching name, code and serial
76
+ // loosely matters more than word-splitting here. Full-text arrives with the core indexer.
77
+ filters.push(or(ilike(assets.name, `%${input.q}%`), ilike(assets.code, `%${input.q}%`), ilike(sql `coalesce(${assets.serialNumber}, '')`, `%${input.q}%`)));
78
+ }
79
+ if (input.categoryId)
80
+ filters.push(eq(assets.categoryId, input.categoryId));
81
+ if (input.status)
82
+ filters.push(eq(assets.status, input.status));
83
+ const order = input.sort === 'name' ? assets.name : input.sort === 'code' ? assets.code : desc(assets.createdAt);
84
+ const rows = await tx
85
+ .select()
86
+ .from(assets)
87
+ .where(and(...filters))
88
+ .orderBy(order)
89
+ .limit(input.limit);
90
+ return { items: rows.map(toAsset), nextCursor: null };
91
+ })),
92
+ get: scoped.assets.get.use(requires('inventory.asset.view')).handler(({ input }) => kernel.database.withWorkspace(input.workspaceId, async (tx) => {
93
+ const [row] = await tx
94
+ .select()
95
+ .from(assets)
96
+ .where(and(eq(assets.workspaceId, input.workspaceId), eq(assets.id, input.assetId)));
97
+ if (!row)
98
+ throw KernError.notFound('Asset');
99
+ return toAsset(row);
100
+ })),
101
+ create: scoped.assets.create
102
+ .use(requires('inventory.asset.manage'))
103
+ .handler(async ({ input, context }) => {
104
+ const row = await kernel.database.withWorkspace(input.workspaceId, async (tx) => {
105
+ const [r] = await tx
106
+ .insert(assets)
107
+ .values({
108
+ id: uuidv7(),
109
+ workspaceId: input.workspaceId,
110
+ code: await nextCode(tx, input.workspaceId),
111
+ name: input.name,
112
+ description: input.description,
113
+ categoryId: input.categoryId ?? null,
114
+ serialNumber: input.serialNumber ?? null,
115
+ location: input.location ?? null,
116
+ purchasedFrom: input.purchasedFrom ?? null,
117
+ purchasedOn: input.purchasedOn ?? null,
118
+ warrantyUntil: input.warrantyUntil ?? null,
119
+ priceMinor: input.priceMinor ?? null,
120
+ currency: input.currency ?? null,
121
+ })
122
+ .returning();
123
+ await record(tx, input, r.id, context.principal.userId, 'created');
124
+ return r;
125
+ });
126
+ // Both, every time. The event is for anything that reacts later; the realtime change is
127
+ // what redraws a screen somebody is looking at now. A mutation that does neither leaves
128
+ // the rest of the product believing the old answer.
129
+ await kernel.emit(inventoryEvents.assetCreated, { assetId: row.id, workspaceId: input.workspaceId }, { workspaceId: input.workspaceId, actorId: context.principal.userId });
130
+ await changed(input.workspaceId, row.id, 'created');
131
+ return toAsset(row);
132
+ }),
133
+ update: scoped.assets.update
134
+ .use(requires('inventory.asset.manage'))
135
+ .handler(async ({ input, context }) => {
136
+ const row = await kernel.database.withWorkspace(input.workspaceId, async (tx) => {
137
+ const [prev] = await tx
138
+ .select()
139
+ .from(assets)
140
+ .where(and(eq(assets.workspaceId, input.workspaceId), eq(assets.id, input.assetId)))
141
+ .for('update');
142
+ if (!prev)
143
+ throw KernError.notFound('Asset');
144
+ const patch = {
145
+ name: input.name ?? prev.name,
146
+ description: input.description ?? prev.description,
147
+ categoryId: input.categoryId !== undefined ? (input.categoryId ?? null) : prev.categoryId,
148
+ serialNumber: input.serialNumber !== undefined ? (input.serialNumber ?? null) : prev.serialNumber,
149
+ location: input.location !== undefined ? (input.location ?? null) : prev.location,
150
+ purchasedFrom: input.purchasedFrom !== undefined ? (input.purchasedFrom ?? null) : prev.purchasedFrom,
151
+ purchasedOn: input.purchasedOn !== undefined ? (input.purchasedOn ?? null) : prev.purchasedOn,
152
+ warrantyUntil: input.warrantyUntil !== undefined ? (input.warrantyUntil ?? null) : prev.warrantyUntil,
153
+ priceMinor: input.priceMinor !== undefined ? (input.priceMinor ?? null) : prev.priceMinor,
154
+ currency: input.currency !== undefined ? (input.currency ?? null) : prev.currency,
155
+ updatedAt: new Date(),
156
+ };
157
+ const [r] = await tx.update(assets).set(patch).where(eq(assets.id, input.assetId)).returning();
158
+ // Field-level diffs are the timeline — write only what actually moved.
159
+ const fields = [
160
+ 'name',
161
+ 'description',
162
+ 'categoryId',
163
+ 'serialNumber',
164
+ 'location',
165
+ 'purchasedFrom',
166
+ 'purchasedOn',
167
+ 'warrantyUntil',
168
+ 'priceMinor',
169
+ 'currency',
170
+ ];
171
+ const iso = (v) => (v instanceof Date ? v.toISOString() : v) ?? null;
172
+ const changes = fields
173
+ .filter((f) => iso(patch[f]) !== iso(prev[f]))
174
+ .map((f) => ({ field: f, from: iso(prev[f]), to: iso(patch[f]) }));
175
+ if (changes.length > 0)
176
+ await record(tx, input, input.assetId, context.principal.userId, 'updated', changes);
177
+ return r;
178
+ });
179
+ await kernel.emit(inventoryEvents.assetUpdated, { assetId: row.id, workspaceId: input.workspaceId }, { workspaceId: input.workspaceId, actorId: context.principal.userId });
180
+ await changed(input.workspaceId, row.id, 'updated');
181
+ return toAsset(row);
182
+ }),
183
+ archive: scoped.assets.archive
184
+ .use(requires('inventory.asset.manage'))
185
+ .handler(async ({ input, context }) => {
186
+ const row = await kernel.database.withWorkspace(input.workspaceId, async (tx) => {
187
+ const [r] = await tx
188
+ .update(assets)
189
+ .set({ archivedAt: input.archived ? new Date() : null, updatedAt: new Date() })
190
+ .where(and(eq(assets.workspaceId, input.workspaceId), eq(assets.id, input.assetId)))
191
+ .returning();
192
+ if (!r)
193
+ throw KernError.notFound('Asset');
194
+ await record(tx, input, input.assetId, context.principal.userId, input.archived ? 'retired' : 'restored');
195
+ return r;
196
+ });
197
+ await kernel.emit(inventoryEvents.assetArchived, { assetId: row.id, workspaceId: input.workspaceId }, { workspaceId: input.workspaceId, actorId: context.principal.userId });
198
+ await changed(input.workspaceId, row.id, 'updated');
199
+ return toAsset(row);
200
+ }),
201
+ },
202
+ });
203
+ }
204
+ //# sourceMappingURL=_impl.js.map