parse-stack-next 4.5.0 → 5.0.1
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.
- checksums.yaml +4 -4
- data/.bundle/config +2 -0
- data/.env.sample +17 -3
- data/.github/workflows/codeql.yml +44 -0
- data/.github/workflows/docs.yml +39 -0
- data/.github/workflows/release.yml +32 -0
- data/.github/workflows/ruby.yml +8 -6
- data/.gitignore +4 -0
- data/.vscode/settings.json +3 -0
- data/CHANGELOG.md +305 -72
- data/Gemfile.lock +10 -3
- data/LICENSE.txt +1 -1
- data/README.md +190 -219
- data/Rakefile +1 -1
- data/SECURITY.md +30 -0
- data/assets/parse-stack-next-avatar.png +0 -0
- data/assets/parse-stack-next-avatar.svg +37 -0
- data/assets/parse-stack-next-banner.png +0 -0
- data/assets/parse-stack-next-banner.svg +45 -0
- data/assets/parse-stack-next-social-preview.png +0 -0
- data/docs/atlas_vector_search_guide.md +511 -0
- data/docs/client_sdk_guide.md +1320 -0
- data/docs/mcp_guide.md +225 -104
- data/docs/mongodb_direct_guide.md +21 -4
- data/docs/usage_guide.md +585 -0
- data/examples/transaction_example.rb +28 -28
- data/lib/parse/acl_scope.rb +2 -2
- data/lib/parse/agent/mcp_rack_app.rb +184 -16
- data/lib/parse/agent/metadata_dsl.rb +16 -16
- data/lib/parse/agent/pipeline_validator.rb +28 -1
- data/lib/parse/agent/prompts.rb +5 -5
- data/lib/parse/agent/tools.rb +287 -14
- data/lib/parse/agent.rb +209 -12
- data/lib/parse/api/analytics.rb +27 -5
- data/lib/parse/api/files.rb +6 -2
- data/lib/parse/api/push.rb +21 -4
- data/lib/parse/api/server.rb +59 -0
- data/lib/parse/api/users.rb +26 -2
- data/lib/parse/atlas_search/index_manager.rb +84 -0
- data/lib/parse/atlas_search.rb +37 -9
- data/lib/parse/cache/pool.rb +88 -0
- data/lib/parse/cache/redis.rb +249 -0
- data/lib/parse/client/body_builder.rb +94 -0
- data/lib/parse/client/caching.rb +109 -9
- data/lib/parse/client/response.rb +27 -0
- data/lib/parse/client.rb +74 -3
- data/lib/parse/console.rb +203 -0
- data/lib/parse/embeddings/cohere.rb +484 -0
- data/lib/parse/embeddings/fixture.rb +130 -0
- data/lib/parse/embeddings/jina.rb +454 -0
- data/lib/parse/embeddings/local_http.rb +492 -0
- data/lib/parse/embeddings/openai.rb +520 -0
- data/lib/parse/embeddings/provider.rb +264 -0
- data/lib/parse/embeddings/qwen.rb +431 -0
- data/lib/parse/embeddings/voyage.rb +550 -0
- data/lib/parse/embeddings.rb +225 -0
- data/lib/parse/graphql/scalars.rb +53 -0
- data/lib/parse/graphql/type_generator.rb +264 -0
- data/lib/parse/graphql.rb +48 -0
- data/lib/parse/live_query/client.rb +24 -5
- data/lib/parse/live_query/subscription.rb +17 -6
- data/lib/parse/live_query.rb +9 -4
- data/lib/parse/model/associations/collection_proxy.rb +2 -2
- data/lib/parse/model/associations/has_many.rb +32 -1
- data/lib/parse/model/associations/has_one.rb +17 -0
- data/lib/parse/model/associations/pointer_collection_proxy.rb +3 -3
- data/lib/parse/model/classes/user.rb +307 -11
- data/lib/parse/model/clp.rb +1 -1
- data/lib/parse/model/core/create_lock.rb +14 -2
- data/lib/parse/model/core/embed_managed.rb +296 -0
- data/lib/parse/model/core/fetching.rb +4 -4
- data/lib/parse/model/core/indexing.rb +53 -14
- data/lib/parse/model/core/parse_reference.rb +3 -3
- data/lib/parse/model/core/properties.rb +70 -1
- data/lib/parse/model/core/querying.rb +57 -1
- data/lib/parse/model/core/vector_searchable.rb +285 -0
- data/lib/parse/model/file.rb +16 -4
- data/lib/parse/model/model.rb +26 -10
- data/lib/parse/model/object.rb +63 -6
- data/lib/parse/model/pointer.rb +16 -2
- data/lib/parse/model/shortnames.rb +2 -0
- data/lib/parse/model/validations/uniqueness_validator.rb +3 -3
- data/lib/parse/model/vector.rb +102 -0
- data/lib/parse/mongodb.rb +90 -8
- data/lib/parse/pipeline_security.rb +59 -2
- data/lib/parse/query/constraints.rb +16 -14
- data/lib/parse/query/ordering.rb +1 -1
- data/lib/parse/query.rb +137 -64
- data/lib/parse/stack/generators/templates/model.erb +2 -2
- data/lib/parse/stack/generators/templates/model_installation.rb +1 -1
- data/lib/parse/stack/generators/templates/model_role.rb +1 -1
- data/lib/parse/stack/generators/templates/model_session.rb +1 -1
- data/lib/parse/stack/generators/templates/parse.rb +1 -1
- data/lib/parse/stack/generators/templates/webhooks.rb +1 -1
- data/lib/parse/stack/version.rb +1 -1
- data/lib/parse/stack.rb +375 -73
- data/lib/parse/two_factor_auth/user_extension.rb +5 -2
- data/lib/parse/vector_search.rb +341 -0
- data/parse-stack-next.gemspec +10 -9
- data/scripts/docker/docker-compose.test.yml +18 -0
- data/scripts/start-parse.sh +6 -0
- data/scripts/vector_prototype/create_vector_index.js +105 -0
- data/scripts/vector_prototype/fetch_embeddings.py +241 -0
- data/scripts/vector_prototype/fixture_manifest.json +9 -0
- data/scripts/vector_prototype/query_prototype.rb +84 -0
- data/scripts/vector_prototype/run.sh +34 -0
- metadata +77 -5
- data/parse-stack.png +0 -0
data/README.md
CHANGED
|
@@ -1,48 +1,60 @@
|
|
|
1
|
-
|
|
1
|
+

