Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions crates/perry-api-manifest/src/entries/part_1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1192,20 +1192,37 @@ pub(crate) const API_MANIFEST_PART_1: &[ApiEntry] = &[
}],
TypeSpec::String,
),
// Second arg is npm slugify's replacement-or-options overload
// (string | { replacement, lower, strict, trim }) — Any, matching
// the NA_JSV dispatch slot.
method_sig(
"slugify",
"default",
false,
None,
&[p_str("p0"), p_str("p1"), p_str("p2")],
&[
p_str("p0"),
ParamSpec::Named {
name: "p1",
ty: TypeSpec::Any,
optional: true,
},
],
TypeSpec::String,
),
method_sig(
"slugify",
"slugify",
false,
None,
&[p_str("p0"), p_str("p1"), p_str("p2")],
&[
p_str("p0"),
ParamSpec::Named {
name: "p1",
ty: TypeSpec::Any,
optional: true,
},
],
TypeSpec::String,
),
method_sig(
Expand Down
86 changes: 82 additions & 4 deletions crates/perry-api-manifest/src/entries/part_2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,32 @@ pub(crate) const API_MANIFEST_PART_2: &[ApiEntry] = &[
&[p_any("p0"), p_any("p1")],
TypeSpec::Number,
),
method_sig("dayjs", "default", false, None, &[], TypeSpec::Any),
method_sig("dayjs", "dayjs", false, None, &[], TypeSpec::Any),
// Factory takes an optional input (string | number | undefined) —
// matches the 1-slot NA_JSV dispatch row (js_dayjs_factory).
method_sig(
"dayjs",
"default",
false,
None,
&[ParamSpec::Named {
name: "input",
ty: TypeSpec::Any,
optional: true,
}],
TypeSpec::Any,
),
method_sig(
"dayjs",
"dayjs",
false,
None,
&[ParamSpec::Named {
name: "input",
ty: TypeSpec::Any,
optional: true,
}],
TypeSpec::Any,
),
method("dayjs", "format", true, None),
method("dayjs", "year", true, None),
method("dayjs", "month", true, None),
Expand All @@ -199,8 +223,57 @@ pub(crate) const API_MANIFEST_PART_2: &[ApiEntry] = &[
method("dayjs", "isValid", true, None),
method("dayjs", "diff", true, None),
method("dayjs", "clone", true, None),
method_sig("moment", "default", false, None, &[], TypeSpec::Any),
method_sig("moment", "moment", false, None, &[], TypeSpec::Any),
// moment factory + instance methods (wired to the same handle-based
// date runtime as dayjs; see native_table/dates.rs moment rows).
method_sig(
"moment",
"default",
false,
None,
&[ParamSpec::Named {
name: "input",
ty: TypeSpec::Any,
optional: true,
}],
TypeSpec::Any,
),
method_sig(
"moment",
"moment",
false,
None,
&[ParamSpec::Named {
name: "input",
ty: TypeSpec::Any,
optional: true,
}],
TypeSpec::Any,
),
method("moment", "format", true, None),
method("moment", "toISOString", true, None),
method("moment", "valueOf", true, None),
method("moment", "unix", true, None),
method("moment", "year", true, None),
method("moment", "month", true, None),
method("moment", "date", true, None),
method("moment", "day", true, None),
method("moment", "hour", true, None),
method("moment", "minute", true, None),
method("moment", "second", true, None),
method("moment", "millisecond", true, None),
method("moment", "add", true, None),
method("moment", "subtract", true, None),
method("moment", "startOf", true, None),
method("moment", "endOf", true, None),
method("moment", "diff", true, None),
method("moment", "isBefore", true, None),
method("moment", "isAfter", true, None),
method("moment", "isSame", true, None),
method("moment", "isBetween", true, None),
method("moment", "isValid", true, None),
method("moment", "clone", true, None),
method("moment", "fromNow", true, None),
method("moment", "toDate", true, None),
method_sig(
"sharp",
"default",
Expand Down Expand Up @@ -548,6 +621,11 @@ pub(crate) const API_MANIFEST_PART_2: &[ApiEntry] = &[
method("cron", "stop", true, None),
method("cron", "isRunning", true, None),
method("cron", "nextDate", true, None),
// npm `cron` package class form: `new CronJob(cronTime, onTick,
// onComplete?, start?)` — constructed by the lower_builtin_new arm
// (js_cron_job_new; no auto-start, matching the npm package). The
// instance methods reuse the ("cron", true, …) rows above.
class("cron", "CronJob"),
method_sig(
"perry/tui",
"Text",
Expand Down
11 changes: 10 additions & 1 deletion crates/perry-api-manifest/src/entries/part_4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -697,9 +697,18 @@ pub(crate) const API_MANIFEST_PART_4: &[ApiEntry] = &[
method("date-fns", "startOfDay", false, None),
method("date-fns", "endOfDay", false, None),
// --- rate-limiter-flexible — perry-ext-ratelimit. Surface mirrors
// the npm package's RateLimiterMemory class. ---
// the npm package's RateLimiterMemory class. Construction is a
// lower_builtin_new arm (js_ratelimit_new_from_options); the
// instance methods dispatch via the NATIVE_MODULE_TABLE rows in
// lower_call/native_table/extras.rs. ---
class("rate-limiter-flexible", "RateLimiterMemory"),
class("rate-limiter-flexible", "RateLimiterAbstract"),
method("rate-limiter-flexible", "consume", true, None),
method("rate-limiter-flexible", "get", true, None),
method("rate-limiter-flexible", "delete", true, None),
method("rate-limiter-flexible", "block", true, None),
method("rate-limiter-flexible", "penalty", true, None),
method("rate-limiter-flexible", "reward", true, None),
// --- fetch — well-known alias for perry-ext-fetch. Same surface
// as node-fetch (the more common alias above). ---
method("fetch", "default", false, None),
Expand Down
24 changes: 23 additions & 1 deletion crates/perry-codegen/src/codegen/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -992,6 +992,24 @@ pub(super) fn compile_module_entry(
let has_timers = ctx.block().call(I32, "js_timer_has_pending", &[]);
let has_callbacks = ctx.block().call(I32, "js_callback_timer_has_pending", &[]);
let has_intervals = ctx.block().call(I32, "js_interval_timer_has_pending", &[]);
// Cron jobs (node-cron schedule() / npm cron's CronJob).
// Guarded on `needs_stdlib` like js_stdlib_init_dispatch
// above — the runtime-only link doesn't carry the cron
// symbols (and a cron import always pulls stdlib in).
// With stdlib linked the symbol always resolves:
// perry-ext-cron or the bundled scheduler provide the
// real queue; perry-stdlib exports a 0-returning stub
// otherwise. Without this gate (and the tick in
// loop_body below) a program whose only live work is a
// running cron job exits immediately and scheduled
// callbacks never fire — the CRON_TIMERS machinery
// existed but nothing in the generated event loop drove
// it.
let has_cron = if cross_module.needs_stdlib {
ctx.block().call(I32, "js_cron_timer_has_pending", &[])
} else {
"0".to_string()
};
let has_stdlib = ctx.block().call(I32, "js_stdlib_has_active_handles", &[]);
// #591: TASK_QUEUE may carry a pending `.then` continuation
// that was queued by `js_run_stdlib_pump`'s resolution path
Expand All @@ -1003,7 +1021,8 @@ pub(super) fn compile_module_entry(
let any1 = ctx.block().or(I32, &has_timers, &has_callbacks);
let any2 = ctx.block().or(I32, &has_intervals, &has_stdlib);
let any3 = ctx.block().or(I32, &any1, &any2);
let any = ctx.block().or(I32, &any3, &has_microtasks);
let any4 = ctx.block().or(I32, &any3, &has_cron);
let any = ctx.block().or(I32, &any4, &has_microtasks);
let cmp = ctx.block().icmp_ne(I32, &any, &zero);
ctx.block().cond_br(&cmp, &body_label, &exit_label);

Expand All @@ -1015,6 +1034,9 @@ pub(super) fn compile_module_entry(
let _ = ctx.block().call(I32, "js_timer_tick", &[]);
let _ = ctx.block().call(I32, "js_callback_timer_tick", &[]);
let _ = ctx.block().call(I32, "js_interval_timer_tick", &[]);
if cross_module.needs_stdlib {
let _ = ctx.block().call(I32, "js_cron_timer_tick", &[]);
}
ctx.block().call_void("js_run_stdlib_pump", &[]);
// Issue #84: condvar-backed wait. Returns immediately when
// a tokio worker (net/ws/http/fetch/redis/spawn) notifies
Expand Down
70 changes: 70 additions & 0 deletions crates/perry-codegen/src/lower_call/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ pub(super) fn lower_builtin_new(
"Redis" => Some(&["ioredis", "redis"]),
"MongoClient" => Some(&["mongodb"]),
"Decimal" => Some(&["decimal.js"]),
"RateLimiterMemory" => Some(&["rate-limiter-flexible"]),
"CronJob" => Some(&["cron", "node-cron"]),
_ => None,
};
if let Some(sources) = required_sources {
Expand Down Expand Up @@ -698,6 +700,74 @@ pub(super) fn lower_builtin_new(
let handle = blk.call(I64, "js_ioredis_new", &[(I64, "0")]);
Ok(Some(nanbox_pointer_inline(blk, &handle)))
}
// rate-limiter-flexible `new RateLimiterMemory({ points, duration })`.
// Gated on the import source above. The options object crosses as
// raw NaN-box bits (i64) so the runtime parses `points`/`duration`
// by name; missing arg → TAG_UNDEFINED → npm defaults (4 points /
// 1 s). Pre-fix this fell to the js_object_alloc(0,0) placeholder
// and every method call dispatched against `{}`. Instance methods
// (consume/get/delete/block/penalty/reward) are wired in
// NATIVE_MODULE_TABLE for module "rate-limiter-flexible".
"RateLimiterMemory" => {
let options = if let Some(arg) = args.first() {
lower_expr(ctx, arg)?
} else {
double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED))
};
for arg in args.iter().skip(1) {
let _ = lower_expr(ctx, arg)?;
}
let blk = ctx.block();
let options_bits = blk.bitcast_double_to_i64(&options);
let handle = blk.call(
I64,
"js_ratelimit_new_from_options",
&[(I64, &options_bits)],
);
Ok(Some(nanbox_pointer_inline(blk, &handle)))
}
// npm `cron` package: `new CronJob(cronTime, onTick, onComplete?,
// start?)`. Gated on the import source above. Unlike node-cron's
// `schedule()` factory (which auto-starts), a CronJob only begins
// firing when the 4th argument is truthy or `job.start()` is
// called — `js_cron_job_new` implements that. The onTick closure
// is UNBOXED to a raw ClosureHeader pointer (unbox_to_i64): the
// cron tick calls it via js_closure_call0 on the raw pointer, so
// tagged NaN-box bits would throw "value is not a function" on
// the first fire. onComplete is lowered for side effects only.
// start/stop/isRunning/nextDate dispatch via the existing
// ("cron", true, …) NATIVE_MODULE_TABLE rows.
"CronJob" => {
let expr_ptr = if let Some(arg) = args.first() {
get_raw_string_ptr(ctx, arg)?
} else {
"0".to_string()
};
let on_tick = if let Some(arg) = args.get(1) {
lower_expr(ctx, arg)?
} else {
double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED))
};
if let Some(arg) = args.get(2) {
let _ = lower_expr(ctx, arg)?;
}
let start = if let Some(arg) = args.get(3) {
lower_expr(ctx, arg)?
} else {
double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED))
};
for arg in args.iter().skip(4) {
let _ = lower_expr(ctx, arg)?;
}
let blk = ctx.block();
let cb_ptr = unbox_to_i64(blk, &on_tick);
let handle = blk.call(
I64,
"js_cron_job_new",
&[(I64, &expr_ptr), (I64, &cb_ptr), (DOUBLE, &start)],
);
Ok(Some(nanbox_pointer_inline(blk, &handle)))
}
// async_hooks.AsyncLocalStorage — `new AsyncLocalStorage()` produces a
// real handle so `.run(store, cb)` / `.getStore()` / `.enterWith(store)`
// / `.exit(cb)` / `.disable()` find their registered store stack.
Expand Down
Loading
Loading