@urk/adapters 0.1.3 → 0.1.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.
Files changed (63) hide show
  1. package/README.md +10 -2
  2. package/dist/adapters.d.ts +7 -7
  3. package/dist/adapters.d.ts.map +1 -1
  4. package/dist/adapters.js +7 -7
  5. package/dist/adapters.js.map +1 -1
  6. package/dist/{audio.d.ts → audio/index.d.ts} +1 -1
  7. package/dist/audio/index.d.ts.map +1 -0
  8. package/dist/{audio.js → audio/index.js} +1 -1
  9. package/dist/audio/index.js.map +1 -0
  10. package/dist/dom.d.ts +5 -5
  11. package/dist/dom.d.ts.map +1 -1
  12. package/dist/dom.js +5 -5
  13. package/dist/dom.js.map +1 -1
  14. package/dist/{input.d.ts → input/index.d.ts} +1 -1
  15. package/dist/input/index.d.ts.map +1 -0
  16. package/dist/{input.js → input/index.js} +1 -1
  17. package/dist/input/index.js.map +1 -0
  18. package/dist/{loading.d.ts → loading/index.d.ts} +1 -1
  19. package/dist/loading/index.d.ts.map +1 -0
  20. package/dist/{loading.js → loading/index.js} +1 -1
  21. package/dist/loading/index.js.map +1 -0
  22. package/dist/{pointer.d.ts → pointer/index.d.ts} +1 -1
  23. package/dist/pointer/index.d.ts.map +1 -0
  24. package/dist/{pointer.js → pointer/index.js} +1 -1
  25. package/dist/pointer/index.js.map +1 -0
  26. package/dist/{storage.d.ts → storage/index.d.ts} +1 -1
  27. package/dist/storage/index.d.ts.map +1 -0
  28. package/dist/{storage.js → storage/index.js} +1 -1
  29. package/dist/storage/index.js.map +1 -0
  30. package/dist/{three.d.ts → three/index.d.ts} +1 -1
  31. package/dist/three/index.d.ts.map +1 -0
  32. package/dist/{three.js → three/index.js} +1 -1
  33. package/dist/three/index.js.map +1 -0
  34. package/dist/{ui-widgets.d.ts → ui-widgets/index.d.ts} +1 -1
  35. package/dist/ui-widgets/index.d.ts.map +1 -0
  36. package/dist/{ui-widgets.js → ui-widgets/index.js} +1 -1
  37. package/dist/ui-widgets/index.js.map +1 -0
  38. package/package.json +3 -4
  39. package/dist/audio.d.ts.map +0 -1
  40. package/dist/audio.js.map +0 -1
  41. package/dist/input.d.ts.map +0 -1
  42. package/dist/input.js.map +0 -1
  43. package/dist/loading.d.ts.map +0 -1
  44. package/dist/loading.js.map +0 -1
  45. package/dist/pointer.d.ts.map +0 -1
  46. package/dist/pointer.js.map +0 -1
  47. package/dist/storage.d.ts.map +0 -1
  48. package/dist/storage.js.map +0 -1
  49. package/dist/three.d.ts.map +0 -1
  50. package/dist/three.js.map +0 -1
  51. package/dist/ui-widgets.d.ts.map +0 -1
  52. package/dist/ui-widgets.js.map +0 -1
  53. package/src/adapters.ts +0 -12
  54. package/src/audio.ts +0 -458
  55. package/src/contracts.ts +0 -15
  56. package/src/dom.ts +0 -16
  57. package/src/index.ts +0 -5
  58. package/src/input.ts +0 -207
  59. package/src/loading.ts +0 -174
  60. package/src/pointer.ts +0 -203
  61. package/src/storage.ts +0 -194
  62. package/src/three.ts +0 -178
  63. package/src/ui-widgets.ts +0 -120
