@perspective-dev/client 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/LICENSE.md +193 -0
  2. package/README.md +3 -0
  3. package/dist/cdn/perspective-server.worker.js +2 -0
  4. package/dist/cdn/perspective-server.worker.js.map +7 -0
  5. package/dist/cdn/perspective.js +3 -0
  6. package/dist/cdn/perspective.js.map +7 -0
  7. package/dist/esm/perspective-server.worker.d.ts +1 -0
  8. package/dist/esm/perspective.browser.d.ts +14 -0
  9. package/dist/esm/perspective.inline.js +3 -0
  10. package/dist/esm/perspective.inline.js.map +7 -0
  11. package/dist/esm/perspective.js +3 -0
  12. package/dist/esm/perspective.js.map +7 -0
  13. package/dist/esm/perspective.node.d.ts +60 -0
  14. package/dist/esm/perspective.node.js +2431 -0
  15. package/dist/esm/perspective.node.js.map +7 -0
  16. package/dist/esm/ts-rs/Aggregate.d.ts +1 -0
  17. package/dist/esm/ts-rs/ColumnWindow.d.ts +4 -0
  18. package/dist/esm/ts-rs/DeleteOptions.d.ts +6 -0
  19. package/dist/esm/ts-rs/Expressions.d.ts +3 -0
  20. package/dist/esm/ts-rs/Filter.d.ts +2 -0
  21. package/dist/esm/ts-rs/FilterReducer.d.ts +1 -0
  22. package/dist/esm/ts-rs/FilterTerm.d.ts +2 -0
  23. package/dist/esm/ts-rs/OnUpdateMode.d.ts +9 -0
  24. package/dist/esm/ts-rs/OnUpdateOptions.d.ts +7 -0
  25. package/dist/esm/ts-rs/Scalar.d.ts +5 -0
  26. package/dist/esm/ts-rs/Sort.d.ts +2 -0
  27. package/dist/esm/ts-rs/SortDir.d.ts +1 -0
  28. package/dist/esm/ts-rs/SystemInfo.d.ts +40 -0
  29. package/dist/esm/ts-rs/TableInitOptions.d.ts +22 -0
  30. package/dist/esm/ts-rs/TableReadFormat.d.ts +7 -0
  31. package/dist/esm/ts-rs/UpdateOptions.d.ts +8 -0
  32. package/dist/esm/ts-rs/ViewConfigUpdate.d.ts +90 -0
  33. package/dist/esm/ts-rs/ViewOnUpdateResp.d.ts +4 -0
  34. package/dist/esm/ts-rs/ViewWindow.d.ts +23 -0
  35. package/dist/esm/wasm/browser.d.ts +21 -0
  36. package/dist/esm/wasm/decompress.d.ts +1 -0
  37. package/dist/esm/wasm/emscripten_api.d.ts +5 -0
  38. package/dist/esm/wasm/engine.d.ts +40 -0
  39. package/dist/esm/wasm/perspective-server.poly.d.ts +1 -0
  40. package/dist/esm/websocket.d.ts +4 -0
  41. package/dist/wasm/perspective-js.d.ts +712 -0
  42. package/dist/wasm/perspective-js.js +1934 -0
  43. package/dist/wasm/perspective-js.wasm +0 -0
  44. package/dist/wasm/perspective-js.wasm.d.ts +75 -0
  45. package/package.json +68 -0
  46. package/src/rust/client.rs +483 -0
  47. package/src/rust/lib.rs +70 -0
  48. package/src/rust/table.rs +364 -0
  49. package/src/rust/table_data.rs +159 -0
  50. package/src/rust/utils/browser.rs +39 -0
  51. package/src/rust/utils/console_logger.rs +236 -0
  52. package/src/rust/utils/errors.rs +288 -0
  53. package/src/rust/utils/futures.rs +174 -0
  54. package/src/rust/utils/json.rs +252 -0
  55. package/src/rust/utils/local_poll_loop.rs +63 -0
  56. package/src/rust/utils/mod.rs +32 -0
  57. package/src/rust/utils/serde.rs +46 -0
  58. package/src/rust/utils/trace_allocator.rs +98 -0
  59. package/src/rust/view.rs +355 -0
  60. package/src/ts/perspective-server.worker.ts +54 -0
  61. package/src/ts/perspective.browser.ts +132 -0
  62. package/src/ts/perspective.cdn.ts +22 -0
  63. package/src/ts/perspective.inline.ts +27 -0
  64. package/src/ts/perspective.node.ts +315 -0
  65. package/src/ts/ts-rs/Aggregate.ts +3 -0
  66. package/src/ts/ts-rs/ColumnWindow.ts +3 -0
  67. package/src/ts/ts-rs/DeleteOptions.ts +6 -0
  68. package/src/ts/ts-rs/Expressions.ts +3 -0
  69. package/src/ts/ts-rs/Filter.ts +4 -0
  70. package/src/ts/ts-rs/FilterReducer.ts +3 -0
  71. package/src/ts/ts-rs/FilterTerm.ts +4 -0
  72. package/src/ts/ts-rs/OnUpdateData.ts +8 -0
  73. package/src/ts/ts-rs/OnUpdateMode.ts +11 -0
  74. package/src/ts/ts-rs/OnUpdateOptions.ts +7 -0
  75. package/src/ts/ts-rs/Scalar.ts +7 -0
  76. package/src/ts/ts-rs/Sort.ts +4 -0
  77. package/src/ts/ts-rs/SortDir.ts +3 -0
  78. package/src/ts/ts-rs/SystemInfo.ts +41 -0
  79. package/src/ts/ts-rs/TableInitOptions.ts +21 -0
  80. package/src/ts/ts-rs/TableReadFormat.ts +9 -0
  81. package/src/ts/ts-rs/UpdateOptions.ts +7 -0
  82. package/src/ts/ts-rs/ViewConfigUpdate.ts +87 -0
  83. package/src/ts/ts-rs/ViewOnUpdateResp.ts +3 -0
  84. package/src/ts/ts-rs/ViewWindow.ts +17 -0
  85. package/src/ts/wasm/browser.ts +123 -0
  86. package/src/ts/wasm/decompress.ts +64 -0
  87. package/src/ts/wasm/emscripten_api.ts +63 -0
  88. package/src/ts/wasm/engine.ts +271 -0
  89. package/src/ts/wasm/perspective-server.poly.ts +244 -0
  90. package/src/ts/websocket.ts +95 -0
  91. package/tsconfig.json +20 -0
