mirror of
https://github.com/clockworklabs/SpacetimeDB.git
synced 2026-05-06 07:26:43 -04:00
Merge remote-tracking branch 'origin/master' into jdetter/vm-cache
This commit is contained in:
@@ -993,6 +993,10 @@ jobs:
|
||||
const publicRef = (context.eventName === 'pull_request') ? context.payload.pull_request.head.ref : context.sha;
|
||||
const publicPrNumber = context.payload.pull_request?.number ?? context.payload.inputs?.pr_number;
|
||||
const preDispatch = new Date().toISOString();
|
||||
const inputs = { public_ref: publicRef };
|
||||
if (publicPrNumber) {
|
||||
inputs.public_pr_number = String(publicPrNumber);
|
||||
}
|
||||
|
||||
// Dispatch the workflow in the target repository
|
||||
await github.rest.actions.createWorkflowDispatch({
|
||||
@@ -1000,7 +1004,7 @@ jobs:
|
||||
repo: targetRepo,
|
||||
workflow_id: workflowId,
|
||||
ref: targetRef,
|
||||
inputs: { public_ref: publicRef, public_pr_number: String(publicPrNumber) }
|
||||
inputs,
|
||||
});
|
||||
|
||||
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
|
||||
|
||||
@@ -97,17 +97,22 @@ public:
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* auto module_id = ctx.identity();
|
||||
* auto module_id = ctx.database_identity();
|
||||
* std::string url = "http://localhost:3000/v1/database/" +
|
||||
* module_id.to_hex() + "/schema?version=9";
|
||||
* @endcode
|
||||
*/
|
||||
Identity identity() const {
|
||||
Identity database_identity() const {
|
||||
std::array<uint8_t, 32> id_bytes;
|
||||
::identity(id_bytes.data());
|
||||
return Identity(id_bytes);
|
||||
}
|
||||
|
||||
[[deprecated("Use database_identity() instead.")]]
|
||||
Identity identity() const {
|
||||
return database_identity();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the random number generator for this procedure call
|
||||
*
|
||||
|
||||
@@ -58,11 +58,16 @@ public:
|
||||
return *rng_instance;
|
||||
}
|
||||
|
||||
Identity identity() const {
|
||||
Identity database_identity() const {
|
||||
std::array<uint8_t, 32> buffer;
|
||||
::identity(buffer.data());
|
||||
return Identity(buffer);
|
||||
}
|
||||
|
||||
[[deprecated("Use database_identity() instead.")]]
|
||||
Identity identity() const {
|
||||
return database_identity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new random UUID v4.
|
||||
|
||||
@@ -69,7 +69,9 @@ public:
|
||||
// Access to ReducerContext methods
|
||||
Identity sender() const { return ctx_.sender(); }
|
||||
const AuthCtx& sender_auth() const { return ctx_.sender_auth(); }
|
||||
Identity identity() const { return ctx_.identity(); }
|
||||
Identity database_identity() const { return ctx_.database_identity(); }
|
||||
[[deprecated("Use database_identity() instead.")]]
|
||||
Identity identity() const { return database_identity(); }
|
||||
StdbRng& rng() const { return ctx_.rng(); }
|
||||
|
||||
/**
|
||||
|
||||
+6
-2
@@ -655,9 +655,13 @@ namespace SpacetimeDB
|
||||
|
||||
// **Note:** must be 0..=u32::MAX
|
||||
internal int CounterUuid;
|
||||
public Identity DatabaseIdentity => Internal.IReducerContext.GetDatabaseIdentity();
|
||||
|
||||
// We need this property to be non-static for parity with client SDK.
|
||||
public Identity Identity => Internal.IReducerContext.GetIdentity();
|
||||
// We keep this property for compatibility with existing module code.
|
||||
[global::System.Obsolete(
|
||||
"ReducerContext.Identity is deprecated. Use DatabaseIdentity instead."
|
||||
)]
|
||||
public Identity Identity => DatabaseIdentity;
|
||||
|
||||
internal ReducerContext(
|
||||
Identity identity,
|
||||
|
||||
+6
-2
@@ -57,9 +57,13 @@ namespace SpacetimeDB
|
||||
|
||||
// **Note:** must be 0..=u32::MAX
|
||||
internal int CounterUuid;
|
||||
public Identity DatabaseIdentity => Internal.IReducerContext.GetDatabaseIdentity();
|
||||
|
||||
// We need this property to be non-static for parity with client SDK.
|
||||
public Identity Identity => Internal.IReducerContext.GetIdentity();
|
||||
// We keep this property for compatibility with existing module code.
|
||||
[global::System.Obsolete(
|
||||
"ReducerContext.Identity is deprecated. Use DatabaseIdentity instead."
|
||||
)]
|
||||
public Identity Identity => DatabaseIdentity;
|
||||
|
||||
internal ReducerContext(
|
||||
Identity identity,
|
||||
|
||||
+6
-2
@@ -499,9 +499,13 @@ namespace SpacetimeDB
|
||||
|
||||
// **Note:** must be 0..=u32::MAX
|
||||
internal int CounterUuid;
|
||||
public Identity DatabaseIdentity => Internal.IReducerContext.GetDatabaseIdentity();
|
||||
|
||||
// We need this property to be non-static for parity with client SDK.
|
||||
public Identity Identity => Internal.IReducerContext.GetIdentity();
|
||||
// We keep this property for compatibility with existing module code.
|
||||
[global::System.Obsolete(
|
||||
"ReducerContext.Identity is deprecated. Use DatabaseIdentity instead."
|
||||
)]
|
||||
public Identity Identity => DatabaseIdentity;
|
||||
|
||||
internal ReducerContext(
|
||||
Identity identity,
|
||||
|
||||
@@ -2189,8 +2189,10 @@ public class Module : IIncrementalGenerator
|
||||
public readonly AuthCtx SenderAuth;
|
||||
// **Note:** must be 0..=u32::MAX
|
||||
internal int CounterUuid;
|
||||
// We need this property to be non-static for parity with client SDK.
|
||||
public Identity Identity => Internal.IReducerContext.GetIdentity();
|
||||
public Identity DatabaseIdentity => Internal.IReducerContext.GetDatabaseIdentity();
|
||||
// We keep this property for compatibility with existing module code.
|
||||
[global::System.Obsolete("ReducerContext.Identity is deprecated. Use DatabaseIdentity instead.")]
|
||||
public Identity Identity => DatabaseIdentity;
|
||||
|
||||
internal ReducerContext(Identity identity, ConnectionId? connectionId, Random random,
|
||||
Timestamp time, AuthCtx? senderAuth = null)
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
namespace SpacetimeDB.Internal;
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using SpacetimeDB.BSATN;
|
||||
|
||||
public interface IReducerContext
|
||||
{
|
||||
public static Identity GetIdentity()
|
||||
public static Identity GetDatabaseIdentity()
|
||||
{
|
||||
FFI.identity(out var identity);
|
||||
return identity;
|
||||
}
|
||||
|
||||
[Obsolete("IReducerContext.GetIdentity() is deprecated. Use GetDatabaseIdentity() instead.")]
|
||||
public static Identity GetIdentity() => GetDatabaseIdentity();
|
||||
}
|
||||
|
||||
public interface IReducer
|
||||
|
||||
@@ -103,6 +103,8 @@ export interface JwtClaims {
|
||||
*/
|
||||
export type ReducerCtx<SchemaDef extends UntypedSchemaDef> = Readonly<{
|
||||
sender: Identity;
|
||||
databaseIdentity: Identity;
|
||||
/** @deprecated Use `databaseIdentity` instead. */
|
||||
identity: Identity;
|
||||
timestamp: Timestamp;
|
||||
connectionId: ConnectionId | null;
|
||||
|
||||
@@ -75,6 +75,8 @@ export interface ProcedureOpts {
|
||||
|
||||
export interface ProcedureCtx<S extends UntypedSchemaDef> {
|
||||
readonly sender: Identity;
|
||||
readonly databaseIdentity: Identity;
|
||||
/** @deprecated Use `databaseIdentity` instead. */
|
||||
readonly identity: Identity;
|
||||
readonly timestamp: Timestamp;
|
||||
readonly connectionId: ConnectionId | null;
|
||||
@@ -195,10 +197,14 @@ const ProcedureCtxImpl = class ProcedureCtx<S extends UntypedSchemaDef>
|
||||
this.#dbView = dbView;
|
||||
}
|
||||
|
||||
get identity() {
|
||||
get databaseIdentity() {
|
||||
return (this.#identity ??= new Identity(sys.identity()));
|
||||
}
|
||||
|
||||
get identity() {
|
||||
return this.databaseIdentity;
|
||||
}
|
||||
|
||||
get random() {
|
||||
return (this.#random ??= makeRandom(this.timestamp));
|
||||
}
|
||||
|
||||
@@ -221,10 +221,14 @@ export const ReducerCtxImpl = class ReducerCtx<
|
||||
me.#senderAuth = undefined;
|
||||
}
|
||||
|
||||
get identity() {
|
||||
get databaseIdentity() {
|
||||
return (this.#identity ??= new Identity(sys.identity()));
|
||||
}
|
||||
|
||||
get identity() {
|
||||
return this.databaseIdentity;
|
||||
}
|
||||
|
||||
get senderAuth() {
|
||||
return (this.#senderAuth ??= AuthCtxImpl.fromSystemTables(
|
||||
this.connectionId,
|
||||
|
||||
@@ -687,7 +687,7 @@ pub use spacetimedb_bindings_macro::table;
|
||||
///
|
||||
/// #[reducer]
|
||||
/// fn scheduled(ctx: &ReducerContext, args: ScheduledArgs) -> Result<(), String> {
|
||||
/// if ctx.sender() != ctx.identity() {
|
||||
/// if ctx.sender() != ctx.database_identity() {
|
||||
/// return Err("Reducer `scheduled` may not be invoked by clients, only via scheduling.".into());
|
||||
/// }
|
||||
/// // Reducer body...
|
||||
@@ -1081,7 +1081,7 @@ impl ReducerContext {
|
||||
}
|
||||
|
||||
/// Read the current module's [`Identity`].
|
||||
pub fn identity(&self) -> Identity {
|
||||
pub fn database_identity(&self) -> Identity {
|
||||
// Hypothetically, we *could* read the module identity out of the system tables.
|
||||
// However, this would be:
|
||||
// - Onerous, because we have no tooling to inspect the system tables from module code.
|
||||
@@ -1093,6 +1093,12 @@ impl ReducerContext {
|
||||
Identity::from_byte_array(spacetimedb_bindings_sys::identity())
|
||||
}
|
||||
|
||||
/// Read the current module's [`Identity`].
|
||||
#[deprecated(note = "Use `ReducerContext::database_identity` instead.")]
|
||||
pub fn identity(&self) -> Identity {
|
||||
self.database_identity()
|
||||
}
|
||||
|
||||
/// Create an anonymous (no sender) read-only view context
|
||||
pub fn as_anonymous_read_only(&self) -> AnonymousViewContext {
|
||||
AnonymousViewContext::default()
|
||||
@@ -1459,9 +1465,14 @@ pub trait DbContext {
|
||||
///
|
||||
/// This method is provided for times when a programmer wants to be generic over the `DbContext` type.
|
||||
/// Concrete-typed code is expected to read the `.db` field off the particular `DbContext` implementor.
|
||||
/// Currently, being this generic is only meaningful in clients,
|
||||
/// as `ReducerContext` is the only implementor of `DbContext` within modules.
|
||||
fn db(&self) -> &Self::DbView;
|
||||
|
||||
/// Get a read-only view into the tables.
|
||||
///
|
||||
/// This method is provided for times when a programmer wants to be generic over the `DbContext` type.
|
||||
/// Concrete-typed code is expected to read the `.db` field off the particular `DbContext` implementor.
|
||||
#[cfg(feature = "unstable")]
|
||||
fn db_read_only(&self) -> &LocalReadOnly;
|
||||
}
|
||||
|
||||
impl DbContext for AnonymousViewContext {
|
||||
@@ -1470,6 +1481,11 @@ impl DbContext for AnonymousViewContext {
|
||||
fn db(&self) -> &Self::DbView {
|
||||
&self.db
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable")]
|
||||
fn db_read_only(&self) -> &LocalReadOnly {
|
||||
&self.db
|
||||
}
|
||||
}
|
||||
|
||||
impl DbContext for ReducerContext {
|
||||
@@ -1478,6 +1494,11 @@ impl DbContext for ReducerContext {
|
||||
fn db(&self) -> &Self::DbView {
|
||||
&self.db
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable")]
|
||||
fn db_read_only(&self) -> &LocalReadOnly {
|
||||
self.db.get_read_only()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable")]
|
||||
@@ -1487,6 +1508,10 @@ impl DbContext for TxContext {
|
||||
fn db(&self) -> &Self::DbView {
|
||||
&self.db
|
||||
}
|
||||
|
||||
fn db_read_only(&self) -> &LocalReadOnly {
|
||||
self.db.get_read_only()
|
||||
}
|
||||
}
|
||||
|
||||
impl DbContext for ViewContext {
|
||||
@@ -1495,6 +1520,11 @@ impl DbContext for ViewContext {
|
||||
fn db(&self) -> &Self::DbView {
|
||||
&self.db
|
||||
}
|
||||
|
||||
#[cfg(feature = "unstable")]
|
||||
fn db_read_only(&self) -> &LocalReadOnly {
|
||||
&self.db
|
||||
}
|
||||
}
|
||||
|
||||
// `ProcedureContext` is *not* a `DbContext`
|
||||
@@ -1508,6 +1538,13 @@ impl DbContext for ViewContext {
|
||||
#[non_exhaustive]
|
||||
pub struct Local {}
|
||||
|
||||
impl Local {
|
||||
#[cfg(feature = "unstable")]
|
||||
fn get_read_only(&self) -> &LocalReadOnly {
|
||||
&LocalReadOnly {}
|
||||
}
|
||||
}
|
||||
|
||||
/// The [JWT] of an [`AuthCtx`].
|
||||
///
|
||||
/// [JWT]: https://en.wikipedia.org/wiki/JSON_Web_Token
|
||||
|
||||
@@ -28,10 +28,97 @@ impl Uninstall {
|
||||
Ok(None) => {}
|
||||
Err(e) => tracing::warn!("{e:#}"),
|
||||
}
|
||||
let dir = paths.cli_bin_dir.version_dir(&version);
|
||||
if !dir.0.exists() {
|
||||
anyhow::bail!("v{version} is not installed");
|
||||
}
|
||||
if yes.confirm(format!("Uninstall v{version}?"))? {
|
||||
let dir = paths.cli_bin_dir.version_dir(&version);
|
||||
std::fs::remove_dir_all(dir)?;
|
||||
std::fs::remove_dir_all(&dir)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use spacetimedb_paths::FromPathUnchecked;
|
||||
use spacetimedb_paths::RootDir;
|
||||
|
||||
fn make_temp_paths() -> (tempfile::TempDir, SpacetimePaths) {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let base = tmp.path().join("spacetime");
|
||||
std::fs::create_dir_all(&base).unwrap();
|
||||
let root = RootDir::from_path_unchecked(base);
|
||||
let paths = SpacetimePaths::from_root_dir(&root);
|
||||
(tmp, paths)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_uninstall_nonexistent_version_errors_before_prompt() {
|
||||
let (_tmp, paths) = make_temp_paths();
|
||||
let uninstall = Uninstall {
|
||||
version: "9.9.9".to_owned(),
|
||||
yes: ForceYes { yes: true },
|
||||
};
|
||||
let result = uninstall.exec(&paths);
|
||||
assert!(result.is_err());
|
||||
let err = result.unwrap_err();
|
||||
assert!(
|
||||
err.to_string().contains("9.9.9"),
|
||||
"error should mention the version number"
|
||||
);
|
||||
assert!(
|
||||
err.to_string().contains("not installed"),
|
||||
"error should say 'not installed'"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_uninstall_current_version_errors() {
|
||||
let (_tmp, paths) = make_temp_paths();
|
||||
// Create the "current" symlink target so it exists on disk
|
||||
let current_dir = paths.cli_bin_dir.version_dir("2.0.0");
|
||||
std::fs::create_dir_all(¤t_dir.0).unwrap();
|
||||
paths.cli_bin_dir.set_current_version("2.0.0").unwrap();
|
||||
|
||||
let uninstall = Uninstall {
|
||||
version: "2.0.0".to_owned(),
|
||||
yes: ForceYes { yes: true },
|
||||
};
|
||||
let result = uninstall.exec(&paths);
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().to_string().contains("currently used version"),);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_uninstall_current_keyword_errors() {
|
||||
let (_tmp, paths) = make_temp_paths();
|
||||
let uninstall = Uninstall {
|
||||
version: "current".to_owned(),
|
||||
yes: ForceYes { yes: true },
|
||||
};
|
||||
let result = uninstall.exec(&paths);
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().to_string().contains("cannot remove `current`"),);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_uninstall_existing_version_with_yes() {
|
||||
let (_tmp, paths) = make_temp_paths();
|
||||
let version_dir = paths.cli_bin_dir.version_dir("1.0.0");
|
||||
std::fs::create_dir_all(&version_dir.0).unwrap();
|
||||
// Create a dummy file so we can verify the directory existed
|
||||
std::fs::write(version_dir.0.join("spacetime"), "dummy").unwrap();
|
||||
|
||||
assert!(version_dir.0.exists(), "version dir should exist before");
|
||||
|
||||
let uninstall = Uninstall {
|
||||
version: "1.0.0".to_owned(),
|
||||
yes: ForceYes { yes: true },
|
||||
};
|
||||
uninstall.exec(&paths).unwrap();
|
||||
|
||||
assert!(!version_dir.0.exists(), "version dir should be removed after uninstall");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,9 +147,7 @@ impl DbConnectionBuilder {
|
||||
}
|
||||
```
|
||||
|
||||
Chain a call to `.on_connect_error(callback)` to your builder to register a callback to run when your connection fails.
|
||||
|
||||
A known bug in the SpacetimeDB Rust client SDK currently causes this callback never to be invoked. [`on_disconnect`](#callback-on_disconnect) callbacks are invoked instead.
|
||||
Chain a call to `.on_connect_error(callback)` to your builder to register a callback to run when a connection attempt fails asynchronously. Errors which prevent `build` from creating the connection are returned by `build` instead.
|
||||
|
||||
#### Callback `on_disconnect`
|
||||
|
||||
@@ -162,7 +160,7 @@ impl DbConnectionBuilder {
|
||||
}
|
||||
```
|
||||
|
||||
Chain a call to `.on_disconnect(callback)` to your builder to register a callback to run when your `DbConnection` disconnects from the remote database, either as a result of a call to [`disconnect`](#method-disconnect) or due to an error.
|
||||
Chain a call to `.on_disconnect(callback)` to your builder to register a callback to run when your established `DbConnection` disconnects from the remote database, either as a result of a call to [`disconnect`](#method-disconnect) or due to an error.
|
||||
|
||||
#### Method `with_token`
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ Step-by-step guides for common tasks.
|
||||
|
||||
- **Deployment**
|
||||
- [Deploy to MainCloud](./00100-how-to/00100-deploy/00100-maincloud.md) - Deploy to SpacetimeDB's managed cloud
|
||||
- [Railway](./00100-how-to/00100-deploy/00300-railway.md) - Deploy SpacetimeDB with the official Railway template
|
||||
- [Self-Hosting](./00100-how-to/00100-deploy/00200-self-hosting.md) - Run SpacetimeDB on your own infrastructure
|
||||
|
||||
- **Database Features**
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
---
|
||||
title: Railway
|
||||
slug: /how-to/deploy/railway
|
||||
---
|
||||
|
||||
Railway is a hosted platform for deploying infrastructure and application services. If you want to run SpacetimeDB without managing your own VM, the official Railway template is a quick way to get started.
|
||||
|
||||
The template deploys the first-party `clockworklabs/spacetime` image, exposes port `3000`, and provisions persistent storage at `/stdb`. Once the service is running, you can publish one or more databases to it with the SpacetimeDB CLI.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. A [Railway account](https://railway.com/)
|
||||
2. The SpacetimeDB CLI installed: [Install SpacetimeDB](https://spacetimedb.com/install)
|
||||
3. A SpacetimeDB module project ready to publish
|
||||
|
||||
## Step 1: Deploy the Railway template
|
||||
|
||||
Open the official deployment template:
|
||||
|
||||
[SpacetimeDB Template](https://railway.com/deploy/spacetimedb)
|
||||
|
||||
Then:
|
||||
|
||||
1. Click **Deploy Now**.
|
||||
2. Create a new Railway project or choose an existing one.
|
||||
3. Wait for the deployment to finish.
|
||||
4. In Railway, open your service and copy its public domain or attach a custom domain.
|
||||
|
||||
That domain is the base URL your CLI and clients will use to connect to this SpacetimeDB instance.
|
||||
|
||||
## Step 2: Add the Railway deployment to your CLI
|
||||
|
||||
Register your Railway deployment as a named server:
|
||||
|
||||
```bash
|
||||
spacetime server add --url https://<your-railway-domain> railway
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```bash
|
||||
spacetime server add --url https://my-railway-app.up.railway.app railway
|
||||
```
|
||||
|
||||
You can optionally verify the connection:
|
||||
|
||||
```bash
|
||||
spacetime server ping railway
|
||||
```
|
||||
|
||||
## Step 3: Publish your database
|
||||
|
||||
From your SpacetimeDB project, publish a database to the Railway deployment:
|
||||
|
||||
```bash
|
||||
spacetime publish my-database --server railway
|
||||
```
|
||||
|
||||
To update an existing database later, run the same command again.
|
||||
|
||||
## Step 4: Connect clients
|
||||
|
||||
After publishing, connect your client to your Railway-hosted database using your Railway domain as the server URI and your database name.
|
||||
|
||||
See [Connecting to SpacetimeDB](../../../00200-core-concepts/00600-clients/00300-connection.md) for the current client connection patterns across supported SDKs.
|
||||
|
||||
## Notes
|
||||
|
||||
- The Railway template sets up the SpacetimeDB server itself, but it does not publish your module for you. You still deploy your database schema and logic with `spacetime publish`.
|
||||
- A single Railway-hosted SpacetimeDB instance can host multiple databases.
|
||||
- If you want full control over the host, reverse proxy, and operating system setup, see [Self-hosting](./00200-self-hosting.md).
|
||||
@@ -280,7 +280,7 @@ SPACETIMEDB_REDUCER(list_over_age, ReducerContext ctx, uint8_t age) {
|
||||
|
||||
// Log module identity
|
||||
SPACETIMEDB_REDUCER(log_module_identity, ReducerContext ctx) {
|
||||
LOG_INFO("Module identity: " + ctx.identity().to_string());
|
||||
LOG_INFO("Module identity: " + ctx.database_identity().to_string());
|
||||
return Ok();
|
||||
}
|
||||
|
||||
@@ -550,8 +550,8 @@ SPACETIMEDB_REDUCER(test_btree_index_args, ReducerContext ctx) {
|
||||
|
||||
// Test reducer for assertions
|
||||
SPACETIMEDB_REDUCER(assert_caller_identity_is_module_identity, ReducerContext ctx) {
|
||||
LOG_INFO("Sender: " + ctx.sender().to_string() + " Identity: " + ctx.identity().to_string());
|
||||
if (ctx.sender() != ctx.identity()) {
|
||||
LOG_INFO("Sender: " + ctx.sender().to_string() + " Identity: " + ctx.database_identity().to_string());
|
||||
if (ctx.sender() != ctx.database_identity()) {
|
||||
LOG_ERROR("Assertion failed: caller identity does not match module identity");
|
||||
} else {
|
||||
LOG_INFO("Assertion passed: caller identity matches module identity");
|
||||
@@ -693,7 +693,7 @@ SPACETIMEDB_PROCEDURE(Unit, with_tx, ProcedureContext ctx) {
|
||||
|
||||
// Hit SpacetimeDB's schema HTTP route and return its result as a string
|
||||
SPACETIMEDB_PROCEDURE(std::string, get_my_schema_via_http, ProcedureContext ctx) {
|
||||
Identity module_identity = ctx.identity();
|
||||
Identity module_identity = ctx.database_identity();
|
||||
std::string url = "http://localhost:3000/v1/database/" + module_identity.to_string() + "/schema?version=9";
|
||||
|
||||
auto result = ctx.http.get(url);
|
||||
|
||||
@@ -300,7 +300,7 @@ static partial class Module
|
||||
public static void log_module_identity(ReducerContext ctx)
|
||||
{
|
||||
// Note: converting to lowercase to match the Rust formatting.
|
||||
Log.Info($"Module identity: {ctx.Identity.ToString().ToLower()}");
|
||||
Log.Info($"Module identity: {ctx.DatabaseIdentity.ToString().ToLower()}");
|
||||
}
|
||||
|
||||
[Reducer]
|
||||
@@ -492,7 +492,7 @@ static partial class Module
|
||||
public static void assert_caller_identity_is_module_identity(ReducerContext ctx)
|
||||
{
|
||||
var caller = ctx.Sender;
|
||||
var owner = ctx.Identity;
|
||||
var owner = ctx.DatabaseIdentity;
|
||||
if (!caller.Equals(owner))
|
||||
{
|
||||
throw new Exception($"Caller {caller} is not the owner {owner}");
|
||||
|
||||
@@ -331,7 +331,7 @@ export const listOverAge = spacetimedb.reducer(
|
||||
|
||||
// log_module_identity()
|
||||
export const log_module_identity = spacetimedb.reducer(ctx => {
|
||||
console.info(`Module identity: ${ctx.identity}`);
|
||||
console.info(`Module identity: ${ctx.databaseIdentity}`);
|
||||
});
|
||||
|
||||
// test(arg: TestAlias(TestA), arg2: TestB, arg3: TestC, arg4: TestF)
|
||||
@@ -494,7 +494,7 @@ export const test_btree_index_args = spacetimedb.reducer(ctx => {
|
||||
export const assert_caller_identity_is_module_identity = spacetimedb.reducer(
|
||||
ctx => {
|
||||
const caller = ctx.sender;
|
||||
const owner = ctx.identity;
|
||||
const owner = ctx.databaseIdentity;
|
||||
if (String(caller) !== String(owner)) {
|
||||
throw new Error(`Caller ${caller} is not the owner ${owner}`);
|
||||
} else {
|
||||
@@ -507,7 +507,7 @@ export const assert_caller_identity_is_module_identity = spacetimedb.reducer(
|
||||
//
|
||||
// This is a silly thing to do, but an effective test of the procedure HTTP API.
|
||||
export const getMySchemaViaHttp = spacetimedb.procedure(t.string(), ctx => {
|
||||
const module_identity = ctx.identity;
|
||||
const module_identity = ctx.databaseIdentity;
|
||||
try {
|
||||
const response = ctx.http.fetch(
|
||||
`http://localhost:3000/v1/database/${module_identity}/schema?version=9`
|
||||
|
||||
@@ -295,7 +295,7 @@ pub fn list_over_age(ctx: &ReducerContext, age: u8) {
|
||||
|
||||
#[spacetimedb::reducer]
|
||||
fn log_module_identity(ctx: &ReducerContext) {
|
||||
log::info!("Module identity: {}", ctx.identity());
|
||||
log::info!("Module identity: {}", ctx.database_identity());
|
||||
}
|
||||
|
||||
#[spacetimedb::reducer]
|
||||
@@ -508,7 +508,7 @@ fn test_btree_index_args(ctx: &ReducerContext) {
|
||||
#[spacetimedb::reducer]
|
||||
fn assert_caller_identity_is_module_identity(ctx: &ReducerContext) {
|
||||
let caller = ctx.sender();
|
||||
let owner = ctx.identity();
|
||||
let owner = ctx.database_identity();
|
||||
if caller != owner {
|
||||
panic!("Caller {caller} is not the owner {owner}");
|
||||
} else {
|
||||
|
||||
@@ -142,7 +142,7 @@ SPACETIMEDB_PROCEDURE(Unit, insert_with_tx_rollback, ProcedureContext ctx) {
|
||||
// Test HTTP GET request to the module's own schema endpoint
|
||||
SPACETIMEDB_PROCEDURE(std::string, read_my_schema, ProcedureContext ctx) {
|
||||
// Get the module identity (database address)
|
||||
Identity module_identity = ctx.identity();
|
||||
Identity module_identity = ctx.database_identity();
|
||||
std::string identity_hex = module_identity.to_hex_string();
|
||||
|
||||
LOG_INFO("read_my_schema using identity: " + identity_hex);
|
||||
|
||||
@@ -91,7 +91,7 @@ export const will_panic = spacetimedb.procedure(t.unit(), _ctx => {
|
||||
});
|
||||
|
||||
export const read_my_schema = spacetimedb.procedure(t.string(), ctx => {
|
||||
const module_identity = ctx.identity;
|
||||
const module_identity = ctx.databaseIdentity;
|
||||
const response = ctx.http.fetch(
|
||||
`http://localhost:3000/v1/database/${module_identity}/schema?version=9`
|
||||
);
|
||||
|
||||
@@ -12,7 +12,7 @@ using RegressionTests.Shared;
|
||||
using SpacetimeDB;
|
||||
using SpacetimeDB.Types;
|
||||
|
||||
const string HOST = "http://localhost:3000";
|
||||
string HOST = Environment.GetEnvironmentVariable("SPACETIMEDB_SERVER_URL") ?? "http://localhost:3000";
|
||||
const string DBNAME = "btree-repro";
|
||||
const string THROW_ERROR_MESSAGE = "this is an error";
|
||||
const uint UPDATED_WHERE_TEST_VALUE = 42;
|
||||
|
||||
@@ -9,7 +9,7 @@ using RegressionTests.Shared;
|
||||
using SpacetimeDB;
|
||||
using SpacetimeDB.Types;
|
||||
|
||||
const string HOST = "http://localhost:3000";
|
||||
string HOST = Environment.GetEnvironmentVariable("SPACETIMEDB_SERVER_URL") ?? "http://localhost:3000";
|
||||
const string DBNAME = "procedure-tests";
|
||||
|
||||
uint waiting = 0;
|
||||
|
||||
@@ -9,7 +9,7 @@ using RegressionTests.Shared;
|
||||
using SpacetimeDB;
|
||||
using SpacetimeDB.Types;
|
||||
|
||||
const string HOST = "http://localhost:3000";
|
||||
string HOST = Environment.GetEnvironmentVariable("SPACETIMEDB_SERVER_URL") ?? "http://localhost:3000";
|
||||
const string DBNAME = "republish-test";
|
||||
|
||||
uint waiting = 0;
|
||||
|
||||
@@ -7,6 +7,7 @@ set -ueo pipefail
|
||||
SDK_PATH="$(dirname "$0")/.."
|
||||
SDK_PATH="$(realpath "$SDK_PATH")"
|
||||
STDB_PATH="$SDK_PATH/../.."
|
||||
SPACETIMEDB_SERVER_URL="${SPACETIMEDB_SERVER_URL:-local}"
|
||||
|
||||
# Regenerate Bindings
|
||||
"$SDK_PATH/tools~/gen-regression-tests.sh"
|
||||
@@ -15,13 +16,13 @@ STDB_PATH="$SDK_PATH/../.."
|
||||
cargo build --manifest-path "$STDB_PATH/crates/standalone/Cargo.toml"
|
||||
|
||||
# Publish module for btree test
|
||||
cargo run --manifest-path "$STDB_PATH/crates/cli/Cargo.toml" -- publish -c -y --server local -p "$SDK_PATH/examples~/regression-tests/server" btree-repro
|
||||
cargo run --manifest-path "$STDB_PATH/crates/cli/Cargo.toml" -- publish -c -y --server "$SPACETIMEDB_SERVER_URL" -p "$SDK_PATH/examples~/regression-tests/server" btree-repro
|
||||
|
||||
# Publish module for republishing module test
|
||||
cargo run --manifest-path "$STDB_PATH/crates/cli/Cargo.toml" -- publish -c -y --server local -p "$SDK_PATH/examples~/regression-tests/republishing/server-initial" republish-test
|
||||
cargo run --manifest-path "$STDB_PATH/crates/cli/Cargo.toml" call --server local republish-test insert 1
|
||||
cargo run --manifest-path "$STDB_PATH/crates/cli/Cargo.toml" -- publish --server local -p "$SDK_PATH/examples~/regression-tests/republishing/server-republish" --break-clients republish-test
|
||||
cargo run --manifest-path "$STDB_PATH/crates/cli/Cargo.toml" call --server local republish-test insert 2
|
||||
cargo run --manifest-path "$STDB_PATH/crates/cli/Cargo.toml" -- publish -c -y --server "$SPACETIMEDB_SERVER_URL" -p "$SDK_PATH/examples~/regression-tests/republishing/server-initial" republish-test
|
||||
cargo run --manifest-path "$STDB_PATH/crates/cli/Cargo.toml" call --server "$SPACETIMEDB_SERVER_URL" republish-test insert 1
|
||||
cargo run --manifest-path "$STDB_PATH/crates/cli/Cargo.toml" -- publish --server "$SPACETIMEDB_SERVER_URL" -p "$SDK_PATH/examples~/regression-tests/republishing/server-republish" --break-clients republish-test
|
||||
cargo run --manifest-path "$STDB_PATH/crates/cli/Cargo.toml" call --server "$SPACETIMEDB_SERVER_URL" republish-test insert 2
|
||||
|
||||
echo "Cleanup obj~ folders generated in $SDK_PATH/examples~/regression-tests/procedure-client"
|
||||
# There is a bug in the code generator that creates obj~ folders in the output directory using a Rust project.
|
||||
@@ -29,7 +30,7 @@ rm -rf "$SDK_PATH/examples~/regression-tests/procedure-client"/*/obj~
|
||||
rm -rf "$SDK_PATH/examples~/regression-tests/procedure-client/module_bindings"/*/obj~
|
||||
|
||||
# Publish module for procedure tests
|
||||
cargo run --manifest-path "$STDB_PATH/crates/cli/Cargo.toml" -- publish -c -y --server local -p "$STDB_PATH/modules/sdk-test-procedure" procedure-tests
|
||||
cargo run --manifest-path "$STDB_PATH/crates/cli/Cargo.toml" -- publish -c -y --server "$SPACETIMEDB_SERVER_URL" -p "$STDB_PATH/modules/sdk-test-procedure" procedure-tests
|
||||
|
||||
# Run client for btree test
|
||||
cd "$SDK_PATH/examples~/regression-tests/client" && dotnet run -c Debug
|
||||
|
||||
@@ -137,18 +137,25 @@ impl<M: SpacetimeModule> DbContextImpl<M> {
|
||||
fn process_message(&self, msg: ParsedMessage<M>) -> crate::Result<()> {
|
||||
self.debug_log(|out| writeln!(out, "`process_message`: {msg:?}"));
|
||||
match msg {
|
||||
// Error: treat this as an erroneous disconnect.
|
||||
ParsedMessage::Error(e) => {
|
||||
let disconnect_ctx = self.make_event_ctx(Some(e.clone()));
|
||||
self.invoke_disconnected(&disconnect_ctx);
|
||||
Err(e)
|
||||
}
|
||||
// Error: route as a connection error if we never finished connecting,
|
||||
// otherwise treat it as an erroneous disconnect.
|
||||
ParsedMessage::Error(e) => Err(self.end_connection(Some(e))),
|
||||
|
||||
// Initial `IdentityToken` message:
|
||||
// confirm that the received identity and connection ID are what we expect,
|
||||
// store them,
|
||||
// then invoke the on_connect callback.
|
||||
// store them, then invoke the on_connect callback.
|
||||
ParsedMessage::IdentityToken(identity, token, conn_id) => {
|
||||
let on_connect = {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
match inner.connection_lifecycle {
|
||||
ConnectionLifecycle::Connecting => {
|
||||
inner.connection_lifecycle = ConnectionLifecycle::Connected;
|
||||
inner.on_connect.take()
|
||||
}
|
||||
ConnectionLifecycle::Connected => None,
|
||||
ConnectionLifecycle::Ended => return Ok(()),
|
||||
}
|
||||
};
|
||||
{
|
||||
// Don't hold the `self.identity` lock while running callbacks.
|
||||
// Callbacks can (will) call [`DbContext::identity`], which acquires that lock,
|
||||
@@ -170,8 +177,7 @@ impl<M: SpacetimeModule> DbContextImpl<M> {
|
||||
}
|
||||
*conn_id_store = Some(conn_id);
|
||||
}
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
if let Some(on_connect) = inner.on_connect.take() {
|
||||
if let Some(on_connect) = on_connect {
|
||||
let ctx = <M::DbConnection as DbConnection>::new(self.clone());
|
||||
on_connect(&ctx, identity, &token);
|
||||
}
|
||||
@@ -306,23 +312,47 @@ impl<M: SpacetimeModule> DbContextImpl<M> {
|
||||
applied_diff.invoke_row_callbacks(&row_event_ctx, &mut inner.db_callbacks);
|
||||
}
|
||||
|
||||
/// Invoke the on-disconnect callback, and mark [`Self::is_active`] false.
|
||||
fn invoke_disconnected(&self, ctx: &M::ErrorContext) {
|
||||
/// Mark the connection lifecycle as ended, route the terminal event to the
|
||||
/// appropriate connection callback, and mark [`Self::is_active`] false.
|
||||
///
|
||||
/// Returns the terminal error that should be returned from `advance_*` methods.
|
||||
fn end_connection(&self, callback_error: Option<crate::Error>) -> crate::Error {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
// When we disconnect, we first call the on_disconnect method,
|
||||
// then we call the `on_error` method for all subscriptions.
|
||||
// We don't change the client cache at all.
|
||||
let return_error = callback_error.clone().unwrap_or(crate::Error::Disconnected);
|
||||
|
||||
let lifecycle = inner.connection_lifecycle;
|
||||
if lifecycle == ConnectionLifecycle::Ended {
|
||||
return return_error;
|
||||
}
|
||||
inner.connection_lifecycle = ConnectionLifecycle::Ended;
|
||||
|
||||
// Set `send_chan` to `None`, since `Self::is_active` checks that.
|
||||
*self.send_chan.lock().unwrap() = None;
|
||||
|
||||
// Grap the `on_disconnect` callback and invoke it.
|
||||
if let Some(disconnect_callback) = inner.on_disconnect.take() {
|
||||
disconnect_callback(ctx, ctx.event().clone());
|
||||
}
|
||||
match lifecycle {
|
||||
ConnectionLifecycle::Connecting => {
|
||||
let callback_error = callback_error.unwrap_or_else(|| crate::Error::FailedToConnect {
|
||||
source: InternalError::new("Connection closed before receiving the initial connection message"),
|
||||
});
|
||||
let ctx: M::ErrorContext = self.make_event_ctx(Some(callback_error.clone()));
|
||||
if let Some(connect_error_callback) = inner.on_connect_error.take() {
|
||||
connect_error_callback(&ctx, callback_error.clone());
|
||||
}
|
||||
callback_error
|
||||
}
|
||||
ConnectionLifecycle::Connected => {
|
||||
let ctx: M::ErrorContext = self.make_event_ctx(callback_error.clone());
|
||||
if let Some(disconnect_callback) = inner.on_disconnect.take() {
|
||||
disconnect_callback(&ctx, callback_error.clone());
|
||||
}
|
||||
|
||||
// Call the `on_disconnect` method for all subscriptions.
|
||||
inner.subscriptions.on_disconnect(ctx);
|
||||
// Call the `on_disconnect` method for all subscriptions.
|
||||
inner.subscriptions.on_disconnect(&ctx);
|
||||
|
||||
return_error
|
||||
}
|
||||
ConnectionLifecycle::Ended => return_error,
|
||||
}
|
||||
}
|
||||
|
||||
fn make_event_ctx<E, Ctx: AbstractEventContext<Module = M, Event = E>>(&self, event: E) -> Ctx {
|
||||
@@ -447,10 +477,19 @@ impl<M: SpacetimeModule> DbContextImpl<M> {
|
||||
|
||||
// Disconnect: close the connection.
|
||||
PendingMutation::Disconnect => {
|
||||
{
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
if inner.connection_lifecycle == ConnectionLifecycle::Connecting {
|
||||
// If the user cancels before the initial connection finishes,
|
||||
// don't report that as a connection error.
|
||||
inner.connection_lifecycle = ConnectionLifecycle::Ended;
|
||||
}
|
||||
}
|
||||
// Set `send_chan` to `None`, since `Self::is_active` checks that.
|
||||
// This will close the WebSocket loop in websocket.rs,
|
||||
// sending a close frame to the server,
|
||||
// eventually resulting in disconnect callbacks being called.
|
||||
// eventually resulting in disconnect callbacks being called
|
||||
// if the initial connection had completed.
|
||||
*self.send_chan.lock().unwrap() = None;
|
||||
}
|
||||
|
||||
@@ -540,11 +579,7 @@ impl<M: SpacetimeModule> DbContextImpl<M> {
|
||||
// `Stream::poll_next`. No comment on whether this is a good mental
|
||||
// model or not.
|
||||
let res = match get_lock_sync(&self.recv).try_next() {
|
||||
Ok(None) => {
|
||||
let disconnect_ctx = self.make_event_ctx(None);
|
||||
self.invoke_disconnected(&disconnect_ctx);
|
||||
Err(crate::Error::Disconnected)
|
||||
}
|
||||
Ok(None) => Err(self.end_connection(None)),
|
||||
Err(_) => Ok(false),
|
||||
Ok(Some(msg)) => self.process_message(msg).map(|_| true),
|
||||
};
|
||||
@@ -599,11 +634,7 @@ impl<M: SpacetimeModule> DbContextImpl<M> {
|
||||
pub fn advance_one_message_blocking(&self) -> crate::Result<()> {
|
||||
match self.runtime.block_on(self.get_message()) {
|
||||
Message::Local(pending) => self.apply_mutation(pending),
|
||||
Message::Ws(None) => {
|
||||
let disconnect_ctx = self.make_event_ctx(None);
|
||||
self.invoke_disconnected(&disconnect_ctx);
|
||||
Err(crate::Error::Disconnected)
|
||||
}
|
||||
Message::Ws(None) => Err(self.end_connection(None)),
|
||||
Message::Ws(Some(msg)) => self.process_message(msg),
|
||||
}
|
||||
}
|
||||
@@ -614,11 +645,7 @@ impl<M: SpacetimeModule> DbContextImpl<M> {
|
||||
pub async fn advance_one_message_async(&self) -> crate::Result<()> {
|
||||
match self.get_message().await {
|
||||
Message::Local(pending) => self.apply_mutation(pending),
|
||||
Message::Ws(None) => {
|
||||
let disconnect_ctx = self.make_event_ctx(None);
|
||||
self.invoke_disconnected(&disconnect_ctx);
|
||||
Err(crate::Error::Disconnected)
|
||||
}
|
||||
Message::Ws(None) => Err(self.end_connection(None)),
|
||||
Message::Ws(Some(msg)) => self.process_message(msg),
|
||||
}
|
||||
}
|
||||
@@ -784,6 +811,16 @@ type OnConnectErrorCallback<M> = Box<dyn FnOnce(&<M as SpacetimeModule>::ErrorCo
|
||||
type OnDisconnectCallback<M> =
|
||||
Box<dyn FnOnce(&<M as SpacetimeModule>::ErrorContext, Option<crate::Error>) + Send + 'static>;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum ConnectionLifecycle {
|
||||
/// Waiting for the server's initial connection message.
|
||||
Connecting,
|
||||
/// The server has sent the initial connection message.
|
||||
Connected,
|
||||
/// The connection has already reached a terminal lifecycle state.
|
||||
Ended,
|
||||
}
|
||||
|
||||
/// All the stuff in a [`DbContextImpl`] which can safely be locked while invoking callbacks.
|
||||
pub(crate) struct DbContextImplInner<M: SpacetimeModule> {
|
||||
/// `Some` if not within the context of an outer runtime. The `Runtime` must
|
||||
@@ -796,9 +833,8 @@ pub(crate) struct DbContextImplInner<M: SpacetimeModule> {
|
||||
reducer_callbacks: ReducerCallbacks<M>,
|
||||
pub(crate) subscriptions: SubscriptionManager<M>,
|
||||
|
||||
connection_lifecycle: ConnectionLifecycle,
|
||||
on_connect: Option<OnConnectCallback<M>>,
|
||||
#[allow(unused)]
|
||||
// TODO: Make use of this to handle `ParsedMessage::Error` before receiving `IdentityToken`.
|
||||
on_connect_error: Option<OnConnectErrorCallback<M>>,
|
||||
on_disconnect: Option<OnDisconnectCallback<M>>,
|
||||
|
||||
@@ -1040,9 +1076,10 @@ but you must call one of them, or else the connection will never progress.
|
||||
/// If this method is not invoked, or `None` is supplied,
|
||||
/// the SpacetimeDB host will generate a new anonymous `Identity`.
|
||||
///
|
||||
/// If the passed token is invalid or rejected by the host,
|
||||
/// the connection will fail asynchrnonously.
|
||||
// FIXME: currently this causes `disconnect` to be called rather than `on_connect_error`.
|
||||
/// If the token is rejected before a connection context is created, [`Self::build`]
|
||||
/// returns an error. If the host reports the rejection after the WebSocket is
|
||||
/// established but before the initial connection message, [`Self::on_connect_error`]
|
||||
/// is invoked.
|
||||
pub fn with_token(mut self, token: Option<impl Into<String>>) -> Self {
|
||||
self.token = token.map(|token| token.into());
|
||||
self
|
||||
@@ -1095,9 +1132,10 @@ but you must call one of them, or else the connection will never progress.
|
||||
self
|
||||
}
|
||||
|
||||
/// Register a callback to run when the connection is successfully initiated.
|
||||
/// Register a callback to run when the connection is successfully established.
|
||||
///
|
||||
/// The callback will receive three arguments:
|
||||
/// The connection is established after the initial connection message is
|
||||
/// received from the host. The callback will receive three arguments:
|
||||
/// - The `DbConnection` which has successfully connected.
|
||||
/// - The `Identity` of the successful connection.
|
||||
/// - The private access token which can be used to later re-authenticate as the same `Identity`.
|
||||
@@ -1116,9 +1154,11 @@ Instead of registering multiple `on_connect` callbacks, register a single callba
|
||||
self
|
||||
}
|
||||
|
||||
/// Register a callback to run when the connection fails asynchronously,
|
||||
/// e.g. due to invalid credentials.
|
||||
// FIXME: currently never called; `on_disconnect` is called instead.
|
||||
/// Register a callback to run when a connection attempt fails asynchronously.
|
||||
///
|
||||
/// This callback is invoked only before the initial connection message is
|
||||
/// received from the host. Errors which prevent [`Self::build`] from creating
|
||||
/// a connection are returned by [`Self::build`] instead.
|
||||
pub fn on_connect_error(mut self, callback: impl FnOnce(&M::ErrorContext, crate::Error) + Send + 'static) -> Self {
|
||||
if self.on_connect_error.is_some() {
|
||||
panic!(
|
||||
@@ -1132,8 +1172,11 @@ Instead of registering multiple `on_connect_error` callbacks, register a single
|
||||
self
|
||||
}
|
||||
|
||||
/// Register a callback to run when the connection is closed.
|
||||
// FIXME: currently also called when the connection fails asynchronously, instead of `on_connect_error`.
|
||||
/// Register a callback to run when an established connection is closed.
|
||||
///
|
||||
/// The connection is established after the initial connection message is
|
||||
/// received from the host. Connection failures before that point invoke
|
||||
/// [`Self::on_connect_error`] instead.
|
||||
pub fn on_disconnect(
|
||||
mut self,
|
||||
callback: impl FnOnce(&M::ErrorContext, Option<crate::Error>) + Send + 'static,
|
||||
@@ -1166,6 +1209,7 @@ fn build_db_ctx_inner<M: SpacetimeModule>(
|
||||
reducer_callbacks: ReducerCallbacks::default(),
|
||||
subscriptions: SubscriptionManager::default(),
|
||||
|
||||
connection_lifecycle: ConnectionLifecycle::Connecting,
|
||||
on_connect: on_connect_cb,
|
||||
on_connect_error: on_connect_error_cb,
|
||||
on_disconnect: on_disconnect_cb,
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
script_dir="$(readlink -f "$(dirname "$0")")"
|
||||
stdb_root="$(realpath "$script_dir/../")"
|
||||
|
||||
set -euox pipefail
|
||||
|
||||
cd "$stdb_root"
|
||||
|
||||
tools/clippy.sh
|
||||
|
||||
cargo test --all
|
||||
|
||||
if which python3 >/dev/null ; then
|
||||
python3 -m smoketests
|
||||
elif which python >/dev/null ; then
|
||||
python -m smoketests
|
||||
else
|
||||
echo "Can't find python, not running smoketests"
|
||||
fi
|
||||
|
||||
if which dotnet >/dev/null ; then
|
||||
dotnet test crates/bindings-csharp
|
||||
else
|
||||
echo "Can't find dotnet, not running smoketests"
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user