@lucent-lang/lucent 0.0.4 → 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 (152) hide show
  1. package/README.md +97 -0
  2. package/app.plugin.js +46 -10
  3. package/dist/{bench-CR3E7e2e.js → bench-B555m0wU.js} +10 -6
  4. package/dist/{build-CAIrRSYA.js → build-UZ5Zm5m6.js} +1 -1
  5. package/dist/{build-check-QozuNJm5.js → build-check-Dmf8_yiw.js} +28 -13
  6. package/dist/{check-1SCmYlqA.js → check-D-H9WLG4.js} +1 -1
  7. package/dist/cli.js +62 -13
  8. package/dist/{codes-CIuwXt1S.js → codes-D-6LxUqD.js} +163 -7
  9. package/dist/compiler.js +4 -4
  10. package/dist/{dashboard-B-NzjjaN.js → dashboard-C8iF5-6T.js} +2 -2
  11. package/dist/{dev-BYWMP7oC.js → dev-B3lSx5CF.js} +62 -23
  12. package/dist/{diagnostic-BM9sXmwl.js → diagnostic-BvwLZQS1.js} +3 -1
  13. package/dist/{doctor-C0PrfWT6.js → doctor-CTAX3euc.js} +1 -1
  14. package/dist/{doctor-DdR4JnVc.js → doctor-CrgOAV5L.js} +1 -1
  15. package/dist/{explain--vV1oaRl.js → explain-Byit07F-.js} +1 -1
  16. package/dist/{packages-HyskKIsY.js → packages-CSmKeaAh.js} +2 -33
  17. package/dist/pipeline-BBY0df_2.js +700 -0
  18. package/dist/project-BLhWwnns.js +342 -0
  19. package/dist/sdk-coverage-DFGiDOzR.js +113 -0
  20. package/dist/sdk-diff-DpPYWzrl.js +109 -0
  21. package/dist/sdk-lock-QOxEFD1o.js +38 -0
  22. package/dist/{sdk-prefetch-mo04JDZV.js → sdk-prefetch-DX6ztlPN.js} +2 -2
  23. package/dist/{sdk-search-Iv4K3-8a.js → sdk-search-BShBL1LL.js} +2 -2
  24. package/dist/{sdk-show-CkrdlqlP.js → sdk-show-B3bI-0M7.js} +5 -4
  25. package/dist/sdk-usage-ljX5bTuZ.js +95 -0
  26. package/dist/src-DlICbK91.js +39682 -0
  27. package/dist/trace-Bi4hkmPx.js +139 -0
  28. package/lib/core.cjs +368 -1
  29. package/lib/sdk/android.d.ts +54 -1
  30. package/lib/sdk/compose.d.ts +94 -0
  31. package/lib/sdk/compose.schemas.json.gz +0 -0
  32. package/lib/sdk/core.d.ts +201 -0
  33. package/lib/sdk/ios.d.ts +75 -2
  34. package/lib/sdk/ui.d.ts +129 -0
  35. package/metro/transformer.cjs +11 -2
  36. package/package.json +2 -1
  37. package/runtime/cpp/lucent/abort.cpp +59 -11
  38. package/runtime/cpp/lucent/abort.h +22 -6
  39. package/runtime/cpp/lucent/array.h +12 -2
  40. package/runtime/cpp/lucent/async.h +101 -21
  41. package/runtime/cpp/lucent/bigint.cpp +898 -0
  42. package/runtime/cpp/lucent/bigint.h +302 -0
  43. package/runtime/cpp/lucent/buffer.cpp +228 -0
  44. package/runtime/cpp/lucent/buffer.h +328 -0
  45. package/runtime/cpp/lucent/builtins.cpp +4 -1
  46. package/runtime/cpp/lucent/bytes.h +21 -2
  47. package/runtime/cpp/lucent/callback.cpp +29 -0
  48. package/runtime/cpp/lucent/callback.h +230 -0
  49. package/runtime/cpp/lucent/compute.cpp +370 -0
  50. package/runtime/cpp/lucent/compute.h +455 -0
  51. package/runtime/cpp/lucent/conversions.h +111 -0
  52. package/runtime/cpp/lucent/core.h +16 -5
  53. package/runtime/cpp/lucent/equality.h +107 -28
  54. package/runtime/cpp/lucent/execution.cpp +416 -0
  55. package/runtime/cpp/lucent/execution.h +270 -0
  56. package/runtime/cpp/lucent/extension.cpp +97 -0
  57. package/runtime/cpp/lucent/extension.h +210 -0
  58. package/runtime/cpp/lucent/helpers.h +12 -5
  59. package/runtime/cpp/lucent/items.h +52 -0
  60. package/runtime/cpp/lucent/jserror.cpp +9 -0
  61. package/runtime/cpp/lucent/jserror.h +11 -0
  62. package/runtime/cpp/lucent/jsi/convert.cpp +138 -0
  63. package/runtime/cpp/lucent/jsi/convert.h +128 -59
  64. package/runtime/cpp/lucent/jsi/host.cpp +213 -35
  65. package/runtime/cpp/lucent/jsi/host.h +116 -16
  66. package/runtime/cpp/lucent/json.h +19 -1
  67. package/runtime/cpp/lucent/lifecycle.cpp +260 -0
  68. package/runtime/cpp/lucent/lifecycle.h +124 -0
  69. package/runtime/cpp/lucent/lucent.h +7 -0
  70. package/runtime/cpp/lucent/map.h +41 -7
  71. package/runtime/cpp/lucent/native.cpp +35 -47
  72. package/runtime/cpp/lucent/native.h +116 -26
  73. package/runtime/cpp/lucent/number.cpp +38 -38
  74. package/runtime/cpp/lucent/number.h +33 -0
  75. package/runtime/cpp/lucent/operation.h +65 -0
  76. package/runtime/cpp/lucent/ops.h +14 -6
  77. package/runtime/cpp/lucent/platform/android.cpp +223 -10
  78. package/runtime/cpp/lucent/platform/android.h +227 -4
  79. package/runtime/cpp/lucent/platform/android_activity.cpp +258 -0
  80. package/runtime/cpp/lucent/platform/android_requests.h +140 -0
  81. package/runtime/cpp/lucent/platform/compose.h +256 -0
  82. package/runtime/cpp/lucent/platform/ios.h +325 -25
  83. package/runtime/cpp/lucent/platform/ios.mm +1 -14
  84. package/runtime/cpp/lucent/platform/ios_ui.h +192 -0
  85. package/runtime/cpp/lucent/platform/ios_ui.mm +454 -0
  86. package/runtime/cpp/lucent/platform/swiftui.h +190 -0
  87. package/runtime/cpp/lucent/presentation.cpp +11 -0
  88. package/runtime/cpp/lucent/presentation.h +109 -0
  89. package/runtime/cpp/lucent/reactive.cpp +406 -0
  90. package/runtime/cpp/lucent/reactive.h +566 -0
  91. package/runtime/cpp/lucent/report.cpp +40 -0
  92. package/runtime/cpp/lucent/report.h +16 -0
  93. package/runtime/cpp/lucent/resource.cpp +83 -0
  94. package/runtime/cpp/lucent/resource.h +75 -0
  95. package/runtime/cpp/lucent/scheduler.cpp +38 -124
  96. package/runtime/cpp/lucent/scheduler.h +102 -83
  97. package/runtime/cpp/lucent/scope.cpp +308 -0
  98. package/runtime/cpp/lucent/scope.h +452 -0
  99. package/runtime/cpp/lucent/sizing.h +263 -0
  100. package/runtime/cpp/lucent/slots.h +231 -0
  101. package/runtime/cpp/lucent/trace.cpp +539 -0
  102. package/runtime/cpp/lucent/trace.h +251 -0
  103. package/runtime/cpp/lucent/transport.cpp +9 -0
  104. package/runtime/cpp/lucent/transport.h +341 -0
  105. package/runtime/cpp/lucent/view.cpp +123 -0
  106. package/runtime/cpp/lucent/view.h +157 -0
  107. package/runtime/cpp/rn/LucentComponentView.h +121 -0
  108. package/runtime/cpp/rn/LucentComponentView.mm +479 -0
  109. package/runtime/cpp/rn/LucentModule.cpp +37 -7
  110. package/runtime/cpp/rn/LucentModule.h +3 -0
  111. package/runtime/cpp/rn/LucentViewChildren.h +80 -0
  112. package/runtime/cpp/rn/LucentViewChildren.mm +211 -0
  113. package/runtime/cpp/rn/LucentViewRegistry.h +62 -0
  114. package/runtime/cpp/rn/LucentViewRequests.cpp +91 -0
  115. package/runtime/cpp/rn/LucentViewRequests.h +104 -0
  116. package/runtime/cpp/rn/LucentViewSizing.h +345 -0
  117. package/runtime/cpp/rn/LucentViewSlots.h +242 -0
  118. package/runtime/cpp/rn/LucentViewValues.h +125 -0
  119. package/runtime/cpp/rn/LucentViews.h +440 -0
  120. package/runtime/cpp/rn/LucentViewsAndroid.cpp +439 -0
  121. package/runtime/cpp/rn/LucentViewsAndroid.h +138 -0
  122. package/runtime/cpp/third_party/dragonbox/LICENSE-Boost +23 -0
  123. package/runtime/cpp/third_party/dragonbox/README.md +16 -0
  124. package/runtime/cpp/third_party/dragonbox/dragonbox.h +4440 -0
  125. package/runtime/js/index.d.ts +11 -1
  126. package/runtime/js/index.js +84 -8
  127. package/runtime/js/views.d.ts +38 -0
  128. package/runtime/js/views.js +226 -0
  129. package/runtime/native/android/CMakeLists.txt +4 -1
  130. package/runtime/native/android/src/compose/java/dev/lucent/compose/CompositionRules.kt +44 -0
  131. package/runtime/native/android/src/compose/java/dev/lucent/compose/LucentComposition.kt +161 -0
  132. package/runtime/native/android/src/main/java/dev/lucent/LucentActivities.java +292 -0
  133. package/runtime/native/android/src/main/java/dev/lucent/LucentChildren.java +174 -0
  134. package/runtime/native/android/src/main/java/dev/lucent/LucentHostView.java +232 -0
  135. package/runtime/native/android/src/main/java/dev/lucent/LucentInitializer.java +46 -0
  136. package/runtime/native/android/src/main/java/dev/lucent/LucentPackage.java +4 -3
  137. package/runtime/native/android/src/main/java/dev/lucent/LucentRequestActivity.java +92 -0
  138. package/runtime/native/android/src/main/java/dev/lucent/LucentSlotView.java +102 -0
  139. package/runtime/native/android/src/main/java/dev/lucent/LucentViewManager.java +149 -0
  140. package/runtime/native/android/src/main/java/dev/lucent/LucentViewManagers.java +16 -0
  141. package/runtime/native/android/src/main/java/dev/lucent/LucentViews.java +44 -0
  142. package/runtime/test/jsi/harness.cpp +9 -0
  143. package/schemas/build.schema.json +96 -1
  144. package/schemas/check.schema.json +56 -1
  145. package/schemas/lucent.schema.json +495 -9
  146. package/schemas/sdk-diff.schema.json +70 -0
  147. package/schemas/sdk-lock.schema.json +67 -0
  148. package/ts-plugin/index.js +77 -4
  149. package/dist/pipeline-CH7-2cxu.js +0 -232
  150. package/dist/project-D8tEet0k.js +0 -150
  151. package/dist/sdk-coverage-BoW_3Efd.js +0 -64
  152. package/dist/src-BD7f1_aZ.js +0 -11493