|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# Parse Stack Next
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
A full-featured Ruby client SDK for [Parse Server](http://parseplatform.org/). [parse-stack-next](https://github.com/neurosynq/parse-stack-next) is a Ruby client SDK, REST client, and Active Model ORM for [Parse Server](http://parseplatform.org/), combining a low-level API client, a query engine, an object-relational mapper (ORM), and a Cloud Code Webhooks rack application in a single gem.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
### What's new in 5.0
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
- **RAG foundation** — `:vector` property type, `Parse::Embeddings` provider registry shipping built-in adapters for OpenAI, Cohere (v3 + v4.0 Matryoshka text-mode), Voyage (incl. open-weight `voyage-4-nano` and `voyage-multimodal-3` text-mode), Jina v3/v4/v5/code, Qwen 3 (DashScope), and a generic `LocalHTTP` client for Ollama / LM Studio / vLLM / TEI. `Klass.find_similar(vector:/text:, k:)` over Atlas `$vectorSearch`, and an `embed` class macro that digest-tracks source fields so vectors only recompute when content changes
|
|
10
|
+
- **`Parse::Cache::Redis`** — Moneta-compatible Redis cache wrapper with a built-in `ConnectionPool`, optional `cache_namespace:` for multi-tenant Redis sharing, and graceful degrade on pool saturation
|
|
11
|
+
- **`ActiveSupport::Notifications` instrumentation** — `parse.cache.*`, `parse.mongodb.aggregate`, `parse.mongodb.find`, and `parse.embeddings.embed` events with stable, PII-safe payload schemas; in-core slow-query log via `Parse.slow_query_threshold_ms`
|
|
12
|
+
- **MCP transport hardening** — Streamable HTTP `Mcp-Session-Id` header (renamed from `X-MCP-Session-Id`, **breaking**), `MCP-Protocol-Version` validation, `DELETE /` session termination, structured-content (`outputSchema`) on built-in tools, optional `health_path:` liveness probe
|
|
13
|
+
- **`Parse::GraphQL::TypeGenerator`** — generate `graphql-ruby` types directly from your `Parse::Object` subclasses (no Parse Server round-trip), with `:vector` columns surfaced as `[Float]` and association registries (`has_one_associations`, `has_many_associations`) populated at DSL time
|
|
14
|
+
- **LiveQuery promoted to stable** — the experimental warning is removed; `Parse.live_query_enabled = true` is retained as a network-egress safety toggle, not a stability gate
|
|
15
|
+
- **Server-version deprecation warning** — one-shot warning when connecting to Parse Server below the supported floor (currently 7.0.0); silence with `Parse.suppress_server_version_warning = true`
|
|
16
|
+
- **`mongo_relation_index :field, dedup: true`** — register a compound `{owningId, relatedId}` UNIQUE on relation join collections to prevent duplicate-pair subscriptions without breaking `has_many` semantics
|
|
17
|
+
|
|
18
|
+
See [CHANGELOG.md](./CHANGELOG.md) for the full 5.0 entry, including security-hardening notes and Ruby 3.x cleanup.
|
|
19
|
+
|
|
20
|
+
### Core capabilities
|
|
10
21
|
|
|
11
|
-
**This is an extended and enhanced fork with additional features including:**
|
|
12
22
|
- MongoDB Aggregation Framework support
|
|
13
|
-
- **MongoDB Atlas Search**
|
|
14
|
-
- **Direct MongoDB Queries**
|
|
15
|
-
- **Schema Introspection & Migration**
|
|
16
|
-
- **Enhanced Role Management**
|
|
17
|
-
- **Read Preference Support**
|
|
18
|
-
- **Class-Level Permissions (CLP)**
|
|
19
|
-
- Advanced ACL query constraints (readable_by
|
|
20
|
-
- **Owner-aware default ACL policy** (`acl_policy :owner_else_private`) —
|
|
23
|
+
- **MongoDB Atlas Search** — full-text search, autocomplete, faceted search with direct MongoDB access
|
|
24
|
+
- **Direct MongoDB Queries** — bypass Parse Server's REST surface for high-performance reads, with SDK-side ACL/CLP/`protectedFields` enforcement for scoped agents
|
|
25
|
+
- **Schema Introspection & Migration** — compare local models with server schema and generate migrations
|
|
26
|
+
- **Enhanced Role Management** — helper methods for role hierarchies, user membership, and subscription queries
|
|
27
|
+
- **Read Preference Support** — route reads to MongoDB secondary replicas
|
|
28
|
+
- **Class-Level Permissions (CLP)** — define and filter protected fields based on roles and user ownership
|
|
29
|
+
- Advanced ACL query constraints (`readable_by`, `writable_by`)
|
|
30
|
+
- **Owner-aware default ACL policy** (`acl_policy :owner_else_private`) — per-class defaults granting read/write only to the record's owner, with a secure or public fallback for server-context creates
|
|
21
31
|
- Full transaction support with automatic retry
|
|
22
32
|
- Comprehensive integration testing with Docker
|
|
23
33
|
- Enhanced change tracking and webhooks
|
|
24
|
-
- Request idempotency
|
|
34
|
+
- Request idempotency with `Retry-After` header support
|
|
25
35
|
- Timezone support for date operations
|
|
26
36
|
- Partial fetch with smart autofetch and serialization control
|
|
27
37
|
- Multi-Factor Authentication (MFA/2FA) support
|
|
28
|
-
- LiveQuery real-time subscriptions with TLS/SSL, circuit breaker, health monitoring
|
|
29
|
-
- AI/LLM
|
|
30
|
-
- And many more improvements (see [CHANGELOG.md](./CHANGELOG.md))
|
|
38
|
+
- LiveQuery real-time subscriptions with TLS/SSL, circuit breaker, and health monitoring
|
|
39
|
+
- AI/LLM agent integration (MCP-spec compliant) with security hardening — rate limiting, injection protection, agent ACL scopes
|
|
31
40
|
|
|
32
|
-
Below is a [quick start guide](#overview). See also the [Usage Guide](./
|
|
41
|
+
Below is a [quick start guide](#overview). See also the [Usage Guide](./docs/usage_guide.md) for practical examples covering queries, aggregation, ACLs, and more.
|
|
33
42
|
|
|
34
|
-
> **Note:**
|
|
43
|
+
> **Note:** API reference docs are published at [neurosynq.github.io/parse-stack-next](https://neurosynq.github.io/parse-stack-next/index.html). Generated via YARD from the current source; covers the full 5.x surface.
|
|
35
44
|
|
|
36
45
|
### Credits
|
|
37
46
|
|
|
38
|
-
This project is
|
|
47
|
+
This project (`parse-stack-next`) is a continuation of the [Parse Stack framework](https://github.com/modernistik/parse-stack) originally created by [Modernistik](https://www.modernistik.com). We are grateful for their foundational work and continue to build upon it under the [neurosynq](https://github.com/neurosynq) organization.
|
|
39
48
|
|
|
40
49
|
### Code Status
|
|
41
|
-
[](https://
|
|
42
|
-
[](https://rubygems.org/gems/parse-stack)
|
|
50
|
+
[](https://rubygems.org/gems/parse-stack-next)
|
|
51
|
+
[](https://rubygems.org/gems/parse-stack-next)
|
|
43
52
|
[](https://github.com/neurosynq/parse-stack-next/releases)
|
|
44
53
|
|
|
45
54
|
#### Tutorial Videos
|
|
55
|
+
|
|
56
|
+
The following videos were recorded for the original parse-stack gem. The model, query, and association surface they cover is unchanged in parse-stack-next, so they remain a useful introduction; see the [Usage Guide](./docs/usage_guide.md) for v5.x-specific features (vector search, Redis cache, agent tools).
|
|
57
|
+
|
|
46
58
|
1. Getting Started: https://youtu.be/zoYSGmciDlQ
|
|
47
59
|
2. Custom Classes and Relations: https://youtu.be/tfSesotfU7w
|
|
48
60
|
3. Working with Existing Schemas: https://youtu.be/EJGPT7YWyXA
|
|
@@ -55,22 +67,21 @@ Add this line to your application's `Gemfile`:
|
|
|
55
67
|
```ruby
|
|
56
68
|
gem 'parse-stack-next'
|
|
57
69
|
```
|
|
58
|
-
|
|
59
|
-
> The gem is published as `parse-stack-next` on RubyGems. Older references in this README to `parse-stack` point to the same codebase — use `parse-stack-next` in new projects.
|
|
60
|
-
|
|
61
70
|
And then execute:
|
|
62
71
|
```bash
|
|
63
72
|
$ bundle
|
|
64
73
|
```
|
|
65
74
|
Or install it yourself as:
|
|
66
75
|
```bash
|
|
67
|
-
$ gem install parse-stack
|
|
76
|
+
$ gem install parse-stack-next
|
|
68
77
|
```
|
|
78
|
+
|
|
79
|
+
> **Note:** The Ruby require path and module namespace are unchanged. You still `require 'parse/stack'` and reference classes under `Parse::Object`, `Parse::Query`, etc. Only the gem name on RubyGems has changed.
|
|
69
80
|
### Rack / Sinatra
|
|
70
81
|
Parse-Stack API, models and webhooks easily integrate in your existing Rack/Sinatra based applications.
|
|
71
82
|
|
|
72
83
|
### Rails
|
|
73
|
-
Parse-Stack comes with support for Rails by adding additional rake tasks and generators. After adding `parse-stack` as a gem dependency in your Gemfile and running `bundle`, you should run the install script:
|
|
84
|
+
Parse-Stack comes with support for Rails by adding additional rake tasks and generators. After adding `parse-stack-next` as a gem dependency in your Gemfile and running `bundle`, you should run the install script:
|
|
74
85
|
```bash
|
|
75
86
|
$ rails g parse_stack:install
|
|
76
87
|
```
|
|
@@ -162,148 +173,53 @@ result = Parse.call_function :myFunctionName, {param: value}
|
|
|
162
173
|
|
|
163
174
|
```
|
|
164
175
|
|
|
165
|
-
##
|
|
166
|
-
|
|
167
|
-
**Current version: 4.4.3** | **Ruby 3.2+ required**
|
|
168
|
-
|
|
169
|
-
### 4.4.3 - Pointer-Shape Strictness & Pipeline Forward-Pass
|
|
170
|
-
|
|
171
|
-
- **`Parse.strict_pointer_shapes`** — opt-in flag (or `PARSE_STRICT_POINTER_SHAPES=true`)
|
|
172
|
-
that converts unresolvable pointer-shape constraints from a one-shot
|
|
173
|
-
warning + silent-zero result into a `Parse::Query::PointerShapeError`
|
|
174
|
-
raise. Recommended for test/CI and any LLM-driven workload where
|
|
175
|
-
"0 results" reads as a real answer instead of a shape mismatch.
|
|
176
|
-
- **Pipeline forward-pass field tracking** — the agent's pipeline
|
|
177
|
-
access-policy walker now tracks fields introduced by upstream stages
|
|
178
|
-
(`$group._id` and accumulator keys, `$addFields`/`$set` outputs,
|
|
179
|
-
`$lookup.as`). The canonical "group → match → sort → limit" pattern
|
|
180
|
-
on synthetic accumulator outputs no longer hits `:field_denied`
|
|
181
|
-
against the source class's `agent_fields` allowlist.
|
|
182
|
-
- **Pointer `query_hint:` in `get_schema`** — every Pointer field
|
|
183
|
-
surfaces an inline shape hint covering equality (objectId string OR
|
|
184
|
-
full `{__type: "Pointer", ...}` hash) and `$in/$nin` (bare-id array
|
|
185
|
-
with SDK-side normalization). Hidden targets collapse to a
|
|
186
|
-
`<targetClass>` placeholder.
|
|
187
|
-
|
|
188
|
-
### 4.4.2 - Schema-Aware Walker, Pipeline-Local Aliases
|
|
189
|
-
|
|
190
|
-
- **Schema-aware expression-value rewriter** — the `$author` → `$_p_author`
|
|
191
|
-
pretty-name rewrite inside expression values now consults the queried
|
|
192
|
-
class's declared properties. References whose name is neither a Parse
|
|
193
|
-
property nor a universal built-in pass through verbatim, so aliases
|
|
194
|
-
introduced by an upstream `$project`/`$addFields`/`$set`/`$group`
|
|
195
|
-
stage survive into downstream stages exactly as written. Result rows
|
|
196
|
-
are keyed by the literal alias the caller used.
|
|
197
|
-
- **`first_or_create!` accepts query-option keys in `synchronize:`** —
|
|
198
|
-
filter-lock fingerprint includes constraint operators
|
|
199
|
-
(`Parse::Operation` keys) so locks scoped on inequality/range
|
|
200
|
-
constraints no longer collide across distinct callers.
|
|
201
|
-
|
|
202
|
-
### 4.4.1 - Filter-Lock Compatibility
|
|
203
|
-
|
|
204
|
-
- **`create_lock` accepts `Parse::Operation` keys** — the synchronize
|
|
205
|
-
lock-key derivation handles operator-shaped query attributes
|
|
206
|
-
(`:status.gt => Date.today`) the same way `Parse::Query` does,
|
|
207
|
-
matching the filter-lock fingerprint against the resolved storage
|
|
208
|
-
fields.
|
|
209
|
-
|
|
210
|
-
### 4.4 - MongoDB Index Management, CLP on Mongo-Direct, Agent ACL Scope
|
|
211
|
-
|
|
212
|
-
- **`Model.describe`** — operator-facing introspection aggregator on every
|
|
213
|
-
`Parse::Object` subclass. Reports local model declarations, server
|
|
214
|
-
schema, CLP, default ACLs, Atlas Search indexes, and MongoDB indexes.
|
|
215
|
-
Local-only by default; opt into server fetches with `network: true`.
|
|
216
|
-
See [docs/mongodb_direct_guide.md](docs/mongodb_direct_guide.md).
|
|
217
|
-
- **`mongo_index` DSL** — model-declarative MongoDB indexes:
|
|
218
|
-
`mongo_index :title, :year`, `mongo_index :vin, unique: true`,
|
|
219
|
-
`mongo_geo_index :location`, `mongo_relation_index :users, bidirectional: true`.
|
|
220
|
-
Validation (pointer auto-rewrite, parallel-array rejection, `_id` guard,
|
|
221
|
-
64-cap, idempotency) runs at class load.
|
|
222
|
-
- **`parse_reference` auto-indexing** — every `parse_reference` field
|
|
223
|
-
auto-registers a `unique: true, sparse: true` index declaration.
|
|
224
|
-
Removes the operator-must-remember dedup floor. Opt out per-field
|
|
225
|
-
with `index: false` or `unique_index: false`.
|
|
226
|
-
- **`Parse::MongoDB.configure_writer`** — separate write-capable
|
|
227
|
-
connection for index management (`create_index` / `drop_index` /
|
|
228
|
-
`writer_indexes`). The reader URI stays read-only. Triple-gated:
|
|
229
|
-
writer configured + `index_mutations_enabled = true` +
|
|
230
|
-
`ENV["PARSE_MONGO_INDEX_MUTATIONS"]=1` — all re-checked per call.
|
|
231
|
-
- **`Parse::Schema::IndexMigrator`** — reconciles declared indexes
|
|
232
|
-
against the actual MongoDB state. Plan returns per-collection diff;
|
|
233
|
-
apply is additive by default (`drop: true` for orphan removal).
|
|
234
|
-
- **`rake parse:mongo:indexes:plan` / `:apply`** — operator workflow
|
|
235
|
-
for index migrations. Plan is read-only; apply requires the triple-gate.
|
|
236
|
-
- **CLP and `protectedFields` enforced on mongo-direct** — `Parse::CLPScope`
|
|
237
|
-
gates `Parse::MongoDB.aggregate` at the operation level for scoped
|
|
238
|
-
agents (`session_token:` / `acl_user:` / `acl_role:`) AND strips
|
|
239
|
-
`protectedFields` from result rows. The mongo-direct path is the
|
|
240
|
-
only first-class enforcement surface for ACL + CLP + protectedFields
|
|
241
|
-
on scoped reads (Parse Server's REST aggregate enforces NEITHER).
|
|
242
|
-
- **`Parse::Agent.new(acl_user:|acl_role:)`** — declared identity
|
|
243
|
-
scope without a session token. Built-in tools auto-promote to
|
|
244
|
-
mongo-direct so SDK-side enforcement runs. Sub-agent identity must
|
|
245
|
-
be a subset of the parent's reach.
|
|
246
|
-
- See [docs/mongodb_index_optimization_guide.md](docs/mongodb_index_optimization_guide.md)
|
|
247
|
-
for when to use each index type and how to budget the 64-per-collection cap.
|
|
248
|
-
|
|
249
|
-
### 4.0 - Security Hardening and Modernization
|
|
250
|
-
- Minimum Ruby version bumped to 3.2 (Ruby 3.1 reached EOL March 2025)
|
|
251
|
-
- Minimum Rails/ActiveSupport bumped to 6.1 (was unbounded at 5.x)
|
|
252
|
-
- CI tests against Ruby 3.2, 3.3, 3.4, and 3.5
|
|
253
|
-
- LiveQuery TLS hostname verification (`post_connection_check`)
|
|
254
|
-
- Webhook endpoint fails closed when no key is configured (opt out via `Parse::Webhooks.allow_unauthenticated = true`)
|
|
255
|
-
- `Parse::Error.new(code, message)` two-argument constructor with `#code` reader
|
|
256
|
-
- `include`d pointer fields now auto-added to `keys` when a key allowlist is set
|
|
257
|
-
|
|
258
|
-
### 3.3 - Ruby Version Update
|
|
259
|
-
- Minimum Ruby version bumped to 3.1 (Ruby 3.0 reached EOL March 2024)
|
|
260
|
-
- CI tests against Ruby 3.1, 3.2, 3.3, and 3.4
|
|
261
|
-
|
|
262
|
-
### 3.2 - Class-Level Permissions (CLP)
|
|
263
|
-
Define operation permissions and protected fields directly in models:
|
|
176
|
+
## Release History
|
|
264
177
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
end
|
|
271
|
-
```
|
|
178
|
+
**Current version: 5.0.1** | **Ruby 3.2+ required**
|
|
179
|
+
|
|
180
|
+
The 5.0 highlights (vector search / RAG, pooled Redis cache, AS::N instrumentation, MCP transport hardening, GraphQL type generation) are summarized in the [What's new in 5.0](#whats-new-in-50) section above. Earlier releases are recorded below.
|
|
181
|
+
|
|
182
|
+
Per-version detail lives in [CHANGELOG.md](./CHANGELOG.md) and on the [Releases page](https://github.com/neurosynq/parse-stack-next/releases). The compact summary below is the major-line view.
|
|
272
183
|
|
|
273
|
-
###
|
|
274
|
-
- **MongoDB Atlas Search** - Full-text search, autocomplete, faceted search
|
|
275
|
-
- **Direct MongoDB Queries** - `results_direct`, `first_direct` bypassing Parse Server
|
|
276
|
-
- **Schema Introspection** - `Parse::Schema.diff`, `Parse::Schema.migration`
|
|
277
|
-
- **Read Preference** - `read_pref(:secondary)` for replica set reads
|
|
278
|
-
- **Role Management** - `find_or_create`, `add_users`, `add_child_role`, `all_users`
|
|
184
|
+
### 4.x — MongoDB index management, agent ACL scope, CLP enforced on mongo-direct, and `parse-stack-next` debut
|
|
279
185
|
|
|
280
|
-
|
|
281
|
-
- **
|
|
282
|
-
-
|
|
283
|
-
- **
|
|
284
|
-
-
|
|
285
|
-
- **
|
|
286
|
-
-
|
|
186
|
+
- **`mongo_index` DSL** (`mongo_index`, `mongo_geo_index`, `mongo_relation_index`) with class-load validation (pointer auto-rewrite, parallel-array rejection, `_id` guard, 64-per-collection cap). `parse_reference` fields auto-register a unique-sparse index.
|
|
187
|
+
- **Index migration tooling** — `Parse::MongoDB.configure_writer` (separate write connection, triple-gated), `Parse::Schema::IndexMigrator` (plan / apply with optional orphan drop), and `rake parse:mongo:indexes:plan` / `:apply`.
|
|
188
|
+
- **`Model.describe`** — operator introspection aggregator (local declarations + optional server fetch covering schema, CLP, default ACLs, Atlas Search, MongoDB indexes).
|
|
189
|
+
- **CLP + `protectedFields` enforced on mongo-direct** — `Parse::CLPScope` gates `Parse::MongoDB.aggregate` for scoped agents (`session_token:` / `acl_user:` / `acl_role:`) and strips protected fields from result rows. This is the only first-class enforcement surface for ACL + CLP + protectedFields on scoped reads; Parse Server's REST aggregate enforces neither.
|
|
190
|
+
- **`Parse::Agent.new(acl_user:|acl_role:)`** — declared agent identity without a session token; built-in tools auto-promote to mongo-direct. Sub-agent identity must be a subset of the parent's reach.
|
|
191
|
+
- **Pipeline correctness** — schema-aware `$author` → `$_p_author` rewriter respects pipeline-local aliases; forward-pass field tracking through `$group`/`$addFields`/`$set`/`$lookup.as`; pointer `query_hint:` surfaced in `get_schema`.
|
|
192
|
+
- **`Parse.strict_pointer_shapes`** — opt-in flag that converts unresolvable pointer-shape constraints into a `PointerShapeError` raise (recommended for test/CI and LLM-driven workloads).
|
|
193
|
+
- **`first_or_create!` / `create_lock` accept `Parse::Operation` keys** in `synchronize:`, fixing filter-lock fingerprint collisions on inequality/range constraints.
|
|
194
|
+
- **Security & modernization** — Ruby 3.2 floor, Rails/ActiveSupport 6.1 floor, CI on Ruby 3.2–3.5. LiveQuery TLS hostname verification. Webhook endpoint fails closed when no key is configured. `Parse::Error.new(code, message)` two-argument constructor. `include`d pointer fields auto-added to `keys` when an allowlist is set.
|
|
195
|
+
- **4.5.0 — first release of this gem.** `parse-stack-next` debuts on RubyGems under the [neurosynq](https://github.com/neurosynq) organization, continuing from the upstream `parse-stack` 4.4.x line. The Ruby require path (`require 'parse/stack'`) and the `Parse::*` namespace are unchanged from upstream — only the gem name on RubyGems is new.
|
|
287
196
|
|
|
288
|
-
|
|
197
|
+
### 3.x — Atlas Search, MongoDB-direct, CLP, AI agent, push, MFA, LiveQuery
|
|
289
198
|
|
|
290
|
-
**
|
|
199
|
+
- **MongoDB Atlas Search** — full-text search, autocomplete, faceted search.
|
|
200
|
+
- **Direct MongoDB queries** — `results_direct`, `first_direct`, `count_direct` bypassing Parse Server's REST surface.
|
|
201
|
+
- **Schema tools** — `Parse::Schema.diff`, `Parse::Schema.migration`, plus `read_pref(:secondary)` for replica reads.
|
|
202
|
+
- **Role management** — `find_or_create`, `add_users`, `add_child_role`, `all_users` (with hierarchy walks).
|
|
203
|
+
- **Class-Level Permissions (CLP)** declared in models — `set_clp :find, public: true`, `protect_fields "*", [:internal_notes]`.
|
|
204
|
+
- **AI/LLM agent** — `Parse::Agent` with natural-language queries over a tool interface.
|
|
205
|
+
- **Push builder API** — `to_channel`, `with_alert`, `silent!`, `send!`; installation channels (`subscribe`, `unsubscribe`).
|
|
206
|
+
- **Session lifecycle** — `expired?`, `time_remaining`, `logout_all!`.
|
|
207
|
+
- **MFA** — TOTP and SMS two-factor authentication.
|
|
208
|
+
- **LiveQuery** — real-time WebSocket subscriptions (promoted to stable in 5.0).
|
|
209
|
+
- **Ruby 3.1 floor** (3.0 reached EOL March 2024).
|
|
291
210
|
|
|
292
|
-
###
|
|
293
|
-
- **Transactions** - `Parse::Object.transaction` with automatic retry
|
|
294
|
-
- **MongoDB Aggregation** - `group_by`, `count_distinct`, custom pipelines
|
|
295
|
-
- **ACL Query Constraints** - `readable_by`, `writable_by`, `publicly_readable`
|
|
296
|
-
- **Request Idempotency** - Automatic duplicate prevention (enabled by default)
|
|
297
|
-
- **Enhanced Change Tracking** - Works correctly in `after_save` hooks
|
|
298
|
-
- **LiveQuery** (Experimental) - Real-time subscriptions with circuit breaker
|
|
211
|
+
### 2.x — aggregation, transactions, idempotency, ACL constraints
|
|
299
212
|
|
|
300
|
-
|
|
301
|
-
-
|
|
302
|
-
-
|
|
303
|
-
-
|
|
304
|
-
-
|
|
213
|
+
- **Transactions** — `Parse::Object.transaction` with automatic retry.
|
|
214
|
+
- **MongoDB aggregation** — `group_by`, `count_distinct`, custom pipelines.
|
|
215
|
+
- **ACL query constraints** — `readable_by`, `writable_by`, `publicly_readable`.
|
|
216
|
+
- **Request idempotency** — automatic duplicate prevention, enabled by default.
|
|
217
|
+
- **Change tracking** — works correctly in `after_save` hooks.
|
|
218
|
+
- **Breaking from 1.x** — Ruby 3.0 floor, Faraday 2.x (no `faraday_middleware`), `distinct` returns object IDs by default (pass `return_pointers: true` for pointers), `constaint` → `constraint` typo fix.
|
|
305
219
|
|
|
306
|
-
|
|
220
|
+
### 1.x — initial Parse Server SDK
|
|
221
|
+
|
|
222
|
+
The 1.x line is the original [`modernistik/parse-stack`](https://github.com/modernistik/parse-stack) — Active Model ORM, REST client, query DSL, associations, and Cloud Code webhooks for Parse Server. `parse-stack-next` is a continuation of that work; the first release published under the new gem name is **4.5.0** (above), on RubyGems as [`parse-stack-next`](https://rubygems.org/gems/parse-stack-next).
|
|
307
223
|
|
|
308
224
|
## Table of Contents
|
|
309
225
|
|
|
@@ -448,6 +364,7 @@ For complete details, see the [CHANGELOG](./CHANGELOG.md) and [Releases](https:/
|
|
|
448
364
|
- [Saved Audiences](#saved-audiences)
|
|
449
365
|
- [Push Status Tracking](#push-status-tracking)
|
|
450
366
|
- [Installation Channel Management](#installation-channel-management)
|
|
367
|
+
- [Analytics](#analytics)
|
|
451
368
|
- [Cloud Code Webhooks](#cloud-code-webhooks)
|
|
452
369
|
- [Cloud Code Functions](#cloud-code-functions)
|
|
453
370
|
- [Cloud Code Triggers](#cloud-code-triggers)
|
|
@@ -476,16 +393,16 @@ For complete details, see the [CHANGELOG](./CHANGELOG.md) and [Releases](https:/
|
|
|
476
393
|
## Architecture
|
|
477
394
|
The architecture of `Parse::Stack` is broken into four main components.
|
|
478
395
|
|
|
479
|
-
### [Parse::Client](https://
|
|
396
|
+
### [Parse::Client](https://neurosynq.github.io/parse-stack-next/Parse/Client.html)
|
|
480
397
|
This class is the core and low level API for the Parse Server REST interface that is used by the other components. It can manage multiple sessions, which means you can have multiple client instances pointing to different Parse Server applications at the same time. It handles sending raw requests as well as providing Request/Response objects for all API handlers. The connection engine is Faraday, which means it is open to add any additional middleware for features you'd like to implement.
|
|
481
398
|
|
|
482
|
-
### [Parse::Query](https://
|
|
399
|
+
### [Parse::Query](https://neurosynq.github.io/parse-stack-next/Parse/Query.html)
|
|
483
400
|
This class implements the [Parse REST Querying](http://docs.parseplatform.org/rest/guide/#queries) interface in the [DataMapper finder syntax style](http://datamapper.org/docs/find.html). It compiles a set of query constraints and utilizes `Parse::Client` to send the request and provide the raw results. This class can be used without the need to define models.
|
|
484
401
|
|
|
485
|
-
### [Parse::Object](https://
|
|
402
|
+
### [Parse::Object](https://neurosynq.github.io/parse-stack-next/Parse/Object.html)
|
|
486
403
|
This component is main class for all object relational mapping subclasses for your application. It provides features in order to map your remote Parse records to a local ruby object. It implements the Active::Model interface to provide a lot of additional features, CRUD operations, querying, including dirty tracking, JSON serialization, save/destroy callbacks and others. While we are overlooking some functionality, for simplicity, you will mainly be working with Parse::Object as your superclass. While not required, it is highly recommended that you define a model (Parse::Object subclass) for all the Parse classes in your application.
|
|
487
404
|
|
|
488
|
-
### [Parse::Webhooks](https://
|
|
405
|
+
### [Parse::Webhooks](https://neurosynq.github.io/parse-stack-next/Parse/Webhooks.html)
|
|
489
406
|
Parse provides a feature called [Cloud Code Webhooks](http://blog.parse.com/announcements/introducing-cloud-code-webhooks/). For most applications, save/delete triggers and cloud functions tend to be implemented by Parse's own hosted Javascript solution called Cloud Code. However, Parse provides the ability to have these hooks utilize your hosted solution instead of their own, since their environment is limited in terms of resources and tools.
|
|
490
407
|
|
|
491
408
|
## Field Naming Conventions
|
|
@@ -647,6 +564,10 @@ As a shortcut, if you are planning on using REDIS and have configured the use of
|
|
|
647
564
|
Parse.setup(cache: 'redis://localhost:6379', ...)
|
|
648
565
|
```
|
|
649
566
|
|
|
567
|
+
Redis is the recommended cache backend for multi-process / multi-dyno deployments: an in-memory `Moneta.new(:Memory)` store is local to a single Ruby process, so two Puma workers (or two web dynos) each hold their own cache and a write through one will not invalidate the other. A shared Redis backend gives every process the same view, and the existing PUT/POST/DELETE invalidation in `Parse::Middleware::Caching` runs against the shared store. Cache reads degrade gracefully on `Redis::CannotConnectError` / `Redis::TimeoutError` — the middleware disables caching for the failing request and lets the underlying GET pass through to Parse Server.
|
|
568
|
+
|
|
569
|
+
The cache surface is opt-in at two layers. Object fetches (`Model.find(id)`, `obj.reload!` in non-write-only mode) cache by default once a store is configured. Query results do **not** cache by default — pass `cache: true` per call (e.g. `Song.all(limit: 500, cache: true)`) or set `Parse.default_query_cache = true` for opt-out behavior. Both layers honor `cache: false` / `Cache-Control: no-cache` to skip the cache for an individual request.
|
|
570
|
+
|
|
650
571
|
#### `:expires`
|
|
651
572
|
Sets the default cache expiration time (in seconds) for successful non-empty `GET` requests when using the caching middleware. The default value is 3 seconds. If `:expires` is set to 0, caching will be disabled. You can always clear the current state of the cache using the `clear_cache!` method on your `Parse::Client` instance.
|
|
652
573
|
|
|
@@ -771,7 +692,7 @@ You can always combine both approaches by defining special attributes before you
|
|
|
771
692
|
|
|
772
693
|
```
|
|
773
694
|
|
|
774
|
-
## [Parse Config](https://
|
|
695
|
+
## [Parse Config](https://neurosynq.github.io/parse-stack-next/Parse/API/Config.html)
|
|
775
696
|
Getting your configuration variables once you have a default client setup can be done with `Parse.config`. The first time this method is called, Parse-Stack will get the configuration from Parse Server, and cache it. To force a reload of the config, use `config!`. You
|
|
776
697
|
|
|
777
698
|
```ruby
|
|
@@ -794,7 +715,7 @@ Getting your configuration variables once you have a default client setup can be
|
|
|
794
715
|
## Core Classes
|
|
795
716
|
While some native data types are similar to the ones supported by Ruby natively, other ones are more complex and require their dedicated classes.
|
|
796
717
|
|
|
797
|
-
### [Parse::Pointer](https://
|
|
718
|
+
### [Parse::Pointer](https://neurosynq.github.io/parse-stack-next/Parse/Pointer.html)
|
|
798
719
|
An important concept is the `Parse::Pointer` class. This is the superclass of `Parse::Object` and represents the pointer type in Parse. A `Parse::Pointer` only contains data about the specific Parse class and the `id` for the object. Therefore, creating an instance of any Parse::Object subclass with only the `:id` field set will be considered in "pointer" state even though its specific class is not `Parse::Pointer` type. The only case that you may have a Parse::Pointer is in the case where an object was received for one of your classes and the framework has no registered class handler for it. Using the example above, assume you have the tables `Post`, `Comment` and `Author` defined in your remote Parse application, but have only defined `Post` and `Commentary` locally.
|
|
799
720
|
|
|
800
721
|
```ruby
|
|
@@ -858,7 +779,7 @@ pointer.title # Raises Parse::AutofetchTriggeredError instead of fetching
|
|
|
858
779
|
|
|
859
780
|
The effect is that for any unknown classes that the framework encounters, it will generate Parse::Pointer instances until you define those classes with valid properties and associations. While this might be ok for some classes you do not use, we still recommend defining all your Parse classes locally in the framework.
|
|
860
781
|
|
|
861
|
-
### [Parse::File](https://
|
|
782
|
+
### [Parse::File](https://neurosynq.github.io/parse-stack-next/Parse/File.html)
|
|
862
783
|
This class represents a Parse file pointer. `Parse::File` has helper methods to upload Parse files directly to Parse and manage file associations with your classes. Using our Song class example:
|
|
863
784
|
|
|
864
785
|
```ruby
|
|
@@ -912,7 +833,7 @@ file.url # => https://www.example.com/file.png
|
|
|
912
833
|
|
|
913
834
|
```
|
|
914
835
|
|
|
915
|
-
### [Parse::Date](https://
|
|
836
|
+
### [Parse::Date](https://neurosynq.github.io/parse-stack-next/Parse/Date.html)
|
|
916
837
|
This class manages dates in the special JSON format it requires for properties of type `:date`. `Parse::Date` subclasses `DateTime`, which allows you to use any features or methods available to `DateTime` with `Parse::Date`. While the conversion between `Time` and `DateTime` objects to a `Parse::Date` object is done implicitly for you, you can use the added special methods, `DateTime#parse_date` and `Time#parse_date`, for special occasions.
|
|
917
838
|
|
|
918
839
|
```ruby
|
|
@@ -921,7 +842,7 @@ This class manages dates in the special JSON format it requires for properties o
|
|
|
921
842
|
song.save # ok
|
|
922
843
|
```
|
|
923
844
|
|
|
924
|
-
### [Parse::GeoPoint](https://
|
|
845
|
+
### [Parse::GeoPoint](https://neurosynq.github.io/parse-stack-next/Parse/GeoPoint.html)
|
|
925
846
|
This class manages the GeoPoint data type that Parse provides to support geo-queries. To define a GeoPoint property, use the `:geopoint` data type. Please note that latitudes should not be between -90.0 and 90.0, and longitudes should be between -180.0 and 180.0.
|
|
926
847
|
|
|
927
848
|
```ruby
|
|
@@ -953,7 +874,7 @@ We include helper methods to calculate distances between GeoPoints: `distance_in
|
|
|
953
874
|
# ~180.793 km
|
|
954
875
|
```
|
|
955
876
|
|
|
956
|
-
### [Parse::Bytes](https://
|
|
877
|
+
### [Parse::Bytes](https://neurosynq.github.io/parse-stack-next/Parse/Bytes.html)
|
|
957
878
|
The `Bytes` data type represents the storage format for binary content in a Parse column. The content is needs to be encoded into a base64 string.
|
|
958
879
|
|
|
959
880
|
```ruby
|
|
@@ -965,7 +886,7 @@ The `Bytes` data type represents the storage format for binary content in a Pars
|
|
|
965
886
|
decoded = bytes.decoded # same as Base64.decode64
|
|
966
887
|
```
|
|
967
888
|
|
|
968
|
-
### [Parse::TimeZone](https://
|
|
889
|
+
### [Parse::TimeZone](https://neurosynq.github.io/parse-stack-next/Parse/TimeZone.html)
|
|
969
890
|
While Parse does not provide a native time zone data type, Parse-Stack provides a class to make it easier to manage time zone attributes, usually stored IANA string identifiers, with your ruby code. This is done by utilizing the features provided by [`ActiveSupport::TimeZone`](http://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html). In addition to setting a column as a time zone field, we also add special validations to verify it is of the right IANA identifier.
|
|
970
891
|
|
|
971
892
|
```ruby
|
|
@@ -988,9 +909,9 @@ event.time_zone = 'Galaxy/Andromeda'
|
|
|
988
909
|
event.time_zone.valid? # => false
|
|
989
910
|
```
|
|
990
911
|
|
|
991
|
-
### [Parse::ACL](https://
|
|
912
|
+
### [Parse::ACL](https://neurosynq.github.io/parse-stack-next/Parse/ACL.html)
|
|
992
913
|
The `ACL` class represents the access control lists for each record. An ACL is represented by a JSON object with the keys being `Parse::User` object ids or the special key of `*`, which indicates the public access permissions.
|
|
993
|
-
The value of each key in the hash is a [`Parse::ACL::Permission`](https://
|
|
914
|
+
The value of each key in the hash is a [`Parse::ACL::Permission`](https://neurosynq.github.io/parse-stack-next/Parse/ACL/Permission.html) object which defines the boolean permission state for `read` and `write`.
|
|
994
915
|
|
|
995
916
|
The example below illustrates a Parse ACL JSON object where there is a public read permission, but public write is prevented. In addition, the user with id `3KmCvT7Zsb` and the `Admins` role, are allowed to both read and write on this record.
|
|
996
917
|
|
|
@@ -1056,9 +977,15 @@ There are four policies:
|
|
|
1056
977
|
| Policy | When an owner is resolvable | When no owner is resolvable |
|
|
1057
978
|
|---|---|---|
|
|
1058
979
|
| `:public` | public read + write | public read + write |
|
|
980
|
+
| `:public_read` | public read, master-key write | public read, master-key write |
|
|
1059
981
|
| `:private` | master-key only | master-key only |
|
|
1060
982
|
| `:owner_else_public` | owner read + write only | public read + write |
|
|
1061
983
|
| `:owner_else_private` | owner read + write only | master-key only |
|
|
984
|
+
| `:owner_but_public_read` | owner read + write *and* public read | public read, master-key write |
|
|
985
|
+
|
|
986
|
+
`:public_read` (v5.0+) stamps `{"*": {"read": true}}` — anyone can read the row, but no client can mutate it through ACL (only the master key can write). Useful for catalog / lookup / reference data.
|
|
987
|
+
|
|
988
|
+
`:owner_but_public_read` (v5.0+) is the "single-author public post" case: the resolved owner gets full R/W and the rest of the world gets read-only access in the same ACL — `{"*": {"read": true}, "<ownerId>": {"read": true, "write": true}}`. When no owner resolves at save (no `as:` and no resolvable `owner:` field), it degrades to `:public_read` semantics rather than the all-or-nothing fallback used by the `:owner_else_*` family.
|
|
1062
989
|
|
|
1063
990
|
```ruby
|
|
1064
991
|
class Post < Parse::Object
|
|
@@ -1320,8 +1247,8 @@ clp.filter_fields(doc_data, user: "other_user")
|
|
|
1320
1247
|
|
|
1321
1248
|
This also works with arrays of pointers (e.g., `owners: [user1, user2]`).
|
|
1322
1249
|
|
|
1323
|
-
### [Parse::Session](https://
|
|
1324
|
-
This class represents the data and columns contained in the standard Parse `_Session` collection. You may add additional properties and methods to this class. See [Session API Reference](https://
|
|
1250
|
+
### [Parse::Session](https://neurosynq.github.io/parse-stack-next/Parse/Session.html)
|
|
1251
|
+
This class represents the data and columns contained in the standard Parse `_Session` collection. You may add additional properties and methods to this class. See [Session API Reference](https://neurosynq.github.io/parse-stack-next/Parse/Session.html). You may call `Parse.use_shortnames!` to use `Session` in addition to `Parse::Session`.
|
|
1325
1252
|
|
|
1326
1253
|
You can get a specific `Parse::Session` given a session_token by using the `session` method. You can also find the user tied to a specific Parse session or session token with `Parse::User.session`.
|
|
1327
1254
|
|
|
@@ -1341,19 +1268,19 @@ some_object.destroy( session: user.session_token )
|
|
|
1341
1268
|
|
|
1342
1269
|
```
|
|
1343
1270
|
|
|
1344
|
-
### [Parse::Installation](https://
|
|
1345
|
-
This class represents the data and columns contained in the standard Parse `_Installation` collection. You may add additional properties and methods to this class. See [Installation API Reference](https://
|
|
1271
|
+
### [Parse::Installation](https://neurosynq.github.io/parse-stack-next/Parse/Installation.html)
|
|
1272
|
+
This class represents the data and columns contained in the standard Parse `_Installation` collection. You may add additional properties and methods to this class. See [Installation API Reference](https://neurosynq.github.io/parse-stack-next/Parse/Installation.html). You may call `Parse.use_shortnames!` to use `Installation` in addition to `Parse::Installation`.
|
|
1346
1273
|
|
|
1347
|
-
### [Parse::Product](https://
|
|
1348
|
-
This class represents the data and columns contained in the standard Parse `_Product` collection. You may add additional properties and methods to this class. See [Product API Reference](https://
|
|
1274
|
+
### [Parse::Product](https://neurosynq.github.io/parse-stack-next/Parse/Product.html)
|
|
1275
|
+
This class represents the data and columns contained in the standard Parse `_Product` collection. You may add additional properties and methods to this class. See [Product API Reference](https://neurosynq.github.io/parse-stack-next/Parse/Product.html). You may call `Parse.use_shortnames!` to use `Product` in addition to `Parse::Product`.
|
|
1349
1276
|
|
|
1350
1277
|
The `_Product` collection backs the original Parse iOS SDK's `PFProduct` downloadable-content in-app-purchase flow. That feature was tied to hosted Parse and is not actively used by modern Parse Server deployments — most apps now verify in-app purchase receipts directly against the Apple App Store or Google Play. The class is retained for backwards compatibility with legacy applications that still read or write product metadata. It is also marked `agent_hidden` by default so it does not surface through MCP / agent tooling; applications that genuinely need agent access can call `Parse::Product.agent_unhidden` at boot.
|
|
1351
1278
|
|
|
1352
|
-
### [Parse::Role](https://
|
|
1353
|
-
This class represents the data and columns contained in the standard Parse `_Role` collection. You may add additional properties and methods to this class. See [Roles API Reference](https://
|
|
1279
|
+
### [Parse::Role](https://neurosynq.github.io/parse-stack-next/Parse/Role.html)
|
|
1280
|
+
This class represents the data and columns contained in the standard Parse `_Role` collection. You may add additional properties and methods to this class. See [Roles API Reference](https://neurosynq.github.io/parse-stack-next/Parse/Role.html). You may call `Parse.use_shortnames!` to use `Role` in addition to `Parse::Role`.
|
|
1354
1281
|
|
|
1355
1282
|
#### Default ACL (master-only)
|
|
1356
|
-
Parse Server requires every `_Role` row to ship with an ACL — the requirement is hard-coded in `SchemaController.requiredColumns` and cannot be disabled by config. `Parse::Role` declares `acl_policy :private`, so every role saved without an explicit ACL is stamped with `{}` (master-key only). This is intentional: anonymous and authenticated-but-non-master clients cannot enumerate role names, read
|
|
1283
|
+
Parse Server requires every `_Role` row to ship with an ACL — the requirement is hard-coded in `SchemaController.requiredColumns` and cannot be disabled by config. `Parse::Role` declares `acl_policy :private`, so every role saved without an explicit ACL is stamped with `{}` (master-key only). This is intentional: anonymous and authenticated-but-non-master clients cannot enumerate role names, read subscription, or walk the role hierarchy. Parse Server's internal role-subscription expansion (used during ACL evaluation) runs with master context, so the master-only default does not break permission checks on other classes.
|
|
1357
1284
|
|
|
1358
1285
|
To opt into broader access, pass an explicit ACL:
|
|
1359
1286
|
|
|
@@ -1397,7 +1324,7 @@ admin.child_roles_count # Direct child roles
|
|
|
1397
1324
|
admin.total_users_count # All users including child roles
|
|
1398
1325
|
```
|
|
1399
1326
|
|
|
1400
|
-
### [Parse::JobStatus](https://
|
|
1327
|
+
### [Parse::JobStatus](https://neurosynq.github.io/parse-stack-next/Parse/JobStatus.html)
|
|
1401
1328
|
|
|
1402
1329
|
This class represents the data and columns contained in the standard Parse `_JobStatus` collection. Parse Server writes a row here every time a background job — registered server-side via `Parse.Cloud.job(...)` — runs, recording its outcome and any status/message updates emitted via `request.message(...)`.
|
|
1403
1330
|
|
|
@@ -1444,7 +1371,7 @@ Parse::JobStatus.cleanup_older_than!(days: 7, terminal_only: false)
|
|
|
1444
1371
|
|
|
1445
1372
|
The helper requires master-key access (Parse Server's default `_JobStatus` CLP). Run from a periodic cron or scheduled job to keep `_JobStatus` from growing unboundedly.
|
|
1446
1373
|
|
|
1447
|
-
### [Parse::JobSchedule](https://
|
|
1374
|
+
### [Parse::JobSchedule](https://neurosynq.github.io/parse-stack-next/Parse/JobSchedule.html)
|
|
1448
1375
|
|
|
1449
1376
|
This class represents the data and columns contained in the standard Parse `_JobSchedule` collection. Rows here define recurring runs for background jobs registered via `Parse.Cloud.job(...)`. The collection is populated by the Parse Dashboard's "Schedule a Job" UI.
|
|
1450
1377
|
|
|
@@ -1461,8 +1388,8 @@ schedule.parsed_params # => { "dryRun" => false } — JSON-decoded
|
|
|
1461
1388
|
|
|
1462
1389
|
`params` is stored on the wire as a JSON-encoded **string** per Parse Server's canonical schema (Object columns reject `$` and `.` in nested keys, which would otherwise break common payload shapes). Use `#parsed_params` to decode; it returns `nil` for blank or invalid JSON instead of raising. `last_run` is a raw `Number` whose unit is scheduler-defined — most external schedulers write `Date.now()` milliseconds, but the canonical schema does not pin a unit.
|
|
1463
1390
|
|
|
1464
|
-
### [Parse::User](https://
|
|
1465
|
-
This class represents the data and columns contained in the standard Parse `_User` collection. You may add additional properties and methods to this class. See [User API Reference](https://
|
|
1391
|
+
### [Parse::User](https://neurosynq.github.io/parse-stack-next/Parse/User.html)
|
|
1392
|
+
This class represents the data and columns contained in the standard Parse `_User` collection. You may add additional properties and methods to this class. See [User API Reference](https://neurosynq.github.io/parse-stack-next/Parse/User.html). You may call `Parse.use_shortnames!` to use `User` in addition to `Parse::User`.
|
|
1466
1393
|
|
|
1467
1394
|
#### Signup
|
|
1468
1395
|
You can signup new users in two ways. You can either use a class method `Parse::User.signup` to create a new user with the minimum fields of username, password and email, or create a `Parse::User` object can call the `signup!` method. If signup fails, it will raise the corresponding exception.
|
|
@@ -1931,7 +1858,7 @@ band.drummer # Artist object
|
|
|
1931
1858
|
###### `:field`
|
|
1932
1859
|
This option allows you to set the name of the remote Parse column for this property. Using this will explicitly set the remote property name to the value of this option. The value provided for this option will affect the name of the alias method that is generated when `alias` option is used. **By default, the name of the remote column is the lower-first camel case version of the property name. As an example, for a property with key `:my_property_name`, the framework will implicitly assume that the remote column is `myPropertyName`.**
|
|
1933
1860
|
|
|
1934
|
-
#### [Has One](https://
|
|
1861
|
+
#### [Has One](https://neurosynq.github.io/parse-stack-next/Parse/Associations/HasOne.html)
|
|
1935
1862
|
The `has_one` creates a one-to-one association with another Parse class. This association says that the other class in the association contains a foreign pointer column which references instances of this class. If your model contains a column that is a Parse pointer to another class, you should use `belongs_to` for that association instead.
|
|
1936
1863
|
|
|
1937
1864
|
Defining a `has_one` property generates a helper query method to fetch a particular record from a foreign class. This is useful for setting up the inverse relationship accessors of a `belongs_to`. In the case of the `has_one` relationship, the `:field` option represents the name of the column of the foreign class where the Parse pointer is stored. By default, the lower-first camel case version of the Parse class name is used.
|
|
@@ -1990,7 +1917,7 @@ user.band_by_status(false)
|
|
|
1990
1917
|
|
|
1991
1918
|
```
|
|
1992
1919
|
|
|
1993
|
-
#### [Has Many](https://
|
|
1920
|
+
#### [Has Many](https://neurosynq.github.io/parse-stack-next/Parse/Associations/HasMany.html)
|
|
1994
1921
|
Parse has many ways to implement one-to-many and many-to-many associations: `Array`, `Parse Relation` or through a `Query`. How you decide to implement your associations, will affect how `has_many` works in Parse-Stack. Parse natively supports one-to-many and many-to-many relationships using `Array` and `Relations`, as described in [Relational Data](http://docs.parseplatform.org/js/guide/#relational-data). Both of these methods require you define a specific column type in your Parse table that will be used to store information about the association.
|
|
1995
1922
|
|
|
1996
1923
|
In addition to `Array` and `Relation`, Parse-Stack also implements the standard `has_many` behavior prevalent in other frameworks through a query where the associated class contains a foreign pointer to the local class, usually the inverse of a `belongs_to`. This requires that the associated class has a defined column
|
|
@@ -2234,7 +2161,7 @@ author.posts # => Posts where author's name is a tag
|
|
|
2234
2161
|
```
|
|
2235
2162
|
|
|
2236
2163
|
## Creating, Saving and Deleting Records
|
|
2237
|
-
This section provides some of the basic methods when creating, updating and deleting objects from Parse. Additional documentation for these APIs can be found under [Parse::Core::Actions](https://
|
|
2164
|
+
This section provides some of the basic methods when creating, updating and deleting objects from Parse. Additional documentation for these APIs can be found under [Parse::Core::Actions](https://neurosynq.github.io/parse-stack-next/Parse/Core/Actions.html). To illustrate the various methods available for saving Parse records, we use this example class:
|
|
2238
2165
|
|
|
2239
2166
|
```ruby
|
|
2240
2167
|
|
|
@@ -2397,7 +2324,7 @@ To commit a new record or changes to an existing record to Parse, use the `#save
|
|
|
2397
2324
|
The save operation can handle both creating and updating existing objects. If you do not want to update the association data of a changed object, you may use the `#update` method to only save the changed property values. In the case where you want to force update an object even though it has not changed, to possibly trigger your `before_save` hooks, you can use the `#update!` method. In addition, just like with other ActiveModel objects, you may call `reload!` to fetch the current record again from the data store.
|
|
2398
2325
|
|
|
2399
2326
|
### Saving applying User ACLs
|
|
2400
|
-
You may save and delete objects from Parse on behalf of a logged in user by passing the session token to the call to `save` or `destroy`. Doing so will allow Parse to apply the ACLs of this user against the record to see if the user is authorized to read or write the record. See [Parse::Actions](https://
|
|
2327
|
+
You may save and delete objects from Parse on behalf of a logged in user by passing the session token to the call to `save` or `destroy`. Doing so will allow Parse to apply the ACLs of this user against the record to see if the user is authorized to read or write the record. See [Parse::Actions](https://neurosynq.github.io/parse-stack-next/Parse/Core/Actions.html).
|
|
2401
2328
|
|
|
2402
2329
|
```ruby
|
|
2403
2330
|
user = Parse::User.login('myuser','pass')
|
|
@@ -2902,7 +2829,7 @@ user = User.first(keys: [:id, :first_name, :email])
|
|
|
2902
2829
|
user.to_json # Only includes id, first_name, email (plus metadata)
|
|
2903
2830
|
|
|
2904
2831
|
# Useful for webhook responses - returns only requested fields
|
|
2905
|
-
Parse::Webhooks.route :function, :
|
|
2832
|
+
Parse::Webhooks.route :function, :getWorkspaceMembers do
|
|
2906
2833
|
users = User.all(:id.in => user_ids, keys: [:id, :first_name, :icon_image])
|
|
2907
2834
|
users # Returns only the requested fields, no autofetch triggered
|
|
2908
2835
|
end
|
|
@@ -3158,7 +3085,7 @@ users_by_city = User.group_objects_by(:city)
|
|
|
3158
3085
|
|
|
3159
3086
|
# Advanced options
|
|
3160
3087
|
User.group_by(:tags, flatten_arrays: true).count # Flatten array fields
|
|
3161
|
-
User.group_by(:
|
|
3088
|
+
User.group_by(:workspace, return_pointers: true).count # Use pointers for efficiency
|
|
3162
3089
|
```
|
|
3163
3090
|
|
|
3164
3091
|
**Available aggregation methods:** `count`, `sum(field)`, `min(field)`, `max(field)`, `avg(field)`
|
|
@@ -3168,7 +3095,7 @@ User.group_by(:team, return_pointers: true).count # Use pointers for efficiency
|
|
|
3168
3095
|
Finds the distinct values for a specified field across a single collection or
|
|
3169
3096
|
view and returns the results in an array. You may mix this with additional query constraints.
|
|
3170
3097
|
|
|
3171
|
-
**⚠️ Breaking Change in v1.12.0**: For pointer fields, `distinct` now returns object IDs directly by default instead of full pointer hash objects like `{"__type"=>"Pointer", "className"=>"
|
|
3098
|
+
**⚠️ Breaking Change in v1.12.0**: For pointer fields, `distinct` now returns object IDs directly by default instead of full pointer hash objects like `{"__type"=>"Pointer", "className"=>"Workspace", "objectId"=>"abc123"}`. Use `return_pointers: true` to get Parse::Pointer objects.
|
|
3172
3099
|
|
|
3173
3100
|
```ruby
|
|
3174
3101
|
# Return a list of unique city names
|
|
@@ -3177,15 +3104,15 @@ view and returns the results in an array. You may mix this with additional query
|
|
|
3177
3104
|
# ex. ["San Diego", "Los Angeles", "San Juan"]
|
|
3178
3105
|
|
|
3179
3106
|
# For pointer fields, now returns object IDs by default (v1.12.0+)
|
|
3180
|
-
|
|
3107
|
+
Document.distinct(:author_workspace)
|
|
3181
3108
|
# => ["team1", "team2", "team3"] # Just the object IDs
|
|
3182
3109
|
|
|
3183
3110
|
# Pre-v1.12.0 behavior returned full pointer hashes:
|
|
3184
|
-
# [{"__type"=>"Pointer", "className"=>"
|
|
3111
|
+
# [{"__type"=>"Pointer", "className"=>"Workspace", "objectId"=>"team1"}, ...]
|
|
3185
3112
|
|
|
3186
3113
|
# To get Parse::Pointer objects in v1.12.0+
|
|
3187
|
-
|
|
3188
|
-
# => [#<Parse::Pointer @parse_class="
|
|
3114
|
+
Document.distinct(:author_workspace, return_pointers: true)
|
|
3115
|
+
# => [#<Parse::Pointer @parse_class="Workspace" @id="team1">, ...]
|
|
3189
3116
|
|
|
3190
3117
|
# same using query instance
|
|
3191
3118
|
query = Parse::Query.new("_User")
|
|
@@ -3195,7 +3122,7 @@ view and returns the results in an array. You may mix this with additional query
|
|
|
3195
3122
|
```
|
|
3196
3123
|
|
|
3197
3124
|
### Query Expressions
|
|
3198
|
-
The set of supported expressions based on what is available through the Parse REST API. _For those who don't prefer the DataMapper style syntax, we have provided method accessors for each of the expressions._ A full description of supported query operations, please refer to the [`Parse::Query`](https://
|
|
3125
|
+
The set of supported expressions based on what is available through the Parse REST API. _For those who don't prefer the DataMapper style syntax, we have provided method accessors for each of the expressions._ A full description of supported query operations, please refer to the [`Parse::Query`](https://neurosynq.github.io/parse-stack-next/Parse/Query.html) API reference.
|
|
3199
3126
|
|
|
3200
3127
|
#### :order
|
|
3201
3128
|
Specify a field to sort by.
|
|
@@ -3368,6 +3295,8 @@ A true/false value. If you provided a master key as part of `Parse.setup()`, it
|
|
|
3368
3295
|
Song.all limit: 3, use_master_key: false
|
|
3369
3296
|
```
|
|
3370
3297
|
|
|
3298
|
+
As of v5.0, `Parse::Query` initializes `@use_master_key` to `nil` (tri-state: "no caller preference") rather than `true`. Server-mode behavior is unchanged — when nothing in the call chain expresses a preference, the request layer still sends the master key. The difference matters for `Parse.client_mode = true` processes and inside `Parse.with_session(user) { … }` blocks: the previous `true` default short-circuited those resolutions and silently master-key-stamped queries. Explicitly passing `use_master_key: true` (or calling `query.use_master_key = true`) still forces the header. `Parse::Query#assert_mongo_direct_routable!` treats a configured master key on the client as an ambient credential in server mode: direct-only constraints (Atlas Search-shaped operators, etc.) route through the mongo-direct path as long as `Parse.client_mode` is false and `use_master_key` was not explicitly set to `false`. The gate still raises `Parse::Query::MongoDirectRequired` for client-mode processes or queries that explicitly opt out of the master key without supplying a `session_token` / `.scope_to_user(user)` / `.scope_to_role(role)`.
|
|
3299
|
+
|
|
3371
3300
|
#### :session
|
|
3372
3301
|
This will make sure that the query is performed on behalf (and with the privileges) of an authenticated user which will cause record ACLs to be enforced. If a session token is provided, caching will be disabled for this request. You may pass a string representing the session token, an authenticated `Parse::User` instance or a `Parse::Session` instance.
|
|
3373
3302
|
|
|
@@ -3387,7 +3316,7 @@ The `where` clause is based on utilizing a set of constraints on the defined col
|
|
|
3387
3316
|
{ :column.constraint => value }
|
|
3388
3317
|
```
|
|
3389
3318
|
|
|
3390
|
-
## [Query Constraints](https://
|
|
3319
|
+
## [Query Constraints](https://neurosynq.github.io/parse-stack-next/Parse/Constraint.html)
|
|
3391
3320
|
Most of the constraints supported by Parse are available to `Parse::Query`. Assuming you have a column named `field`, here are some examples. For an explanation of the constraints, please see [Parse Query Constraints documentation](http://docs.parseplatform.org/rest/guide/#queries). You can build your own custom query constraints by creating a `Parse::Constraint` subclass. For all these `where` clauses assume `q` is a `Parse::Query` object.
|
|
3392
3321
|
|
|
3393
3322
|
#### Equals
|
|
@@ -3810,7 +3739,7 @@ songs = find_songs Artist.pointer(artist_id)
|
|
|
3810
3739
|
|
|
3811
3740
|
```
|
|
3812
3741
|
|
|
3813
|
-
### [Geo Queries](https://
|
|
3742
|
+
### [Geo Queries](https://neurosynq.github.io/parse-stack-next/Parse/Constraint/NearSphereQueryConstraint.html)
|
|
3814
3743
|
Equivalent to the `$nearSphere` Parse query operation. This is only applicable if the field is of type `GeoPoint`. This will query Parse and return a list of results ordered by distance with the nearest object being first.
|
|
3815
3744
|
|
|
3816
3745
|
```ruby
|
|
@@ -3836,7 +3765,7 @@ PlaceObject.all :location.near => geopoint.max_miles(10)
|
|
|
3836
3765
|
|
|
3837
3766
|
We will support `$maxDistanceInKilometers` (for kms) and `$maxDistanceInRadians` (for radian angle) in the future.
|
|
3838
3767
|
|
|
3839
|
-
#### [Bounding Box Constraint](https://
|
|
3768
|
+
#### [Bounding Box Constraint](https://neurosynq.github.io/parse-stack-next/Parse/Constraint/WithinGeoBoxQueryConstraint.html)
|
|
3840
3769
|
Equivalent to the `$within` Parse query operation and `$box` geopoint constraint. The rectangular bounding box is defined by a southwest point as the first parameter, followed by the a northeast point. Please note that Geo box queries that cross the international date lines are not currently supported by Parse.
|
|
3841
3770
|
|
|
3842
3771
|
```ruby
|
|
@@ -3851,7 +3780,7 @@ ne = Parse::GeoPoint.new 36.12, -115.31 # Las Vegas
|
|
|
3851
3780
|
PlaceObject.all :location.within_box => [sw,ne]
|
|
3852
3781
|
```
|
|
3853
3782
|
|
|
3854
|
-
#### [Polygon Area Constraint](https://
|
|
3783
|
+
#### [Polygon Area Constraint](https://neurosynq.github.io/parse-stack-next/Parse/Constraint/WithinPolygonQueryConstraint.html)
|
|
3855
3784
|
Equivalent to the `$geoWithin` Parse query operation and `$polygon` geopoint constraint. The polygon area is described by a list of `Parse::GeoPoint` objects and should contain 3 or more points. This feature is only available in Parse-Server version 2.4.2 and later.
|
|
3856
3785
|
|
|
3857
3786
|
```ruby
|
|
@@ -3867,7 +3796,7 @@ Equivalent to the `$geoWithin` Parse query operation and `$polygon` geopoint con
|
|
|
3867
3796
|
SunkenShip.all :location.within_polygon => [bermuda, san_juan, miami]
|
|
3868
3797
|
```
|
|
3869
3798
|
|
|
3870
|
-
#### [Full Text Search Constraint](https://
|
|
3799
|
+
#### [Full Text Search Constraint](https://neurosynq.github.io/parse-stack-next/Parse/Constraint/FullTextSearchQueryConstraint.html)
|
|
3871
3800
|
Equivalent to the `$text` Parse query operation and `$search` parameter constraint for efficient search capabilities. By creating indexes on one or more columns your strings are turned into tokens for full text search functionality. The `$search` key can take any number of parameters in hash form. *Requires Parse Server 2.5.0 or later*
|
|
3872
3801
|
|
|
3873
3802
|
```ruby
|
|
@@ -4268,7 +4197,7 @@ Parse::Push.new
|
|
|
4268
4197
|
Parse::Push.new
|
|
4269
4198
|
.to_channels("sports", "alerts")
|
|
4270
4199
|
.with_title("Game Alert")
|
|
4271
|
-
.with_body("Your
|
|
4200
|
+
.with_body("Your workspace is playing now!")
|
|
4272
4201
|
.with_badge(1)
|
|
4273
4202
|
.with_sound("alert.caf")
|
|
4274
4203
|
.with_data(game_id: "12345", action: "open_game")
|
|
@@ -4424,9 +4353,29 @@ push.data = { uri: "app://deep_link_path" }
|
|
|
4424
4353
|
push.send
|
|
4425
4354
|
```
|
|
4426
4355
|
|
|
4427
|
-
##
|
|
4356
|
+
## Analytics
|
|
4357
|
+
|
|
4358
|
+
`Parse.track_event(name, dimensions: {}, **opts)` (v5.0+) is the top-level shortcut for sending events to Parse Server's `POST /events/<name>` endpoint. The dimensions hash MUST be passed via the `dimensions:` keyword — under Ruby 3 kwarg separation, loose symbol arguments are absorbed by `**opts` and never reach the POST body. The event name is validated against `[\w\-\.]` at the SDK boundary so the value cannot escape the `/events/` path segment.
|
|
4359
|
+
|
|
4360
|
+
```ruby
|
|
4361
|
+
# Server-side, master-key in process
|
|
4362
|
+
Parse.track_event("post_viewed", dimensions: { source: "feed", workspace: "w1" })
|
|
4363
|
+
|
|
4364
|
+
# No dimensions
|
|
4365
|
+
Parse.track_event("AppOpened")
|
|
4366
|
+
|
|
4367
|
+
# Client-mode, scoped to a session
|
|
4368
|
+
Parse.track_event("search",
|
|
4369
|
+
dimensions: { query: "tabby cats" },
|
|
4370
|
+
session_token: user.session_token, use_master_key: false,
|
|
4371
|
+
)
|
|
4372
|
+
```
|
|
4373
|
+
|
|
4374
|
+
Parse Server's default `analyticsAdapter` is a no-op: events POST'd are accepted (HTTP 200) but are not persisted and cannot be read back through the SDK. Operators who wire in a custom adapter decide what (if anything) to do with each event. The legacy parse.com eight-dimension cap does NOT apply to Parse Server out of the box. If you need to query analytics events from the SDK, persist them to a regular `Parse::Object` subclass instead. The underlying request is a blocking HTTP POST — wrap in a thread or background job if you do not want it on the request path.
|
|
4375
|
+
|
|
4376
|
+
## AI Agent Integration
|
|
4428
4377
|
|
|
4429
|
-
Parse Stack includes
|
|
4378
|
+
Parse Stack includes first-class support for AI/LLM agents to interact with your Parse data through a standardized tool interface, including an MCP 2025-06-18 Streamable HTTP transport. This enables natural language querying and intelligent data exploration with rate limiting, prompt-injection protection, and per-agent ACL scoping.
|
|
4430
4379
|
|
|
4431
4380
|
### Basic Usage
|
|
4432
4381
|
|
|
@@ -4468,6 +4417,28 @@ agent = Parse::Agent.new(permissions: :write)
|
|
|
4468
4417
|
agent = Parse::Agent.new(permissions: :admin)
|
|
4469
4418
|
```
|
|
4470
4419
|
|
|
4420
|
+
### Client Mode (v5.0)
|
|
4421
|
+
|
|
4422
|
+
When `Parse::Agent` is constructed against a `Parse::Client` that carries no master key and a non-empty `session_token:`, it switches to *client mode*. The dispatch ceiling is a small allowlist of session-token REST tools (`list_tools`, `get_object`, `get_objects`, `query_class`, `count_objects`, `get_sample_objects`, and — gated by `allow_mutations:` — `create_object`, `update_object`, `delete_object`). Aggregate, atlas-search, schema-introspection, explain, and generic `call_method` are refused because they require either the master key or a direct MongoDB connection.
|
|
4423
|
+
|
|
4424
|
+
```ruby
|
|
4425
|
+
# An unprivileged client + a user's session token
|
|
4426
|
+
Parse.setup(server_url: "...", application_id: "...", api_key: "...") # no master_key
|
|
4427
|
+
agent = Parse::Agent.new(session_token: user.session_token)
|
|
4428
|
+
|
|
4429
|
+
agent.client_mode? # => true
|
|
4430
|
+
agent.allow_mutations? # => false (default in client mode)
|
|
4431
|
+
|
|
4432
|
+
# Read tools work; Parse Server enforces ACL + CLP + protectedFields natively.
|
|
4433
|
+
agent.execute(:query_class, class_name: "Post", limit: 10)
|
|
4434
|
+
|
|
4435
|
+
# Mutations are opt-in per agent:
|
|
4436
|
+
writer = Parse::Agent.new(session_token: user.session_token, allow_mutations: true)
|
|
4437
|
+
writer.execute(:create_object, class_name: "Post", fields: { title: "Hi" })
|
|
4438
|
+
```
|
|
4439
|
+
|
|
4440
|
+
Custom tools default to master-key-only. Mark a registered tool eligible for client mode with `Parse::Agent::Tools.register(:my_tool, ..., client_safe: true)`; the handler is then responsible for routing through `agent.client` with `agent.session_token` (never the master key). Sub-agents cannot widen the parent's `allow_mutations:` gate.
|
|
4441
|
+
|
|
4471
4442
|
### Agent Metadata DSL
|
|
4472
4443
|
|
|
4473
4444
|
Annotate your models with agent-friendly metadata:
|
|
@@ -4479,12 +4450,12 @@ class Song < Parse::Object
|
|
|
4479
4450
|
|
|
4480
4451
|
property :title, :string, _description: "The song title"
|
|
4481
4452
|
property :plays, :integer, _description: "Total play count"
|
|
4482
|
-
property :
|
|
4453
|
+
property :archived, :boolean
|
|
4483
4454
|
|
|
4484
4455
|
# Per-class "valid state" predicate applied by default on every read tool
|
|
4485
4456
|
# (query_class, count_objects, aggregate). Opt out per-call with
|
|
4486
4457
|
# `apply_canonical_filter: false`.
|
|
4487
|
-
agent_canonical_filter "
|
|
4458
|
+
agent_canonical_filter "archived" => { "$ne" => true }
|
|
4488
4459
|
|
|
4489
4460
|
# Expose methods with permission levels
|
|
4490
4461
|
agent_readonly :find_popular, "Find songs with high play counts"
|
|
@@ -5175,7 +5146,7 @@ Song.query.writable_by_role(["Admin", "Editor"]).results(mongo_direct: true) #
|
|
|
5175
5146
|
|
|
5176
5147
|
Parse-Stack provides intelligent dirty tracking for ACL objects, correctly handling in-place modifications and content comparison.
|
|
5177
5148
|
|
|
5178
|
-
**`acl_was`
|
|
5149
|
+
**`acl_was` Posts Original State:**
|
|
5179
5150
|
|
|
5180
5151
|
When modifying an ACL in place (via `apply`, `apply_role`, etc.), `acl_was` correctly returns the state *before* any modifications:
|
|
5181
5152
|
|
|
@@ -5201,19 +5172,19 @@ obj.acl_changed? # => true
|
|
|
5201
5172
|
Setting an ACL to identical values does not mark the object as dirty:
|
|
5202
5173
|
|
|
5203
5174
|
```ruby
|
|
5204
|
-
|
|
5205
|
-
|
|
5175
|
+
subscription = Subscription.find(id)
|
|
5176
|
+
subscription.clear_changes!
|
|
5206
5177
|
|
|
5207
5178
|
# Rebuild ACL to the same values (common in before_save hooks)
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5179
|
+
subscription.acl = Parse::ACL.new
|
|
5180
|
+
subscription.acl.apply(:public, true, false)
|
|
5181
|
+
subscription.acl.apply_role("Admin", true, true)
|
|
5211
5182
|
# ... same permissions as before ...
|
|
5212
5183
|
|
|
5213
5184
|
# If content is identical, object is NOT dirty
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5185
|
+
subscription.acl_changed? # => false
|
|
5186
|
+
subscription.dirty? # => false
|
|
5187
|
+
subscription.save # No unnecessary server request
|
|
5217
5188
|
```
|
|
5218
5189
|
|
|
5219
5190
|
**New Objects:**
|