@@ -0,0 +1,70 @@
1
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2
+ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3
+ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4
+ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5
+ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6
+ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7
+ // ┃ Copyright (c) 2017, the Perspective Authors. ┃
8
+ // ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9
+ // ┃ This file is part of the Perspective library, distributed under the terms ┃
10
+ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
+
13
+ //! <div class="warning">
14
+ //! The examples in this module are in JavaScript. See <a href="https://docs.rs/crate/perspective/latest"><code>perspective</code></a> docs for the Rust API.
15
+ //! </div>
16
+
17
+ #![warn(
18
+ clippy::all,
19
+ clippy::panic_in_result_fn,
20
+ clippy::await_holding_refcell_ref,
21
+ rustdoc::broken_intra_doc_links,
22
+ unstable_features
23
+ )]
24
+ #![allow(non_snake_case)]
25
+
26
+ extern crate alloc;
27
+
28
+ mod client;
29
+ mod table;
30
+ mod table_data;
31
+ pub mod utils;
32
+ mod view;
33
+
34
+ #[cfg(feature = "export-init")]
35
+ use wasm_bindgen::prelude::*;
36
+
37
+ pub use crate::client::Client;
38
+ pub use crate::table::*;
39
+ pub use crate::table_data::*;
40
+
41
+ #[cfg(feature = "export-init")]
42
+ #[wasm_bindgen(typescript_custom_section)]
43
+ const TS_APPEND_CONTENT: &'static str = r#"
44
+ export type * from "../../src/ts/ts-rs/ViewWindow.d.ts";
45
+ export type * from "../../src/ts/ts-rs/ColumnWindow.d.ts";
46
+ export type * from "../../src/ts/ts-rs/TableInitOptions.d.ts";
47
+ export type * from "../../src/ts/ts-rs/ViewConfigUpdate.d.ts";
48
+ export type * from "../../src/ts/ts-rs/ViewOnUpdateResp.d.ts";
49
+ export type * from "../../src/ts/ts-rs/OnUpdateOptions.d.ts";
50
+ export type * from "../../src/ts/ts-rs/UpdateOptions.d.ts";
51
+ export type * from "../../src/ts/ts-rs/DeleteOptions.d.ts";
52
+ export type * from "../../src/ts/ts-rs/SystemInfo.d.ts";
53
+
54
+ import type {ColumnWindow} from "../../src/ts/ts-rs/ColumnWindow.d.ts";
55
+ import type {ViewWindow} from "../../src/ts/ts-rs/ViewWindow.d.ts";
56
+ import type {TableInitOptions} from "../../src/ts/ts-rs/TableInitOptions.d.ts";
57
+ import type {ViewConfigUpdate} from "../../src/ts/ts-rs/ViewConfigUpdate.d.ts";
58
+ import type * as on_update_args from "../../src/ts/ts-rs/ViewOnUpdateResp.d.ts";
59
+ import type {OnUpdateOptions} from "../../src/ts/ts-rs/OnUpdateOptions.d.ts";
60
+ import type {UpdateOptions} from "../../src/ts/ts-rs/UpdateOptions.d.ts";
61
+ import type {DeleteOptions} from "../../src/ts/ts-rs/DeleteOptions.d.ts";
62
+ import type {SystemInfo} from "../../src/ts/ts-rs/SystemInfo.d.ts";
63
+ "#;
64
+
65
+ #[cfg(feature = "export-init")]
66
+ #[wasm_bindgen]
67
+ pub fn init() {
68
+ console_error_panic_hook::set_once();
69
+ utils::set_global_logging();
70
+ }
@@ -0,0 +1,364 @@
1
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2
+ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3
+ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4
+ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5
+ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6
+ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7
+ // ┃ Copyright (c) 2017, the Perspective Authors. ┃
8
+ // ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9
+ // ┃ This file is part of the Perspective library, distributed under the terms ┃
10
+ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
+
13
+ use js_sys::Function;
14
+ use perspective_client::config::*;
15
+ use perspective_client::{DeleteOptions, UpdateData, UpdateOptions, assert_table_api};
16
+ use wasm_bindgen::prelude::*;
17
+ use wasm_bindgen_derive::TryFromJsValue;
18
+ use wasm_bindgen_futures::spawn_local;
19
+
20
+ use crate::Client;
21
+ use crate::table_data::UpdateDataExt;
22
+ use crate::utils::{ApiFuture, ApiResult, JsValueSerdeExt, LocalPollLoop};
23
+ pub use crate::view::*;
24
+
25
+ #[derive(TryFromJsValue, Clone, PartialEq)]
26
+ #[wasm_bindgen]
27
+ pub struct Table(pub(crate) perspective_client::Table);
28
+
29
+ assert_table_api!(Table);
30
+
31
+ impl From<perspective_client::Table> for Table {
32
+ fn from(value: perspective_client::Table) -> Self {
33
+ Table(value)
34
+ }
35
+ }
36
+
37
+ /// [`Table`] is Perspective's columnar data frame, analogous to a Pandas/Polars
38
+ /// `DataFrame` or Apache Arrow, supporting append & in-place updates, removal
39
+ /// by index, and update notifications.
40
+ ///
41
+ /// A [`Table`] contains columns, each of which have a unique name, are strongly
42
+ /// and consistently typed, and contains rows of data conforming to the column's
43
+ /// type. Each column in a [`Table`] must have the same number of rows, though
44
+ /// not every row must contain data; null-values are used to indicate missing
45
+ /// values in the dataset. The schema of a [`Table`] is _immutable after
46
+ /// creation_, which means the column names and data types cannot be changed
47
+ /// after the [`Table`] has been created. Columns cannot be added or deleted
48
+ /// after creation either, but a [`View`] can be used to select an arbitrary set
49
+ /// of columns from the [`Table`].
50
+ impl Table {
51
+ pub fn get_table(&self) -> &'_ perspective_client::Table {
52
+ &self.0
53
+ }
54
+ }
55
+
56
+ #[wasm_bindgen]
57
+ extern "C" {
58
+ // TODO Fix me
59
+ #[wasm_bindgen(typescript_type = "\
60
+ string | ArrayBuffer | Record<string, unknown[]> | Record<string, unknown>[]")]
61
+ pub type JsTableInitData;
62
+
63
+ #[wasm_bindgen(typescript_type = "ViewConfigUpdate")]
64
+ pub type JsViewConfig;
65
+
66
+ #[wasm_bindgen(typescript_type = "UpdateOptions")]
67
+ pub type JsUpdateOptions;
68
+
69
+ #[wasm_bindgen(typescript_type = "DeleteOptions")]
70
+ pub type JsDeleteOptions;
71
+ }
72
+
73
+ #[wasm_bindgen]
74
+ impl Table {
75
+ /// Returns the name of the index column for the table.
76
+ ///
77
+ /// # JavaScript Examples
78
+ ///
79
+ /// ```javascript
80
+ /// const table = await client.table("x,y\n1,2\n3,4", { index: "x" });
81
+ /// const index = table.get_index(); // "x"
82
+ /// ```
83
+ #[wasm_bindgen]
84
+ pub async fn get_index(&self) -> Option<String> {
85
+ self.0.get_index()
86
+ }
87
+
88
+ /// Get a copy of the [`Client`] this [`Table`] came from.
89
+ #[wasm_bindgen]
90
+ pub async fn get_client(&self) -> Client {
91
+ Client {
92
+ close: None,
93
+ client: self.0.get_client(),
94
+ }
95
+ }
96
+
97
+ /// Returns the user-specified name for this table, or the auto-generated
98
+ /// name if a name was not specified when the table was created.
99
+ #[wasm_bindgen]
100
+ pub async fn get_name(&self) -> String {
101
+ self.0.get_name().to_owned()
102
+ }
103
+
104
+ /// Returns the user-specified row limit for this table.
105
+ #[wasm_bindgen]
106
+ pub async fn get_limit(&self) -> Option<u32> {
107
+ self.0.get_limit()
108
+ }
109
+
110
+ /// Removes all the rows in the [`Table`], but preserves everything else
111
+ /// including the schema, index, and any callbacks or registered
112
+ /// [`View`] instances.
113
+ ///
114
+ /// Calling [`Table::clear`], like [`Table::update`] and [`Table::remove`],
115
+ /// will trigger an update event to any registered listeners via
116
+ /// [`View::on_update`].
117
+ #[wasm_bindgen]
118
+ pub async fn clear(&self) -> ApiResult<()> {
119
+ self.0.clear().await?;
120
+ Ok(())
121
+ }
122
+
123
+ /// Delete this [`Table`] and cleans up associated resources.
124
+ ///
125
+ /// [`Table`]s do not stop consuming resources or processing updates when
126
+ /// they are garbage collected in their host language - you must call
127
+ /// this method to reclaim these.
128
+ ///
129
+ /// # Arguments
130
+ ///
131
+ /// - `options` An options dictionary.
132
+ /// - `lazy` Whether to delete this [`Table`] _lazily_. When false (the
133
+ /// default), the delete will occur immediately, assuming it has no
134
+ /// [`View`] instances registered to it (which must be deleted first,
135
+ /// otherwise this method will throw an error). When true, the
136
+ /// [`Table`] will only be marked for deltion once its [`View`]
137
+ /// dependency count reaches 0.
138
+ ///
139
+ /// # JavaScript Examples
140
+ ///
141
+ /// ```javascript
142
+ /// const table = await client.table("x,y\n1,2\n3,4");
143
+ ///
144
+ /// // ...
145
+ ///
146
+ /// await table.delete({ lazy: true });
147
+ /// ```
148
+ #[wasm_bindgen]
149
+ pub async fn delete(self, options: Option<JsDeleteOptions>) -> ApiResult<()> {
150
+ let options = options
151
+ .into_serde_ext::<Option<DeleteOptions>>()?
152
+ .unwrap_or_default();
153
+
154
+ self.0.delete(options).await?;
155
+ Ok(())
156
+ }
157
+
158
+ /// Returns the number of rows in a [`Table`].
159
+ #[wasm_bindgen]
160
+ pub async fn size(&self) -> ApiResult<f64> {
161
+ Ok(self.0.size().await? as f64)
162
+ }
163
+
164
+ /// Returns a table's [`Schema`], a mapping of column names to column types.
165
+ ///
166
+ /// The mapping of a [`Table`]'s column names to data types is referred to
167
+ /// as a [`Schema`]. Each column has a unique name and a data type, one
168
+ /// of:
169
+ ///
170
+ /// - `"boolean"` - A boolean type
171
+ /// - `"date"` - A timesonze-agnostic date type (month/day/year)
172
+ /// - `"datetime"` - A millisecond-precision datetime type in the UTC
173
+ /// timezone
174
+ /// - `"float"` - A 64 bit float
175
+ /// - `"integer"` - A signed 32 bit integer (the integer type supported by
176
+ /// JavaScript)
177
+ /// - `"string"` - A [`String`] data type (encoded internally as a
178
+ /// _dictionary_)
179
+ ///
180
+ /// Note that all [`Table`] columns are _nullable_, regardless of the data
181
+ /// type.
182
+ #[wasm_bindgen]
183
+ pub async fn schema(&self) -> ApiResult<JsValue> {
184
+ let schema = self.0.schema().await?;
185
+ Ok(JsValue::from_serde_ext(&schema)?)
186
+ }
187
+
188
+ /// Returns the column names of this [`Table`] in "natural" order (the
189
+ /// ordering implied by the input format).
190
+ ///
191
+ /// # JavaScript Examples
192
+ ///
193
+ /// ```javascript
194
+ /// const columns = await table.columns();
195
+ /// ```
196
+ #[wasm_bindgen]
197
+ pub async fn columns(&self) -> ApiResult<JsValue> {
198
+ let columns = self.0.columns().await?;
199
+ Ok(JsValue::from_serde_ext(&columns)?)
200
+ }
201
+
202
+ /// Create a unique channel ID on this [`Table`], which allows
203
+ /// `View::on_update` callback calls to be associated with the
204
+ /// `Table::update` which caused them.
205
+ #[wasm_bindgen]
206
+ pub async fn make_port(&self) -> ApiResult<i32> {
207
+ Ok(self.0.make_port().await?)
208
+ }
209
+
210
+ /// Register a callback which is called exactly once, when this [`Table`] is
211
+ /// deleted with the [`Table::delete`] method.
212
+ ///
213
+ /// [`Table::on_delete`] resolves when the subscription message is sent, not
214
+ /// when the _delete_ event occurs.
215
+ #[wasm_bindgen]
216
+ pub fn on_delete(&self, on_delete: Function) -> ApiFuture<u32> {
217
+ let table = self.clone();
218
+ ApiFuture::new(async move {
219
+ let emit = LocalPollLoop::new(move |()| on_delete.call0(&JsValue::UNDEFINED));
220
+ let on_delete = Box::new(move || spawn_local(emit.poll(())));
221
+ Ok(table.0.on_delete(on_delete).await?)
222
+ })
223
+ }
224
+
225
+ /// Removes a listener with a given ID, as returned by a previous call to
226
+ /// [`Table::on_delete`].
227
+ #[wasm_bindgen]
228
+ pub fn remove_delete(&self, callback_id: u32) -> ApiFuture<()> {
229
+ let client = self.0.clone();
230
+ ApiFuture::new(async move {
231
+ client.remove_delete(callback_id).await?;
232
+ Ok(())
233
+ })
234
+ }
235
+
236
+ /// Removes rows from this [`Table`] with the `index` column values
237
+ /// supplied.
238
+ ///
239
+ /// # Arguments
240
+ ///
241
+ /// - `indices` - A list of `index` column values for rows that should be
242
+ /// removed.
243
+ ///
244
+ /// # JavaScript Examples
245
+ ///
246
+ /// ```javascript
247
+ /// await table.remove([1, 2, 3]);
248
+ /// ```
249
+ #[wasm_bindgen]
250
+ pub async fn remove(&self, value: &JsValue, options: Option<JsUpdateOptions>) -> ApiResult<()> {
251
+ let options = options
252
+ .into_serde_ext::<Option<UpdateOptions>>()?
253
+ .unwrap_or_default();
254
+
255
+ let input = UpdateData::from_js_value(value, options.format)?;
256
+ self.0.remove(input).await?;
257
+ Ok(())
258
+ }
259
+
260
+ /// Replace all rows in this [`Table`] with the input data, coerced to this
261
+ /// [`Table`]'s existing [`perspective_client::Schema`], notifying any
262
+ /// derived [`View`] and [`View::on_update`] callbacks.
263
+ ///
264
+ /// Calling [`Table::replace`] is an easy way to replace _all_ the data in a
265
+ /// [`Table`] without losing any derived [`View`] instances or
266
+ /// [`View::on_update`] callbacks. [`Table::replace`] does _not_ infer
267
+ /// data types like [`Client::table`] does, rather it _coerces_ input
268
+ /// data to the `Schema` like [`Table::update`]. If you need a [`Table`]
269
+ /// with a different `Schema`, you must create a new one.
270
+ ///
271
+ /// # JavaScript Examples
272
+ ///
273
+ /// ```javascript
274
+ /// await table.replace("x,y\n1,2");
275
+ /// ```
276
+ #[wasm_bindgen]
277
+ pub async fn replace(
278
+ &self,
279
+ input: &JsValue,
280
+ options: Option<JsUpdateOptions>,
281
+ ) -> ApiResult<()> {
282
+ let options = options
283
+ .into_serde_ext::<Option<UpdateOptions>>()?
284
+ .unwrap_or_default();
285
+
286
+ let input = UpdateData::from_js_value(input, options.format)?;
287
+ self.0.replace(input).await?;
288
+ Ok(())
289
+ }
290
+
291
+ /// Updates the rows of this table and any derived [`View`] instances.
292
+ ///
293
+ /// Calling [`Table::update`] will trigger the [`View::on_update`] callbacks
294
+ /// register to derived [`View`], and the call itself will not resolve until
295
+ /// _all_ derived [`View`]'s are notified.
296
+ ///
297
+ /// When updating a [`Table`] with an `index`, [`Table::update`] supports
298
+ /// partial updates, by omitting columns from the update data.
299
+ ///
300
+ /// # Arguments
301
+ ///
302
+ /// - `input` - The input data for this [`Table`]. The schema of a [`Table`]
303
+ /// is immutable after creation, so this method cannot be called with a
304
+ /// schema.
305
+ /// - `options` - Options for this update step - see [`UpdateOptions`].
306
+ ///
307
+ /// # JavaScript Examples
308
+ ///
309
+ /// ```javascript
310
+ /// await table.update("x,y\n1,2");
311
+ /// ```
312
+ #[wasm_bindgen]
313
+ pub fn update(
314
+ &self,
315
+ input: JsTableInitData,
316
+ options: Option<JsUpdateOptions>,
317
+ ) -> ApiFuture<()> {
318
+ let table = self.clone();
319
+ ApiFuture::new(async move {
320
+ let options = options
321
+ .into_serde_ext::<Option<UpdateOptions>>()?
322
+ .unwrap_or_default();
323
+
324
+ let input = UpdateData::from_js_value(&input, options.format)?;
325
+ Ok(table.0.update(input, options).await?)
326
+ })
327
+ }
328
+
329
+ /// Create a new [`View`] from this table with a specified
330
+ /// [`ViewConfigUpdate`].
331
+ ///
332
+ /// See [`View`] struct.
333
+ ///
334
+ /// # JavaScript Examples
335
+ ///
336
+ /// ```javascript
337
+ /// const view = await table.view({
338
+ /// columns: ["Sales"],
339
+ /// aggregates: { Sales: "sum" },
340
+ /// group_by: ["Region", "Country"],
341
+ /// filter: [["Category", "in", ["Furniture", "Technology"]]],
342
+ /// });
343
+ /// ```
344
+ #[wasm_bindgen]
345
+ pub async fn view(&self, config: Option<JsViewConfig>) -> ApiResult<View> {
346
+ let config = config
347
+ .map(|config| js_sys::JSON::stringify(&config))
348
+ .transpose()?
349
+ .and_then(|x| x.as_string())
350
+ .map(|x| serde_json::from_str(x.as_str()))
351
+ .transpose()?;
352
+
353
+ let view = self.0.view(config).await?;
354
+ Ok(View(view))
355
+ }
356
+
357
+ /// Validates the given expressions.
358
+ #[wasm_bindgen]
359
+ pub async fn validate_expressions(&self, exprs: &JsValue) -> ApiResult<JsValue> {
360
+ let exprs = JsValue::into_serde_ext::<Expressions>(exprs.clone())?;
361
+ let columns = self.0.validate_expressions(exprs).await?;
362
+ Ok(JsValue::from_serde_ext(&columns)?)
363
+ }
364
+ }
@@ -0,0 +1,159 @@
1
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2
+ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3
+ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4
+ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5
+ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6
+ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7
+ // ┃ Copyright (c) 2017, the Perspective Authors. ┃
8
+ // ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9
+ // ┃ This file is part of the Perspective library, distributed under the terms ┃
10
+ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
+
13
+ use extend::ext;
14
+ use js_sys::{Array, ArrayBuffer, Function, JSON, Object, Reflect, Uint8Array};
15
+ use perspective_client::config::ColumnType;
16
+ use perspective_client::{TableData, TableReadFormat, UpdateData};
17
+ use wasm_bindgen::convert::TryFromJsValue;
18
+ use wasm_bindgen::prelude::*;
19
+
20
+ use crate::apierror;
21
+ use crate::utils::{ApiError, ApiResult, JsValueSerdeExt, ToApiError};
22
+ pub use crate::view::*;
23
+
24
+ #[ext]
25
+ impl Vec<(String, ColumnType)> {
26
+ fn from_js_value(value: &JsValue) -> ApiResult<Vec<(String, ColumnType)>> {
27
+ Ok(Object::keys(value.unchecked_ref())
28
+ .iter()
29
+ .map(|x| -> Result<_, JsValue> {
30
+ let key = x.as_string().into_apierror()?;
31
+ let val = Reflect::get(value, &x)?
32
+ .as_string()
33
+ .into_apierror()?
34
+ .into_serde_ext()?;
35
+
36
+ Ok((key, val))
37
+ })
38
+ .collect::<Result<Vec<_>, _>>()?)
39
+ }
40
+ }
41
+
42
+ #[ext]
43
+ pub(crate) impl TableData {
44
+ fn from_js_value(value: &JsValue, format: Option<TableReadFormat>) -> ApiResult<TableData> {
45
+ if let Some(result) = UpdateData::from_js_value_partial(value, format)? {
46
+ Ok(result.into())
47
+ } else if value.is_instance_of::<Object>() && Reflect::has(value, &"__get_model".into())? {
48
+ let val = Reflect::get(value, &"__get_model".into())?
49
+ .dyn_into::<Function>()?
50
+ .call0(value)?;
51
+
52
+ let view = View::try_from_js_value(val)?;
53
+ Ok(TableData::View(view.0))
54
+ } else if value.is_instance_of::<Object>() {
55
+ let all_strings = || {
56
+ Object::values(value.unchecked_ref())
57
+ .to_vec()
58
+ .iter()
59
+ .all(|x| x.is_string())
60
+ };
61
+
62
+ let all_arrays = || {
63
+ Object::values(value.unchecked_ref())
64
+ .to_vec()
65
+ .iter()
66
+ .all(|x| x.is_instance_of::<Array>())
67
+ };
68
+
69
+ if all_strings() {
70
+ Ok(TableData::Schema(Vec::from_js_value(value)?))
71
+ } else if all_arrays() {
72
+ let json = JSON::stringify(value)?.as_string().into_apierror()?;
73
+ Ok(UpdateData::JsonColumns(json).into())
74
+ } else {
75
+ Err(apierror!(TableError(value.clone())))
76
+ }
77
+ } else {
78
+ Err(apierror!(TableError(value.clone())))
79
+ }
80
+ }
81
+ }
82
+
83
+ #[ext]
84
+ pub(crate) impl UpdateData {
85
+ fn from_js_value_partial(
86
+ value: &JsValue,
87
+ format: Option<TableReadFormat>,
88
+ ) -> ApiResult<Option<UpdateData>> {
89
+ if value.is_undefined() {
90
+ Err(apierror!(TableError(value.clone())))
91
+ } else if value.is_string() {
92
+ match format {
93
+ None | Some(TableReadFormat::Csv) => {
94
+ Ok(Some(UpdateData::Csv(value.as_string().into_apierror()?)))
95
+ },
96
+ Some(TableReadFormat::JsonString) => Ok(Some(UpdateData::JsonRows(
97
+ value.as_string().into_apierror()?,
98
+ ))),
99
+ Some(TableReadFormat::ColumnsString) => Ok(Some(UpdateData::JsonColumns(
100
+ value.as_string().into_apierror()?,
101
+ ))),
102
+ Some(TableReadFormat::Arrow) => Ok(Some(UpdateData::Arrow(
103
+ value.as_string().into_apierror()?.into_bytes().into(),
104
+ ))),
105
+ Some(TableReadFormat::Ndjson) => {
106
+ Ok(Some(UpdateData::Ndjson(value.as_string().into_apierror()?)))
107
+ },
108
+ }
109
+ } else if value.is_instance_of::<ArrayBuffer>() {
110
+ let uint8array = Uint8Array::new(value);
111
+ let slice = uint8array.to_vec();
112
+ match format {
113
+ Some(TableReadFormat::Csv) => Ok(Some(UpdateData::Csv(String::from_utf8(slice)?))),
114
+ Some(TableReadFormat::JsonString) => {
115
+ Ok(Some(UpdateData::JsonRows(String::from_utf8(slice)?)))
116
+ },
117
+ Some(TableReadFormat::ColumnsString) => {
118
+ Ok(Some(UpdateData::JsonColumns(String::from_utf8(slice)?)))
119
+ },
120
+ Some(TableReadFormat::Ndjson) => {
121
+ Ok(Some(UpdateData::Ndjson(String::from_utf8(slice)?)))
122
+ },
123
+ None | Some(TableReadFormat::Arrow) => Ok(Some(UpdateData::Arrow(slice.into()))),
124
+ }
125
+ } else if let Some(uint8array) = value.dyn_ref::<Uint8Array>() {
126
+ let slice = uint8array.to_vec();
127
+ match format {
128
+ Some(TableReadFormat::Csv) => Ok(Some(UpdateData::Csv(String::from_utf8(slice)?))),
129
+ Some(TableReadFormat::JsonString) => {
130
+ Ok(Some(UpdateData::JsonRows(String::from_utf8(slice)?)))
131
+ },
132
+ Some(TableReadFormat::ColumnsString) => {
133
+ Ok(Some(UpdateData::JsonColumns(String::from_utf8(slice)?)))
134
+ },
135
+ Some(TableReadFormat::Ndjson) => {
136
+ Ok(Some(UpdateData::Ndjson(String::from_utf8(slice)?)))
137
+ },
138
+ None | Some(TableReadFormat::Arrow) => Ok(Some(UpdateData::Arrow(slice.into()))),
139
+ }
140
+ } else if value.is_instance_of::<Array>() {
141
+ let rows = JSON::stringify(value)?.as_string().into_apierror()?;
142
+ Ok(Some(UpdateData::JsonRows(rows)))
143
+ } else {
144
+ Ok(None)
145
+ }
146
+ }
147
+
148
+ fn from_js_value(value: &JsValue, format: Option<TableReadFormat>) -> ApiResult<UpdateData> {
149
+ match TableData::from_js_value(value, format)? {
150
+ TableData::Schema(_) => Err(ApiError::new(
151
+ "Method cannot be called with `Schema` argument",
152
+ )),
153
+ TableData::Update(x) => Ok(x),
154
+ TableData::View(_) => Err(ApiError::new(
155
+ "Method cannot be called with `Schema` argument",
156
+ )),
157
+ }
158
+ }
159
+ }
@@ -0,0 +1,39 @@
1
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2
+ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3
+ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4
+ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5
+ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6
+ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7
+ // ┃ Copyright (c) 2017, the Perspective Authors. ┃
8
+ // ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9
+ // ┃ This file is part of the Perspective library, distributed under the terms ┃
10
+ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
+
13
+ /// Convenient short-name functions for common browser globals. These types are
14
+ /// not `send` and cannot be made static.
15
+ pub mod global {
16
+ pub fn window() -> web_sys::Window {
17
+ web_sys::window().unwrap()
18
+ }
19
+
20
+ pub fn document() -> web_sys::Document {
21
+ window().document().unwrap()
22
+ }
23
+
24
+ pub fn performance() -> web_sys::Performance {
25
+ window().performance().unwrap()
26
+ }
27
+
28
+ pub fn body() -> web_sys::HtmlElement {
29
+ document().body().unwrap()
30
+ }
31
+
32
+ pub fn navigator() -> web_sys::Navigator {
33
+ window().navigator()
34
+ }
35
+
36
+ pub fn clipboard() -> web_sys::Clipboard {
37
+ navigator().clipboard()
38
+ }
39
+ }