package/lib/sdk/core.d.ts CHANGED
@@ -21,3 +21,204 @@ export declare function utf8Decode(bytes: Uint8Array): string;
21
21
 
22
22
  /** Milliseconds from a monotonic clock, for measuring durations. */
23
23
  export declare function now(): number;
24
+
25
+ /**
26
+ * A promise of what a callback API reports once, such as a native listener.
27
+ * `register` starts listening, at once, and may return the cleanup that
28
+ * stops it:
29
+ *
30
+ * ```ts
31
+ * fromCallback<string>((resolve, reject) => {
32
+ * const subscription = source.listen(resolve, reject);
33
+ * return () => subscription.cancel();
34
+ * }, signal);
35
+ * ```
36
+ *
37
+ * The first of `resolve`, `reject` and the signal aborting settles the
38
+ * promise; later calls do nothing. The cleanup runs exactly once, as soon as
39
+ * the promise settles, or right after `register` returns if it settled
40
+ * during registration. A throw from `register` rejects the promise. With an
41
+ * aborted signal, the promise rejects with the signal's reason and
42
+ * `register` is not called. A cleanup that throws is reported as uncaught;
43
+ * the promise keeps its outcome. `resolve` takes a value, not a promise.
44
+ *
45
+ * Called from another thread (a `main` block, say), `resolve` and `reject`
46
+ * take effect on the thread `fromCallback` was called on, in the order
47
+ * they were called.
48
+ */
49
+ export declare function fromCallback<T>(
50
+ register: (resolve: (value: T) => void, reject: (reason: Error) => void) => (() => void) | void,
51
+ signal?: AbortSignal,
52
+ ): Promise<T>;
53
+
54
+ /**
55
+ * Passes what a listener reports to `onValue` until the subscription ends,
56
+ * and resolves when it does. `register` starts listening, at once, and may
57
+ * return the cleanup that stops it.
58
+ *
59
+ * `next(value)` calls `onValue(value)` while the subscription is open, and
60
+ * does nothing after. The first of `end()`, `fail(error)`, `onValue`
61
+ * throwing and the signal aborting ends it: the promise resolves (`end`) or
62
+ * rejects (with the error, or the signal's reason), and the cleanup runs
63
+ * exactly once. Registration, an aborted signal, a throwing cleanup and
64
+ * calls from other threads behave as in `fromCallback`.
65
+ */
66
+ export declare function subscribe<T>(
67
+ register: (
68
+ next: (value: T) => void,
69
+ end: () => void,
70
+ fail: (error: Error) => void,
71
+ ) => (() => void) | void,
72
+ onValue: (value: T) => void,
73
+ signal?: AbortSignal,
74
+ ): Promise<void>;
75
+
76
+ /** How a compute task runs. */
77
+ export interface ComputeOptions {
78
+ /** Aborting it cancels the task: the promise rejects with its reason at once. */
79
+ signal?: AbortSignal;
80
+ }
81
+
82
+ /**
83
+ * Runs `task(input)` on a pool of worker threads and resolves with its
84
+ * result, on the thread that called `compute`. `task` is a function declared
85
+ * at the top level of a module, taking one parameter: the compiler checks
86
+ * everything it runs can run on a worker (no module state, no main-thread
87
+ * or unknown-thread native code, nothing asynchronous) and that `input` and
88
+ * its result are data.
89
+ *
90
+ * `input` is copied when `compute` is called: later changes by the caller
91
+ * do not reach the task. Objects reached twice are copied once, and cycles
92
+ * survive. The result comes back as it is.
93
+ *
94
+ * A task that throws rejects the promise with its error. With `signal`,
95
+ * aborting it rejects the promise at once with the signal's reason: a
96
+ * queued task never starts, and a running one stops at the next iteration
97
+ * of a loop in the task or in a module function it calls. Loops in
98
+ * closures, methods and generic functions, and native calls, run to their
99
+ * end first; the result is then dropped. The pool
100
+ * holds a bounded number of waiting tasks; beyond that, `compute` rejects
101
+ * with a QuotaExceededError.
102
+ *
103
+ * ```ts
104
+ * function edgePositions(bytes: Uint8Array): number[] {
105
+ * const positions: number[] = [];
106
+ * for (let i = 1; i < bytes.length; i++)
107
+ * if (Math.abs(bytes[i]! - bytes[i - 1]!) > 40) positions.push(i);
108
+ * return positions;
109
+ * }
110
+ *
111
+ * export async function edges(bytes: Uint8Array, signal: AbortSignal): Promise<number[]> {
112
+ * return await compute(edgePositions, bytes, { signal });
113
+ * }
114
+ * ```
115
+ */
116
+ export declare function compute<T, R>(
117
+ task: (input: T) => R,
118
+ input: T,
119
+ options?: ComputeOptions,
120
+ ): Promise<Awaited<R>>;
121
+
122
+ /**
123
+ * Bytes native code owns, handed to compute tasks and JavaScript without
124
+ * copying them. A `Uint8Array` is copied whenever it crosses; a
125
+ * NativeBuffer moves.
126
+ *
127
+ * The bytes are reached through a borrow, for the length of one call:
128
+ * `withRead` lends them to its callback as a `ByteSpan`, `withWrite` as a
129
+ * `MutableByteSpan`. Reads share the buffer; a write needs it to itself.
130
+ * A borrow that would conflict with one in progress (a write inside a
131
+ * read, a close or transfer inside either) throws an InvalidStateError at
132
+ * once. The compiler keeps a span inside its callback: it cannot be
133
+ * returned, stored, captured by a closure that outlives the call, passed
134
+ * to code that keeps it, or held across `await`.
135
+ *
136
+ * `transfer()` moves the bytes to a new buffer, uncopied: every reference
137
+ * to the old one then refuses them. Passing a buffer to `compute`, alone
138
+ * or inside the input, moves it the same way, and a task's buffer comes
139
+ * back as it is. Copies are explicit: `NativeBuffer.from(bytes)` and
140
+ * `toUint8Array()`, which `NativeBuffer.stats()` counts.
141
+ *
142
+ * ```ts
143
+ * function scan(buffer: NativeBuffer): number {
144
+ * using owned = buffer;
145
+ * return owned.withRead((bytes) => {
146
+ * let total = 0;
147
+ * for (let i = 0; i < bytes.length; i++) total += bytes[i]!;
148
+ * return total;
149
+ * });
150
+ * }
151
+ *
152
+ * export async function sample(): Promise<number> {
153
+ * const buffer = NativeBuffer.allocate(4096);
154
+ * buffer.withWrite((bytes) => bytes.fill(7));
155
+ * return await compute(scan, buffer.transfer());
156
+ * }
157
+ * ```
158
+ *
159
+ * JavaScript sees a buffer as an opaque object with the same methods; its
160
+ * borrows lend it a copy of the bytes (copied back after `withWrite`), so
161
+ * JavaScript never holds memory a worker may be writing.
162
+ */
163
+ export declare class NativeBuffer {
164
+ private constructor();
165
+
166
+ /** A buffer of `size` zeroed bytes: a RangeError unless `size` is a whole number from 0. */
167
+ static allocate(size: number): NativeBuffer;
168
+
169
+ /** A new buffer holding a copy of `bytes`. */
170
+ static from(bytes: Uint8Array): NativeBuffer;
171
+
172
+ /** What every buffer has done since the app started. */
173
+ static stats(): NativeBufferStats;
174
+
175
+ /** How many bytes it holds: 0 once closed or transferred. */
176
+ readonly byteLength: number;
177
+
178
+ /** Calls `read` with the bytes, while no one writes them, and returns what it returns. */
179
+ withRead<R>(read: (bytes: ByteSpan) => R): R;
180
+
181
+ /** Calls `write` with the bytes, while nothing else borrows them, and returns what it returns. */
182
+ withWrite<R>(write: (bytes: MutableByteSpan) => R): R;
183
+
184
+ /** A copy of the bytes, independent of the buffer from then on. */
185
+ toUint8Array(): Uint8Array;
186
+
187
+ /** Moves the bytes, uncopied, to a new buffer: every reference to this one refuses them from now on. */
188
+ transfer(): NativeBuffer;
189
+
190
+ /** Releases the bytes. Closing a closed or transferred buffer does nothing. */
191
+ close(): void;
192
+
193
+ [Symbol.dispose](): void;
194
+ }
195
+
196
+ /** The bytes `withRead` lends, read like a `Uint8Array`'s: `bytes[i]` is undefined out of range. */
197
+ export interface ByteSpan {
198
+ readonly length: number;
199
+ readonly [index: number]: number;
200
+ }
201
+
202
+ /** The bytes `withWrite` lends, written like a `Uint8Array`'s: values wrap to 0–255, writes out of range do nothing. */
203
+ export interface MutableByteSpan extends ByteSpan {
204
+ [index: number]: number;
205
+
206
+ /** Sets the bytes from `start` to `end` (counted from the end when negative) to `value`. */
207
+ fill(value: number, start?: number, end?: number): void;
208
+
209
+ /** Copies `source` in at `offset`: a RangeError if it does not fit. */
210
+ set(source: Uint8Array, offset?: number): void;
211
+ }
212
+
213
+ /** Counts of what native buffers have done, for checking that a path does not copy. */
214
+ export interface NativeBufferStats {
215
+ /** Buffers allocated, including those `from` made. */
216
+ readonly allocated: number;
217
+ /** Buffers native code handed over with its own memory. */
218
+ readonly adopted: number;
219
+ /** Moves of a buffer's bytes to a new buffer: `transfer()` and handoffs to tasks. */
220
+ readonly transfers: number;
221
+ /** Copies in and out (`from`, `toUint8Array`, and JavaScript's borrows), and their bytes. */
222
+ readonly copies: number;
223
+ readonly bytesCopied: number;
224
+ }
package/lib/sdk/ios.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  // lucent:ios — iOS helpers for platform code.
2
2
 
