@leavepulse/control-sdk 0.3.31

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 (46) hide show
  1. package/README.md +2 -0
  2. package/auth-types.ts +5296 -0
  3. package/client.ts +320 -0
  4. package/index.ts +110 -0
  5. package/models.ts +232 -0
  6. package/package.json +28 -0
  7. package/procedures.ts +451 -0
  8. package/resources/ControlAgentRelease.ts +40 -0
  9. package/resources/ControlAlert.ts +39 -0
  10. package/resources/ControlCfAccount.ts +82 -0
  11. package/resources/ControlDcimAcceptance.ts +126 -0
  12. package/resources/ControlDcimCable.ts +70 -0
  13. package/resources/ControlDcimComponent.ts +62 -0
  14. package/resources/ControlDcimDevice.ts +71 -0
  15. package/resources/ControlDcimFeed.ts +61 -0
  16. package/resources/ControlDcimLocation.ts +55 -0
  17. package/resources/ControlDcimOutlet.ts +61 -0
  18. package/resources/ControlDcimPdu.ts +58 -0
  19. package/resources/ControlDcimPort.ts +61 -0
  20. package/resources/ControlDcimPowerLink.ts +34 -0
  21. package/resources/ControlDcimRack.ts +61 -0
  22. package/resources/ControlEdge.ts +43 -0
  23. package/resources/ControlEnrollToken.ts +45 -0
  24. package/resources/ControlEnvGroup.ts +60 -0
  25. package/resources/ControlHost.ts +184 -0
  26. package/resources/ControlNode.ts +48 -0
  27. package/resources/ControlProject.ts +36 -0
  28. package/resources/ControlRule.ts +56 -0
  29. package/resources/ControlSchedule.ts +56 -0
  30. package/resources/ControlService.ts +101 -0
  31. package/runtime/cache-policy.ts +371 -0
  32. package/runtime/cache.ts +129 -0
  33. package/runtime/credentials.ts +139 -0
  34. package/runtime/device.ts +199 -0
  35. package/runtime/errors.ts +225 -0
  36. package/runtime/etag-store.ts +252 -0
  37. package/runtime/json.ts +25 -0
  38. package/runtime/oauth2.ts +150 -0
  39. package/runtime/page.ts +81 -0
  40. package/runtime/realtime-client.ts +339 -0
  41. package/runtime/realtime.ts +257 -0
  42. package/runtime/realtime_pb/leavepulse/realtime/v1/ws_pb.ts +464 -0
  43. package/runtime/resource.ts +84 -0
  44. package/runtime/snowflake.ts +7 -0
  45. package/runtime/transport.ts +404 -0
  46. package/types.ts +7279 -0