package/src/audio.ts DELETED
@@ -1,458 +0,0 @@
1
- /**
2
- * Company: EonHive Inc.
3
- * Title: Audio Adapter
4
- * Purpose: Provide a small browser-native audio transport capability for URK.
5
- * Author: Stan Nesi
6
- * Created: 2026-04-23
7
- * Updated: 2026-04-23
8
- * Notes: Vibe coded with Codex.
9
- */
10
-
11
- import type { AdapterRegistration } from '@urk/core';
12
-
13
- export interface AudioTrackDefinition {
14
- id: string;
15
- src: string;
16
- label: string;
17
- loop?: boolean;
18
- volume?: number;
19
- }
20
-
21
- export type AudioPlaybackStatus = 'idle' | 'loading' | 'ready' | 'playing' | 'paused' | 'error';
22
-
23
- export interface AudioTransportSnapshot {
24
- activeTrackId: string | null;
25
- status: AudioPlaybackStatus;
26
- muted: boolean;
27
- volume: number;
28
- loadedTrackIds: string[];
29
- errorMessage?: string;
30
- }
31
-
32
- export type AudioListener = (snapshot: AudioTransportSnapshot) => void;
33
-
34
- export interface AudioAdapterApi {
35
- registerTrack(track: AudioTrackDefinition): void;
36
- preload(trackId: string): Promise<void>;
37
- play(trackId?: string): Promise<void>;
38
- pause(): void;
39
- stop(): void;
40
- setMuted(next: boolean): void;
41
- setVolume(next: number): void;
42
- getSnapshot(): AudioTransportSnapshot;
43
- subscribe(listener: AudioListener): () => void;
44
- }
45
-
46
- type RegisteredTrack = {
47
- definition: AudioTrackDefinition;
48
- element: HTMLAudioElement;
49
- onEnded: () => void;
50
- onError: () => void;
51
- };
52
-
53
- function clampVolume(value: number): number {
54
- return Math.max(0, Math.min(value, 1));
55
- }
56
-
57
- function cloneSnapshot(snapshot: AudioTransportSnapshot): AudioTransportSnapshot {
58
- return {
59
- ...snapshot,
60
- loadedTrackIds: [...snapshot.loadedTrackIds],
61
- };
62
- }
63
-
64
- function createEmptySnapshot(): AudioTransportSnapshot {
65
- return {
66
- activeTrackId: null,
67
- status: 'idle',
68
- muted: false,
69
- volume: 1,
70
- loadedTrackIds: [],
71
- };
72
- }
73
-
74
- function formatTrackError(track: AudioTrackDefinition, action: 'preload' | 'playback'): string {
75
- return `Failed ${action} for audio track: ${track.label}`;
76
- }
77
-
78
- export function createAudioAdapter(
79
- id = 'audio-adapter',
80
- ): AdapterRegistration<AudioAdapterApi> {
81
- let teardown: (() => void) | null = null;
82
-
83
- return {
84
- id,
85
- capability: 'audio',
86
- isSupported() {
87
- return typeof window !== 'undefined' && typeof Audio !== 'undefined';
88
- },
89
- setup(ctx) {
90
- const tracks = new Map<string, RegisteredTrack>();
91
- const loadedTrackIds = new Set<string>();
92
- const listeners = new Set<AudioListener>();
93
- let snapshot = createEmptySnapshot();
94
-
95
- const publish = (
96
- partial: Partial<AudioTransportSnapshot> = {},
97
- extraEvent?:
98
- | {
99
- type: 'audio:ended' | 'audio:error';
100
- payload: unknown;
101
- }
102
- | undefined,
103
- ): AudioTransportSnapshot => {
104
- snapshot = {
105
- ...snapshot,
106
- ...partial,
107
- loadedTrackIds: [...loadedTrackIds],
108
- };
109
-
110
- const next = cloneSnapshot(snapshot);
111
-
112
- ctx.events.emit({
113
- type: 'audio:changed',
114
- source: id,
115
- payload: next,
116
- timestamp: Date.now(),
117
- });
118
-
119
- if (extraEvent) {
120
- ctx.events.emit({
121
- type: extraEvent.type,
122
- source: id,
123
- payload: extraEvent.payload,
124
- timestamp: Date.now(),
125
- });
126
- }
127
-
128
- for (const listener of [...listeners]) {
129
- listener(next);
130
- }
131
-
132
- return next;
133
- };
134
-
135
- const requireTrack = (trackId: string): RegisteredTrack => {
136
- const record = tracks.get(trackId);
137
-
138
- if (!record) {
139
- throw new Error(`Unknown audio track: ${trackId}`);
140
- }
141
-
142
- return record;
143
- };
144
-
145
- const applyElementSettings = (record: RegisteredTrack): void => {
146
- record.element.loop = record.definition.loop ?? false;
147
- record.element.muted = snapshot.muted;
148
- record.element.volume = clampVolume(snapshot.volume * (record.definition.volume ?? 1));
149
- };
150
-
151
- const stopElement = (record: RegisteredTrack): void => {
152
- record.element.pause();
153
-
154
- try {
155
- record.element.currentTime = 0;
156
- } catch {
157
- // Some browsers can reject currentTime writes if metadata is unavailable.
158
- }
159
- };
160
-
161
- const preloadElement = async (record: RegisteredTrack): Promise<void> => {
162
- if (loadedTrackIds.has(record.definition.id) || record.element.readyState >= 3) {
163
- loadedTrackIds.add(record.definition.id);
164
- return;
165
- }
166
-
167
- await new Promise<void>((resolve, reject) => {
168
- const onReady = (): void => {
169
- cleanup();
170
- loadedTrackIds.add(record.definition.id);
171
- resolve();
172
- };
173
-
174
- const onError = (): void => {
175
- cleanup();
176
- reject(new Error(formatTrackError(record.definition, 'preload')));
177
- };
178
-
179
- const cleanup = (): void => {
180
- record.element.removeEventListener('canplaythrough', onReady);
181
- record.element.removeEventListener('loadeddata', onReady);
182
- record.element.removeEventListener('error', onError);
183
- };
184
-
185
- record.element.addEventListener('canplaythrough', onReady, { once: true });
186
- record.element.addEventListener('loadeddata', onReady, { once: true });
187
- record.element.addEventListener('error', onError, { once: true });
188
- record.element.load();
189
- });
190
- };
191
-
192
- const preloadTrack = async (
193
- record: RegisteredTrack,
194
- shouldSetLoading: boolean,
195
- ): Promise<void> => {
196
- if (shouldSetLoading) {
197
- publish({
198
- status: 'loading',
199
- errorMessage: undefined,
200
- });
201
- }
202
-
203
- try {
204
- await preloadElement(record);
205
- publish({
206
- status: shouldSetLoading ? 'ready' : snapshot.status,
207
- errorMessage: undefined,
208
- });
209
- } catch (error) {
210
- const message =
211
- error instanceof Error ? error.message : formatTrackError(record.definition, 'preload');
212
-
213
- publish(
214
- {
215
- status: 'error',
216
- errorMessage: message,
217
- },
218
- {
219
- type: 'audio:error',
220
- payload: {
221
- trackId: record.definition.id,
222
- label: record.definition.label,
223
- message,
224
- },
225
- },
226
- );
227
-
228
- throw new Error(message);
229
- }
230
- };
231
-
232
- teardown = (): void => {
233
- for (const record of tracks.values()) {
234
- record.element.pause();
235
- record.element.removeEventListener('ended', record.onEnded);
236
- record.element.removeEventListener('error', record.onError);
237
- record.element.src = '';
238
- record.element.load();
239
- }
240
-
241
- tracks.clear();
242
- loadedTrackIds.clear();
243
- listeners.clear();
244
- };
245
-
246
- return {
247
- registerTrack(track) {
248
- if (tracks.has(track.id)) {
249
- throw new Error(`Audio track already registered: ${track.id}`);
250
- }
251
-
252
- const element = new Audio(track.src);
253
- element.preload = 'auto';
254
-
255
- const onEnded = (): void => {
256
- if (snapshot.activeTrackId !== track.id) {
257
- return;
258
- }
259
-
260
- publish(
261
- {
262
- status: 'ready',
263
- errorMessage: undefined,
264
- },
265
- {
266
- type: 'audio:ended',
267
- payload: {
268
- trackId: track.id,
269
- label: track.label,
270
- },
271
- },
272
- );
273
- };
274
-
275
- const onError = (): void => {
276
- const message = formatTrackError(track, 'playback');
277
-
278
- if (snapshot.status === 'error' && snapshot.errorMessage === message) {
279
- return;
280
- }
281
-
282
- publish(
283
- {
284
- activeTrackId: snapshot.activeTrackId ?? track.id,
285
- status: 'error',
286
- errorMessage: message,
287
- },
288
- {
289
- type: 'audio:error',
290
- payload: {
291
- trackId: track.id,
292
- label: track.label,
293
- message,
294
- },
295
- },
296
- );
297
- };
298
-
299
- const record: RegisteredTrack = {
300
- definition: {
301
- ...track,
302
- volume: clampVolume(track.volume ?? 1),
303
- },
304
- element,
305
- onEnded,
306
- onError,
307
- };
308
-
309
- element.addEventListener('ended', onEnded);
310
- element.addEventListener('error', onError);
311
- applyElementSettings(record);
312
- tracks.set(track.id, record);
313
- },
314
- async preload(trackId) {
315
- const record = requireTrack(trackId);
316
- const shouldSetLoading =
317
- snapshot.status === 'idle' ||
318
- snapshot.status === 'loading' ||
319
- snapshot.status === 'ready';
320
-
321
- await preloadTrack(record, shouldSetLoading);
322
- },
323
- async play(trackId) {
324
- const nextTrackId = trackId ?? snapshot.activeTrackId;
325
-
326
- if (!nextTrackId) {
327
- return;
328
- }
329
-
330
- const nextRecord = requireTrack(nextTrackId);
331
- const previousTrackId = snapshot.activeTrackId;
332
-
333
- if (previousTrackId && previousTrackId !== nextTrackId) {
334
- stopElement(requireTrack(previousTrackId));
335
- }
336
-
337
- if (!loadedTrackIds.has(nextTrackId) && nextRecord.element.readyState < 3) {
338
- publish({
339
- activeTrackId: nextTrackId,
340
- status: 'loading',
341
- errorMessage: undefined,
342
- });
343
-
344
- await preloadTrack(nextRecord, false);
345
- }
346
-
347
- if (
348
- previousTrackId !== nextTrackId ||
349
- nextRecord.element.ended ||
350
- (nextRecord.element.duration > 0 &&
351
- nextRecord.element.currentTime >= nextRecord.element.duration)
352
- ) {
353
- try {
354
- nextRecord.element.currentTime = 0;
355
- } catch {
356
- // Ignore currentTime reset failures for browsers without loaded metadata yet.
357
- }
358
- }
359
-
360
- applyElementSettings(nextRecord);
361
-
362
- try {
363
- await nextRecord.element.play();
364
- publish({
365
- activeTrackId: nextTrackId,
366
- status: 'playing',
367
- errorMessage: undefined,
368
- });
369
- } catch (error) {
370
- const message =
371
- error instanceof Error ? error.message : formatTrackError(nextRecord.definition, 'playback');
372
-
373
- publish(
374
- {
375
- activeTrackId: nextTrackId,
376
- status: 'error',
377
- errorMessage: message,
378
- },
379
- {
380
- type: 'audio:error',
381
- payload: {
382
- trackId: nextTrackId,
383
- label: nextRecord.definition.label,
384
- message,
385
- },
386
- },
387
- );
388
-
389
- throw new Error(message);
390
- }
391
- },
392
- pause() {
393
- if (!snapshot.activeTrackId) {
394
- return;
395
- }
396
-
397
- const record = requireTrack(snapshot.activeTrackId);
398
- record.element.pause();
399
-
400
- publish({
401
- status: 'paused',
402
- errorMessage: undefined,
403
- });
404
- },
405
- stop() {
406
- if (!snapshot.activeTrackId) {
407
- return;
408
- }
409
-
410
- stopElement(requireTrack(snapshot.activeTrackId));
411
-
412
- publish({
413
- status: 'ready',
414
- errorMessage: undefined,
415
- });
416
- },
417
- setMuted(next) {
418
- const muted = Boolean(next);
419
-
420
- for (const record of tracks.values()) {
421
- record.element.muted = muted;
422
- }
423
-
424
- publish({
425
- muted,
426
- errorMessage: undefined,
427
- });
428
- },
429
- setVolume(next) {
430
- const volume = clampVolume(next);
431
-
432
- for (const record of tracks.values()) {
433
- record.element.volume = clampVolume(volume * (record.definition.volume ?? 1));
434
- }
435
-
436
- publish({
437
- volume,
438
- errorMessage: undefined,
439
- });
440
- },
441
- getSnapshot() {
442
- return cloneSnapshot(snapshot);
443
- },
444
- subscribe(listener) {
445
- listeners.add(listener);
446
-
447
- return () => {
448
- listeners.delete(listener);
449
- };
450
- },
451
- };
452
- },
453
- dispose() {
454
- teardown?.();
455
- teardown = null;
456
- },
457
- };
458
- }
package/src/contracts.ts DELETED
@@ -1,15 +0,0 @@
1
- /**
2
- * Company: EonHive Inc.
3
- * Title: Adapter Contracts
4
- * Purpose: Re-export the canonical kernel adapter contracts from @urk/core.
5
- * Author: Stan Nesi
6
- * Created: 2026-04-12
7
- * Updated: 2026-04-15
8
- * Notes: Vibe coded with Codex.
9
- */
10
-
11
- export type {
12
- AdapterRegistration,
13
- KernelEvent,
14
- RuntimeContext,
15
- } from '@urk/core';
package/src/dom.ts DELETED
@@ -1,16 +0,0 @@
1
- /**
2
- * Company: EonHive Inc.
3
- * Title: DOM Adapter Exports
4
- * Purpose: Provide a dependency-light adapter surface for DOM-only URK consumers.
5
- * Author: Stan Nesi
6
- * Created: 2026-04-23
7
- * Updated: 2026-04-23
8
- * Notes: Vibe coded with Codex.
9
- */
10
-
11
- export * from './loading.js';
12
- export * from './input.js';
13
- export * from './pointer.js';
14
- export * from './storage.js';
15
- export * from './ui-widgets.js';
16
- export * from './contracts.js';
package/src/index.ts DELETED
@@ -1,5 +0,0 @@
1
- /**
2
- * URK Adapters - Contract-first capability adapters.
3
- */
4
-
5
- export * from './adapters.js';
package/src/input.ts DELETED
@@ -1,207 +0,0 @@
1
- /**
2
- * Company: EonHive Inc.
3
- * Title: Input Adapter
4
- * Purpose: Normalize keyboard input into a small reusable runtime capability.
5
- * Author: Stan Nesi
6
- * Created: 2026-04-20
7
- * Updated: 2026-04-20
8
- * Notes: Vibe coded with Codex.
9
- */
10
-
11
- import type { AdapterRegistration, RuntimeContext } from '@urk/core';
12
-
13
- export type InputEventPhase = 'down' | 'up';
14
-
15
- export interface InputKeyEvent {
16
- code: string;
17
- key: string;
18
- phase: InputEventPhase;
19
- repeat: boolean;
20
- altKey: boolean;
21
- ctrlKey: boolean;
22
- metaKey: boolean;
23
- shiftKey: boolean;
24
- nativeEvent: KeyboardEvent;
25
- }
26
-
27
- export interface InputBinding {
28
- code: string;
29
- phase?: InputEventPhase;
30
- allowRepeat?: boolean;
31
- handler: (event: InputKeyEvent) => void;
32
- }
33
-
34
- export type InputListener = (event: InputKeyEvent) => void;
35
-
36
- export interface InputAdapterApi {
37
- isPressed(code: string): boolean;
38
- bindKey(binding: InputBinding): () => void;
39
- subscribe(listener: InputListener): () => void;
40
- clear(): void;
41
- }
42
-
43
- type InputTarget = Window | Document | HTMLElement;
44
-
45
- type BoundInputBinding = {
46
- code: string;
47
- phase: InputEventPhase;
48
- allowRepeat: boolean;
49
- handler: (event: InputKeyEvent) => void;
50
- };
51
-
52
- function resolveInputTarget(ctx: RuntimeContext): InputTarget {
53
- const serviceTarget = ctx.services.get<unknown>('input:target');
54
-
55
- if (serviceTarget === undefined) {
56
- return window;
57
- }
58
-
59
- if (serviceTarget === window) {
60
- return window;
61
- }
62
-
63
- if (typeof Document !== 'undefined' && serviceTarget instanceof Document) {
64
- return serviceTarget;
65
- }
66
-
67
- if (typeof HTMLElement !== 'undefined' && serviceTarget instanceof HTMLElement) {
68
- return serviceTarget;
69
- }
70
-
71
- throw new Error('Service input:target must be a Window, Document, or HTMLElement.');
72
- }
73
-
74
- function createInputEvent(
75
- phase: InputEventPhase,
76
- nativeEvent: KeyboardEvent,
77
- ): InputKeyEvent {
78
- return {
79
- code: nativeEvent.code || nativeEvent.key,
80
- key: nativeEvent.key,
81
- phase,
82
- repeat: nativeEvent.repeat,
83
- altKey: nativeEvent.altKey,
84
- ctrlKey: nativeEvent.ctrlKey,
85
- metaKey: nativeEvent.metaKey,
86
- shiftKey: nativeEvent.shiftKey,
87
- nativeEvent,
88
- };
89
- }
90
-
91
- export function createInputAdapter(
92
- id = 'input-adapter',
93
- ): AdapterRegistration<InputAdapterApi> {
94
- let disposeTargetListeners: (() => void) | null = null;
95
-
96
- return {
97
- id,
98
- capability: 'input',
99
- isSupported() {
100
- return typeof window !== 'undefined' && typeof document !== 'undefined';
101
- },
102
- setup(ctx) {
103
- const target = resolveInputTarget(ctx);
104
- const resetTarget = window;
105
- const pressedCodes = new Set<string>();
106
- const bindings = new Set<BoundInputBinding>();
107
- const listeners = new Set<InputListener>();
108
-
109
- const publish = (event: InputKeyEvent): void => {
110
- ctx.events.emit({
111
- type: event.phase === 'down' ? 'input:key-down' : 'input:key-up',
112
- source: id,
113
- payload: event,
114
- timestamp: Date.now(),
115
- });
116
-
117
- for (const listener of [...listeners]) {
118
- listener(event);
119
- }
120
-
121
- for (const binding of [...bindings]) {
122
- if (binding.code !== event.code || binding.phase !== event.phase) {
123
- continue;
124
- }
125
-
126
- if (event.repeat && !binding.allowRepeat) {
127
- continue;
128
- }
129
-
130
- binding.handler(event);
131
- }
132
- };
133
-
134
- const onKeyDown = (nativeEvent: Event): void => {
135
- if (!(nativeEvent instanceof KeyboardEvent)) {
136
- return;
137
- }
138
-
139
- const event = createInputEvent('down', nativeEvent);
140
- pressedCodes.add(event.code);
141
- publish(event);
142
- };
143
-
144
- const onKeyUp = (nativeEvent: Event): void => {
145
- if (!(nativeEvent instanceof KeyboardEvent)) {
146
- return;
147
- }
148
-
149
- const event = createInputEvent('up', nativeEvent);
150
- pressedCodes.delete(event.code);
151
- publish(event);
152
- };
153
-
154
- const onBlur = (): void => {
155
- pressedCodes.clear();
156
- };
157
-
158
- target.addEventListener('keydown', onKeyDown);
159
- target.addEventListener('keyup', onKeyUp);
160
- resetTarget.addEventListener('blur', onBlur);
161
-
162
- // Keep the public API minimal while still ensuring dispose can tear down DOM listeners.
163
- disposeTargetListeners = () => {
164
- target.removeEventListener('keydown', onKeyDown);
165
- target.removeEventListener('keyup', onKeyUp);
166
- resetTarget.removeEventListener('blur', onBlur);
167
- };
168
-
169
- return {
170
- isPressed(code) {
171
- return pressedCodes.has(code);
172
- },
173
- bindKey(binding) {
174
- const normalizedBinding: BoundInputBinding = {
175
- code: binding.code,
176
- phase: binding.phase ?? 'down',
177
- allowRepeat: binding.allowRepeat ?? false,
178
- handler: binding.handler,
179
- };
180
-
181
- bindings.add(normalizedBinding);
182
-
183
- return () => {
184
- bindings.delete(normalizedBinding);
185
- };
186
- },
187
- subscribe(listener) {
188
- listeners.add(listener);
189
-
190
- return () => {
191
- listeners.delete(listener);
192
- };
193
- },
194
- clear() {
195
- pressedCodes.clear();
196
- bindings.clear();
197
- listeners.clear();
198
- },
199
- };
200
- },
201
- dispose(_ctx, api) {
202
- disposeTargetListeners?.();
203
- disposeTargetListeners = null;
204
- api.clear();
205
- },
206
- };
207
- }