@native-sdk/core 0.5.3 → 0.5.4
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/package.json +1 -1
- package/sdk/core.ts +225 -3
package/package.json
CHANGED
package/sdk/core.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// return path (NS1017) — they never live in the Model, in a Msg, in a local,
|
|
11
11
|
// or in a helper.
|
|
12
12
|
//
|
|
13
|
-
// The
|
|
13
|
+
// The v3 command set:
|
|
14
14
|
//
|
|
15
15
|
// Cmd.none no effects (what a bare `return model` means)
|
|
16
16
|
// Cmd.persist() ask the host to persist the committed model
|
|
@@ -103,6 +103,50 @@
|
|
|
103
103
|
// forget control verbs whose consequences arrive
|
|
104
104
|
// on the event stream; aimed at a key with no
|
|
105
105
|
// open stream they no-op.
|
|
106
|
+
// Cmd.imageLoad(id, { path?, url?, cachePath?, expectedBytes? }, { event })
|
|
107
|
+
// load an image at runtime by its model-owned
|
|
108
|
+
// numeric ImageId (the id markup binds:
|
|
109
|
+
// <image image="{id}"/>, avatar likewise): the
|
|
110
|
+
// host resolves the source cascade (local path
|
|
111
|
+
// first, then a verified cache entry, then the
|
|
112
|
+
// network), decodes through the platform codec,
|
|
113
|
+
// registers the pixels under the id, and
|
|
114
|
+
// dispatches exactly ONE `event` arm (the
|
|
115
|
+
// five-field record below, the requested id
|
|
116
|
+
// echoed) — state "loaded" with the decoded
|
|
117
|
+
// width/height, or one failure class. One load
|
|
118
|
+
// per id at a time: a duplicate live id
|
|
119
|
+
// dispatches state "rejected".
|
|
120
|
+
// Cmd.imageCancel(id) end the in-flight load under the id, if any
|
|
121
|
+
// — loud, the spawn discipline: the load's
|
|
122
|
+
// event arm delivers state "cancelled" and the
|
|
123
|
+
// id frees for a fresh load once it lands. An
|
|
124
|
+
// id with no live load no-ops.
|
|
125
|
+
// Cmd.imageUnregister(id) free the registry slot under the id: the
|
|
126
|
+
// pixels are released, views referencing the
|
|
127
|
+
// id draw their fallback, and the slot is
|
|
128
|
+
// open for another load. Synchronous registry
|
|
129
|
+
// surgery like registration itself — not an
|
|
130
|
+
// effect, no Msg follows; an id with no
|
|
131
|
+
// registration no-ops. A load IN FLIGHT under
|
|
132
|
+
// the id is untouched: its terminal still
|
|
133
|
+
// registers — cancel the load first to keep
|
|
134
|
+
// the slot free.
|
|
135
|
+
//
|
|
136
|
+
// The window verbs (fire-and-forget, no result Msg — the window's own
|
|
137
|
+
// frame event carries the state):
|
|
138
|
+
//
|
|
139
|
+
// Cmd.showWindow(label) un-hide + activate the window with the
|
|
140
|
+
// declared label — the counterpart to a
|
|
141
|
+
// `close_policy = "hide"` hide and the tray
|
|
142
|
+
// "Open" consequence; also restores a
|
|
143
|
+
// minimized window. An unknown label is a
|
|
144
|
+
// no-op.
|
|
145
|
+
// Cmd.quitApp() graceful terminate, the tray "Quit"
|
|
146
|
+
// consequence: the host quits through the
|
|
147
|
+
// SAME shutdown path a last-window close
|
|
148
|
+
// takes, so the stop hook runs exactly once
|
|
149
|
+
// and a recording session seals its journal.
|
|
106
150
|
//
|
|
107
151
|
// The keyed-effect discipline is ONE rule: a keyed effect REPLACES its live
|
|
108
152
|
// predecessor (the superseded effect's result is dropped — no message), and
|
|
@@ -248,12 +292,87 @@ export type AudioEventArm = {
|
|
|
248
292
|
};
|
|
249
293
|
|
|
250
294
|
/// The Msg arms an audio event stream may target: arms whose payload is
|
|
251
|
-
/// exactly the six AudioEventArm fields.
|
|
295
|
+
/// exactly the six AudioEventArm fields. The `state` check runs BOTH
|
|
296
|
+
/// directions: the `&` constraint holds the arm's states to AudioState,
|
|
297
|
+
/// and the tuple-wrapped reverse check holds AudioState to the arm's
|
|
298
|
+
/// states — a narrower union would silently drop event states the host
|
|
299
|
+
/// emits, so it is refused here, not discovered at runtime.
|
|
252
300
|
export type AudioEventKind<M extends Msgish> = M extends Msgish
|
|
253
301
|
? [Exclude<keyof M, "kind">] extends [keyof AudioEventArm]
|
|
254
302
|
? [keyof AudioEventArm] extends [Exclude<keyof M, "kind">]
|
|
255
303
|
? M extends Msgish & AudioEventArm
|
|
256
|
-
? M["
|
|
304
|
+
? [AudioState] extends [M["state"]]
|
|
305
|
+
? M["kind"]
|
|
306
|
+
: never
|
|
307
|
+
: never
|
|
308
|
+
: never
|
|
309
|
+
: never
|
|
310
|
+
: never;
|
|
311
|
+
|
|
312
|
+
/// The image load result states, mirroring the engine's outcome vocabulary:
|
|
313
|
+
/// "loaded" means the pixels are registered under the requested id (width and
|
|
314
|
+
/// height carry what the codec decoded); every other state is the failure
|
|
315
|
+
/// class — "rejected" (a refused command: invalid id, no source, a duplicate
|
|
316
|
+
/// live id), "not_found" (missing local file, no url), "io_failed" (a local
|
|
317
|
+
/// read failure), "connect_failed"/"tls_failed"/"protocol_failed"/"timed_out"
|
|
318
|
+
/// (the fetch taxonomy), "http_status" (a non-2xx answer; `status` carries
|
|
319
|
+
/// it), "cancelled", "too_large" (source or decoded pixels over budget),
|
|
320
|
+
/// "unsupported" (no platform codec), "decode_failed", "registry_full", and
|
|
321
|
+
/// "alloc_failed" (the host refused the memory the registration needed —
|
|
322
|
+
/// resource exhaustion, not corrupt bytes: the same source may load once
|
|
323
|
+
/// memory frees).
|
|
324
|
+
export type ImageState =
|
|
325
|
+
| "loaded"
|
|
326
|
+
| "rejected"
|
|
327
|
+
| "not_found"
|
|
328
|
+
| "io_failed"
|
|
329
|
+
| "connect_failed"
|
|
330
|
+
| "tls_failed"
|
|
331
|
+
| "protocol_failed"
|
|
332
|
+
| "timed_out"
|
|
333
|
+
| "http_status"
|
|
334
|
+
| "cancelled"
|
|
335
|
+
| "too_large"
|
|
336
|
+
| "unsupported"
|
|
337
|
+
| "decode_failed"
|
|
338
|
+
| "registry_full"
|
|
339
|
+
| "alloc_failed";
|
|
340
|
+
|
|
341
|
+
/// The payload shape of an image result arm — five fields, matched by NAME
|
|
342
|
+
/// (the AudioEventArm convention). `id` is the requested ImageId echoed
|
|
343
|
+
/// verbatim, so two concurrent loads sharing one event arm stay
|
|
344
|
+
/// distinguishable in `update` (an id the wire cannot carry exactly — 0,
|
|
345
|
+
/// negatives, fractions, 2^53 and past — echoes 0, the no-image sentinel:
|
|
346
|
+
/// there is no honest integer to echo); `state` must be a named
|
|
347
|
+
/// string-literal-union alias carrying exactly the fifteen ImageState
|
|
348
|
+
/// members (any declaration order — the host matches members by name);
|
|
349
|
+
/// `width`/`height` are the decoded pixel dimensions ("loaded" only, 0
|
|
350
|
+
/// otherwise); `status` is the HTTP status for url loads that performed an
|
|
351
|
+
/// exchange, 0 when none occurred (local paths, cache hits) — 0 is signal,
|
|
352
|
+
/// not a missing value: a cache hit is a real "loaded" with no exchange
|
|
353
|
+
/// behind it, so apps can distinguish a network load from a cached one.
|
|
354
|
+
export type ImageEventArm = {
|
|
355
|
+
readonly id: number;
|
|
356
|
+
readonly state: ImageState;
|
|
357
|
+
readonly width: number;
|
|
358
|
+
readonly height: number;
|
|
359
|
+
readonly status: number;
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
/// The Msg arms an image load result may target: arms whose payload is
|
|
363
|
+
/// exactly the five ImageEventArm fields. The `state` check runs BOTH
|
|
364
|
+
/// directions (the AudioEventKind convention): the `&` constraint holds
|
|
365
|
+
/// the arm's states to ImageState, and the tuple-wrapped reverse check
|
|
366
|
+
/// holds ImageState to the arm's states — a narrower union would
|
|
367
|
+
/// silently drop result states the host emits, so it is refused here,
|
|
368
|
+
/// not discovered at runtime.
|
|
369
|
+
export type ImageEventKind<M extends Msgish> = M extends Msgish
|
|
370
|
+
? [Exclude<keyof M, "kind">] extends [keyof ImageEventArm]
|
|
371
|
+
? [keyof ImageEventArm] extends [Exclude<keyof M, "kind">]
|
|
372
|
+
? M extends Msgish & ImageEventArm
|
|
373
|
+
? [ImageState] extends [M["state"]]
|
|
374
|
+
? M["kind"]
|
|
375
|
+
: never
|
|
257
376
|
: never
|
|
258
377
|
: never
|
|
259
378
|
: never
|
|
@@ -335,6 +454,26 @@ export interface AudioRoute<M extends Msgish> {
|
|
|
335
454
|
readonly event: AudioEventKind<M>;
|
|
336
455
|
}
|
|
337
456
|
|
|
457
|
+
/// A `Cmd.imageLoad` source: the audio cascade's shape exactly. The local
|
|
458
|
+
/// `path` is tried first; a missing file falls through to `url` (fetched
|
|
459
|
+
/// whole, installed at `cachePath` when given and verified against
|
|
460
|
+
/// `expectedBytes` — 0/omitted means unknown size, existence alone qualifies
|
|
461
|
+
/// a cache entry; omit `cachePath` and the host derives the conventional
|
|
462
|
+
/// content-addressed path when a caches directory is configured). At least
|
|
463
|
+
/// one of path/url must be present.
|
|
464
|
+
export interface ImageSource {
|
|
465
|
+
readonly path?: Uint8Array;
|
|
466
|
+
readonly url?: Uint8Array;
|
|
467
|
+
readonly cachePath?: Uint8Array;
|
|
468
|
+
readonly expectedBytes?: number;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/// `Cmd.imageLoad` routing: the ONE terminal result dispatches the `event`
|
|
472
|
+
/// arm (the five-field ImageEventArm record, matched by field name).
|
|
473
|
+
export interface ImageRoute<M extends Msgish> {
|
|
474
|
+
readonly event: ImageEventKind<M>;
|
|
475
|
+
}
|
|
476
|
+
|
|
338
477
|
/// The closed HTTP verb set of `Cmd.fetch` (wire value = declaration order).
|
|
339
478
|
export type FetchMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD";
|
|
340
479
|
|
|
@@ -431,6 +570,19 @@ export type Cmd<M extends Msgish> =
|
|
|
431
570
|
/// Seek position (ms) / volume (0..1); 0 for the value-less verbs.
|
|
432
571
|
readonly value: number;
|
|
433
572
|
}
|
|
573
|
+
| { readonly op: "window_show"; readonly label: string }
|
|
574
|
+
| { readonly op: "quit_app" }
|
|
575
|
+
| {
|
|
576
|
+
readonly op: "image_load";
|
|
577
|
+
readonly id: number;
|
|
578
|
+
readonly eventKind: string;
|
|
579
|
+
readonly path: Uint8Array;
|
|
580
|
+
readonly url: Uint8Array;
|
|
581
|
+
readonly cachePath: Uint8Array;
|
|
582
|
+
readonly expectedBytes: number;
|
|
583
|
+
}
|
|
584
|
+
| { readonly op: "image_cancel"; readonly id: number }
|
|
585
|
+
| { readonly op: "image_unregister"; readonly id: number }
|
|
434
586
|
| { readonly op: "batch"; readonly cmds: readonly Cmd<M>[] };
|
|
435
587
|
|
|
436
588
|
/// The wire encoding of a host record payload, byte-identical to what the
|
|
@@ -659,6 +811,76 @@ export const Cmd = {
|
|
|
659
811
|
return { op: "audio_ctl", key, verb: "volume", value: volume };
|
|
660
812
|
},
|
|
661
813
|
|
|
814
|
+
/// Show the window with the declared `label`: un-hide + activate — the
|
|
815
|
+
/// counterpart to a `close_policy = "hide"` hide and the tray "Open"
|
|
816
|
+
/// consequence; also restores a minimized window. Fire-and-forget: no
|
|
817
|
+
/// result Msg (the window's own frame event carries the state), and an
|
|
818
|
+
/// unknown label is a no-op. The label is a string literal — window
|
|
819
|
+
/// labels are declarations.
|
|
820
|
+
showWindow(label: string): Cmd<never> {
|
|
821
|
+
return { op: "window_show", label };
|
|
822
|
+
},
|
|
823
|
+
|
|
824
|
+
/// Quit the app for real — the graceful terminate, and the tray "Quit"
|
|
825
|
+
/// consequence. The host quits through the SAME shutdown path a
|
|
826
|
+
/// last-window close takes, so the stop hook runs exactly once and a
|
|
827
|
+
/// recording session seals its journal. Fire-and-forget.
|
|
828
|
+
quitApp(): Cmd<never> {
|
|
829
|
+
return { op: "quit_app" };
|
|
830
|
+
},
|
|
831
|
+
|
|
832
|
+
/// Load an image at runtime under the model-owned numeric ImageId your
|
|
833
|
+
/// markup binds (`<image image="{id}"/>`, `<avatar image="{id}"/>`):
|
|
834
|
+
/// resolve the source cascade (local path first, then a verified cache
|
|
835
|
+
/// entry, then the network), decode through the platform codec, register
|
|
836
|
+
/// the pixels under `id`, and dispatch exactly ONE `event` arm — state
|
|
837
|
+
/// "loaded" with the decoded width/height, or one failure class, always
|
|
838
|
+
/// echoing the requested id so concurrent loads sharing the arm stay
|
|
839
|
+
/// distinguishable. Failure is never silent, and views referencing the
|
|
840
|
+
/// id repaint on the next
|
|
841
|
+
/// frame. One load per id at a time: a duplicate live id dispatches state
|
|
842
|
+
/// "rejected" (finish or re-key instead — ids are model data). Ids are
|
|
843
|
+
/// positive integers below 2^53 outside the reserved bit-63 namespace;
|
|
844
|
+
/// 0 is the no-image sentinel and dispatches "rejected".
|
|
845
|
+
imageLoad<M extends Msgish>(id: number, source: ImageSource, route: ImageRoute<M>): Cmd<M> {
|
|
846
|
+
return {
|
|
847
|
+
op: "image_load",
|
|
848
|
+
id,
|
|
849
|
+
eventKind: route.event,
|
|
850
|
+
path: source.path ?? new Uint8Array(0),
|
|
851
|
+
url: source.url ?? new Uint8Array(0),
|
|
852
|
+
cachePath: source.cachePath ?? new Uint8Array(0),
|
|
853
|
+
expectedBytes: source.expectedBytes ?? 0,
|
|
854
|
+
};
|
|
855
|
+
},
|
|
856
|
+
|
|
857
|
+
/// End the in-flight image load under `id`, if any — LOUDLY: the load's
|
|
858
|
+
/// one terminal arrives as its own `event` arm with state "cancelled"
|
|
859
|
+
/// (ending an in-flight load is an observable event, the spawn cancel
|
|
860
|
+
/// discipline), and the id is free for a fresh load once that terminal
|
|
861
|
+
/// lands. Aimed at an id with no live load it no-ops (the load it aimed
|
|
862
|
+
/// at already delivered its terminal). Image loads are keyed by their
|
|
863
|
+
/// numeric id, so the string-keyed `Cmd.cancel` never touches them —
|
|
864
|
+
/// this is their cancel, the way `Cmd.audioStop` is audio's.
|
|
865
|
+
imageCancel(id: number): Cmd<never> {
|
|
866
|
+
return { op: "image_cancel", id };
|
|
867
|
+
},
|
|
868
|
+
|
|
869
|
+
/// Free the registry slot under `id`: the pixels are released, views
|
|
870
|
+
/// referencing the id draw their fallback (avatar initials) on the next
|
|
871
|
+
/// frame, and the slot — one of the registry's 16 — is open for another
|
|
872
|
+
/// load (the gallery eviction move: unregister the evictee, load the
|
|
873
|
+
/// newcomer under a fresh id). Like registration itself this is
|
|
874
|
+
/// synchronous registry surgery, not an effect: no Msg follows, and an
|
|
875
|
+
/// id with no registration no-ops (whatever it aimed at is already
|
|
876
|
+
/// gone, `Cmd.imageCancel`'s idle rule). It frees only the CURRENT
|
|
877
|
+
/// registration — a load IN FLIGHT under the id is untouched and its
|
|
878
|
+
/// terminal still registers the pixels; to keep the slot free, end the
|
|
879
|
+
/// load with `Cmd.imageCancel(id)` first.
|
|
880
|
+
imageUnregister(id: number): Cmd<never> {
|
|
881
|
+
return { op: "image_unregister", id };
|
|
882
|
+
},
|
|
883
|
+
|
|
662
884
|
/// Several commands from one dispatch, performed in order.
|
|
663
885
|
batch<M extends Msgish>(cmds: readonly Cmd<M>[]): Cmd<M> {
|
|
664
886
|
return { op: "batch", cmds };
|