@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.
- package/README.md +97 -0
- package/app.plugin.js +46 -10
- package/dist/{bench-CR3E7e2e.js → bench-B555m0wU.js} +10 -6
- package/dist/{build-CAIrRSYA.js → build-UZ5Zm5m6.js} +1 -1
- package/dist/{build-check-QozuNJm5.js → build-check-Dmf8_yiw.js} +28 -13
- package/dist/{check-1SCmYlqA.js → check-D-H9WLG4.js} +1 -1
- package/dist/cli.js +62 -13
- package/dist/{codes-CIuwXt1S.js → codes-D-6LxUqD.js} +163 -7
- package/dist/compiler.js +4 -4
- package/dist/{dashboard-B-NzjjaN.js → dashboard-C8iF5-6T.js} +2 -2
- package/dist/{dev-BYWMP7oC.js → dev-B3lSx5CF.js} +62 -23
- package/dist/{diagnostic-BM9sXmwl.js → diagnostic-BvwLZQS1.js} +3 -1
- package/dist/{doctor-C0PrfWT6.js → doctor-CTAX3euc.js} +1 -1
- package/dist/{doctor-DdR4JnVc.js → doctor-CrgOAV5L.js} +1 -1
- package/dist/{explain--vV1oaRl.js → explain-Byit07F-.js} +1 -1
- package/dist/{packages-HyskKIsY.js → packages-CSmKeaAh.js} +2 -33
- package/dist/pipeline-BBY0df_2.js +700 -0
- package/dist/project-BLhWwnns.js +342 -0
- package/dist/sdk-coverage-DFGiDOzR.js +113 -0
- package/dist/sdk-diff-DpPYWzrl.js +109 -0
- package/dist/sdk-lock-QOxEFD1o.js +38 -0
- package/dist/{sdk-prefetch-mo04JDZV.js → sdk-prefetch-DX6ztlPN.js} +2 -2
- package/dist/{sdk-search-Iv4K3-8a.js → sdk-search-BShBL1LL.js} +2 -2
- package/dist/{sdk-show-CkrdlqlP.js → sdk-show-B3bI-0M7.js} +5 -4
- package/dist/sdk-usage-ljX5bTuZ.js +95 -0
- package/dist/src-DlICbK91.js +39682 -0
- package/dist/trace-Bi4hkmPx.js +139 -0
- package/lib/core.cjs +368 -1
- package/lib/sdk/android.d.ts +54 -1
- package/lib/sdk/compose.d.ts +94 -0
- package/lib/sdk/compose.schemas.json.gz +0 -0
- package/lib/sdk/core.d.ts +201 -0
- package/lib/sdk/ios.d.ts +75 -2
- package/lib/sdk/ui.d.ts +129 -0
- package/metro/transformer.cjs +11 -2
- package/package.json +2 -1
- package/runtime/cpp/lucent/abort.cpp +59 -11
- package/runtime/cpp/lucent/abort.h +22 -6
- package/runtime/cpp/lucent/array.h +12 -2
- package/runtime/cpp/lucent/async.h +101 -21
- package/runtime/cpp/lucent/bigint.cpp +898 -0
- package/runtime/cpp/lucent/bigint.h +302 -0
- package/runtime/cpp/lucent/buffer.cpp +228 -0
- package/runtime/cpp/lucent/buffer.h +328 -0
- package/runtime/cpp/lucent/builtins.cpp +4 -1
- package/runtime/cpp/lucent/bytes.h +21 -2
- package/runtime/cpp/lucent/callback.cpp +29 -0
- package/runtime/cpp/lucent/callback.h +230 -0
- package/runtime/cpp/lucent/compute.cpp +370 -0
- package/runtime/cpp/lucent/compute.h +455 -0
- package/runtime/cpp/lucent/conversions.h +111 -0
- package/runtime/cpp/lucent/core.h +16 -5
- package/runtime/cpp/lucent/equality.h +107 -28
- package/runtime/cpp/lucent/execution.cpp +416 -0
- package/runtime/cpp/lucent/execution.h +270 -0
- package/runtime/cpp/lucent/extension.cpp +97 -0
- package/runtime/cpp/lucent/extension.h +210 -0
- package/runtime/cpp/lucent/helpers.h +12 -5
- package/runtime/cpp/lucent/items.h +52 -0
- package/runtime/cpp/lucent/jserror.cpp +9 -0
- package/runtime/cpp/lucent/jserror.h +11 -0
- package/runtime/cpp/lucent/jsi/convert.cpp +138 -0
- package/runtime/cpp/lucent/jsi/convert.h +128 -59
- package/runtime/cpp/lucent/jsi/host.cpp +213 -35
- package/runtime/cpp/lucent/jsi/host.h +116 -16
- package/runtime/cpp/lucent/json.h +19 -1
- package/runtime/cpp/lucent/lifecycle.cpp +260 -0
- package/runtime/cpp/lucent/lifecycle.h +124 -0
- package/runtime/cpp/lucent/lucent.h +7 -0
- package/runtime/cpp/lucent/map.h +41 -7
- package/runtime/cpp/lucent/native.cpp +35 -47
- package/runtime/cpp/lucent/native.h +116 -26
- package/runtime/cpp/lucent/number.cpp +38 -38
- package/runtime/cpp/lucent/number.h +33 -0
- package/runtime/cpp/lucent/operation.h +65 -0
- package/runtime/cpp/lucent/ops.h +14 -6
- package/runtime/cpp/lucent/platform/android.cpp +223 -10
- package/runtime/cpp/lucent/platform/android.h +227 -4
- package/runtime/cpp/lucent/platform/android_activity.cpp +258 -0
- package/runtime/cpp/lucent/platform/android_requests.h +140 -0
- package/runtime/cpp/lucent/platform/compose.h +256 -0
- package/runtime/cpp/lucent/platform/ios.h +325 -25
- package/runtime/cpp/lucent/platform/ios.mm +1 -14
- package/runtime/cpp/lucent/platform/ios_ui.h +192 -0
- package/runtime/cpp/lucent/platform/ios_ui.mm +454 -0
- package/runtime/cpp/lucent/platform/swiftui.h +190 -0
- package/runtime/cpp/lucent/presentation.cpp +11 -0
- package/runtime/cpp/lucent/presentation.h +109 -0
- package/runtime/cpp/lucent/reactive.cpp +406 -0
- package/runtime/cpp/lucent/reactive.h +566 -0
- package/runtime/cpp/lucent/report.cpp +40 -0
- package/runtime/cpp/lucent/report.h +16 -0
- package/runtime/cpp/lucent/resource.cpp +83 -0
- package/runtime/cpp/lucent/resource.h +75 -0
- package/runtime/cpp/lucent/scheduler.cpp +38 -124
- package/runtime/cpp/lucent/scheduler.h +102 -83
- package/runtime/cpp/lucent/scope.cpp +308 -0
- package/runtime/cpp/lucent/scope.h +452 -0
- package/runtime/cpp/lucent/sizing.h +263 -0
- package/runtime/cpp/lucent/slots.h +231 -0
- package/runtime/cpp/lucent/trace.cpp +539 -0
- package/runtime/cpp/lucent/trace.h +251 -0
- package/runtime/cpp/lucent/transport.cpp +9 -0
- package/runtime/cpp/lucent/transport.h +341 -0
- package/runtime/cpp/lucent/view.cpp +123 -0
- package/runtime/cpp/lucent/view.h +157 -0
- package/runtime/cpp/rn/LucentComponentView.h +121 -0
- package/runtime/cpp/rn/LucentComponentView.mm +479 -0
- package/runtime/cpp/rn/LucentModule.cpp +37 -7
- package/runtime/cpp/rn/LucentModule.h +3 -0
- package/runtime/cpp/rn/LucentViewChildren.h +80 -0
- package/runtime/cpp/rn/LucentViewChildren.mm +211 -0
- package/runtime/cpp/rn/LucentViewRegistry.h +62 -0
- package/runtime/cpp/rn/LucentViewRequests.cpp +91 -0
- package/runtime/cpp/rn/LucentViewRequests.h +104 -0
- package/runtime/cpp/rn/LucentViewSizing.h +345 -0
- package/runtime/cpp/rn/LucentViewSlots.h +242 -0
- package/runtime/cpp/rn/LucentViewValues.h +125 -0
- package/runtime/cpp/rn/LucentViews.h +440 -0
- package/runtime/cpp/rn/LucentViewsAndroid.cpp +439 -0
- package/runtime/cpp/rn/LucentViewsAndroid.h +138 -0
- package/runtime/cpp/third_party/dragonbox/LICENSE-Boost +23 -0
- package/runtime/cpp/third_party/dragonbox/README.md +16 -0
- package/runtime/cpp/third_party/dragonbox/dragonbox.h +4440 -0
- package/runtime/js/index.d.ts +11 -1
- package/runtime/js/index.js +84 -8
- package/runtime/js/views.d.ts +38 -0
- package/runtime/js/views.js +226 -0
- package/runtime/native/android/CMakeLists.txt +4 -1
- package/runtime/native/android/src/compose/java/dev/lucent/compose/CompositionRules.kt +44 -0
- package/runtime/native/android/src/compose/java/dev/lucent/compose/LucentComposition.kt +161 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentActivities.java +292 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentChildren.java +174 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentHostView.java +232 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentInitializer.java +46 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentPackage.java +4 -3
- package/runtime/native/android/src/main/java/dev/lucent/LucentRequestActivity.java +92 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentSlotView.java +102 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentViewManager.java +149 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentViewManagers.java +16 -0
- package/runtime/native/android/src/main/java/dev/lucent/LucentViews.java +44 -0
- package/runtime/test/jsi/harness.cpp +9 -0
- package/schemas/build.schema.json +96 -1
- package/schemas/check.schema.json +56 -1
- package/schemas/lucent.schema.json +495 -9
- package/schemas/sdk-diff.schema.json +70 -0
- package/schemas/sdk-lock.schema.json +67 -0
- package/ts-plugin/index.js +77 -4
- package/dist/pipeline-CH7-2cxu.js +0 -232
- package/dist/project-D8tEet0k.js +0 -150
- package/dist/sdk-coverage-BoW_3Efd.js +0 -64
- 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
|
-
/**
|
|
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
|
-
|
|
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>;
|
package/lib/sdk/ui.d.ts
ADDED
|
@@ -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
|
+
};
|
package/metro/transformer.cjs
CHANGED
|
@@ -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, "
|
|
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(), "
|
|
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
|
|
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
|
-
|
|
17
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
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`
|
|
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
|
-
|
|
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>
|