3
+ import type { UIViewController } from "lucent:ios/UIKit";
4
+
3
5
  /** Whether the OS is at least `major.minor` (Swift's `#available`). */
4
6
  export declare function available(platform: "ios", major: number, minor?: number): boolean;
5
7
 
@@ -26,8 +28,79 @@ export declare function asData(value: NSObject | null): Uint8Array | null;
26
28
  /** Swift's `as? Date`. */
27
29
  export declare function asDate(value: NSObject | null): Date | null;
28
30
 
29
- /** An out-parameter of a C function (`CFTypeRef *`): pass it, then read `value`. */
31
+ /**
32
+ * What a method writes through a pointer (`CGFloat *`, `NSRange *`,
33
+ * `NSDate **`, `CFTypeRef *`): pass it, then read `value`. For a pointer the
34
+ * method also reads (a number or a struct), set `value` first.
35
+ */
30
36
  export declare class Out<T> {
31
37
  constructor();
32
- readonly value: T | null;
38
+ value: T | null;
33
39
  }
40
+
41
+ /**
42
+ * UIApplication's lifecycle notifications, by name: `"didBecomeActive"` is
43
+ * `UIApplication.didBecomeActiveNotification`.
44
+ */
45
+ export type AppEvent =
46
+ | "didBecomeActive"
47
+ | "willResignActive"
48
+ | "didEnterBackground"
49
+ | "willEnterForeground"
50
+ | "didReceiveMemoryWarning"
51
+ | "willTerminate";
52
+
53
+ /**
54
+ * UIScene's lifecycle notifications, by name: `"willConnect"` is
55
+ * `UIScene.willConnectNotification`.
56
+ */
57
+ export type SceneEvent =
58
+ | "willConnect"
59
+ | "didDisconnect"
60
+ | "didActivate"
61
+ | "willDeactivate"
62
+ | "willEnterForeground"
63
+ | "didEnterBackground";
64
+
65
+ /**
66
+ * Calls `listener` each time the app posts `event`, until the returned
67
+ * function is called or `signal` aborts. It runs on the main thread, as
68
+ * UIKit posts it, so main-thread APIs work there without `main()`. Lucent
69
+ * observes the notifications: the app's delegate, and other modules', stay
70
+ * as they are. What `listener` throws is logged; the app goes on.
71
+ */
72
+ export declare function onAppEvent(
73
+ event: AppEvent,
74
+ listener: () => void,
75
+ signal?: AbortSignal,
76
+ ): () => void;
77
+
78
+ /**
79
+ * Like `onAppEvent`, for each scene's `event`: `listener` gets the scene's
80
+ * session's `persistentIdentifier`.
81
+ */
82
+ export declare function onSceneEvent(
83
+ event: SceneEvent,
84
+ listener: (scene: string) => void,
85
+ signal?: AbortSignal,
86
+ ): () => void;
87
+
88
+ /**
89
+ * Presents the view controller `build` returns from the scene the person is
90
+ * using (the top view controller of its key window), and resolves with the
91
+ * value given to `resolve`, or rejects with the error given to `reject`.
92
+ * `build` runs on the main thread, like `main()`'s function: make the view
93
+ * controller there, and call `resolve` or `reject` from its delegate or
94
+ * completion handler.
95
+ *
96
+ * It settles once, and whatever settles it dismisses the view controller if
97
+ * it is still shown. It rejects with an `AbortError` when `signal` aborts,
98
+ * when the person dismisses the view controller (swiping a sheet down), when
99
+ * the view controller goes before settling, or when its scene disconnects;
100
+ * with an `InvalidStateError` when no scene is in the foreground or UIKit
101
+ * does not present it.
102
+ */
103
+ export declare function present<T>(
104
+ build: (resolve: (value: T) => void, reject: (reason: Error) => void) => UIViewController,
105
+ signal?: AbortSignal,
106
+ ): Promise<T>;
@@ -0,0 +1,129 @@
1
+ /**
2
+ * lucent:ui — the helpers a component's setup uses.
3
+ *
4
+ * Experimental and internal: this module resolves only when the
5
+ * LUCENT_VIEWS environment variable is `fabric`, and changes without notice
6
+ * until Lucent's views are public.
7
+ *
8
+ * A component's setup runs once per mount, on the main thread: it makes
9
+ * the native objects the component shows and owns directly
10
+ * (`const label = new UILabel()`), and the mount releases them when it
11
+ * ends. Its props are read where they are used (`props.value`): inside
12
+ * `effect`, a read tracks the prop, and the effect runs again when a
13
+ * commit changes it.
14
+ *
15
+ * @experimental
16
+ */
17
+
18
+ /**
19
+ * Runs `run` now, and again after a prop or signal it read changes. Before
20
+ * it runs again, and when the view goes, what it registered with
21
+ * `onDispose` runs.
22
+ */
23
+ export declare function effect(run: () => void): void;
24
+
25
+ /** A value of the view's own, which effects track like props. */
26
+ export interface Signal<T> {
27
+ /** The value; inside an effect, the effect runs again when it changes. */
28
+ get(): T;
29
+ /** The value, without tracking it. */
30
+ peek(): T;
31
+ /** Changes it, unless `value` is the same (Object.is), running what read it. */
32
+ set(value: T): void;
33
+ }
34
+
35
+ export declare function signal<T>(initial: T): Signal<T>;
36
+
37
+ /**
38
+ * A signal given to a view of a toolkit body that changes it: SwiftUI's
39
+ * Binding, or Compose's value and its change callback. The view shows the
40
+ * signal's value, and a change the user makes sets the signal (which may
41
+ * keep another value: the view shows what it keeps).
42
+ */
43
+ export interface Bound<T> {
44
+ readonly __lucentBound: T;
45
+ }
46
+
47
+ /** `signal`, bound to the view of a body it is given to: only there. */
48
+ export declare function bind<T>(signal: Signal<T>): Bound<T>;
49
+
50
+ /**
51
+ * The numbers from `from` to `to`, both included, given to a view of a
52
+ * toolkit body that takes a range (a slider's, a stepper's bounds):
53
+ * SwiftUI's `from ... to`. Written where the view takes it, as
54
+ * bind(signal) is.
55
+ */
56
+ export interface ClosedRange<T> {
57
+ readonly __lucentRange: T;
58
+ }
59
+
60
+ /** The closed range from `from` to `to`, for the view of a body it is given to: only there. */
61
+ export declare function range(from: number, to: number): ClosedRange<number>;
62
+
63
+ /**
64
+ * Gives the component's React ref `commands`: called once, at the top of
65
+ * setup, with an object literal of functions. A command returning nothing
66
+ * runs on the main thread; one returning a value (or a promise) answers
67
+ * JavaScript's promise.
68
+ */
69
+ export declare function expose<T extends object>(commands: T): void;
70
+
71
+ /**
72
+ * Runs `cleanup` when the view goes; inside an effect, before the effect
73
+ * runs again.
74
+ */
75
+ export declare function onDispose(cleanup: () => void): void;
76
+
77
+ /**
78
+ * The type of a component's `children` prop: the React elements
79
+ * JavaScript nests in it, which React Native mounts in the view `slot`
80
+ * gives setup. Setup never reads them.
81
+ */
82
+ export interface Children {
83
+ readonly __lucentChildren: never;
84
+ }
85
+
86
+ /**
87
+ * The view a component's React children are mounted in, for a component
88
+ * whose props declare `children: Children`: called once, in a `const` at
89
+ * the top of setup, with the platform's container class
90
+ * (`slot<UIView>()` on iOS, `slot<ViewGroup>()` on Android).
91
+ *
92
+ * Put it in the view setup returns, where the children belong: it fills
93
+ * the view it is added to unless setup sizes it. React lays the children
94
+ * out in the component's own coordinates, wherever the slot is, and they
95
+ * show within the slot's bounds. React Native adds, moves and removes
96
+ * them: setup never changes the slot's subviews.
97
+ */
98
+ export declare function slot<T extends object>(): T;
99
+
100
+ /**
101
+ * Has the view measured again, once the main thread's current work ends:
102
+ * for a change to its native content its host would not hear of. The host
103
+ * hears of every function setup makes running (an effect, a native
104
+ * callback, a command), since their code may change the view; code after
105
+ * an `await` runs in none of them.
106
+ */
107
+ export declare function invalidateSize(): void;
108
+
109
+ declare const delivery: unique symbol;
110
+
111
+ /**
112
+ * Marks an event prop's callback type: JavaScript hears each of its
113
+ * events at a lower priority than a discrete one (a tap), so React may
114
+ * batch what they update. For events that come often: a drag, a timer.
115
+ * An unmarked event is discrete.
116
+ */
117
+ export type Continuous<F extends (...args: never[]) => void> = F & {
118
+ readonly [delivery]?: "continuous";
119
+ };
120
+
121
+ /**
122
+ * Marks an event prop's callback type: continuous, and while the view's
123
+ * latest event waiting for JavaScript is one of this type, a new one
124
+ * replaces it, so JavaScript hears the latest value. For events whose
125
+ * latest value is all that counts: a scroll position, a level.
126
+ */
127
+ export type Coalesced<F extends (...args: never[]) => void> = F & {
128
+ readonly [delivery]?: "coalesced";
129
+ };
@@ -27,9 +27,18 @@ function moduleName(filename) {
27
27
  return base(path.basename(filename));
28
28
  }