@@ -0,0 +1,464 @@
1
+ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,import_extension=js"
2
+ // @generated from file leavepulse/realtime/v1/ws.proto (package leavepulse.realtime.v1, syntax proto3)
3
+ /* eslint-disable */
4
+
5
+ import type { GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1";
6
+ import { fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1";
7
+ import type { Message } from "@bufbuild/protobuf";
8
+
9
+ /**
10
+ * Describes the file leavepulse/realtime/v1/ws.proto.
11
+ */
12
+ export const file_leavepulse_realtime_v1_ws: GenFile =
13
+ /*@__PURE__*/
14
+ fileDesc(
15
+ "Ch9sZWF2ZXB1bHNlL3JlYWx0aW1lL3YxL3dzLnByb3RvEhZsZWF2ZXB1bHNlLnJlYWx0aW1lLnYxIskCCgtDbGllbnRGcmFtZRIsCgRhdXRoGAEgASgLMhwubGVhdmVwdWxzZS5yZWFsdGltZS52MS5BdXRoSAASNgoJc3Vic2NyaWJlGAIgASgLMiEubGVhdmVwdWxzZS5yZWFsdGltZS52MS5TdWJzY3JpYmVIABI6Cgt1bnN1YnNjcmliZRgDIAEoCzIjLmxlYXZlcHVsc2UucmVhbHRpbWUudjEuVW5zdWJzY3JpYmVIABIuCgVpbnB1dBgEIAEoCzIdLmxlYXZlcHVsc2UucmVhbHRpbWUudjEuSW5wdXRIABIyCgdyZXF1ZXN0GAUgASgLMh8ubGVhdmVwdWxzZS5yZWFsdGltZS52MS5SZXF1ZXN0SAASLAoEcGluZxgGIAEoCzIcLmxlYXZlcHVsc2UucmVhbHRpbWUudjEuUGluZ0gAQgYKBGJvZHkiFQoEQXV0aBINCgV0b2tlbhgBIAEoCSIaCglTdWJzY3JpYmUSDQoFdG9waWMYASABKAkiHAoLVW5zdWJzY3JpYmUSDQoFdG9waWMYASABKAkiKQoFSW5wdXQSEgoKc2Vzc2lvbl9pZBgBIAEoCRIMCgRkYXRhGAIgASgMIjYKB1JlcXVlc3QSCgoCaWQYASABKAkSDgoGbWV0aG9kGAIgASgJEg8KB3BheWxvYWQYAyABKAwiBgoEUGluZyL/AgoLU2VydmVyRnJhbWUSMgoHd2VsY29tZRgBIAEoCzIfLmxlYXZlcHVsc2UucmVhbHRpbWUudjEuV2VsY29tZUgAEjgKCnN1YnNjcmliZWQYAiABKAsyIi5sZWF2ZXB1bHNlLnJlYWx0aW1lLnYxLlN1YnNjcmliZWRIABI8Cgx1bnN1YnNjcmliZWQYAyABKAsyJC5sZWF2ZXB1bHNlLnJlYWx0aW1lLnYxLlVuc3Vic2NyaWJlZEgAEi4KBWV2ZW50GAQgASgLMh0ubGVhdmVwdWxzZS5yZWFsdGltZS52MS5FdmVudEgAEi4KBXJlcGx5GAUgASgLMh0ubGVhdmVwdWxzZS5yZWFsdGltZS52MS5SZXBseUgAEi4KBWVycm9yGAYgASgLMh0ubGVhdmVwdWxzZS5yZWFsdGltZS52MS5FcnJvckgAEiwKBHBpbmcYByABKAsyHC5sZWF2ZXB1bHNlLnJlYWx0aW1lLnYxLlBpbmdIAEIGCgRib2R5IjAKB1dlbGNvbWUSFQoNYXV0aGVudGljYXRlZBgBIAEoCBIOCgZ0b3BpY3MYAiADKAkiGwoKU3Vic2NyaWJlZBINCgV0b3BpYxgBIAEoCSIdCgxVbnN1YnNjcmliZWQSDQoFdG9waWMYASABKAkiOAoFRXZlbnQSDQoFdG9waWMYASABKAkSEgoKc2Vzc2lvbl9pZBgCIAEoCRIMCgRkYXRhGAMgASgMIlIKBVJlcGx5EgoKAmlkGAEgASgJEg8KB3BheWxvYWQYAiABKAwSLAoFZXJyb3IYAyABKAsyHS5sZWF2ZXB1bHNlLnJlYWx0aW1lLnYxLkVycm9yIkEKBUVycm9yEgwKBGNvZGUYASABKAkSDwoHbWVzc2FnZRgCIAEoCRINCgV0b3BpYxgDIAEoCRIKCgJpZBgEIAEoCUICUAFiBnByb3RvMw",
16
+ );
17
+
18
+ /**
19
+ * @generated from message leavepulse.realtime.v1.ClientFrame
20
+ */
21
+ export type ClientFrame = Message<"leavepulse.realtime.v1.ClientFrame"> & {
22
+ /**
23
+ * @generated from oneof leavepulse.realtime.v1.ClientFrame.body
24
+ */
25
+ body:
26
+ | {
27
+ /**
28
+ * @generated from field: leavepulse.realtime.v1.Auth auth = 1;
29
+ */
30
+ value: Auth;
31
+ case: "auth";
32
+ }
33
+ | {
34
+ /**
35
+ * @generated from field: leavepulse.realtime.v1.Subscribe subscribe = 2;
36
+ */
37
+ value: Subscribe;
38
+ case: "subscribe";
39
+ }
40
+ | {
41
+ /**
42
+ * @generated from field: leavepulse.realtime.v1.Unsubscribe unsubscribe = 3;
43
+ */
44
+ value: Unsubscribe;
45
+ case: "unsubscribe";
46
+ }
47
+ | {
48
+ /**
49
+ * Bidirectional session: a chunk of client->server stream data (e.g. console
50
+ * stdin). session_id scopes it to one owned session.
51
+ *
52
+ * @generated from field: leavepulse.realtime.v1.Input input = 4;
53
+ */
54
+ value: Input;
55
+ case: "input";
56
+ }
57
+ | {
58
+ /**
59
+ * One-shot request/reply (e.g. "restart this container"). Correlated by id.
60
+ *
61
+ * @generated from field: leavepulse.realtime.v1.Request request = 5;
62
+ */
63
+ value: Request;
64
+ case: "request";
65
+ }
66
+ | {
67
+ /**
68
+ * @generated from field: leavepulse.realtime.v1.Ping ping = 6;
69
+ */
70
+ value: Ping;
71
+ case: "ping";
72
+ }
73
+ | { case: undefined; value?: undefined };
74
+ };
75
+
76
+ /**
77
+ * Describes the message leavepulse.realtime.v1.ClientFrame.
78
+ * Use `create(ClientFrameSchema)` to create a new message.
79
+ */
80
+ export const ClientFrameSchema: GenMessage<ClientFrame> =
81
+ /*@__PURE__*/
82
+ messageDesc(file_leavepulse_realtime_v1_ws, 0);
83
+
84
+ /**
85
+ * Present a JWT to upgrade the socket from anonymous to authenticated. Required
86
+ * before subscribing to a private topic or opening a session. Mirrors the
87
+ * existing JSON {op:"auth", token} frame.
88
+ *
89
+ * @generated from message leavepulse.realtime.v1.Auth
90
+ */
91
+ export type Auth = Message<"leavepulse.realtime.v1.Auth"> & {
92
+ /**
93
+ * @generated from field: string token = 1;
94
+ */
95
+ token: string;
96
+ };
97
+
98
+ /**
99
+ * Describes the message leavepulse.realtime.v1.Auth.
100
+ * Use `create(AuthSchema)` to create a new message.
101
+ */
102
+ export const AuthSchema: GenMessage<Auth> =
103
+ /*@__PURE__*/
104
+ messageDesc(file_leavepulse_realtime_v1_ws, 1);
105
+
106
+ /**
107
+ * @generated from message leavepulse.realtime.v1.Subscribe
108
+ */
109
+ export type Subscribe = Message<"leavepulse.realtime.v1.Subscribe"> & {
110
+ /**
111
+ * @generated from field: string topic = 1;
112
+ */
113
+ topic: string;
114
+ };
115
+
116
+ /**
117
+ * Describes the message leavepulse.realtime.v1.Subscribe.
118
+ * Use `create(SubscribeSchema)` to create a new message.
119
+ */
120
+ export const SubscribeSchema: GenMessage<Subscribe> =
121
+ /*@__PURE__*/
122
+ messageDesc(file_leavepulse_realtime_v1_ws, 2);
123
+
124
+ /**
125
+ * @generated from message leavepulse.realtime.v1.Unsubscribe
126
+ */
127
+ export type Unsubscribe = Message<"leavepulse.realtime.v1.Unsubscribe"> & {
128
+ /**
129
+ * @generated from field: string topic = 1;
130
+ */
131
+ topic: string;
132
+ };
133
+
134
+ /**
135
+ * Describes the message leavepulse.realtime.v1.Unsubscribe.
136
+ * Use `create(UnsubscribeSchema)` to create a new message.
137
+ */
138
+ export const UnsubscribeSchema: GenMessage<Unsubscribe> =
139
+ /*@__PURE__*/
140
+ messageDesc(file_leavepulse_realtime_v1_ws, 3);
141
+
142
+ /**
143
+ * A chunk of upstream session data. data is opaque bytes (the session's own
144
+ * payload type, e.g. raw stdin); the gateway forwards it to the owning service
145
+ * without interpreting it.
146
+ *
147
+ * @generated from message leavepulse.realtime.v1.Input
148
+ */
149
+ export type Input = Message<"leavepulse.realtime.v1.Input"> & {
150
+ /**
151
+ * @generated from field: string session_id = 1;
152
+ */
153
+ sessionId: string;
154
+
155
+ /**
156
+ * @generated from field: bytes data = 2;
157
+ */
158
+ data: Uint8Array;
159
+ };
160
+
161
+ /**
162
+ * Describes the message leavepulse.realtime.v1.Input.
163
+ * Use `create(InputSchema)` to create a new message.
164
+ */
165
+ export const InputSchema: GenMessage<Input> =
166
+ /*@__PURE__*/
167
+ messageDesc(file_leavepulse_realtime_v1_ws, 4);
168
+
169
+ /**
170
+ * A correlated request the owning service answers with exactly one Reply. method
171
+ * names the operation; payload is the request's own protobuf message, opaque to
172
+ * the gateway (it routes by method to the registered handler).
173
+ *
174
+ * @generated from message leavepulse.realtime.v1.Request
175
+ */
176
+ export type Request = Message<"leavepulse.realtime.v1.Request"> & {
177
+ /**
178
+ * client-chosen correlation id, echoed in Reply
179
+ *
180
+ * @generated from field: string id = 1;
181
+ */
182
+ id: string;
183
+
184
+ /**
185
+ * registered request method, e.g. "container.restart"
186
+ *
187
+ * @generated from field: string method = 2;
188
+ */
189
+ method: string;
190
+
191
+ /**
192
+ * @generated from field: bytes payload = 3;
193
+ */
194
+ payload: Uint8Array;
195
+ };
196
+
197
+ /**
198
+ * Describes the message leavepulse.realtime.v1.Request.
199
+ * Use `create(RequestSchema)` to create a new message.
200
+ */
201
+ export const RequestSchema: GenMessage<Request> =
202
+ /*@__PURE__*/
203
+ messageDesc(file_leavepulse_realtime_v1_ws, 5);
204
+
205
+ /**
206
+ * @generated from message leavepulse.realtime.v1.Ping
207
+ */
208
+ export type Ping = Message<"leavepulse.realtime.v1.Ping"> & {};
209
+
210
+ /**
211
+ * Describes the message leavepulse.realtime.v1.Ping.
212
+ * Use `create(PingSchema)` to create a new message.
213
+ */
214
+ export const PingSchema: GenMessage<Ping> =
215
+ /*@__PURE__*/
216
+ messageDesc(file_leavepulse_realtime_v1_ws, 6);
217
+
218
+ /**
219
+ * @generated from message leavepulse.realtime.v1.ServerFrame
220
+ */
221
+ export type ServerFrame = Message<"leavepulse.realtime.v1.ServerFrame"> & {
222
+ /**
223
+ * @generated from oneof leavepulse.realtime.v1.ServerFrame.body
224
+ */
225
+ body:
226
+ | {
227
+ /**
228
+ * @generated from field: leavepulse.realtime.v1.Welcome welcome = 1;
229
+ */
230
+ value: Welcome;
231
+ case: "welcome";
232
+ }
233
+ | {
234
+ /**
235
+ * @generated from field: leavepulse.realtime.v1.Subscribed subscribed = 2;
236
+ */
237
+ value: Subscribed;
238
+ case: "subscribed";
239
+ }
240
+ | {
241
+ /**
242
+ * @generated from field: leavepulse.realtime.v1.Unsubscribed unsubscribed = 3;
243
+ */
244
+ value: Unsubscribed;
245
+ case: "unsubscribed";
246
+ }
247
+ | {
248
+ /**
249
+ * A topic event (fanout) OR a downstream session event. data is the topic's
250
+ * own protobuf payload (e.g. LogChunk for control.logs), opaque here.
251
+ *
252
+ * @generated from field: leavepulse.realtime.v1.Event event = 4;
253
+ */
254
+ value: Event;
255
+ case: "event";
256
+ }
257
+ | {
258
+ /**
259
+ * The answer to a ClientFrame.Request, correlated by id.
260
+ *
261
+ * @generated from field: leavepulse.realtime.v1.Reply reply = 5;
262
+ */
263
+ value: Reply;
264
+ case: "reply";
265
+ }
266
+ | {
267
+ /**
268
+ * @generated from field: leavepulse.realtime.v1.Error error = 6;
269
+ */
270
+ value: Error;
271
+ case: "error";
272
+ }
273
+ | {
274
+ /**
275
+ * @generated from field: leavepulse.realtime.v1.Ping ping = 7;
276
+ */
277
+ value: Ping;
278
+ case: "ping";
279
+ }
280
+ | { case: undefined; value?: undefined };
281
+ };
282
+
283
+ /**
284
+ * Describes the message leavepulse.realtime.v1.ServerFrame.
285
+ * Use `create(ServerFrameSchema)` to create a new message.
286
+ */
287
+ export const ServerFrameSchema: GenMessage<ServerFrame> =
288
+ /*@__PURE__*/
289
+ messageDesc(file_leavepulse_realtime_v1_ws, 7);
290
+
291
+ /**
292
+ * Sent once on connect. authenticated reflects whether a valid token was
293
+ * presented at upgrade (via query/header) before any Auth frame.
294
+ *
295
+ * @generated from message leavepulse.realtime.v1.Welcome
296
+ */
297
+ export type Welcome = Message<"leavepulse.realtime.v1.Welcome"> & {
298
+ /**
299
+ * @generated from field: bool authenticated = 1;
300
+ */
301
+ authenticated: boolean;
302
+
303
+ /**
304
+ * Static topics the client may subscribe to without parameters (advisory; the
305
+ * authoritative manifest is served over HTTP at /realtime/topics).
306
+ *
307
+ * @generated from field: repeated string topics = 2;
308
+ */
309
+ topics: string[];
310
+ };
311
+
312
+ /**
313
+ * Describes the message leavepulse.realtime.v1.Welcome.
314
+ * Use `create(WelcomeSchema)` to create a new message.
315
+ */
316
+ export const WelcomeSchema: GenMessage<Welcome> =
317
+ /*@__PURE__*/
318
+ messageDesc(file_leavepulse_realtime_v1_ws, 8);
319
+
320
+ /**
321
+ * @generated from message leavepulse.realtime.v1.Subscribed
322
+ */
323
+ export type Subscribed = Message<"leavepulse.realtime.v1.Subscribed"> & {
324
+ /**
325
+ * @generated from field: string topic = 1;
326
+ */
327
+ topic: string;
328
+ };
329
+
330
+ /**
331
+ * Describes the message leavepulse.realtime.v1.Subscribed.
332
+ * Use `create(SubscribedSchema)` to create a new message.
333
+ */
334
+ export const SubscribedSchema: GenMessage<Subscribed> =
335
+ /*@__PURE__*/
336
+ messageDesc(file_leavepulse_realtime_v1_ws, 9);
337
+
338
+ /**
339
+ * @generated from message leavepulse.realtime.v1.Unsubscribed
340
+ */
341
+ export type Unsubscribed = Message<"leavepulse.realtime.v1.Unsubscribed"> & {
342
+ /**
343
+ * @generated from field: string topic = 1;
344
+ */
345
+ topic: string;
346
+ };
347
+
348
+ /**
349
+ * Describes the message leavepulse.realtime.v1.Unsubscribed.
350
+ * Use `create(UnsubscribedSchema)` to create a new message.
351
+ */
352
+ export const UnsubscribedSchema: GenMessage<Unsubscribed> =
353
+ /*@__PURE__*/
354
+ messageDesc(file_leavepulse_realtime_v1_ws, 10);
355
+
356
+ /**
357
+ * A payload pushed to the client. For fanout topics topic is set; for session
358
+ * downstream session_id is set instead. data is the channel's own protobuf
359
+ * message (the client decodes it by the channel's declared payload type).
360
+ *
361
+ * @generated from message leavepulse.realtime.v1.Event
362
+ */
363
+ export type Event = Message<"leavepulse.realtime.v1.Event"> & {
364
+ /**
365
+ * set for fanout topics ("" for session events)
366
+ *
367
+ * @generated from field: string topic = 1;
368
+ */
369
+ topic: string;
370
+
371
+ /**
372
+ * set for session events ("" for fanout)
373
+ *
374
+ * @generated from field: string session_id = 2;
375
+ */
376
+ sessionId: string;
377
+
378
+ /**
379
+ * @generated from field: bytes data = 3;
380
+ */
381
+ data: Uint8Array;
382
+ };
383
+
384
+ /**
385
+ * Describes the message leavepulse.realtime.v1.Event.
386
+ * Use `create(EventSchema)` to create a new message.
387
+ */
388
+ export const EventSchema: GenMessage<Event> =
389
+ /*@__PURE__*/
390
+ messageDesc(file_leavepulse_realtime_v1_ws, 11);
391
+
392
+ /**
393
+ * The single answer to a Request. Exactly one of payload / error is meaningful.
394
+ *
395
+ * @generated from message leavepulse.realtime.v1.Reply
396
+ */
397
+ export type Reply = Message<"leavepulse.realtime.v1.Reply"> & {
398
+ /**
399
+ * echoes Request.id
400
+ *
401
+ * @generated from field: string id = 1;
402
+ */
403
+ id: string;
404
+
405
+ /**
406
+ * @generated from field: bytes payload = 2;
407
+ */
408
+ payload: Uint8Array;
409
+
410
+ /**
411
+ * @generated from field: leavepulse.realtime.v1.Error error = 3;
412
+ */
413
+ error?: Error;
414
+ };
415
+
416
+ /**
417
+ * Describes the message leavepulse.realtime.v1.Reply.
418
+ * Use `create(ReplySchema)` to create a new message.
419
+ */
420
+ export const ReplySchema: GenMessage<Reply> =
421
+ /*@__PURE__*/
422
+ messageDesc(file_leavepulse_realtime_v1_ws, 12);
423
+
424
+ /**
425
+ * A transport-level error (bad token, permission denied, unknown topic, ...).
426
+ * topic/id are set when the error pertains to a specific subscribe/request.
427
+ *
428
+ * @generated from message leavepulse.realtime.v1.Error
429
+ */
430
+ export type Error = Message<"leavepulse.realtime.v1.Error"> & {
431
+ /**
432
+ * stable machine code, e.g. "permission_denied"
433
+ *
434
+ * @generated from field: string code = 1;
435
+ */
436
+ code: string;
437
+
438
+ /**
439
+ * human-readable detail
440
+ *
441
+ * @generated from field: string message = 2;
442
+ */
443
+ message: string;
444
+
445
+ /**
446
+ * @generated from field: string topic = 3;
447
+ */
448
+ topic: string;
449
+
450
+ /**
451
+ * request id, when the error answers a Request
452
+ *
453
+ * @generated from field: string id = 4;
454
+ */
455
+ id: string;
456
+ };
457
+
458
+ /**
459
+ * Describes the message leavepulse.realtime.v1.Error.
460
+ * Use `create(ErrorSchema)` to create a new message.
461
+ */
462
+ export const ErrorSchema: GenMessage<Error> =
463
+ /*@__PURE__*/
464
+ messageDesc(file_leavepulse_realtime_v1_ws, 13);
@@ -0,0 +1,84 @@
1
+ // LeavePulse SDK — base resource.
2
+ //
3
+ // Generated resource classes extend this. It holds the backing data, supports
4
+ // in-place hydration for the identity map, and computes runtime capabilities
5
+ // (RFC 0001 §4): a static capability is "usable" only if the user's
6
+ // permissions carry it. Getters here never touch the network.
7
+ //
8
+ // Wrapped responses (RFC §3.1 x-sdk-data-root, e.g.
9
+ // `{ project: {id, ...}, servers: [...] }`) are normalized by the client
10
+ // BEFORE a resource is constructed: the core object is unwrapped and sibling
11
+ // fields (links) are merged onto it. So a resource always sees a flat object
12
+ // with `id` at the top level — the data root never leaks into runtime.
13
+
14
+ import type { Identified } from "./cache";
15
+
16
+ /** Extract an instance id from a payload. Resources key on different fields:
17
+ * most use `id`, some use a domain id (`user_id`). When `idPath` is given the
18
+ * id lives at a nested path (`["summary", "id"]` for an envelope), so we walk it
19
+ * directly. Otherwise: top-level `id`, then the first `*_id` field, so identity
20
+ * mapping works regardless of the schema's id name. */
21
+ export function extractId(
22
+ data: Record<string, unknown>,
23
+ idPath?: readonly string[],
24
+ ): string | number {
25
+ if (idPath && idPath.length > 0) {
26
+ let cursor: unknown = data;
27
+ for (const segment of idPath) {
28
+ if (cursor == null || typeof cursor !== "object") return "";
29
+ cursor = (cursor as Record<string, unknown>)[segment];
30
+ }
31
+ return typeof cursor === "string" || typeof cursor === "number"
32
+ ? cursor
33
+ : "";
34
+ }
35
+ const direct = data.id;
36
+ if (typeof direct === "string" || typeof direct === "number") return direct;
37
+ for (const [key, value] of Object.entries(data)) {
38
+ if (
39
+ key.endsWith("_id") &&
40
+ (typeof value === "string" || typeof value === "number")
41
+ ) {
42
+ return value;
43
+ }
44
+ }
45
+ return "";
46
+ }
47
+
48
+ export abstract class Resource<TData extends Record<string, unknown>>
49
+ implements Identified
50
+ {
51
+ protected data: TData;
52
+
53
+ constructor(data: TData) {
54
+ this.data = data;
55
+ }
56
+
57
+ get id(): string | number {
58
+ return extractId(this.data);
59
+ }
60
+
61
+ /** Replace backing data in place (identity map refresh). */
62
+ _hydrate(data: unknown): void {
63
+ this.data = data as TData;
64
+ }
65
+
66
+ /** Snapshot of the backing data (always from memory). */
67
+ toJSON(): TData {
68
+ return this.data;
69
+ }
70
+
71
+ /**
72
+ * Whether the current principal may perform `capability` on this instance:
73
+ * the static capability must exist AND the permission must be granted in the
74
+ * resource's payload. Emitters expose this as `can_<capability>` getters.
75
+ */
76
+ protected hasCapability(
77
+ _capability: string,
78
+ permissionCode: string,
79
+ ): boolean {
80
+ const perms = (this.data as { user_permissions?: unknown })
81
+ .user_permissions;
82
+ return Array.isArray(perms) && perms.includes(permissionCode);
83
+ }
84
+ }
@@ -0,0 +1,7 @@
1
+ // A 64-bit Snowflake id, carried as a string in TypeScript: the value exceeds
2
+ // JS's safe-integer range (2^53), so a `number` would silently lose its low
3
+ // digits. Backends keep it an int64; only the JS representation is a string.
4
+ //
5
+ // Kept as a plain alias (not a branded type) so ids interoperate with ordinary
6
+ // string handling — template URLs, equality, Map keys — without ceremony.
7
+ export type Snowflake = string;