29
29
 
30
+ /**
31
+ * The native package whose proxies Metro bundles: the project's
32
+ * .lucent/native, or the one LUCENT_OUT names (as `lucent build --out`,
33
+ * relative to the project).
34
+ */
35
+ function nativePackage(projectRoot) {
36
+ return path.resolve(projectRoot, process.env.LUCENT_OUT || path.join(".lucent", "native"));
37
+ }
38
+
30
39
  function proxyFor(filename, projectRoot) {
31
40
  const name = moduleName(filename);
32
- const generated = path.join(projectRoot, ".lucent", "native", "js", `${name}.js`);
41
+ const generated = path.join(nativePackage(projectRoot), "js", `${name}.js`);
33
42
  if (fs.existsSync(generated))
34
43
  return rebase(fs.readFileSync(generated, "utf8"), generated, filename);
35
44
  return `throw new Error(${JSON.stringify(`Lucent: ${path.basename(filename)} has not been compiled. Run \`lucent build\` and rebuild the app.`)});\n`;
@@ -63,7 +72,7 @@ module.exports = {
63
72
  getCacheKey(...args) {
64
73
  const base = typeof upstream.getCacheKey === "function" ? upstream.getCacheKey(...args) : "";
65
74
  // Proxies change when `lucent build` runs: include the manifest in the key.
66
- const manifest = path.join(process.cwd(), ".lucent", "native", "manifest.json");
75
+ const manifest = path.join(nativePackage(process.cwd()), "manifest.json");
67
76
  const stamp = fs.existsSync(manifest) ? fs.readFileSync(manifest, "utf8") : "";
68
77
  return crypto.createHash("sha1").update(base).update(stamp).update("lucent-1").digest("hex");
69
78
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lucent-lang/lucent",
3
- "version": "0.0.4",
3
+ "version": "0.1.0",
4
4
  "description": "Write native React Native modules in TypeScript: the Lucent compiler, CLI, Metro integration, Expo config plugin and editor plugin",
5
5
  "homepage": "https://lucent-lang.dev",
6
6
  "bugs": "https://github.com/Fausto95/lucent/issues",
@@ -54,6 +54,7 @@
54
54
  "ajv": "^8",
55
55
  "ink-testing-library": "^4.0.0",
56
56
  "@lucent-lang/bindgen": "0.0.3",
57
+ "@lucent-lang/codegen": "0.0.3",
57
58
  "@lucent-lang/compiler": "0.0.3",
58
59
  "@lucent-lang/runtime": "0.0.3"
59
60
  },
@@ -1,26 +1,51 @@
1
1
  #include "abort.h"
2
2
 
3
- #include <cstdio>
4
3
  #include <exception>
4
+ #include <memory>
5
5
 
6
+ #include "report.h"
6
7
  #include "scheduler.h"
7
8
 
8
9
  namespace lucent {
9
10
 
11
+ namespace {
12
+
13
+ /// Like an event listener: what it throws is reported, and the rest run.
14
+ void runListener(const std::function<void()>& f) {
15
+ try {
16
+ f();
17
+ } catch (...) {
18
+ reportUncaught(std::current_exception(), "abort listener");
19
+ }
20
+ }
21
+
22
+ } // namespace
23
+
24
+ bool AbortSignalObject::onOwner() const {
25
+ return owner_ ? owner_->isCurrent() : Scheduler::lock().heldByCurrentThread();
26
+ }
27
+
28
+ void AbortSignalObject::toOwner(std::function<void(AbortSignalObject&)> change) {
29
+ auto self = std::static_pointer_cast<AbortSignalObject>(shared_from_this());
30
+
31
+ ExecutionContext::of(owner_).post([self, change = std::move(change)] { change(*self); });
32
+ }
33
+
10
34
  void AbortSignalObject::abort(Error why) {
35
+ if (!onOwner()) {
36
+ toOwner([why = std::move(why)](AbortSignalObject& s) { s.abort(why); });
37
+ return;
38
+ }
39
+
11
40
  if (aborted.load()) return;
41
+
12
42
  reason = std::move(why);
13
43
  aborted.store(true);
44
+
14
45
  auto listeners = std::move(listeners_);
15
46
  listeners_.clear();
16
- for (auto& [id, f] : listeners) {
17
- // Like an event listener: a throwing listener does not stop the others.
18
- try {
19
- f();
20
- } catch (const std::exception& e) {
21
- std::fprintf(stderr, "[lucent] uncaught exception in abort listener: %s\n", e.what());
22
- }
23
- }
47
+
48
+ for (auto& [id, f] : listeners) runListener(f);
24
49
  }
25
50
 
26
51
  void AbortSignalObject::throwIfAborted() const {
@@ -28,12 +53,35 @@ void AbortSignalObject::throwIfAborted() const {
28
53
  }
29
54
 
30
55
  uint64_t AbortSignalObject::add(std::function<void()> f) {
56
+ if (onOwner()) {
57
+ if (aborted.load()) return 0;
58
+
59
+ uint64_t id = nextId_++;
60
+ listeners_.emplace_back(id, std::move(f));
61
+ return id;
62
+ }
63
+
31
64
  uint64_t id = nextId_++;
32
- if (!aborted.load()) listeners_.emplace_back(id, std::move(f));
65
+ toOwner([id, f = std::move(f)](AbortSignalObject& s) mutable { s.join(id, std::move(f)); });
33
66
  return id;
34
67
  }
35
68
 
69
+ void AbortSignalObject::join(uint64_t id, std::function<void()> f) {
70
+ // Added before this context could see the abort: it happened first.
71
+ if (aborted.load()) {
72
+ runListener(f);
73
+ return;
74
+ }
75
+
76
+ listeners_.emplace_back(id, std::move(f));
77
+ }
78
+
36
79
  void AbortSignalObject::remove(uint64_t id) {
80
+ if (!onOwner()) {
81
+ toOwner([id](AbortSignalObject& s) { s.remove(id); });
82
+ return;
83
+ }
84
+
37
85
  std::erase_if(listeners_, [id](const auto& l) { return l.first == id; });
38
86
  }
39
87
 
@@ -51,7 +99,7 @@ Promise<void> delay(double ms, Opt<AbortSignal> signal) {
51
99
  if (s->aborted.load()) return Promise<void>::rejected(s->reason);
52
100
  Promise<void> p;
53
101
  uint64_t id = s->add([p, s] { p.reject(s->reason); });
54
- Scheduler::instance().postDelayed(ms, [p, s, id] {
102
+ ExecutionContext::of(ExecutionContext::currentRef()).postDelayed(ms, [p, s, id] {
55
103
  s->remove(id);
56
104
  p.resolve(undefined);
57
105
  });
@@ -1,8 +1,11 @@
1
1
  // Lucent runtime — AbortController and AbortSignal.
2
2
  //
3
- // `aborted` may be read from any thread. Everything else, including abort
4
- // itself, runs under the Lucent lock. A signal that came from JavaScript is
5
- // aborted from the JS thread by a listener on the JS signal (lucent/jsi).
3
+ // A signal belongs to the execution context it was made in. Any thread may
4
+ // read `aborted` (then `reason`), abort it, or add and remove listeners;
5
+ // from another context, abort and listener changes are posted to the owner,
6
+ // where the listeners run. A signal that came from JavaScript is aborted
7
+ // from the JS thread, in the legacy module context, by a listener on the JS
8
+ // signal (lucent/jsi).
6
9
  #pragma once
7
10
 
8
11
  #include <atomic>
@@ -20,20 +23,30 @@ namespace lucent {
20
23
  class AbortSignalObject : public Object {
21
24
  public:
22
25
  std::atomic<bool> aborted{false};
23
- /// Set once, when the signal aborts.
26
+ /// Set once, when the signal aborts, before `aborted`.
24
27
  Error reason;
25
28
 
26
29
  /// Aborts once: records `why` and runs the listeners in the order they
27
- /// were added, synchronously, like dispatching the `abort` event.
30
+ /// were added, synchronously on the owner, like dispatching the `abort`
31
+ /// event. From another context, posted there. A listener's error is
32
+ /// reported and the others still run.
28
33
  void abort(Error why);
29
34
  void throwIfAborted() const;
30
35
  /// `addEventListener("abort", f)`.
31
36
  void addEventListener(std::function<void()> f) { add(std::move(f)); }
37
+ /// On the owner: 0, and `f` dropped, if the signal has aborted (it would
38
+ /// never run). From another context, `f` joins on the owner, and runs
39
+ /// there at once if the signal aborted before it arrived.
32
40
  uint64_t add(std::function<void()> f);
33
41
  void remove(uint64_t id);
34
42
 
35
43
  private:
36
- uint64_t nextId_ = 1;
44
+ bool onOwner() const;
45
+ void toOwner(std::function<void(AbortSignalObject&)> change);
46
+ void join(uint64_t id, std::function<void()> f);
47
+
48
+ const ContextRef owner_ = ExecutionContext::currentRef();
49
+ std::atomic<uint64_t> nextId_{1};
37
50
  std::vector<std::pair<uint64_t, std::function<void()>>> listeners_;
38
51
  };
39
52
  using AbortSignal = Ref<AbortSignalObject>;
@@ -45,6 +58,9 @@ struct AbortControllerObject : Object {
45
58
  };
46
59
  using AbortController = Ref<AbortControllerObject>;
47
60
 
61
+ inline String toJsString(const AbortSignal&) { return String::fromLatin1("[object AbortSignal]"); }
62
+ inline String toJsString(const AbortController&) { return String::fromLatin1("[object AbortController]"); }
63
+
48
64
  /// The default abort reason.
49
65
  Error abortError();
50
66
 
@@ -385,8 +385,6 @@ class Array {
385
385
  }
386
386
 
387
387
  const void* identity() const { return d_.get(); }
388
- friend bool strictEquals(const Array& a, const Array& b) { return a.d_ == b.d_; }
389
- friend String toJsString(const Array& a) { return a.join(); }
390
388
 
391
389
  /// Raw storage, for the JSI boundary and for-of loops.
392
390
  const std::vector<Elem>& items() const { return *d_; }
@@ -421,6 +419,18 @@ class Array {
421
419
  std::shared_ptr<std::vector<Elem>> d_;
422
420
  };
423
421
 
422
+ /// String(array): its elements joined with ",".
423
+ template <class T>
424
+ String toJsString(const Array<T>& a) {
425
+ return a.join();
426
+ }
427
+
428
+ /// `===`: the same array.
429
+ template <class T>
430
+ bool strictEquals(const Array<T>& a, const Array<T>& b) {
431
+ return a.identity() == b.identity();
432
+ }
433
+
424
434
  template <class T>
425
435
  struct IsArray : std::false_type {};
426
436
  template <class T>